@lazyingart/agintiflow 0.20.174 → 0.20.175

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.174",
3
+ "version": "0.20.175",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
package/public/app.js CHANGED
@@ -1873,6 +1873,28 @@ function renderEmbeddedWorkspaceResult(value = "", entry = {}) {
1873
1873
  `;
1874
1874
  }
1875
1875
 
1876
+ function compactInline(value = "", limit = 180) {
1877
+ return compactMessage(value, limit);
1878
+ }
1879
+
1880
+ function toolArgumentPreview(toolName = "", args = {}, data = {}) {
1881
+ if (toolName === "finish") return "";
1882
+ if (toolName === "run_command" && (args.command || data.command)) return args.command || data.command;
1883
+ if (["write_file", "apply_patch", "read_file", "open_workspace_file", "preview_workspace"].includes(toolName)) {
1884
+ return args.path || args.file || data.path || "";
1885
+ }
1886
+ if (["send_to_canvas", "create_artifact"].includes(toolName)) {
1887
+ return [args.title || data.title || "canvas artifact", args.kind || data.kind || "", args.selected ? "selected" : ""]
1888
+ .filter(Boolean)
1889
+ .join(" · ");
1890
+ }
1891
+ if (["open_url", "web_research", "web_search"].includes(toolName)) return args.url || args.query || args.q || data.url || "";
1892
+ const scalar = ["title", "path", "file", "query", "q", "url", "kind"]
1893
+ .map((key) => args[key] || data[key])
1894
+ .find(Boolean);
1895
+ return scalar ? compactInline(scalar) : "";
1896
+ }
1897
+
1876
1898
  function renderWorkspaceChangeEvent(entry) {
1877
1899
  const data = entry.data || {};
1878
1900
  const toolName = data.toolName || data.action || "file.changed";
@@ -1912,12 +1934,7 @@ function renderToolEvent(entry) {
1912
1934
  const args = data.args || {};
1913
1935
  const resultText = String(data.result || args.result || "");
1914
1936
  const embeddedResult = resultText ? renderEmbeddedWorkspaceResult(resultText, entry) : "";
1915
- const argPreview =
1916
- toolName === "finish"
1917
- ? ""
1918
- : toolName === "run_command" && args.command
1919
- ? args.command
1920
- : args.path || args.url || args.query || args.q || (Object.keys(args).length ? JSON.stringify(args) : "");
1937
+ const argPreview = toolArgumentPreview(toolName, args, data);
1921
1938
  const stdout = data.stdout ? outputPreviewText(data.stdout) : null;
1922
1939
  const stderr = data.stderr ? outputPreviewText(data.stderr) : null;
1923
1940
  return `
@@ -1990,6 +2007,66 @@ function renderStructuredEvent(entry) {
1990
2007
  return "";
1991
2008
  }
1992
2009
 
2010
+ function logToolLifecycleKey(data = {}) {
2011
+ const args = data.args || {};
2012
+ const focusedArgs = {
2013
+ path: args.path || args.file || data.path || "",
2014
+ command: args.command || data.command || "",
2015
+ title: args.title || data.title || "",
2016
+ kind: args.kind || data.kind || "",
2017
+ query: args.query || args.q || "",
2018
+ url: args.url || data.url || "",
2019
+ };
2020
+ return `${data.toolName || ""}:${JSON.stringify(focusedArgs)}`;
2021
+ }
2022
+
2023
+ function logEventDedupContext(entries = [], runResult = "") {
2024
+ const terminalToolKeys = new Set();
2025
+ const finishResultFingerprints = new Set();
2026
+ const workspaceChangePaths = new Set();
2027
+ const runResultFingerprint = compactInline(runResult, 100000);
2028
+
2029
+ for (const entry of entries || []) {
2030
+ const data = entry.data || {};
2031
+ const type = entry.message || entry.eventType || "";
2032
+ if (["tool.completed", "tool.failed", "tool.skipped", "tool.blocked"].includes(type)) {
2033
+ terminalToolKeys.add(logToolLifecycleKey(data));
2034
+ }
2035
+ if (type === "tool.completed" && data.toolName === "finish" && data.result) {
2036
+ finishResultFingerprints.add(compactInline(data.result, 100000));
2037
+ }
2038
+ if (type === "session.finished" && data.result) {
2039
+ finishResultFingerprints.add(compactInline(data.result, 100000));
2040
+ }
2041
+ if (type === "file.changed" && (data.path || data.args?.path)) {
2042
+ workspaceChangePaths.add(String(data.path || data.args?.path || "").replace(/\\/g, "/"));
2043
+ }
2044
+ }
2045
+
2046
+ return { terminalToolKeys, finishResultFingerprints, workspaceChangePaths, runResultFingerprint };
2047
+ }
2048
+
2049
+ function shouldSuppressLogEntry(entry = {}, context = {}) {
2050
+ const data = entry.data || {};
2051
+ const type = entry.message || entry.eventType || "";
2052
+ const toolName = data.toolName || "";
2053
+ const toolKey = logToolLifecycleKey(data);
2054
+ const resultKey = data.result || data.args?.result ? compactInline(data.result || data.args?.result, 100000) : "";
2055
+ if (type === "tool.started" && context.terminalToolKeys?.has(toolKey)) return true;
2056
+ if (type === "tool.started" && toolName === "finish" && resultKey) {
2057
+ return resultKey === context.runResultFingerprint || context.finishResultFingerprints?.has(resultKey);
2058
+ }
2059
+ if (type === "tool.completed" && toolName === "finish" && resultKey && resultKey === context.runResultFingerprint) return true;
2060
+ if (type === "session.finished" && resultKey) {
2061
+ return resultKey === context.runResultFingerprint || context.finishResultFingerprints?.has(resultKey);
2062
+ }
2063
+ if (type === "tool.completed" && ["write_file", "apply_patch"].includes(toolName)) {
2064
+ const pathKey = String(data.path || data.args?.path || data.args?.file || "").replace(/\\/g, "/");
2065
+ if (pathKey && context.workspaceChangePaths?.has(pathKey)) return true;
2066
+ }
2067
+ return false;
2068
+ }
2069
+
1993
2070
  function renderLogs(run) {
1994
2071
  logsEl.dataset.mode = "active";
1995
2072
  const structuredRunResult = run.result ? renderEmbeddedWorkspaceResult(run.result, { at: run.endedAt || run.updatedAt || "" }) : "";
@@ -2003,7 +2080,9 @@ function renderLogs(run) {
2003
2080
  run.error ? `<div class="log-line error">${escapeHtml(`error=${run.error}`)}</div>` : "",
2004
2081
  ];
2005
2082
 
2083
+ const dedupContext = logEventDedupContext(run.logs || [], run.result || "");
2006
2084
  for (const entry of run.logs || []) {
2085
+ if (shouldSuppressLogEntry(entry, dedupContext)) continue;
2007
2086
  const structured = renderStructuredEvent(entry);
2008
2087
  if (structured) {
2009
2088
  parts.push(structured);
@@ -2286,10 +2365,17 @@ function renderChat(chatEntries) {
2286
2365
  }
2287
2366
  const role = entry.role === "assistant" ? "assistant" : "user";
2288
2367
  const label = role === "assistant" ? t("assistantLabel") : t("youLabel");
2289
- const content =
2290
- role === "assistant"
2291
- ? `<div class="markdown-body">${renderMarkdown(entry.content)}</div>`
2292
- : escapeHtml(entry.content).replace(/\n/g, "<br>");
2368
+ let content = escapeHtml(entry.content).replace(/\n/g, "<br>");
2369
+ if (role === "assistant") {
2370
+ const embeddedResult = renderEmbeddedWorkspaceResult(entry.content, entry);
2371
+ if (embeddedResult && entry.suppressEmbeddedWorkspaceDiffs) {
2372
+ content = `<div class="markdown-body">${renderMarkdown(embeddedWorkspaceSummary(entry.content))}</div>`;
2373
+ } else if (embeddedResult) {
2374
+ content = `<article class="event-card event-finish assistant-result-card">${embeddedResult}</article>`;
2375
+ } else {
2376
+ content = `<div class="markdown-body">${renderMarkdown(entry.content)}</div>`;
2377
+ }
2378
+ }
2293
2379
  return `
2294
2380
  <article class="chat-item ${role}">
2295
2381
  <div class="chat-meta">${label}${entry.at ? ` · ${new Date(entry.at).toLocaleString()}` : ""}</div>
package/public/styles.css CHANGED
@@ -1371,6 +1371,10 @@ button.danger {
1371
1371
  box-shadow: none;
1372
1372
  }
1373
1373
 
1374
+ .assistant-result-card {
1375
+ margin: 0;
1376
+ }
1377
+
1374
1378
  #logs .event-fold-note {
1375
1379
  color: #94a3b8;
1376
1380
  }
@@ -896,8 +896,12 @@ try {
896
896
  if (!latest.stdout.includes(" write ") || !latest.stdout.includes("+Created by AgInTiFlow mock mode.")) {
897
897
  throw new Error("bare aginti resume did not replay formatted file-change diff context");
898
898
  }
899
- if (!/\bfinish\b/.test(latest.stdout) || latest.stdout.includes('{"result":"Mock run complete')) {
900
- throw new Error("bare aginti resume did not render finish-result diffs in formatted mode");
899
+ if (latest.stdout.includes('{"result":"Mock run complete')) {
900
+ throw new Error("bare aginti resume rendered raw finish JSON instead of formatted history");
901
+ }
902
+ const mockCompleteCount = (latest.stdout.match(/Mock run complete\./g) || []).length;
903
+ if (mockCompleteCount > 1) {
904
+ throw new Error(`bare aginti resume duplicated the finish result ${mockCompleteCount} times`);
901
905
  }
902
906
  if (latest.stdout.includes("resume note=showing chat transcript only")) {
903
907
  throw new Error("bare aginti resume regressed to chat-only history");
@@ -224,6 +224,14 @@ try {
224
224
  const chatTail = (await page.locator("#chat-thread").innerText().catch(() => "")).slice(-1200);
225
225
  throw new Error(`web UI did not render formatted finish-result diff event; state=${state}\nlogs tail:\n${logsTail}\nchat tail:\n${chatTail}`);
226
226
  }
227
+ const formattedChatText = await page.locator("#chat-thread").innerText();
228
+ if (formattedChatText.includes('{"result":"Mock run complete')) {
229
+ throw new Error("web UI rendered raw finish JSON in chat history");
230
+ }
231
+ const mockCompleteCount = (formattedChatText.match(/Mock run complete\./g) || []).length;
232
+ if (mockCompleteCount > 1) {
233
+ throw new Error(`web UI duplicated the assistant/session finish result ${mockCompleteCount} times`);
234
+ }
227
235
 
228
236
  await page.click("#open-settings");
229
237
  await page.waitForSelector("#settings-modal[open]");
@@ -262,6 +270,7 @@ try {
262
270
  "formatted-plan-event-card",
263
271
  "formatted-file-diff-event-card",
264
272
  "formatted-finish-result-diff-card",
273
+ "deduped-finish-history",
265
274
  "settings-provider-dropdowns",
266
275
  "settings-wrapper-dropdowns",
267
276
  ],
@@ -843,6 +843,47 @@ function embeddedWorkspaceSummary(value = "") {
843
843
  return summary.join("\n").replace(/\n{3,}/g, "\n\n").trim();
844
844
  }
845
845
 
846
+ function normalizedTimelineText(value = "") {
847
+ return String(value || "").replace(/\r\n?/g, "\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
848
+ }
849
+
850
+ function timelineTextFingerprint(value = "") {
851
+ return normalizedTimelineText(value).replace(/\s+/g, " ").trim();
852
+ }
853
+
854
+ function workspaceChangeFingerprint(change = {}) {
855
+ const pathKey = String(change.path || change.args?.path || "").replace(/\\/g, "/").trim();
856
+ const diffKey = normalizedTimelineText(change.diff || "");
857
+ return pathKey || diffKey ? `${pathKey}\n${diffKey}` : "";
858
+ }
859
+
860
+ function eventWorkspaceChangeFingerprint(event = {}) {
861
+ const data = event.data || {};
862
+ if (event.type === "file.changed") return workspaceChangeFingerprint(data);
863
+ if ((event.type === "tool.completed" || event.type === "tool.failed") && data.diff) return workspaceChangeFingerprint(data);
864
+ return "";
865
+ }
866
+
867
+ function eventWorkspaceChangePath(event = {}) {
868
+ const data = event.data || {};
869
+ if (event.type !== "file.changed" && !data.diff) return "";
870
+ return String(data.path || data.args?.path || "").replace(/\\/g, "/").trim();
871
+ }
872
+
873
+ function toolLifecycleKey(event = {}) {
874
+ const data = event.data || {};
875
+ const args = data.args || {};
876
+ const focusedArgs = {
877
+ path: args.path || args.file || data.path || "",
878
+ command: args.command || data.command || "",
879
+ title: args.title || data.title || "",
880
+ kind: args.kind || data.kind || "",
881
+ query: args.query || args.q || "",
882
+ url: args.url || data.url || "",
883
+ };
884
+ return `${data.toolName || ""}:${JSON.stringify(focusedArgs)}`;
885
+ }
886
+
846
887
  function printWorkspaceChange(change = {}) {
847
888
  if (!change?.diff) return;
848
889
  const formatted = formatWorkspaceChange(change);
@@ -852,12 +893,14 @@ function printWorkspaceChange(change = {}) {
852
893
  for (const line of formatted.lines) outputLine(`${gutter}${line}`);
853
894
  }
854
895
 
855
- function printEmbeddedWorkspaceResult(result = "", { labelName = "finish", time = "" } = {}) {
896
+ function printEmbeddedWorkspaceResult(result = "", { labelName = "finish", time = "", bg = ansi.systemBg, includeDiffs = true } = {}) {
856
897
  const changes = embeddedWorkspaceChangesFromText(result);
857
898
  if (changes.length === 0) return false;
858
899
  const summary = embeddedWorkspaceSummary(result);
859
- if (summary) printHistoryBlock(labelName, summary, { time, bg: ansi.systemBg });
860
- for (const change of changes) printWorkspaceChange(change);
900
+ if (summary) printHistoryBlock(labelName, summary, { time, bg });
901
+ if (includeDiffs) {
902
+ for (const change of changes) printWorkspaceChange(change);
903
+ }
861
904
  return true;
862
905
  }
863
906
 
@@ -2296,6 +2339,17 @@ function printHistoryEntry(entry) {
2296
2339
  const role = entry.role === "assistant" ? "aginti>" : entry.role === "user" ? "user>" : String(entry.role || "note");
2297
2340
  const bg = role === "aginti>" ? ansi.agentBg : role === "user>" ? ansi.userBg : ansi.systemBg;
2298
2341
  const time = entry.at ? new Date(entry.at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "";
2342
+ if (
2343
+ entry.role === "assistant" &&
2344
+ printEmbeddedWorkspaceResult(entry.content, {
2345
+ labelName: role,
2346
+ time,
2347
+ bg,
2348
+ includeDiffs: !entry.suppressEmbeddedWorkspaceDiffs,
2349
+ })
2350
+ ) {
2351
+ return;
2352
+ }
2299
2353
  printHistoryBlock(role, entry.content, { time, bg });
2300
2354
  }
2301
2355
 
@@ -2335,16 +2389,66 @@ function isReplayableResumeEvent(event = {}) {
2335
2389
  function resumeTimelineItems(chat = [], events = [], { limit = 0 } = {}) {
2336
2390
  const shownChat = limit > 0 ? chat.slice(-limit) : chat;
2337
2391
  const firstChatTime = limit > 0 && shownChat.length > 0 ? timestampMs(shownChat[0].at) : 0;
2338
- const chatItems = shownChat.map((entry, index) => ({
2339
- kind: "chat",
2340
- entry,
2341
- order: index * 2,
2342
- at: timestampMs(entry.at),
2343
- }));
2344
- const eventItems = events
2392
+ const eventList = Array.isArray(events) ? events : [];
2393
+ const workspaceChangeKeys = new Set(eventList.map(eventWorkspaceChangeFingerprint).filter(Boolean));
2394
+ const workspaceChangePaths = new Set(eventList.map(eventWorkspaceChangePath).filter(Boolean));
2395
+ const chatResultFingerprints = new Set(
2396
+ (Array.isArray(chat) ? chat : [])
2397
+ .filter((entry) => entry?.role === "assistant" && entry?.content)
2398
+ .map((entry) => timelineTextFingerprint(entry.content))
2399
+ .filter(Boolean)
2400
+ );
2401
+ const finishResultFingerprints = new Set(
2402
+ eventList
2403
+ .filter((event) => (event.type === "tool.completed" && event.data?.toolName === "finish") || event.type === "session.finished")
2404
+ .map((event) => timelineTextFingerprint(event.data?.result || ""))
2405
+ .filter(Boolean)
2406
+ );
2407
+ const terminalToolKeys = new Set(
2408
+ eventList
2409
+ .filter((event) => ["tool.completed", "tool.failed", "tool.skipped", "tool.blocked"].includes(event.type))
2410
+ .map(toolLifecycleKey)
2411
+ .filter(Boolean)
2412
+ );
2413
+ const shouldSuppressEvent = (event = {}) => {
2414
+ const type = String(event.type || "");
2415
+ const data = event.data || {};
2416
+ const toolName = String(data.toolName || "");
2417
+ const resultFingerprint = timelineTextFingerprint(data.result || data.args?.result || "");
2418
+ if (type === "tool.started" && terminalToolKeys.has(toolLifecycleKey(event))) return true;
2419
+ if (type === "tool.started" && toolName === "finish" && resultFingerprint) {
2420
+ return chatResultFingerprints.has(resultFingerprint) || finishResultFingerprints.has(resultFingerprint);
2421
+ }
2422
+ if (["tool.completed", "tool.failed", "tool.skipped"].includes(type)) {
2423
+ if (toolName === "finish" && resultFingerprint && chatResultFingerprints.has(resultFingerprint)) return true;
2424
+ if (["write_file", "apply_patch"].includes(toolName)) {
2425
+ const pathKey = String(data.path || data.args?.path || data.args?.file || "").replace(/\\/g, "/").trim();
2426
+ if (pathKey && workspaceChangePaths.has(pathKey)) return true;
2427
+ const changeKey = eventWorkspaceChangeFingerprint(event);
2428
+ if (changeKey && workspaceChangeKeys.has(changeKey)) return true;
2429
+ }
2430
+ }
2431
+ if (type === "session.finished" && resultFingerprint) {
2432
+ return chatResultFingerprints.has(resultFingerprint) || finishResultFingerprints.has(resultFingerprint);
2433
+ }
2434
+ return false;
2435
+ };
2436
+ const chatItems = shownChat.map((entry, index) => {
2437
+ const embeddedChanges = entry.role === "assistant" ? embeddedWorkspaceChangesFromText(entry.content) : [];
2438
+ const suppressEmbeddedWorkspaceDiffs =
2439
+ embeddedChanges.length > 0 && embeddedChanges.every((change) => workspaceChangeKeys.has(workspaceChangeFingerprint(change)));
2440
+ return {
2441
+ kind: "chat",
2442
+ entry: { ...entry, suppressEmbeddedWorkspaceDiffs },
2443
+ order: index * 2,
2444
+ at: timestampMs(entry.at),
2445
+ };
2446
+ });
2447
+ const eventItems = eventList
2345
2448
  .map((event, index) => ({ event, index }))
2346
2449
  .filter(({ event }) => isReplayableResumeEvent(event))
2347
2450
  .filter(({ event }) => !firstChatTime || timestampMs(event.timestamp) >= firstChatTime)
2451
+ .filter(({ event }) => !shouldSuppressEvent(event))
2348
2452
  .map(({ event, index }) => ({
2349
2453
  kind: "event",
2350
2454
  event,
@@ -2463,6 +2567,9 @@ function toolStatusDetails(data = {}) {
2463
2567
  if ((tool === "write_file" || tool === "apply_patch" || tool === "read_file" || tool === "open_workspace_file") && args.path) {
2464
2568
  return `${tool}: ${compactLine(args.path, 58)}`;
2465
2569
  }
2570
+ if ((tool === "send_to_canvas" || tool === "create_artifact") && (args.title || args.kind)) {
2571
+ return `${tool}: ${compactLine([args.title || "canvas artifact", args.kind || "", args.selected ? "selected" : ""].filter(Boolean).join(" · "), 72)}`;
2572
+ }
2466
2573
  if ((tool === "open_url" || tool === "web_research" || tool === "web_search") && (args.url || args.query || args.q)) {
2467
2574
  return `${tool}: ${compactLine(args.url || args.query || args.q, 58)}`;
2468
2575
  }
package/web.js CHANGED
@@ -107,6 +107,124 @@ function compactText(value = "", limit = 120) {
107
107
  return text.length <= limit ? text : `${text.slice(0, Math.max(limit - 1, 1)).trim()}...`;
108
108
  }
109
109
 
110
+ function normalizedTimelineText(value = "") {
111
+ return String(value || "").replace(/\r\n?/g, "\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
112
+ }
113
+
114
+ function timelineTextFingerprint(value = "") {
115
+ return normalizedTimelineText(value).replace(/\s+/g, " ").trim();
116
+ }
117
+
118
+ function embeddedWorkspaceChangesFromText(value = "") {
119
+ const lines = String(value || "").split(/\r?\n/);
120
+ const changes = [];
121
+ let currentTool = "";
122
+
123
+ for (let index = 0; index < lines.length; index += 1) {
124
+ const toolMatch = lines[index].match(/^\s*Tool:\s*(.+?)\s*$/i);
125
+ if (toolMatch) {
126
+ currentTool = toolMatch[1].trim();
127
+ continue;
128
+ }
129
+
130
+ const pathMatch = lines[index].match(/^\s*Path:\s*(.+?)\s*$/i);
131
+ if (!pathMatch) continue;
132
+
133
+ let cursor = index + 1;
134
+ while (cursor < lines.length && !/^\s*Diff:\s*$/i.test(lines[cursor]) && !/^\s*(?:Tool|Path):\s*/i.test(lines[cursor])) {
135
+ cursor += 1;
136
+ }
137
+ if (!/^\s*Diff:\s*$/i.test(lines[cursor] || "")) continue;
138
+
139
+ const diffLines = [];
140
+ cursor += 1;
141
+ while (cursor < lines.length) {
142
+ if (diffLines.length && /^\s*(?:Tool|Path):\s*/i.test(lines[cursor])) break;
143
+ if (
144
+ diffLines.length &&
145
+ /^\s*(?:Output:|No command output|Blocked by guardrail\.|Mock run complete\.)/i.test(lines[cursor]) &&
146
+ !/^[-+@ ]/.test(lines[cursor])
147
+ ) {
148
+ break;
149
+ }
150
+ diffLines.push(lines[cursor]);
151
+ cursor += 1;
152
+ }
153
+ while (diffLines.length && !diffLines[diffLines.length - 1].trim()) diffLines.pop();
154
+ if (diffLines.length) {
155
+ changes.push({
156
+ toolName: currentTool || "write_file",
157
+ path: pathMatch[1].trim(),
158
+ diff: diffLines.join("\n"),
159
+ });
160
+ index = Math.max(index, cursor - 1);
161
+ }
162
+ }
163
+
164
+ return changes;
165
+ }
166
+
167
+ function workspaceChangeFingerprint(change = {}) {
168
+ const pathKey = String(change.path || change.args?.path || "").replace(/\\/g, "/").trim();
169
+ const diffKey = normalizedTimelineText(change.diff || "");
170
+ return pathKey || diffKey ? `${pathKey}\n${diffKey}` : "";
171
+ }
172
+
173
+ function eventWorkspaceChangeFingerprint(event = {}) {
174
+ const data = event.data || {};
175
+ if (event.type === "file.changed") return workspaceChangeFingerprint(data);
176
+ if ((event.type === "tool.completed" || event.type === "tool.failed") && data.diff) return workspaceChangeFingerprint(data);
177
+ return "";
178
+ }
179
+
180
+ function eventWorkspaceChangePath(event = {}) {
181
+ const data = event.data || {};
182
+ if (event.type !== "file.changed" && !data.diff) return "";
183
+ return String(data.path || data.args?.path || "").replace(/\\/g, "/").trim();
184
+ }
185
+
186
+ function toolLifecycleKey(event = {}) {
187
+ const data = event.data || {};
188
+ const args = data.args || {};
189
+ const focusedArgs = {
190
+ path: args.path || args.file || data.path || "",
191
+ command: args.command || data.command || "",
192
+ title: args.title || data.title || "",
193
+ kind: args.kind || data.kind || "",
194
+ query: args.query || args.q || "",
195
+ url: args.url || data.url || "",
196
+ };
197
+ return `${data.toolName || ""}:${JSON.stringify(focusedArgs)}`;
198
+ }
199
+
200
+ function shouldSuppressTimelineEvent(event = {}, context = {}) {
201
+ const type = String(event.type || "");
202
+ const data = event.data || {};
203
+ const toolName = String(data.toolName || "");
204
+ const resultFingerprint = timelineTextFingerprint(data.result || data.args?.result || "");
205
+
206
+ if (type === "tool.started" && context.terminalToolKeys.has(toolLifecycleKey(event))) return true;
207
+ if (type === "tool.started" && toolName === "finish" && resultFingerprint) {
208
+ return context.chatResultFingerprints.has(resultFingerprint) || context.finishResultFingerprints.has(resultFingerprint);
209
+ }
210
+
211
+ if (["tool.completed", "tool.failed", "tool.skipped"].includes(type)) {
212
+ if (toolName === "finish" && resultFingerprint && context.chatResultFingerprints.has(resultFingerprint)) return true;
213
+ if (["write_file", "apply_patch"].includes(toolName)) {
214
+ const pathKey = String(data.path || data.args?.path || data.args?.file || "").replace(/\\/g, "/").trim();
215
+ if (pathKey && context.workspaceChangePaths.has(pathKey)) return true;
216
+ const changeKey = eventWorkspaceChangeFingerprint(event);
217
+ if (changeKey && context.workspaceChangeKeys.has(changeKey)) return true;
218
+ }
219
+ }
220
+
221
+ if (type === "session.finished" && resultFingerprint) {
222
+ return context.chatResultFingerprints.has(resultFingerprint) || context.finishResultFingerprints.has(resultFingerprint);
223
+ }
224
+
225
+ return false;
226
+ }
227
+
110
228
  function toolTimelineSummary(data = {}) {
111
229
  const tool = data.toolName || "unknown";
112
230
  const args = data.args || {};
@@ -224,10 +342,44 @@ function timelineEntryForEvent(event = {}) {
224
342
  }
225
343
 
226
344
  function sessionTimelineFromChatAndEvents(chat = [], events = []) {
345
+ const eventList = Array.isArray(events) ? events : [];
346
+ const workspaceChangeKeys = new Set(eventList.map(eventWorkspaceChangeFingerprint).filter(Boolean));
347
+ const workspaceChangePaths = new Set(eventList.map(eventWorkspaceChangePath).filter(Boolean));
348
+ const chatResultFingerprints = new Set(
349
+ (Array.isArray(chat) ? chat : [])
350
+ .filter((entry) => entry?.role === "assistant" && entry?.content)
351
+ .map((entry) => timelineTextFingerprint(entry.content))
352
+ .filter(Boolean)
353
+ );
354
+ const finishResultFingerprints = new Set(
355
+ eventList
356
+ .filter((event) => (event.type === "tool.completed" && event.data?.toolName === "finish") || event.type === "session.finished")
357
+ .map((event) => timelineTextFingerprint(event.data?.result || ""))
358
+ .filter(Boolean)
359
+ );
360
+ const terminalToolKeys = new Set(
361
+ eventList
362
+ .filter((event) => ["tool.completed", "tool.failed", "tool.skipped", "tool.blocked"].includes(event.type))
363
+ .map(toolLifecycleKey)
364
+ .filter(Boolean)
365
+ );
366
+ const suppressContext = {
367
+ chatResultFingerprints,
368
+ finishResultFingerprints,
369
+ terminalToolKeys,
370
+ workspaceChangeKeys,
371
+ workspaceChangePaths,
372
+ };
227
373
  const chatItems = (Array.isArray(chat) ? chat : [])
228
374
  .filter((entry) => entry?.content)
229
- .map((entry, index) => ({ ...entry, order: index * 2, sortAt: Date.parse(entry.at || "") || 0 }));
230
- const eventItems = (Array.isArray(events) ? events : [])
375
+ .map((entry, index) => {
376
+ const embeddedChanges = entry.role === "assistant" ? embeddedWorkspaceChangesFromText(entry.content) : [];
377
+ const suppressEmbeddedWorkspaceDiffs =
378
+ embeddedChanges.length > 0 && embeddedChanges.every((change) => workspaceChangeKeys.has(workspaceChangeFingerprint(change)));
379
+ return { ...entry, suppressEmbeddedWorkspaceDiffs, order: index * 2, sortAt: Date.parse(entry.at || "") || 0 };
380
+ });
381
+ const eventItems = eventList
382
+ .filter((event) => !shouldSuppressTimelineEvent(event, suppressContext))
231
383
  .map(timelineEntryForEvent)
232
384
  .filter(Boolean)
233
385
  .map((entry, index) => ({ ...entry, order: index * 2 + 1, sortAt: Date.parse(entry.at || "") || 0 }));