@lazyingart/agintiflow 0.20.171 → 0.20.173
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 +137 -7
- package/public/styles.css +139 -0
- package/scripts/smoke-cli-chat.js +9 -3
- package/scripts/smoke-web-api.js +14 -0
- package/scripts/smoke-web-ui.js +41 -3
- package/scripts/smoke-webapp-command.js +24 -2
- package/src/interactive-cli.js +136 -10
- package/web.js +143 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.173",
|
|
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
|
@@ -1782,6 +1782,131 @@ function renderPermissionApproval(entry) {
|
|
|
1782
1782
|
`;
|
|
1783
1783
|
}
|
|
1784
1784
|
|
|
1785
|
+
function renderPlanEvent(entry) {
|
|
1786
|
+
const data = entry.data || {};
|
|
1787
|
+
const plan = data.plan || entry.content || "";
|
|
1788
|
+
return `
|
|
1789
|
+
<article class="event-card event-plan">
|
|
1790
|
+
<div class="event-card-meta">
|
|
1791
|
+
<span>plan</span>
|
|
1792
|
+
<span>${entry.at ? escapeHtml(new Date(entry.at).toLocaleString()) : ""}</span>
|
|
1793
|
+
</div>
|
|
1794
|
+
<div class="markdown-body">${renderMarkdown(plan)}</div>
|
|
1795
|
+
</article>
|
|
1796
|
+
`;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
function renderWorkspaceChangeEvent(entry) {
|
|
1800
|
+
const data = entry.data || {};
|
|
1801
|
+
const toolName = data.toolName || data.action || "file.changed";
|
|
1802
|
+
const isPatch = toolName === "apply_patch" || String(toolName).startsWith("apply_patch");
|
|
1803
|
+
const diff = String(data.diff || "");
|
|
1804
|
+
const maxDiffChars = 9000;
|
|
1805
|
+
const diffBlock = diff
|
|
1806
|
+
? `<pre class="change-diff event-diff">${renderDiffHtml(diff, maxDiffChars)}</pre>${
|
|
1807
|
+
diff.length > maxDiffChars ? `<div class="event-fold-note">... diff truncated for browser preview</div>` : ""
|
|
1808
|
+
}`
|
|
1809
|
+
: "";
|
|
1810
|
+
const hashes =
|
|
1811
|
+
data.beforeHash || data.afterHash
|
|
1812
|
+
? `<div class="event-card-meta"><span>before=${escapeHtml((data.beforeHash || "new").slice(0, 10))}</span><span>after=${escapeHtml((data.afterHash || "").slice(0, 10))}</span></div>`
|
|
1813
|
+
: "";
|
|
1814
|
+
return `
|
|
1815
|
+
<article class="event-card ${isPatch ? "event-patch" : "event-write"}">
|
|
1816
|
+
<div class="event-card-meta">
|
|
1817
|
+
<span>${escapeHtml(isPatch ? "patch" : "write")}</span>
|
|
1818
|
+
<span>${escapeHtml(toolName)}</span>
|
|
1819
|
+
<span>${entry.at ? escapeHtml(new Date(entry.at).toLocaleString()) : ""}</span>
|
|
1820
|
+
</div>
|
|
1821
|
+
<strong class="event-path">${escapeHtml(data.path || entry.content || "")}</strong>
|
|
1822
|
+
${data.created ? `<div class="event-fold-note">created</div>` : ""}
|
|
1823
|
+
${hashes}
|
|
1824
|
+
${diffBlock || `<div class="event-fold-note">No diff preview recorded for this file event.</div>`}
|
|
1825
|
+
</article>
|
|
1826
|
+
`;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
function renderToolEvent(entry) {
|
|
1830
|
+
const data = entry.data || {};
|
|
1831
|
+
const message = entry.message || entry.eventType || "";
|
|
1832
|
+
const failed = message === "tool.failed" || data.ok === false || data.blocked || data.error;
|
|
1833
|
+
const skipped = message === "tool.skipped";
|
|
1834
|
+
const toolName = data.toolName || "tool";
|
|
1835
|
+
const args = data.args || {};
|
|
1836
|
+
const argPreview =
|
|
1837
|
+
toolName === "run_command" && args.command
|
|
1838
|
+
? args.command
|
|
1839
|
+
: args.path || args.url || args.query || args.q || (Object.keys(args).length ? JSON.stringify(args) : "");
|
|
1840
|
+
const stdout = data.stdout ? outputPreviewText(data.stdout) : null;
|
|
1841
|
+
const stderr = data.stderr ? outputPreviewText(data.stderr) : null;
|
|
1842
|
+
return `
|
|
1843
|
+
<article class="event-card ${failed ? "event-failed" : skipped ? "event-muted" : "event-tool"}">
|
|
1844
|
+
<div class="event-card-meta">
|
|
1845
|
+
<span>${escapeHtml(failed ? "tool failed" : skipped ? "tool skipped" : message === "tool.started" ? "tool" : "tool done")}</span>
|
|
1846
|
+
<span>${entry.at ? escapeHtml(new Date(entry.at).toLocaleString()) : ""}</span>
|
|
1847
|
+
</div>
|
|
1848
|
+
<strong class="event-path">${escapeHtml(toolName)}</strong>
|
|
1849
|
+
${argPreview ? `<code class="event-inline-code">${escapeHtml(argPreview)}</code>` : ""}
|
|
1850
|
+
${data.error || data.reason ? `<div class="event-fold-note">${escapeHtml(data.error || data.reason)}</div>` : ""}
|
|
1851
|
+
${stdout ? `<div class="log-stream-title">stdout</div><pre class="event-output">${escapeHtml(stdout.text)}</pre>` : ""}
|
|
1852
|
+
${stdout?.hidden > 0 ? `<div class="event-fold-note">... ${stdout.hidden} more stdout line(s) folded</div>` : ""}
|
|
1853
|
+
${stderr ? `<div class="log-stream-title">stderr</div><pre class="event-output">${escapeHtml(stderr.text)}</pre>` : ""}
|
|
1854
|
+
${stderr?.hidden > 0 ? `<div class="event-fold-note">... ${stderr.hidden} more stderr line(s) folded</div>` : ""}
|
|
1855
|
+
</article>
|
|
1856
|
+
`;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function renderStatusEvent(entry) {
|
|
1860
|
+
const data = entry.data || {};
|
|
1861
|
+
const message = entry.message || entry.eventType || "event";
|
|
1862
|
+
const label = entry.eventLabel || message.replace(/^[^.]+\./, "");
|
|
1863
|
+
const content = data.result || data.error || data.reason || entry.content || "";
|
|
1864
|
+
const failed = message === "session.failed";
|
|
1865
|
+
return `
|
|
1866
|
+
<article class="event-card ${failed ? "event-failed" : "event-muted"}">
|
|
1867
|
+
<div class="event-card-meta">
|
|
1868
|
+
<span>${escapeHtml(label)}</span>
|
|
1869
|
+
<span>${entry.at ? escapeHtml(new Date(entry.at).toLocaleString()) : ""}</span>
|
|
1870
|
+
</div>
|
|
1871
|
+
${content ? `<div class="chat-content">${escapeHtml(content)}</div>` : ""}
|
|
1872
|
+
</article>
|
|
1873
|
+
`;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function renderStructuredEvent(entry) {
|
|
1877
|
+
const message = entry.message || entry.eventType || "";
|
|
1878
|
+
if (message === "plan.created") return renderPlanEvent(entry);
|
|
1879
|
+
if (message === "command.output") return renderCommandOutputLog(entry);
|
|
1880
|
+
if (message === "file.changed") return renderWorkspaceChangeEvent(entry);
|
|
1881
|
+
if (message === "tool.blocked" && entry.data?.permissionAdvice) return renderPermissionApproval(entry);
|
|
1882
|
+
if (message === "tool.blocked") {
|
|
1883
|
+
return `
|
|
1884
|
+
<article class="event-card event-failed">
|
|
1885
|
+
<div class="event-card-meta">
|
|
1886
|
+
<span>permission</span>
|
|
1887
|
+
<span>${entry.at ? escapeHtml(new Date(entry.at).toLocaleString()) : ""}</span>
|
|
1888
|
+
</div>
|
|
1889
|
+
<strong class="event-path">${escapeHtml(entry.data?.toolName || "blocked")}</strong>
|
|
1890
|
+
<div class="chat-content">${escapeHtml(entry.data?.reason || entry.content || "Permission blocked.")}</div>
|
|
1891
|
+
</article>
|
|
1892
|
+
`;
|
|
1893
|
+
}
|
|
1894
|
+
if (message === "tool.started" || message === "tool.completed" || message === "tool.failed" || message === "tool.skipped") {
|
|
1895
|
+
return renderToolEvent(entry);
|
|
1896
|
+
}
|
|
1897
|
+
if (
|
|
1898
|
+
message === "budget.initialized" ||
|
|
1899
|
+
message === "conversation.continued" ||
|
|
1900
|
+
message === "conversation.queued_input_applied" ||
|
|
1901
|
+
message === "session.finished" ||
|
|
1902
|
+
message === "session.failed" ||
|
|
1903
|
+
message === "session.stopped"
|
|
1904
|
+
) {
|
|
1905
|
+
return renderStatusEvent(entry);
|
|
1906
|
+
}
|
|
1907
|
+
return "";
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1785
1910
|
function renderLogs(run) {
|
|
1786
1911
|
logsEl.dataset.mode = "active";
|
|
1787
1912
|
const parts = [
|
|
@@ -1791,15 +1916,12 @@ function renderLogs(run) {
|
|
|
1791
1916
|
];
|
|
1792
1917
|
|
|
1793
1918
|
for (const entry of run.logs || []) {
|
|
1794
|
-
|
|
1795
|
-
|
|
1919
|
+
const structured = renderStructuredEvent(entry);
|
|
1920
|
+
if (structured) {
|
|
1921
|
+
parts.push(structured);
|
|
1796
1922
|
continue;
|
|
1797
1923
|
}
|
|
1798
1924
|
parts.push(`<div class="log-line">${escapeHtml(`[${entry.at}] ${entry.kind}: ${entry.message}`)}</div>`);
|
|
1799
|
-
if (entry.message === "tool.blocked" && entry.data?.permissionAdvice) {
|
|
1800
|
-
parts.push(renderPermissionApproval(entry));
|
|
1801
|
-
continue;
|
|
1802
|
-
}
|
|
1803
1925
|
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
1804
1926
|
parts.push(`<pre class="log-json">${escapeHtml(JSON.stringify(entry.data, null, 2))}</pre>`);
|
|
1805
1927
|
}
|
|
@@ -2044,6 +2166,14 @@ function renderChat(chatEntries) {
|
|
|
2044
2166
|
|
|
2045
2167
|
chatThreadEl.innerHTML = lastChatEntries
|
|
2046
2168
|
.map((entry) => {
|
|
2169
|
+
if (entry.role === "event") {
|
|
2170
|
+
return renderStructuredEvent(entry) || `
|
|
2171
|
+
<article class="chat-item event" data-event-type="${escapeHtml(entry.eventType || "")}">
|
|
2172
|
+
<div class="chat-meta">${escapeHtml(entry.eventLabel || entry.eventType || "event")}${entry.at ? ` · ${new Date(entry.at).toLocaleString()}` : ""}</div>
|
|
2173
|
+
<div class="chat-content">${escapeHtml(entry.content || "").replace(/\n/g, "<br>")}</div>
|
|
2174
|
+
</article>
|
|
2175
|
+
`;
|
|
2176
|
+
}
|
|
2047
2177
|
const role = entry.role === "assistant" ? "assistant" : "user";
|
|
2048
2178
|
const label = role === "assistant" ? t("assistantLabel") : t("youLabel");
|
|
2049
2179
|
const content =
|
|
@@ -2830,7 +2960,7 @@ async function refreshChat() {
|
|
|
2830
2960
|
pendingInboxItems = data.inbox || [];
|
|
2831
2961
|
pendingAfterFinishItems = loadAfterFinishQueue(currentSessionId);
|
|
2832
2962
|
renderPendingMessages();
|
|
2833
|
-
renderChat(data.chat || []);
|
|
2963
|
+
renderChat(data.timeline || data.chat || []);
|
|
2834
2964
|
}
|
|
2835
2965
|
|
|
2836
2966
|
async function savePreferences() {
|
package/public/styles.css
CHANGED
|
@@ -1225,6 +1225,126 @@ button.danger {
|
|
|
1225
1225
|
padding: 0 12px 8px;
|
|
1226
1226
|
}
|
|
1227
1227
|
|
|
1228
|
+
.event-card {
|
|
1229
|
+
width: auto;
|
|
1230
|
+
max-width: 100%;
|
|
1231
|
+
margin: 0 0 12px;
|
|
1232
|
+
padding: 12px;
|
|
1233
|
+
border: 1px solid rgba(20, 184, 166, 0.22);
|
|
1234
|
+
border-radius: 16px;
|
|
1235
|
+
background:
|
|
1236
|
+
radial-gradient(circle at 0% 0%, rgba(20, 184, 166, 0.14), transparent 28%),
|
|
1237
|
+
rgba(255, 255, 255, 0.46);
|
|
1238
|
+
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.07);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
#logs .event-card {
|
|
1242
|
+
background:
|
|
1243
|
+
radial-gradient(circle at 0% 0%, rgba(20, 184, 166, 0.1), transparent 26%),
|
|
1244
|
+
rgba(15, 23, 42, 0.52);
|
|
1245
|
+
color: #e5e7eb;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.event-plan {
|
|
1249
|
+
border-color: rgba(20, 184, 166, 0.34);
|
|
1250
|
+
background:
|
|
1251
|
+
radial-gradient(circle at 0% 0%, rgba(20, 184, 166, 0.18), transparent 28%),
|
|
1252
|
+
linear-gradient(180deg, rgba(240, 253, 250, 0.9), rgba(255, 251, 235, 0.74));
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
#logs .event-plan {
|
|
1256
|
+
background: rgba(15, 23, 42, 0.58);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.event-patch {
|
|
1260
|
+
border-color: rgba(192, 132, 252, 0.38);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.event-write {
|
|
1264
|
+
border-color: rgba(59, 130, 246, 0.28);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
.event-failed {
|
|
1268
|
+
border-color: rgba(248, 113, 113, 0.45);
|
|
1269
|
+
background: rgba(127, 29, 29, 0.12);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
#logs .event-failed {
|
|
1273
|
+
background: rgba(127, 29, 29, 0.3);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
.event-muted {
|
|
1277
|
+
border-color: rgba(148, 163, 184, 0.24);
|
|
1278
|
+
background: rgba(148, 163, 184, 0.1);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.event-card-meta {
|
|
1282
|
+
display: flex;
|
|
1283
|
+
flex-wrap: wrap;
|
|
1284
|
+
gap: 8px;
|
|
1285
|
+
margin-bottom: 8px;
|
|
1286
|
+
color: var(--muted);
|
|
1287
|
+
font-size: 0.78rem;
|
|
1288
|
+
font-weight: 800;
|
|
1289
|
+
letter-spacing: 0.04em;
|
|
1290
|
+
text-transform: uppercase;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
#logs .event-card-meta {
|
|
1294
|
+
color: #99f6e4;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
.event-path {
|
|
1298
|
+
display: block;
|
|
1299
|
+
margin: 4px 0 8px;
|
|
1300
|
+
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
|
1301
|
+
font-size: 0.84rem;
|
|
1302
|
+
overflow-wrap: anywhere;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
.event-inline-code {
|
|
1306
|
+
display: block;
|
|
1307
|
+
width: fit-content;
|
|
1308
|
+
max-width: 100%;
|
|
1309
|
+
margin: 4px 0 8px;
|
|
1310
|
+
padding: 6px 8px;
|
|
1311
|
+
border-radius: 8px;
|
|
1312
|
+
background: rgba(15, 23, 42, 0.08);
|
|
1313
|
+
color: #78350f;
|
|
1314
|
+
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
|
1315
|
+
overflow-wrap: anywhere;
|
|
1316
|
+
white-space: pre-wrap;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
#logs .event-inline-code {
|
|
1320
|
+
background: rgba(255, 255, 255, 0.08);
|
|
1321
|
+
color: #fef3c7;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
.event-output {
|
|
1325
|
+
margin: 4px 0 10px;
|
|
1326
|
+
padding: 10px;
|
|
1327
|
+
border-radius: 10px;
|
|
1328
|
+
background: rgba(15, 23, 42, 0.84);
|
|
1329
|
+
color: #e5e7eb;
|
|
1330
|
+
white-space: pre-wrap;
|
|
1331
|
+
overflow: auto;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
.event-fold-note {
|
|
1335
|
+
margin: 4px 0 8px;
|
|
1336
|
+
color: var(--muted);
|
|
1337
|
+
font-size: 0.8rem;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
#logs .event-fold-note {
|
|
1341
|
+
color: #94a3b8;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
.event-diff {
|
|
1345
|
+
max-height: 360px;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1228
1348
|
.chat-panel {
|
|
1229
1349
|
display: grid;
|
|
1230
1350
|
gap: 12px;
|
|
@@ -1271,6 +1391,25 @@ button.danger {
|
|
|
1271
1391
|
border-bottom-left-radius: 6px;
|
|
1272
1392
|
}
|
|
1273
1393
|
|
|
1394
|
+
.chat-item.event {
|
|
1395
|
+
justify-self: stretch;
|
|
1396
|
+
width: auto;
|
|
1397
|
+
max-width: 100%;
|
|
1398
|
+
border-color: rgba(20, 184, 166, 0.28);
|
|
1399
|
+
border-style: dashed;
|
|
1400
|
+
background:
|
|
1401
|
+
linear-gradient(90deg, rgba(20, 184, 166, 0.12), rgba(245, 158, 11, 0.08)),
|
|
1402
|
+
rgba(255, 255, 255, 0.48);
|
|
1403
|
+
box-shadow: none;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
.chat-item.event[data-event-type="plan.created"] {
|
|
1407
|
+
border-style: solid;
|
|
1408
|
+
background:
|
|
1409
|
+
radial-gradient(circle at 0% 0%, rgba(20, 184, 166, 0.16), transparent 28%),
|
|
1410
|
+
linear-gradient(180deg, rgba(240, 253, 250, 0.88), rgba(255, 251, 235, 0.7));
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1274
1413
|
.chat-meta {
|
|
1275
1414
|
font-size: 0.8rem;
|
|
1276
1415
|
color: var(--muted);
|
|
@@ -890,10 +890,16 @@ try {
|
|
|
890
890
|
if (!latest.stdout.includes(" user>") || !latest.stdout.includes("aginti>") || latest.stdout.includes("user> ")) {
|
|
891
891
|
throw new Error("resume history should use prompt-style user>/aginti> labels");
|
|
892
892
|
}
|
|
893
|
-
if (!latest.stdout.includes("
|
|
894
|
-
throw new Error("bare aginti resume did not
|
|
893
|
+
if (!latest.stdout.includes(" plan ")) {
|
|
894
|
+
throw new Error("bare aginti resume did not replay saved plan/run context");
|
|
895
895
|
}
|
|
896
|
-
if (latest.stdout.includes("
|
|
896
|
+
if (!latest.stdout.includes(" write ") || !latest.stdout.includes("+Created by AgInTiFlow mock mode.")) {
|
|
897
|
+
throw new Error("bare aginti resume did not replay formatted file-change diff context");
|
|
898
|
+
}
|
|
899
|
+
if (latest.stdout.includes("resume note=showing chat transcript only")) {
|
|
900
|
+
throw new Error("bare aginti resume regressed to chat-only history");
|
|
901
|
+
}
|
|
902
|
+
if (latest.stdout.includes("showing=")) {
|
|
897
903
|
throw new Error("resume history should render full saved messages instead of compact previews");
|
|
898
904
|
}
|
|
899
905
|
|
package/scripts/smoke-web-api.js
CHANGED
|
@@ -257,6 +257,11 @@ try {
|
|
|
257
257
|
if (fileRun.status !== "finished") throw new Error(`mock file run failed: ${fileRun.error || "unknown error"}`);
|
|
258
258
|
const hello = await fs.readFile(path.join(runtimeDir, "notes", "hello.md"), "utf8");
|
|
259
259
|
if (!hello.includes("Created by AgInTiFlow mock mode.")) throw new Error("mock file run did not create requested path");
|
|
260
|
+
const fileChat = await fetchJson(`/api/sessions/${encodeURIComponent(fileRunStart.sessionId)}/chat`);
|
|
261
|
+
const fileChange = fileChat.timeline?.find((entry) => entry.role === "event" && entry.eventType === "file.changed");
|
|
262
|
+
if (!fileChange?.data?.diff || !fileChange.data.diff.includes("+Created by AgInTiFlow mock mode.")) {
|
|
263
|
+
throw new Error("chat timeline did not preserve structured file-change diff data");
|
|
264
|
+
}
|
|
260
265
|
|
|
261
266
|
const safeRunStart = await fetchJson("/api/runs", {
|
|
262
267
|
method: "POST",
|
|
@@ -303,6 +308,15 @@ try {
|
|
|
303
308
|
const chat = await fetchJson(`/api/sessions/${encodeURIComponent(runStart.sessionId)}/chat`);
|
|
304
309
|
if (!Array.isArray(chat.chat) || chat.chat.length < 2) throw new Error("chat history was not persisted");
|
|
305
310
|
if (!Array.isArray(chat.inbox)) throw new Error("chat endpoint did not include shared inbox state");
|
|
311
|
+
if (!Array.isArray(chat.events) || !chat.events.some((entry) => entry.type === "plan.created")) {
|
|
312
|
+
throw new Error("chat endpoint did not include saved plan events");
|
|
313
|
+
}
|
|
314
|
+
if (
|
|
315
|
+
!Array.isArray(chat.timeline) ||
|
|
316
|
+
!chat.timeline.some((entry) => entry.role === "event" && entry.eventType === "plan.created" && String(entry.content || "").trim())
|
|
317
|
+
) {
|
|
318
|
+
throw new Error("chat endpoint did not return a resume-ready timeline with the saved plan");
|
|
319
|
+
}
|
|
306
320
|
|
|
307
321
|
const queued = await fetchJson(`/api/sessions/${encodeURIComponent(runStart.sessionId)}/inbox`, {
|
|
308
322
|
method: "POST",
|
package/scripts/smoke-web-ui.js
CHANGED
|
@@ -56,7 +56,7 @@ async function waitForRunState(page, status, timeout = 20000) {
|
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
async function waitForTerminalRunState(page, timeout =
|
|
59
|
+
async function waitForTerminalRunState(page, timeout = 90000) {
|
|
60
60
|
await page.waitForFunction(
|
|
61
61
|
() => ["finished", "failed", "stopped"].includes(document.querySelector("#run-state")?.dataset.status || ""),
|
|
62
62
|
null,
|
|
@@ -92,6 +92,7 @@ try {
|
|
|
92
92
|
if (await page.locator("#run-defaults-card").evaluate((node) => node.open)) throw new Error("run defaults should start folded");
|
|
93
93
|
await page.locator("#commandCwd").fill(runtimeDir.slice(0, Math.max(runtimeDir.lastIndexOf("/"), 1)));
|
|
94
94
|
await page.waitForFunction(() => document.querySelectorAll("#command-cwd-suggestions option").length > 0);
|
|
95
|
+
await page.locator("#commandCwd").fill(runtimeDir);
|
|
95
96
|
if ((await page.locator(".project-status-chip").count()) < 4) throw new Error("project folder status did not render structured chips");
|
|
96
97
|
await page.locator("#run-defaults-card summary").click();
|
|
97
98
|
if (await page.locator("#veniceModeToggle").isChecked()) throw new Error("Venice quick mode should default off");
|
|
@@ -143,6 +144,14 @@ try {
|
|
|
143
144
|
await page.selectOption("#routingMode", "manual");
|
|
144
145
|
await page.selectOption("#provider", "mock");
|
|
145
146
|
await page.selectOption("#model", "mock-agent");
|
|
147
|
+
await page.selectOption("#enableScs", "off");
|
|
148
|
+
if (await page.locator("#aapsModeToggle").isChecked()) await page.locator("label:has(#aapsModeToggle)").click();
|
|
149
|
+
await page.selectOption("#taskProfile", "auto");
|
|
150
|
+
await page.evaluate(() => {
|
|
151
|
+
const input = document.querySelector("#allowShellTool");
|
|
152
|
+
input.checked = false;
|
|
153
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
154
|
+
});
|
|
146
155
|
await page.fill("#maxSteps", "4");
|
|
147
156
|
await page.selectOption("#dynamicSteps", "off");
|
|
148
157
|
await page.fill("#chat-input", "Say hello from the web UI composer in one concise sentence.");
|
|
@@ -151,18 +160,24 @@ try {
|
|
|
151
160
|
if (firstPayload.goal !== "Say hello from the web UI composer in one concise sentence.") {
|
|
152
161
|
throw new Error("composer text was not used as the first run goal");
|
|
153
162
|
}
|
|
154
|
-
if (firstPayload.enableScs !== "
|
|
163
|
+
if (firstPayload.enableScs !== "off" || firstPayload.taskProfile !== "auto") {
|
|
155
164
|
throw new Error(`CLI mode payload mismatch: ${runPayloads.at(-1) || ""}`);
|
|
156
165
|
}
|
|
157
166
|
if (firstPayload.dynamicSteps !== "off") {
|
|
158
167
|
throw new Error(`dynamic steps payload mismatch: ${runPayloads.at(-1) || ""}`);
|
|
159
168
|
}
|
|
169
|
+
if (firstPayload.allowShellTool !== false) {
|
|
170
|
+
throw new Error(`shell tool should be disabled for deterministic renderer smoke: ${runPayloads.at(-1) || ""}`);
|
|
171
|
+
}
|
|
160
172
|
if (firstPayload.veniceMode !== false || firstPayload.routeProvider !== "deepseek" || firstPayload.mainProvider !== "deepseek") {
|
|
161
173
|
throw new Error(`default route/main payload mismatch after Venice off: ${runPayloads.at(-1) || ""}`);
|
|
162
174
|
}
|
|
163
175
|
await waitForRunState(page, "running");
|
|
176
|
+
await page.waitForSelector(".event-plan", { timeout: 12000 });
|
|
177
|
+
if (!(await page.locator(".event-plan .markdown-body").first().innerText()).trim()) {
|
|
178
|
+
throw new Error("web run log did not render the plan as a formatted event card");
|
|
179
|
+
}
|
|
164
180
|
if (!(await page.locator("#stop-run").isVisible())) throw new Error("stop button did not appear while run was active");
|
|
165
|
-
await page.click("#stop-run");
|
|
166
181
|
await waitForTerminalRunState(page);
|
|
167
182
|
if (await page.locator("#stop-run").isVisible()) throw new Error("stop button stayed visible after terminal run state");
|
|
168
183
|
if (!(await page.locator(".toast").first().isVisible().catch(() => false))) {
|
|
@@ -173,6 +188,27 @@ try {
|
|
|
173
188
|
await waitForRunState(page, "idle");
|
|
174
189
|
const resetSubmit = await page.locator("#chat-submit").innerText();
|
|
175
190
|
if (!/start new run/i.test(resetSubmit)) throw new Error("new session did not reset composer to start mode");
|
|
191
|
+
if (await page.locator("#aapsModeToggle").isChecked()) await page.locator("label:has(#aapsModeToggle)").click();
|
|
192
|
+
await page.selectOption("#taskProfile", "code");
|
|
193
|
+
await page.selectOption("#enableScs", "off");
|
|
194
|
+
await page.fill("#chat-input", "Create notes/web-ui-format.md with a short formatted event smoke message.");
|
|
195
|
+
await page.click("#chat-submit");
|
|
196
|
+
await waitForTerminalRunState(page);
|
|
197
|
+
try {
|
|
198
|
+
await page.waitForFunction(
|
|
199
|
+
() =>
|
|
200
|
+
[...document.querySelectorAll(".event-write .change-diff")].some((node) =>
|
|
201
|
+
(node.textContent || "").includes("+Created by AgInTiFlow mock mode.")
|
|
202
|
+
),
|
|
203
|
+
null,
|
|
204
|
+
{ timeout: 30000 }
|
|
205
|
+
);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
const state = await page.locator("#run-state").evaluate((node) => node.dataset.status || "").catch(() => "unknown");
|
|
208
|
+
const logsTail = (await page.locator("#logs").innerText().catch(() => "")).slice(-1200);
|
|
209
|
+
const chatTail = (await page.locator("#chat-thread").innerText().catch(() => "")).slice(-1200);
|
|
210
|
+
throw new Error(`web UI did not render formatted file-change diff event; state=${state}\nlogs tail:\n${logsTail}\nchat tail:\n${chatTail}`);
|
|
211
|
+
}
|
|
176
212
|
|
|
177
213
|
await page.click("#open-settings");
|
|
178
214
|
await page.waitForSelector("#settings-modal[open]");
|
|
@@ -208,6 +244,8 @@ try {
|
|
|
208
244
|
"running-status-toast",
|
|
209
245
|
"terminal-stop-button-hidden",
|
|
210
246
|
"new-session-resets-scope",
|
|
247
|
+
"formatted-plan-event-card",
|
|
248
|
+
"formatted-file-diff-event-card",
|
|
211
249
|
"settings-provider-dropdowns",
|
|
212
250
|
"settings-wrapper-dropdowns",
|
|
213
251
|
],
|
|
@@ -51,6 +51,26 @@ async function killPort(port) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
async function stopChild(child) {
|
|
55
|
+
if (!child || child.exitCode !== null) return;
|
|
56
|
+
try {
|
|
57
|
+
child.stdin?.write("/exit\n");
|
|
58
|
+
} catch {
|
|
59
|
+
// The CLI may have closed stdin after the final prompt; fall back to process signals.
|
|
60
|
+
}
|
|
61
|
+
const exited = await Promise.race([
|
|
62
|
+
new Promise((resolve) => child.once("exit", () => resolve(true))),
|
|
63
|
+
delay(2000).then(() => false),
|
|
64
|
+
]);
|
|
65
|
+
if (exited || child.exitCode !== null) return;
|
|
66
|
+
child.kill("SIGTERM");
|
|
67
|
+
const terminated = await Promise.race([
|
|
68
|
+
new Promise((resolve) => child.once("exit", () => resolve(true))),
|
|
69
|
+
delay(2000).then(() => false),
|
|
70
|
+
]);
|
|
71
|
+
if (!terminated && child.exitCode === null) child.kill("SIGKILL");
|
|
72
|
+
}
|
|
73
|
+
|
|
54
74
|
async function runCase({ port, env = {}, expectHeader, label }) {
|
|
55
75
|
const output = { stdout: "", stderr: "" };
|
|
56
76
|
const child = spawn(process.execPath, [path.join(repoRoot, "bin/aginti-cli.js"), "chat", "--provider", "mock", "--routing", "manual", "--port", String(port)], {
|
|
@@ -109,8 +129,9 @@ async function runCase({ port, env = {}, expectHeader, label }) {
|
|
|
109
129
|
child.stdin.write(`/webapp ${active.port}\n`);
|
|
110
130
|
await waitFor(() => latestWebappEvent(output.stdout, "started|reused")?.port === active.port, child, `${label} /webapp restart after stop`, output);
|
|
111
131
|
} finally {
|
|
112
|
-
child
|
|
132
|
+
await stopChild(child);
|
|
113
133
|
await killPort(port);
|
|
134
|
+
await delay(250);
|
|
114
135
|
}
|
|
115
136
|
}
|
|
116
137
|
|
|
@@ -133,5 +154,6 @@ try {
|
|
|
133
154
|
} finally {
|
|
134
155
|
await killPort(autoPort);
|
|
135
156
|
await killPort(manualPort);
|
|
136
|
-
await
|
|
157
|
+
await delay(250);
|
|
158
|
+
await fs.rm(runtimeDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
137
159
|
}
|
package/src/interactive-cli.js
CHANGED
|
@@ -2207,6 +2207,131 @@ function printHistoryEntry(entry) {
|
|
|
2207
2207
|
printHistoryBlock(role, entry.content, { time, bg });
|
|
2208
2208
|
}
|
|
2209
2209
|
|
|
2210
|
+
function timestampMs(value = "") {
|
|
2211
|
+
const parsed = Date.parse(String(value || ""));
|
|
2212
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
function isReplayableResumeEvent(event = {}) {
|
|
2216
|
+
const type = String(event.type || "");
|
|
2217
|
+
const data = event.data || {};
|
|
2218
|
+
if (type === "plan.created") return Boolean(data.plan);
|
|
2219
|
+
if (
|
|
2220
|
+
[
|
|
2221
|
+
"budget.initialized",
|
|
2222
|
+
"conversation.continued",
|
|
2223
|
+
"conversation.queued_input_applied",
|
|
2224
|
+
"file.changed",
|
|
2225
|
+
"session.failed",
|
|
2226
|
+
"session.finished",
|
|
2227
|
+
"session.stopped",
|
|
2228
|
+
"tool.blocked",
|
|
2229
|
+
"tool.failed",
|
|
2230
|
+
"tool.skipped",
|
|
2231
|
+
"tool.started",
|
|
2232
|
+
].includes(type)
|
|
2233
|
+
) {
|
|
2234
|
+
return true;
|
|
2235
|
+
}
|
|
2236
|
+
if (type === "tool.completed") {
|
|
2237
|
+
const toolName = String(data.toolName || "");
|
|
2238
|
+
return ["run_command", "write_file", "apply_patch", "create_artifact"].includes(toolName) || data.ok === false || data.blocked || data.error;
|
|
2239
|
+
}
|
|
2240
|
+
return false;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
function resumeTimelineItems(chat = [], events = [], { limit = 0 } = {}) {
|
|
2244
|
+
const shownChat = limit > 0 ? chat.slice(-limit) : chat;
|
|
2245
|
+
const firstChatTime = limit > 0 && shownChat.length > 0 ? timestampMs(shownChat[0].at) : 0;
|
|
2246
|
+
const chatItems = shownChat.map((entry, index) => ({
|
|
2247
|
+
kind: "chat",
|
|
2248
|
+
entry,
|
|
2249
|
+
order: index * 2,
|
|
2250
|
+
at: timestampMs(entry.at),
|
|
2251
|
+
}));
|
|
2252
|
+
const eventItems = events
|
|
2253
|
+
.map((event, index) => ({ event, index }))
|
|
2254
|
+
.filter(({ event }) => isReplayableResumeEvent(event))
|
|
2255
|
+
.filter(({ event }) => !firstChatTime || timestampMs(event.timestamp) >= firstChatTime)
|
|
2256
|
+
.map(({ event, index }) => ({
|
|
2257
|
+
kind: "event",
|
|
2258
|
+
event,
|
|
2259
|
+
order: index * 2 + 1,
|
|
2260
|
+
at: timestampMs(event.timestamp),
|
|
2261
|
+
}));
|
|
2262
|
+
return [...chatItems, ...eventItems].sort((left, right) => (left.at || 0) - (right.at || 0) || left.order - right.order);
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
function printResumeEvent(event = {}) {
|
|
2266
|
+
const type = String(event.type || "");
|
|
2267
|
+
const data = event.data || {};
|
|
2268
|
+
if (type === "plan.created") {
|
|
2269
|
+
printWrapped(`${label("plan", ansi.systemBg)} `, data.plan || "");
|
|
2270
|
+
return true;
|
|
2271
|
+
}
|
|
2272
|
+
if (type === "tool.started") {
|
|
2273
|
+
outputLine(`${label("tool", ansi.statusBg)} ${toolStatusDetails(data)}`);
|
|
2274
|
+
return true;
|
|
2275
|
+
}
|
|
2276
|
+
if (type === "tool.completed" || type === "tool.failed" || type === "tool.skipped") {
|
|
2277
|
+
const failed = type === "tool.failed" || data.ok === false || data.blocked || data.error;
|
|
2278
|
+
if (data.diff) {
|
|
2279
|
+
printWorkspaceChange(data);
|
|
2280
|
+
return true;
|
|
2281
|
+
}
|
|
2282
|
+
if (data.toolName === "run_command") {
|
|
2283
|
+
printCommandOutputLog({
|
|
2284
|
+
command: data.args?.command || data.command || "",
|
|
2285
|
+
stdout: data.stdout || "",
|
|
2286
|
+
stderr: data.stderr || "",
|
|
2287
|
+
diagnosticHint: data.diagnosticHint || "",
|
|
2288
|
+
commandPolicy: data.commandPolicy,
|
|
2289
|
+
blocked: Boolean(data.blocked),
|
|
2290
|
+
error: data.error || data.reason || "",
|
|
2291
|
+
permissionAdvice: data.permissionAdvice || null,
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
outputLine(`${label(failed ? "fail" : "done", failed ? ansi.red : ansi.systemBg)} ${toolStatusDetails(data)}`);
|
|
2295
|
+
return true;
|
|
2296
|
+
}
|
|
2297
|
+
if (type === "tool.blocked") {
|
|
2298
|
+
outputLine(`${label("perm", ansi.red)} ${compactLine(data.reason || data.permissionAdvice?.summary || data.toolName || "Permission blocked.", 104)}`);
|
|
2299
|
+
if (data.permissionAdvice?.suggestedCommand) outputLine(`${color(" | ", ansi.red)} rerun: ${compactLine(data.permissionAdvice.suggestedCommand, 120)}`);
|
|
2300
|
+
if (data.permissionAdvice?.trustedHostCommand) outputLine(`${color(" | ", ansi.red)} host: ${compactLine(data.permissionAdvice.trustedHostCommand, 120)}`);
|
|
2301
|
+
return true;
|
|
2302
|
+
}
|
|
2303
|
+
if (type === "file.changed") {
|
|
2304
|
+
if (data.diff) printWorkspaceChange(data);
|
|
2305
|
+
else outputLine(`${label("write", ansi.systemBg)} ${compactLine(formatWorkspaceChange(data).summary, 92)}`);
|
|
2306
|
+
return true;
|
|
2307
|
+
}
|
|
2308
|
+
if (type === "budget.initialized") {
|
|
2309
|
+
outputLine(`${label("state", ansi.systemBg)} budget=${data.currentMaxSteps || data.initialMaxSteps || data.maxSteps || "unknown"} steps`);
|
|
2310
|
+
return true;
|
|
2311
|
+
}
|
|
2312
|
+
if (type === "conversation.continued") {
|
|
2313
|
+
outputLine(`${label("state", ansi.systemBg)} continued=${compactLine(data.prompt || "", 76)}`);
|
|
2314
|
+
return true;
|
|
2315
|
+
}
|
|
2316
|
+
if (type === "conversation.queued_input_applied") {
|
|
2317
|
+
outputLine(`${label("state", ansi.systemBg)} queued_input_applied=${data.priority === "asap" ? "asap" : "normal"}`);
|
|
2318
|
+
return true;
|
|
2319
|
+
}
|
|
2320
|
+
if (type === "session.finished") {
|
|
2321
|
+
outputLine(`${label("state", ansi.systemBg)} status=finished${data.result ? ` result=${compactLine(data.result, 86)}` : ""}`);
|
|
2322
|
+
return true;
|
|
2323
|
+
}
|
|
2324
|
+
if (type === "session.failed") {
|
|
2325
|
+
outputLine(`${label("state", ansi.red)} status=failed${data.error ? ` error=${compactLine(data.error, 86)}` : ""}`);
|
|
2326
|
+
return true;
|
|
2327
|
+
}
|
|
2328
|
+
if (type === "session.stopped") {
|
|
2329
|
+
outputLine(`${label("state", ansi.systemBg)} status=stopped${data.reason ? ` reason=${compactLine(data.reason, 86)}` : ""}`);
|
|
2330
|
+
return true;
|
|
2331
|
+
}
|
|
2332
|
+
return false;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2210
2335
|
async function printResumeHistory(state, { limit = 0 } = {}) {
|
|
2211
2336
|
if (!state.sessionId) return;
|
|
2212
2337
|
const paths = projectPaths(process.cwd());
|
|
@@ -2215,23 +2340,24 @@ async function printResumeHistory(state, { limit = 0 } = {}) {
|
|
|
2215
2340
|
const events = await store.loadEvents().catch(() => []);
|
|
2216
2341
|
const chat = Array.isArray(saved?.chat) ? saved.chat.filter((entry) => entry?.content) : [];
|
|
2217
2342
|
promptHistory.seedFromChat(chat, state.sessionId);
|
|
2218
|
-
const
|
|
2219
|
-
|
|
2343
|
+
const timeline = resumeTimelineItems(chat, events, { limit });
|
|
2344
|
+
const replayedEvents = timeline.filter((item) => item.kind === "event").length;
|
|
2345
|
+
const metadata = `chat=${chat.length}${events.length > 0 ? ` events=${events.length}` : ""}${replayedEvents > 0 ? ` replay=${replayedEvents}` : ""}`;
|
|
2346
|
+
if (timeline.length === 0) {
|
|
2220
2347
|
printSystemLine(`resume history session=${state.sessionId} ${metadata}`);
|
|
2221
|
-
if (events.length > 0) {
|
|
2222
|
-
printSystemLine("resume note=showing chat transcript only; model/tool/run events are saved in events.jsonl and artifacts/");
|
|
2223
|
-
}
|
|
2224
2348
|
return;
|
|
2225
2349
|
}
|
|
2226
2350
|
|
|
2227
|
-
const shown = limit > 0 ? chat.slice(-limit) : chat;
|
|
2228
2351
|
printSystemLine(
|
|
2229
|
-
`resume history session=${state.sessionId} ${metadata}${limit > 0 ? ` showing=${
|
|
2352
|
+
`resume history session=${state.sessionId} ${metadata}${limit > 0 ? ` showing=${timeline.length}/${chat.length + events.length}` : ""}`
|
|
2230
2353
|
);
|
|
2231
|
-
if (events.length >
|
|
2232
|
-
printSystemLine("resume note=showing chat
|
|
2354
|
+
if (events.length > replayedEvents) {
|
|
2355
|
+
printSystemLine("resume note=showing chat plus key run events; full raw events remain in events.jsonl and artifacts/.");
|
|
2356
|
+
}
|
|
2357
|
+
for (const item of timeline) {
|
|
2358
|
+
if (item.kind === "chat") printHistoryEntry(item.entry);
|
|
2359
|
+
else printResumeEvent(item.event);
|
|
2233
2360
|
}
|
|
2234
|
-
for (const entry of shown) printHistoryEntry(entry);
|
|
2235
2361
|
}
|
|
2236
2362
|
|
|
2237
2363
|
function toolStatusDetails(data = {}) {
|
package/web.js
CHANGED
|
@@ -102,6 +102,140 @@ function mapEventLogs(events) {
|
|
|
102
102
|
}));
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
function compactText(value = "", limit = 120) {
|
|
106
|
+
const text = String(value || "").replace(/\s+/g, " ").trim();
|
|
107
|
+
return text.length <= limit ? text : `${text.slice(0, Math.max(limit - 1, 1)).trim()}...`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function toolTimelineSummary(data = {}) {
|
|
111
|
+
const tool = data.toolName || "unknown";
|
|
112
|
+
const args = data.args || {};
|
|
113
|
+
if (tool === "run_command" && args.command) return `${tool}: ${compactText(args.command, 92)}`;
|
|
114
|
+
if ((tool === "write_file" || tool === "apply_patch" || tool === "read_file" || tool === "open_workspace_file") && args.path) {
|
|
115
|
+
return `${tool}: ${compactText(args.path, 92)}`;
|
|
116
|
+
}
|
|
117
|
+
if ((tool === "open_url" || tool === "web_research" || tool === "web_search") && (args.url || args.query || args.q)) {
|
|
118
|
+
return `${tool}: ${compactText(args.url || args.query || args.q, 92)}`;
|
|
119
|
+
}
|
|
120
|
+
return tool;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function timelineEntryForEvent(event = {}) {
|
|
124
|
+
const type = String(event.type || "");
|
|
125
|
+
const data = event.data || {};
|
|
126
|
+
const at = event.timestamp || "";
|
|
127
|
+
if (type === "plan.created" && data.plan) {
|
|
128
|
+
return {
|
|
129
|
+
role: "event",
|
|
130
|
+
eventType: type,
|
|
131
|
+
eventLabel: "plan",
|
|
132
|
+
data,
|
|
133
|
+
content: data.plan,
|
|
134
|
+
markdown: true,
|
|
135
|
+
at,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (type === "tool.started") {
|
|
139
|
+
return {
|
|
140
|
+
role: "event",
|
|
141
|
+
eventType: type,
|
|
142
|
+
eventLabel: "tool",
|
|
143
|
+
data,
|
|
144
|
+
content: toolTimelineSummary(data),
|
|
145
|
+
at,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (type === "tool.completed" || type === "tool.failed" || type === "tool.skipped") {
|
|
149
|
+
const failed = type === "tool.failed" || data.ok === false || data.blocked || data.error;
|
|
150
|
+
const stdout = data.toolName === "run_command" && data.stdout ? `\nstdout: ${compactText(data.stdout, 180)}` : "";
|
|
151
|
+
const stderr = data.toolName === "run_command" && data.stderr ? `\nstderr: ${compactText(data.stderr, 180)}` : "";
|
|
152
|
+
return {
|
|
153
|
+
role: "event",
|
|
154
|
+
eventType: type,
|
|
155
|
+
eventLabel: failed ? "tool failed" : "tool done",
|
|
156
|
+
data,
|
|
157
|
+
content: `${toolTimelineSummary(data)}${stdout}${stderr}`,
|
|
158
|
+
at,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (type === "tool.blocked") {
|
|
162
|
+
return {
|
|
163
|
+
role: "event",
|
|
164
|
+
eventType: type,
|
|
165
|
+
eventLabel: "permission",
|
|
166
|
+
data,
|
|
167
|
+
content: data.permissionAdvice?.summary || data.reason || data.toolName || "Permission blocked.",
|
|
168
|
+
at,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (type === "file.changed") {
|
|
172
|
+
return {
|
|
173
|
+
role: "event",
|
|
174
|
+
eventType: type,
|
|
175
|
+
eventLabel: data.toolName === "apply_patch" ? "patch" : "write",
|
|
176
|
+
data,
|
|
177
|
+
content: [data.toolName || data.action || "file.changed", data.path || "", data.created ? "created" : "updated"]
|
|
178
|
+
.filter(Boolean)
|
|
179
|
+
.join(" "),
|
|
180
|
+
at,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (type === "budget.initialized") {
|
|
184
|
+
return {
|
|
185
|
+
role: "event",
|
|
186
|
+
eventType: type,
|
|
187
|
+
eventLabel: "budget",
|
|
188
|
+
data,
|
|
189
|
+
content: `${data.currentMaxSteps || data.initialMaxSteps || data.maxSteps || "unknown"} steps`,
|
|
190
|
+
at,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (type === "conversation.continued") {
|
|
194
|
+
return {
|
|
195
|
+
role: "event",
|
|
196
|
+
eventType: type,
|
|
197
|
+
eventLabel: "continued",
|
|
198
|
+
data,
|
|
199
|
+
content: data.prompt || "",
|
|
200
|
+
at,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (type === "conversation.queued_input_applied") {
|
|
204
|
+
return {
|
|
205
|
+
role: "event",
|
|
206
|
+
eventType: type,
|
|
207
|
+
eventLabel: "queued input",
|
|
208
|
+
data,
|
|
209
|
+
content: data.priority === "asap" ? "ASAP queued input applied" : "Queued input applied",
|
|
210
|
+
at,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (type === "session.finished" || type === "session.failed" || type === "session.stopped") {
|
|
214
|
+
return {
|
|
215
|
+
role: "event",
|
|
216
|
+
eventType: type,
|
|
217
|
+
eventLabel: type.replace("session.", ""),
|
|
218
|
+
data,
|
|
219
|
+
content: data.result || data.error || data.reason || type,
|
|
220
|
+
at,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function sessionTimelineFromChatAndEvents(chat = [], events = []) {
|
|
227
|
+
const chatItems = (Array.isArray(chat) ? chat : [])
|
|
228
|
+
.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 : [])
|
|
231
|
+
.map(timelineEntryForEvent)
|
|
232
|
+
.filter(Boolean)
|
|
233
|
+
.map((entry, index) => ({ ...entry, order: index * 2 + 1, sortAt: Date.parse(entry.at || "") || 0 }));
|
|
234
|
+
return [...chatItems, ...eventItems]
|
|
235
|
+
.sort((left, right) => (left.sortAt || 0) - (right.sortAt || 0) || left.order - right.order)
|
|
236
|
+
.map(({ sortAt: _sortAt, order: _order, ...entry }) => entry);
|
|
237
|
+
}
|
|
238
|
+
|
|
105
239
|
async function loadStoredRun(sessionId) {
|
|
106
240
|
const meta = db.getSession(sessionId);
|
|
107
241
|
if (!meta) return null;
|
|
@@ -606,11 +740,16 @@ function deriveChatFromState(state, meta) {
|
|
|
606
740
|
|
|
607
741
|
async function loadChat(sessionId) {
|
|
608
742
|
const store = sessionStore(sessionId);
|
|
609
|
-
const [state, meta] = await Promise.all([
|
|
743
|
+
const [state, meta, events] = await Promise.all([
|
|
744
|
+
store.loadState(),
|
|
745
|
+
Promise.resolve(db.getSession(sessionId)),
|
|
746
|
+
store.loadEvents().catch(() => []),
|
|
747
|
+
]);
|
|
610
748
|
|
|
611
749
|
if (!state && !meta) return null;
|
|
612
750
|
|
|
613
751
|
const inbox = await store.loadInbox().catch(() => []);
|
|
752
|
+
const chat = deriveChatFromState(state, meta);
|
|
614
753
|
return {
|
|
615
754
|
sessionId,
|
|
616
755
|
goal: state?.goal || meta?.goal || "",
|
|
@@ -619,7 +758,9 @@ async function loadChat(sessionId) {
|
|
|
619
758
|
model: state?.model || meta?.model || "",
|
|
620
759
|
status: meta?.status || "",
|
|
621
760
|
inbox,
|
|
622
|
-
chat
|
|
761
|
+
chat,
|
|
762
|
+
events: events.slice(-120),
|
|
763
|
+
timeline: sessionTimelineFromChatAndEvents(chat, events).slice(-220),
|
|
623
764
|
};
|
|
624
765
|
}
|
|
625
766
|
|