@axhub/genie 0.2.7 → 0.2.9

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 (131) hide show
  1. package/LICENSE +21 -675
  2. package/dist/api-docs.html +2 -2
  3. package/dist/assets/App-GBcTeeUS.js +460 -0
  4. package/dist/assets/App-qxJ8_QYu.css +32 -0
  5. package/dist/assets/ReviewApp-C9K--AQE.js +1 -0
  6. package/dist/assets/{_basePickBy-C19AekOu.js → _basePickBy-DR_8uFCo.js} +1 -1
  7. package/dist/assets/{_baseUniq-JsnevLw_.js → _baseUniq-D0njlQ_7.js} +1 -1
  8. package/dist/assets/{arc-BLpcuBlf.js → arc-CKlr_Rec.js} +1 -1
  9. package/dist/assets/architectureDiagram-2XIMDMQ5-BmO_uLUH.js +36 -0
  10. package/dist/assets/{blockDiagram-WCTKOSBZ-DQBLwsUS.js → blockDiagram-WCTKOSBZ-DhAeO-56.js} +3 -3
  11. package/dist/assets/c4Diagram-IC4MRINW-C67kFoXx.js +10 -0
  12. package/dist/assets/channel-V3MBjKys.js +1 -0
  13. package/dist/assets/{chunk-4BX2VUAB-De63kbgc.js → chunk-4BX2VUAB-mLLagvJi.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-DtTDDdM9.js → chunk-55IACEB6-Lx-hOjlM.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-DHuwd8tw.js → chunk-FMBD7UC4-Bt-XmVUV.js} +1 -1
  16. package/dist/assets/{chunk-JSJVCQXG-BgytFtmO.js → chunk-JSJVCQXG-Cya6gaDV.js} +1 -1
  17. package/dist/assets/{chunk-KX2RTZJC-nZdp86aN.js → chunk-KX2RTZJC-Bd7Ig6tF.js} +1 -1
  18. package/dist/assets/chunk-NQ4KR5QH-5UAE0Vg-.js +220 -0
  19. package/dist/assets/{chunk-QZHKN3VN-DvUQ3mnO.js → chunk-QZHKN3VN-BAxZ8m7w.js} +1 -1
  20. package/dist/assets/chunk-WL4C6EOR-DjDPvUUP.js +189 -0
  21. package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +1 -0
  22. package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +1 -0
  23. package/dist/assets/clone-BbMGfZwt.js +1 -0
  24. package/dist/assets/cose-bilkent-S5V4N54A-D-60XrkJ.js +1 -0
  25. package/dist/assets/cytoscape.esm-2ZfV8NB5.js +331 -0
  26. package/dist/assets/{dagre-KLK3FWXG-CHYIvW47.js → dagre-KLK3FWXG-bqu3ZS4K.js} +1 -1
  27. package/dist/assets/diagram-E7M64L7V-BueeqoYm.js +24 -0
  28. package/dist/assets/{diagram-IFDJBPK2-Dzsiln_C.js → diagram-IFDJBPK2-D4fDv2E7.js} +1 -1
  29. package/dist/assets/{diagram-P4PSJMXO-DKnGbUpE.js → diagram-P4PSJMXO-WqipY3fN.js} +1 -1
  30. package/dist/assets/erDiagram-INFDFZHY-D0oVnO-x.js +70 -0
  31. package/dist/assets/{flowDiagram-PKNHOUZH-BAZ2-jKp.js → flowDiagram-PKNHOUZH-DzbGyxrr.js} +4 -4
  32. package/dist/assets/ganttDiagram-A5KZAMGK-BwhbbgCP.js +292 -0
  33. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BflpyjGy.js → gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js} +1 -1
  34. package/dist/assets/{graph-suelaXFh.js → graph-DzKos-N0.js} +1 -1
  35. package/dist/assets/highlighted-body-TPN3WLV5-CKDMgz3X.js +1 -0
  36. package/dist/assets/index-DiQlHzGj.js +2 -0
  37. package/dist/assets/index-Drat2nB9.css +1 -0
  38. package/dist/assets/{infoDiagram-LFFYTUFH-pfD1FA3p.js → infoDiagram-LFFYTUFH-BFicZbTf.js} +1 -1
  39. package/dist/assets/ishikawaDiagram-PHBUUO56-CtihxDxl.js +70 -0
  40. package/dist/assets/journeyDiagram-4ABVD52K-Du00J8_d.js +139 -0
  41. package/dist/assets/{kanban-definition-K7BYSVSG-FWinmur1.js → kanban-definition-K7BYSVSG-BJi9S0iQ.js} +5 -5
  42. package/dist/assets/{layout-vcz43XvZ.js → layout-B80Sityu.js} +1 -1
  43. package/dist/assets/{linear-le4gc0vx.js → linear-sRQLOf5H.js} +1 -1
  44. package/dist/assets/mermaid-O7DHMXV3-CBuVs4eJ.js +1038 -0
  45. package/dist/assets/mindmap-definition-YRQLILUH-C5IL_xi-.js +68 -0
  46. package/dist/assets/{pieDiagram-SKSYHLDU-C7PKDh3b.js → pieDiagram-SKSYHLDU-CeTwlJ8z.js} +2 -2
  47. package/dist/assets/quadrantDiagram-337W2JSQ-COfUcLWt.js +7 -0
  48. package/dist/assets/requirementDiagram-Z7DCOOCP-DSb-CJ5B.js +73 -0
  49. package/dist/assets/{sankeyDiagram-WA2Y5GQK-4gulcOP4.js → sankeyDiagram-WA2Y5GQK-8jtuVb45.js} +3 -3
  50. package/dist/assets/sequenceDiagram-2WXFIKYE-C2VpkMwA.js +145 -0
  51. package/dist/assets/{stateDiagram-RAJIS63D-CB4Vl7qM.js → stateDiagram-RAJIS63D-fmwMqxxc.js} +1 -1
  52. package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +1 -0
  53. package/dist/assets/timeline-definition-YZTLITO2-Dx1hP5lg.js +61 -0
  54. package/dist/assets/{treemap-KZPCXAKY-DZSEE6Hz.js → treemap-KZPCXAKY-CkLOdYCZ.js} +58 -58
  55. package/dist/assets/vendor-codemirror-BxPY6emf.js +39 -0
  56. package/dist/assets/vendor-react-xmA_f8ig.js +59 -0
  57. package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
  58. package/dist/assets/{vennDiagram-LZ73GAT5-8E_G06fI.js → vennDiagram-LZ73GAT5-D6KWcnln.js} +4 -4
  59. package/dist/assets/xychartDiagram-JWTSCODW-6fh6qmzN.js +7 -0
  60. package/dist/index.html +5 -5
  61. package/package.json +36 -35
  62. package/server/acp-runtime/client.js +91 -17
  63. package/server/acp-runtime/index.js +5 -16
  64. package/server/acp-runtime/session-store.js +4 -4
  65. package/server/channels/runtime/AgentRuntimeAdapter.js +1 -10
  66. package/server/claude-sdk.js +1 -3
  67. package/server/cli.js +159 -2
  68. package/server/external-agent/service.js +24 -6
  69. package/server/external-agent/ws.js +63 -3
  70. package/server/gemini-cli.js +1 -3
  71. package/server/index.js +120 -19
  72. package/server/openai-codex.js +1 -3
  73. package/server/opencode-cli.js +1 -3
  74. package/server/projects.js +654 -236
  75. package/server/routes/cc-connect.js +1131 -0
  76. package/server/routes/cli-auth.js +1 -73
  77. package/server/routes/commands.js +4 -9
  78. package/server/routes/projects.js +45 -24
  79. package/server/routes/session-core.js +149 -86
  80. package/server/session-core/eventStore.js +45 -18
  81. package/server/session-core/providerAdapters.js +50 -13
  82. package/server/session-core/providerDiscovery.js +8 -3
  83. package/server/session-core/runtimeState.js +8 -0
  84. package/server/utils/ccConnectManager.js +390 -0
  85. package/server/utils/ccConnectState.js +575 -0
  86. package/server/utils/resolveCommandPath.js +71 -0
  87. package/server/utils/workspaceRoots.js +154 -0
  88. package/shared/conversationEvents.js +78 -14
  89. package/dist/assets/App-BWSqiXAT.js +0 -220
  90. package/dist/assets/App-DrlLKa8f.css +0 -1
  91. package/dist/assets/ReviewApp-nz3mbArg.js +0 -1
  92. package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +0 -36
  93. package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +0 -10
  94. package/dist/assets/channel-DkFNxV_H.js +0 -1
  95. package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +0 -220
  96. package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +0 -189
  97. package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +0 -1
  98. package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +0 -1
  99. package/dist/assets/clone-C0lCEIEO.js +0 -1
  100. package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +0 -1
  101. package/dist/assets/cytoscape.esm-5J0xJHOV.js +0 -321
  102. package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +0 -24
  103. package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +0 -70
  104. package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +0 -292
  105. package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +0 -1
  106. package/dist/assets/index-B01NxbUv.css +0 -1
  107. package/dist/assets/index-DW5pGgQ_.js +0 -2
  108. package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +0 -70
  109. package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +0 -139
  110. package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +0 -870
  111. package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +0 -68
  112. package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +0 -7
  113. package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +0 -73
  114. package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +0 -145
  115. package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +0 -1
  116. package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +0 -61
  117. package/dist/assets/vendor-codemirror-CyOKkaQZ.js +0 -31
  118. package/dist/assets/vendor-react-CP4yFTs7.js +0 -8
  119. package/dist/assets/vendor-xterm-DfcmCpbH.js +0 -66
  120. package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +0 -7
  121. package/server/_legacy-providers/README.md +0 -30
  122. package/server/_legacy-providers/claude-sdk.js +0 -956
  123. package/server/_legacy-providers/gemini-cli.js +0 -368
  124. package/server/_legacy-providers/openai-codex.js +0 -705
  125. package/server/_legacy-providers/opencode-cli.js +0 -674
  126. package/server/acp-runtime/client.test.js +0 -688
  127. package/server/acp-runtime/session-store.test.js +0 -89
  128. package/server/cli.test.js +0 -76
  129. package/server/external-agent/service.test.js +0 -53
  130. package/server/external-agent/ws.test.js +0 -289
  131. package/shared/conversationEvents.test.js +0 -403
