@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 +1 -1
- package/public/app.js +96 -10
- package/public/styles.css +4 -0
- package/scripts/smoke-cli-chat.js +6 -2
- package/scripts/smoke-web-ui.js +9 -0
- package/src/interactive-cli.js +117 -10
- package/web.js +154 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
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
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
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
|
@@ -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 (
|
|
900
|
-
throw new Error("bare aginti resume
|
|
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");
|
package/scripts/smoke-web-ui.js
CHANGED
|
@@ -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
|
],
|
package/src/interactive-cli.js
CHANGED
|
@@ -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
|
|
860
|
-
|
|
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
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
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) =>
|
|
230
|
-
|
|
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 }));
|