@@ -0,0 +1,154 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+
4
+ export const UNIX_WORKSPACE_ROOTS = '__workspace_roots__';
5
+
6
+ export function parseWorkspaceRoots(value) {
7
+ if (!value || typeof value !== 'string') {
8
+ return [];
9
+ }
10
+
11
+ const seen = new Set();
12
+ const roots = [];
13
+
14
+ for (const entry of value.split(',')) {
15
+ const trimmed = entry.trim();
16
+ if (!trimmed) continue;
17
+
18
+ const resolved = path.resolve(trimmed);
19
+ if (seen.has(resolved)) continue;
20
+
21
+ seen.add(resolved);
22
+ roots.push(resolved);
23
+ }
24
+
25
+ return roots;
26
+ }
27
+
28
+ export function getDefaultWorkspaceRoots({
29
+ platform = process.platform,
30
+ homeDir = os.homedir()
31
+ } = {}) {
32
+ if (platform === 'win32') {
33
+ return [];
34
+ }
35
+
36
+ const roots = [path.resolve(homeDir)];
37
+
38
+ if (platform === 'darwin') {
39
+ roots.push('/Volumes');
40
+ } else if (platform === 'linux') {
41
+ roots.push('/mnt', '/media');
42
+ const userName = path.basename(homeDir);
43
+ if (userName) {
44
+ roots.push(path.join('/run/media', userName));
45
+ }
46
+ }
47
+
48
+ return [...new Set(roots.map((root) => path.resolve(root)))];
49
+ }
50
+
51
+ export function resolveWorkspaceRootSettings({
52
+ env = process.env,
53
+ platform = process.platform,
54
+ homeDir = os.homedir()
55
+ } = {}) {
56
+ const configuredRoots = parseWorkspaceRoots(env.WORKSPACES_ROOTS);
57
+ const hasWorkspaceRootRestriction = configuredRoots.length > 0 || platform !== 'win32';
58
+ const allowedWorkspaceRoots = configuredRoots.length > 0
59
+ ? configuredRoots
60
+ : (hasWorkspaceRootRestriction ? getDefaultWorkspaceRoots({ platform, homeDir }) : []);
61
+
62
+ return {
63
+ defaultWorkspaceRoot: path.resolve(homeDir),
64
+ hasWorkspaceRootRestriction,
65
+ allowedWorkspaceRoots,
66
+ };
67
+ }
68
+
69
+ const workspaceRootSettings = resolveWorkspaceRootSettings();
70
+
71
+ export const DEFAULT_WORKSPACES_ROOT = workspaceRootSettings.defaultWorkspaceRoot;
72
+ export const HAS_WORKSPACES_ROOT_RESTRICTION = workspaceRootSettings.hasWorkspaceRootRestriction;
73
+ export const WORKSPACES_ROOTS = workspaceRootSettings.allowedWorkspaceRoots;
74
+
75
+ export function expandWorkspacePath(inputPath, homeDir = DEFAULT_WORKSPACES_ROOT) {
76
+ if (!inputPath) return inputPath;
77
+ if (inputPath === '~') {
78
+ return homeDir;
79
+ }
80
+ if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
81
+ return path.join(homeDir, inputPath.slice(2));
82
+ }
83
+ return inputPath;
84
+ }
85
+
86
+ export function isPathWithinAllowedRoots(candidatePath, allowedRoots) {
87
+ const normalizedCandidate = path.resolve(candidatePath);
88
+
89
+ return allowedRoots.some((allowedRoot) => {
90
+ const normalizedRoot = path.resolve(allowedRoot);
91
+ return normalizedCandidate === normalizedRoot
92
+ || normalizedCandidate.startsWith(normalizedRoot + path.sep);
93
+ });
94
+ }
95
+
96
+ export async function resolveAllowedWorkspaceRoots({
97
+ fsImpl,
98
+ allowedWorkspaceRoots = WORKSPACES_ROOTS
99
+ } = {}) {
100
+ const resolvedRoots = [];
101
+ const seen = new Set();
102
+
103
+ for (const root of allowedWorkspaceRoots) {
104
+ let resolvedRoot;
105
+
106
+ try {
107
+ resolvedRoot = await fsImpl.realpath(root);
108
+ } catch (error) {
109
+ resolvedRoot = path.resolve(root);
110
+ }
111
+
112
+ if (seen.has(resolvedRoot)) continue;
113
+ seen.add(resolvedRoot);
114
+ resolvedRoots.push(resolvedRoot);
115
+ }
116
+
117
+ return resolvedRoots;
118
+ }
119
+
120
+ export function formatAllowedWorkspaceRoots(allowedRoots) {
121
+ return allowedRoots.join(', ');
122
+ }
123
+
124
+ export function shouldUseVirtualWorkspaceRoots({
125
+ dirPath,
126
+ hasWorkspaceRootRestriction = HAS_WORKSPACES_ROOT_RESTRICTION
127
+ } = {}) {
128
+ return hasWorkspaceRootRestriction
129
+ && (!dirPath || dirPath === UNIX_WORKSPACE_ROOTS);
130
+ }
131
+
132
+ export function buildWorkspaceRootSuggestions({
133
+ allowedWorkspaceRoots = WORKSPACES_ROOTS,
134
+ homeDir = DEFAULT_WORKSPACES_ROOT
135
+ } = {}) {
136
+ const resolvedHomeDir = path.resolve(homeDir);
137
+ const seen = new Set();
138
+ const suggestions = [];
139
+
140
+ for (const root of allowedWorkspaceRoots) {
141
+ const resolvedRoot = path.resolve(root);
142
+ if (seen.has(resolvedRoot)) continue;
143
+
144
+ seen.add(resolvedRoot);
145
+ suggestions.push({
146
+ path: resolvedRoot,
147
+ name: resolvedRoot === resolvedHomeDir ? 'Home' : (path.basename(resolvedRoot) || resolvedRoot),
148
+ type: 'directory',
149
+ isWorkspaceRoot: true
150
+ });
151
+ }
152
+
153
+ return suggestions;
154
+ }
@@ -185,11 +185,61 @@ function createTextEndEvent({
185
185
  ];
186
186
  }
187
187
 
188
+ function extractProtocolUserMessage(value) {
189
+ const text = String(value || '').trim();
190
+ if (!text) {
191
+ return null;
192
+ }
193
+
194
+ const dynamicContextMatch = text.match(
195
+ /<dynamic_context(?:\s[^>]*)?>[\s\S]*?<\/dynamic_context>\s*<user_message(?:\s[^>]*)?>\s*([\s\S]*?)\s*<\/user_message>/i
196
+ );
197
+ if (!dynamicContextMatch) {
198
+ return null;
199
+ }
200
+
201
+ return String(dynamicContextMatch[1] || '').trim();
202
+ }
203
+
204
+ function hasAcpProtocolTags(value) {
205
+ return /<\/?(?:subagent_notification|environment_context|dynamic_context|user_message)(?:\s[^>]*)?>/i.test(String(value || ''));
206
+ }
207
+
208
+ export function stripAssistantProtocolTags(text) {
209
+ if (typeof text !== 'string') {
210
+ return text;
211
+ }
212
+
213
+ const visibleUserMessage = extractProtocolUserMessage(text);
214
+ if (visibleUserMessage != null) {
215
+ return visibleUserMessage;
216
+ }
217
+
218
+ const hadProtocolTags = hasAcpProtocolTags(text);
219
+ let result = text;
220
+
221
+ result = result.replace(/<subagent_notification(?:\s[^>]*)?>[\s\S]*?<\/subagent_notification>/gi, '');
222
+ result = result.replace(/<environment_context(?:\s[^>]*)?>[\s\S]*?<\/environment_context>/gi, '');
223
+ result = result.replace(/<dynamic_context(?:\s[^>]*)?>[\s\S]*?<\/dynamic_context>/gi, '');
224
+ result = result.replace(/<\/?(?:subagent_notification|environment_context|dynamic_context|user_message)(?:\s[^>]*)?>/gi, '');
225
+
226
+ if (hadProtocolTags) {
227
+ result = result.replace(/["']?\s*\}\s*\}\s*$/g, '');
228
+ }
229
+
230
+ return result.trim();
231
+ }
232
+
188
233
  function extractVisibleUserMessage(value) {
189
234
  const text = String(value || '').trim();
190
235
  if (!text) return '';
191
236
 
192
- if (text.startsWith('# AGENTS.md instructions for ') || text.includes('<environment_context>')) {
237
+ if (
238
+ text.startsWith('# AGENTS.md instructions for ') ||
239
+ text.includes('<environment_context>') ||
240
+ text.startsWith('<subagent_notification>') ||
241
+ text.startsWith('</subagent_notification>')
242
+ ) {
193
243
  return '';
194
244
  }
195
245
 
@@ -202,11 +252,9 @@ function extractVisibleUserMessage(value) {
202
252
  return '';
203
253
  }
204
254
 
205
- const dynamicContextMatch = text.match(
206
- /^<dynamic_context(?:\s[^>]*)?>[\s\S]*?<\/dynamic_context>\s*<user_message>\s*([\s\S]*?)\s*<\/user_message>$/i
207
- );
208
- if (dynamicContextMatch) {
209
- return String(dynamicContextMatch[1] || '').trim();
255
+ const protocolUserMessage = extractProtocolUserMessage(text);
256
+ if (protocolUserMessage != null) {
257
+ return protocolUserMessage;
210
258
  }
211
259
 
212
260
  return text;
@@ -222,12 +270,16 @@ function isHiddenSystemContextContent(value) {
222
270
  text.startsWith('[Dynamic Context]') ||
223
271
  text.startsWith('[Hidden Context]') ||
224
272
  text.startsWith('# AGENTS.md instructions for ') ||
225
- text.startsWith('[DYNAMIC CONTEXT V1]')
273
+ text.startsWith('[DYNAMIC CONTEXT V1]') ||
274
+ text.startsWith('<subagent_notification>') ||
275
+ text.startsWith('</subagent_notification>')
226
276
  ) {
227
277
  return true;
228
278
  }
229
279
 
230
- return /^<dynamic_context(?:\s|>)/i.test(text) || text.includes('<environment_context>');
280
+ return /^<dynamic_context(?:\s|>)/i.test(text)
281
+ || text.includes('<environment_context>')
282
+ || /<subagent_notification(?:\s|>)/i.test(text);
231
283
  }
232
284
 
233
285
  function stringifyToolContent(value) {
@@ -613,8 +665,11 @@ export function normalizeLegacyHistoryEntries(rawMessages = [], provider = 'clau
613
665
  if (msg?.message?.role === 'assistant' && msg?.message?.content) {
614
666
  if (Array.isArray(msg.message.content)) {
615
667
  for (const part of msg.message.content) {
616
- if (part?.type === 'text' && !isHiddenSystemContextContent(part.text)) {
617
- events.push(...createTextSpanEvents({ text: part.text, provider, sessionId, timestamp, rawRef }));
668
+ if (part?.type === 'text') {
669
+ const cleanText = stripAssistantProtocolTags(part.text);
670
+ if (cleanText && !isHiddenSystemContextContent(cleanText)) {
671
+ events.push(...createTextSpanEvents({ text: cleanText, provider, sessionId, timestamp, rawRef }));
672
+ }
618
673
  } else if (part?.type === 'tool_use') {
619
674
  const toolCallId = part.id || createEventId('tool');
620
675
  const result = toolResults.get(part.id);
@@ -633,8 +688,11 @@ export function normalizeLegacyHistoryEntries(rawMessages = [], provider = 'clau
633
688
  }
634
689
  }
635
690
  }
636
- } else if (!isHiddenSystemContextContent(msg.message.content)) {
637
- events.push(...createTextSpanEvents({ text: msg.message.content, provider, sessionId, timestamp, rawRef }));
691
+ } else {
692
+ const cleanText = stripAssistantProtocolTags(msg.message.content);
693
+ if (cleanText && !isHiddenSystemContextContent(cleanText)) {
694
+ events.push(...createTextSpanEvents({ text: cleanText, provider, sessionId, timestamp, rawRef }));
695
+ }
638
696
  }
639
697
  continue;
640
698
  }
@@ -660,6 +718,12 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
660
718
  const clientRequestId = getConversationEventClientRequestId(event);
661
719
  const optimisticIndex = findMatchingOptimisticUserMessageIndex(nextMessages, event, payload);
662
720
  const normalizedContentBlocks = normalizeMessageContentBlocks(payload.contentBlocks);
721
+ const rawText = typeof payload.text === 'string' ? payload.text : '';
722
+ const visibleText = rawText ? extractVisibleUserMessage(rawText) : '';
723
+
724
+ if (rawText.trim() && !visibleText && normalizedContentBlocks.length === 0) {
725
+ break;
726
+ }
663
727
 
664
728
  if (optimisticIndex >= 0) {
665
729
  const existingMessage = nextMessages[optimisticIndex] || {};
@@ -670,7 +734,7 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
670
734
  nextMessages[optimisticIndex] = {
671
735
  ...existingMessage,
672
736
  type: 'user',
673
- content: payload.text || existingMessage.content || '',
737
+ content: visibleText || existingMessage.content || '',
674
738
  timestamp: existingMessage.timestamp || timestamp,
675
739
  provider,
676
740
  contentBlocks: nextContentBlocks,
@@ -683,7 +747,7 @@ export function applyConversationEventToTimelineMessages(messages = [], event, s
683
747
 
684
748
  nextMessages.push({
685
749
  type: 'user',
686
- content: payload.text || '',
750
+ content: visibleText,
687
751
  timestamp,
688
752
  provider,
689
753
  contentBlocks: normalizedContentBlocks,