@lazyingart/agintiflow 0.20.170 → 0.20.172
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 +22 -1
- package/public/styles.css +36 -0
- package/scripts/smoke-cli-chat.js +6 -3
- package/scripts/smoke-web-api.js +9 -0
- package/src/interactive-cli.js +132 -10
- package/src/web-db.js +12 -0
- package/web.js +134 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.172",
|
|
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
|
@@ -1795,6 +1795,15 @@ function renderLogs(run) {
|
|
|
1795
1795
|
parts.push(renderCommandOutputLog(entry));
|
|
1796
1796
|
continue;
|
|
1797
1797
|
}
|
|
1798
|
+
if (entry.message === "plan.created" && entry.data?.plan) {
|
|
1799
|
+
parts.push(`
|
|
1800
|
+
<article class="log-plan">
|
|
1801
|
+
<div class="log-plan-title">${escapeHtml(`[${entry.at}] plan`)}</div>
|
|
1802
|
+
<div class="markdown-body">${renderMarkdown(entry.data.plan)}</div>
|
|
1803
|
+
</article>
|
|
1804
|
+
`);
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1798
1807
|
parts.push(`<div class="log-line">${escapeHtml(`[${entry.at}] ${entry.kind}: ${entry.message}`)}</div>`);
|
|
1799
1808
|
if (entry.message === "tool.blocked" && entry.data?.permissionAdvice) {
|
|
1800
1809
|
parts.push(renderPermissionApproval(entry));
|
|
@@ -2044,6 +2053,18 @@ function renderChat(chatEntries) {
|
|
|
2044
2053
|
|
|
2045
2054
|
chatThreadEl.innerHTML = lastChatEntries
|
|
2046
2055
|
.map((entry) => {
|
|
2056
|
+
if (entry.role === "event") {
|
|
2057
|
+
const label = entry.eventLabel || entry.eventType || "event";
|
|
2058
|
+
const content = entry.markdown
|
|
2059
|
+
? `<div class="markdown-body">${renderMarkdown(entry.content)}</div>`
|
|
2060
|
+
: escapeHtml(entry.content || "").replace(/\n/g, "<br>");
|
|
2061
|
+
return `
|
|
2062
|
+
<article class="chat-item event" data-event-type="${escapeHtml(entry.eventType || "")}">
|
|
2063
|
+
<div class="chat-meta">${escapeHtml(label)}${entry.at ? ` · ${new Date(entry.at).toLocaleString()}` : ""}</div>
|
|
2064
|
+
<div class="chat-content">${content}</div>
|
|
2065
|
+
</article>
|
|
2066
|
+
`;
|
|
2067
|
+
}
|
|
2047
2068
|
const role = entry.role === "assistant" ? "assistant" : "user";
|
|
2048
2069
|
const label = role === "assistant" ? t("assistantLabel") : t("youLabel");
|
|
2049
2070
|
const content =
|
|
@@ -2830,7 +2851,7 @@ async function refreshChat() {
|
|
|
2830
2851
|
pendingInboxItems = data.inbox || [];
|
|
2831
2852
|
pendingAfterFinishItems = loadAfterFinishQueue(currentSessionId);
|
|
2832
2853
|
renderPendingMessages();
|
|
2833
|
-
renderChat(data.chat || []);
|
|
2854
|
+
renderChat(data.timeline || data.chat || []);
|
|
2834
2855
|
}
|
|
2835
2856
|
|
|
2836
2857
|
async function savePreferences() {
|
package/public/styles.css
CHANGED
|
@@ -1155,6 +1155,23 @@ button.danger {
|
|
|
1155
1155
|
color: #fecaca;
|
|
1156
1156
|
}
|
|
1157
1157
|
|
|
1158
|
+
.log-plan {
|
|
1159
|
+
margin: 0 0 12px;
|
|
1160
|
+
padding: 12px;
|
|
1161
|
+
border: 1px solid rgba(94, 234, 212, 0.22);
|
|
1162
|
+
border-radius: 14px;
|
|
1163
|
+
background: rgba(15, 23, 42, 0.54);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.log-plan-title {
|
|
1167
|
+
margin-bottom: 8px;
|
|
1168
|
+
color: #99f6e4;
|
|
1169
|
+
font-size: 0.82rem;
|
|
1170
|
+
font-weight: 800;
|
|
1171
|
+
letter-spacing: 0.04em;
|
|
1172
|
+
text-transform: uppercase;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1158
1175
|
.log-json,
|
|
1159
1176
|
.log-command pre {
|
|
1160
1177
|
margin: 8px 0 12px;
|
|
@@ -1271,6 +1288,25 @@ button.danger {
|
|
|
1271
1288
|
border-bottom-left-radius: 6px;
|
|
1272
1289
|
}
|
|
1273
1290
|
|
|
1291
|
+
.chat-item.event {
|
|
1292
|
+
justify-self: stretch;
|
|
1293
|
+
width: auto;
|
|
1294
|
+
max-width: 100%;
|
|
1295
|
+
border-color: rgba(20, 184, 166, 0.28);
|
|
1296
|
+
border-style: dashed;
|
|
1297
|
+
background:
|
|
1298
|
+
linear-gradient(90deg, rgba(20, 184, 166, 0.12), rgba(245, 158, 11, 0.08)),
|
|
1299
|
+
rgba(255, 255, 255, 0.48);
|
|
1300
|
+
box-shadow: none;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
.chat-item.event[data-event-type="plan.created"] {
|
|
1304
|
+
border-style: solid;
|
|
1305
|
+
background:
|
|
1306
|
+
radial-gradient(circle at 0% 0%, rgba(20, 184, 166, 0.16), transparent 28%),
|
|
1307
|
+
linear-gradient(180deg, rgba(240, 253, 250, 0.88), rgba(255, 251, 235, 0.7));
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1274
1310
|
.chat-meta {
|
|
1275
1311
|
font-size: 0.8rem;
|
|
1276
1312
|
color: var(--muted);
|
|
@@ -890,10 +890,13 @@ 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("showing
|
|
896
|
+
if (latest.stdout.includes("resume note=showing chat transcript only")) {
|
|
897
|
+
throw new Error("bare aginti resume regressed to chat-only history");
|
|
898
|
+
}
|
|
899
|
+
if (latest.stdout.includes("showing=")) {
|
|
897
900
|
throw new Error("resume history should render full saved messages instead of compact previews");
|
|
898
901
|
}
|
|
899
902
|
|
package/scripts/smoke-web-api.js
CHANGED
|
@@ -303,6 +303,15 @@ try {
|
|
|
303
303
|
const chat = await fetchJson(`/api/sessions/${encodeURIComponent(runStart.sessionId)}/chat`);
|
|
304
304
|
if (!Array.isArray(chat.chat) || chat.chat.length < 2) throw new Error("chat history was not persisted");
|
|
305
305
|
if (!Array.isArray(chat.inbox)) throw new Error("chat endpoint did not include shared inbox state");
|
|
306
|
+
if (!Array.isArray(chat.events) || !chat.events.some((entry) => entry.type === "plan.created")) {
|
|
307
|
+
throw new Error("chat endpoint did not include saved plan events");
|
|
308
|
+
}
|
|
309
|
+
if (
|
|
310
|
+
!Array.isArray(chat.timeline) ||
|
|
311
|
+
!chat.timeline.some((entry) => entry.role === "event" && entry.eventType === "plan.created" && String(entry.content || "").trim())
|
|
312
|
+
) {
|
|
313
|
+
throw new Error("chat endpoint did not return a resume-ready timeline with the saved plan");
|
|
314
|
+
}
|
|
306
315
|
|
|
307
316
|
const queued = await fetchJson(`/api/sessions/${encodeURIComponent(runStart.sessionId)}/inbox`, {
|
|
308
317
|
method: "POST",
|
package/src/interactive-cli.js
CHANGED
|
@@ -2207,6 +2207,127 @@ 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.toolName === "run_command") {
|
|
2279
|
+
printCommandOutputLog({
|
|
2280
|
+
command: data.args?.command || data.command || "",
|
|
2281
|
+
stdout: data.stdout || "",
|
|
2282
|
+
stderr: data.stderr || "",
|
|
2283
|
+
diagnosticHint: data.diagnosticHint || "",
|
|
2284
|
+
commandPolicy: data.commandPolicy,
|
|
2285
|
+
blocked: Boolean(data.blocked),
|
|
2286
|
+
error: data.error || data.reason || "",
|
|
2287
|
+
permissionAdvice: data.permissionAdvice || null,
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
outputLine(`${label(failed ? "fail" : "done", failed ? ansi.red : ansi.systemBg)} ${toolStatusDetails(data)}`);
|
|
2291
|
+
return true;
|
|
2292
|
+
}
|
|
2293
|
+
if (type === "tool.blocked") {
|
|
2294
|
+
outputLine(`${label("perm", ansi.red)} ${compactLine(data.reason || data.permissionAdvice?.summary || data.toolName || "Permission blocked.", 104)}`);
|
|
2295
|
+
if (data.permissionAdvice?.suggestedCommand) outputLine(`${color(" | ", ansi.red)} rerun: ${compactLine(data.permissionAdvice.suggestedCommand, 120)}`);
|
|
2296
|
+
if (data.permissionAdvice?.trustedHostCommand) outputLine(`${color(" | ", ansi.red)} host: ${compactLine(data.permissionAdvice.trustedHostCommand, 120)}`);
|
|
2297
|
+
return true;
|
|
2298
|
+
}
|
|
2299
|
+
if (type === "file.changed") {
|
|
2300
|
+
if (data.diff) printWorkspaceChange(data);
|
|
2301
|
+
else outputLine(`${label("write", ansi.systemBg)} ${compactLine(formatWorkspaceChange(data).summary, 92)}`);
|
|
2302
|
+
return true;
|
|
2303
|
+
}
|
|
2304
|
+
if (type === "budget.initialized") {
|
|
2305
|
+
outputLine(`${label("state", ansi.systemBg)} budget=${data.currentMaxSteps || data.initialMaxSteps || data.maxSteps || "unknown"} steps`);
|
|
2306
|
+
return true;
|
|
2307
|
+
}
|
|
2308
|
+
if (type === "conversation.continued") {
|
|
2309
|
+
outputLine(`${label("state", ansi.systemBg)} continued=${compactLine(data.prompt || "", 76)}`);
|
|
2310
|
+
return true;
|
|
2311
|
+
}
|
|
2312
|
+
if (type === "conversation.queued_input_applied") {
|
|
2313
|
+
outputLine(`${label("state", ansi.systemBg)} queued_input_applied=${data.priority === "asap" ? "asap" : "normal"}`);
|
|
2314
|
+
return true;
|
|
2315
|
+
}
|
|
2316
|
+
if (type === "session.finished") {
|
|
2317
|
+
outputLine(`${label("state", ansi.systemBg)} status=finished${data.result ? ` result=${compactLine(data.result, 86)}` : ""}`);
|
|
2318
|
+
return true;
|
|
2319
|
+
}
|
|
2320
|
+
if (type === "session.failed") {
|
|
2321
|
+
outputLine(`${label("state", ansi.red)} status=failed${data.error ? ` error=${compactLine(data.error, 86)}` : ""}`);
|
|
2322
|
+
return true;
|
|
2323
|
+
}
|
|
2324
|
+
if (type === "session.stopped") {
|
|
2325
|
+
outputLine(`${label("state", ansi.systemBg)} status=stopped${data.reason ? ` reason=${compactLine(data.reason, 86)}` : ""}`);
|
|
2326
|
+
return true;
|
|
2327
|
+
}
|
|
2328
|
+
return false;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2210
2331
|
async function printResumeHistory(state, { limit = 0 } = {}) {
|
|
2211
2332
|
if (!state.sessionId) return;
|
|
2212
2333
|
const paths = projectPaths(process.cwd());
|
|
@@ -2215,23 +2336,24 @@ async function printResumeHistory(state, { limit = 0 } = {}) {
|
|
|
2215
2336
|
const events = await store.loadEvents().catch(() => []);
|
|
2216
2337
|
const chat = Array.isArray(saved?.chat) ? saved.chat.filter((entry) => entry?.content) : [];
|
|
2217
2338
|
promptHistory.seedFromChat(chat, state.sessionId);
|
|
2218
|
-
const
|
|
2219
|
-
|
|
2339
|
+
const timeline = resumeTimelineItems(chat, events, { limit });
|
|
2340
|
+
const replayedEvents = timeline.filter((item) => item.kind === "event").length;
|
|
2341
|
+
const metadata = `chat=${chat.length}${events.length > 0 ? ` events=${events.length}` : ""}${replayedEvents > 0 ? ` replay=${replayedEvents}` : ""}`;
|
|
2342
|
+
if (timeline.length === 0) {
|
|
2220
2343
|
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
2344
|
return;
|
|
2225
2345
|
}
|
|
2226
2346
|
|
|
2227
|
-
const shown = limit > 0 ? chat.slice(-limit) : chat;
|
|
2228
2347
|
printSystemLine(
|
|
2229
|
-
`resume history session=${state.sessionId} ${metadata}${limit > 0 ? ` showing=${
|
|
2348
|
+
`resume history session=${state.sessionId} ${metadata}${limit > 0 ? ` showing=${timeline.length}/${chat.length + events.length}` : ""}`
|
|
2230
2349
|
);
|
|
2231
|
-
if (events.length >
|
|
2232
|
-
printSystemLine("resume note=showing chat
|
|
2350
|
+
if (events.length > replayedEvents) {
|
|
2351
|
+
printSystemLine("resume note=showing chat plus key run events; full raw events remain in events.jsonl and artifacts/.");
|
|
2352
|
+
}
|
|
2353
|
+
for (const item of timeline) {
|
|
2354
|
+
if (item.kind === "chat") printHistoryEntry(item.entry);
|
|
2355
|
+
else printResumeEvent(item.event);
|
|
2233
2356
|
}
|
|
2234
|
-
for (const entry of shown) printHistoryEntry(entry);
|
|
2235
2357
|
}
|
|
2236
2358
|
|
|
2237
2359
|
function toolStatusDetails(data = {}) {
|
package/src/web-db.js
CHANGED
|
@@ -245,6 +245,18 @@ export class WebDatabase {
|
|
|
245
245
|
}
|
|
246
246
|
this.savePreferences(preferences);
|
|
247
247
|
}
|
|
248
|
+
if (
|
|
249
|
+
preferences.veniceMode !== true &&
|
|
250
|
+
preferences.routingMode !== "manual" &&
|
|
251
|
+
preferences.provider !== "venice" &&
|
|
252
|
+
(preferences.routeProvider === "venice" || preferences.mainProvider === "venice")
|
|
253
|
+
) {
|
|
254
|
+
preferences.routeProvider = "deepseek";
|
|
255
|
+
preferences.routeModel = "deepseek-v4-flash";
|
|
256
|
+
preferences.mainProvider = "deepseek";
|
|
257
|
+
preferences.mainModel = "deepseek-v4-pro";
|
|
258
|
+
this.savePreferences(preferences);
|
|
259
|
+
}
|
|
248
260
|
return preferences;
|
|
249
261
|
} catch {
|
|
250
262
|
return defaultPreferences(this.baseDir);
|
package/web.js
CHANGED
|
@@ -102,6 +102,131 @@ 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
|
+
content: data.plan,
|
|
133
|
+
markdown: true,
|
|
134
|
+
at,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (type === "tool.started") {
|
|
138
|
+
return {
|
|
139
|
+
role: "event",
|
|
140
|
+
eventType: type,
|
|
141
|
+
eventLabel: "tool",
|
|
142
|
+
content: toolTimelineSummary(data),
|
|
143
|
+
at,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (type === "tool.completed" || type === "tool.failed" || type === "tool.skipped") {
|
|
147
|
+
const failed = type === "tool.failed" || data.ok === false || data.blocked || data.error;
|
|
148
|
+
const stdout = data.toolName === "run_command" && data.stdout ? `\nstdout: ${compactText(data.stdout, 180)}` : "";
|
|
149
|
+
const stderr = data.toolName === "run_command" && data.stderr ? `\nstderr: ${compactText(data.stderr, 180)}` : "";
|
|
150
|
+
return {
|
|
151
|
+
role: "event",
|
|
152
|
+
eventType: type,
|
|
153
|
+
eventLabel: failed ? "tool failed" : "tool done",
|
|
154
|
+
content: `${toolTimelineSummary(data)}${stdout}${stderr}`,
|
|
155
|
+
at,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (type === "tool.blocked") {
|
|
159
|
+
return {
|
|
160
|
+
role: "event",
|
|
161
|
+
eventType: type,
|
|
162
|
+
eventLabel: "permission",
|
|
163
|
+
content: data.permissionAdvice?.summary || data.reason || data.toolName || "Permission blocked.",
|
|
164
|
+
at,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (type === "file.changed") {
|
|
168
|
+
return {
|
|
169
|
+
role: "event",
|
|
170
|
+
eventType: type,
|
|
171
|
+
eventLabel: data.toolName === "apply_patch" ? "patch" : "write",
|
|
172
|
+
content: [data.toolName || data.action || "file.changed", data.path || "", data.created ? "created" : "updated"]
|
|
173
|
+
.filter(Boolean)
|
|
174
|
+
.join(" "),
|
|
175
|
+
at,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (type === "budget.initialized") {
|
|
179
|
+
return {
|
|
180
|
+
role: "event",
|
|
181
|
+
eventType: type,
|
|
182
|
+
eventLabel: "budget",
|
|
183
|
+
content: `${data.currentMaxSteps || data.initialMaxSteps || data.maxSteps || "unknown"} steps`,
|
|
184
|
+
at,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (type === "conversation.continued") {
|
|
188
|
+
return {
|
|
189
|
+
role: "event",
|
|
190
|
+
eventType: type,
|
|
191
|
+
eventLabel: "continued",
|
|
192
|
+
content: data.prompt || "",
|
|
193
|
+
at,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (type === "conversation.queued_input_applied") {
|
|
197
|
+
return {
|
|
198
|
+
role: "event",
|
|
199
|
+
eventType: type,
|
|
200
|
+
eventLabel: "queued input",
|
|
201
|
+
content: data.priority === "asap" ? "ASAP queued input applied" : "Queued input applied",
|
|
202
|
+
at,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (type === "session.finished" || type === "session.failed" || type === "session.stopped") {
|
|
206
|
+
return {
|
|
207
|
+
role: "event",
|
|
208
|
+
eventType: type,
|
|
209
|
+
eventLabel: type.replace("session.", ""),
|
|
210
|
+
content: data.result || data.error || data.reason || type,
|
|
211
|
+
at,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function sessionTimelineFromChatAndEvents(chat = [], events = []) {
|
|
218
|
+
const chatItems = (Array.isArray(chat) ? chat : [])
|
|
219
|
+
.filter((entry) => entry?.content)
|
|
220
|
+
.map((entry, index) => ({ ...entry, order: index * 2, sortAt: Date.parse(entry.at || "") || 0 }));
|
|
221
|
+
const eventItems = (Array.isArray(events) ? events : [])
|
|
222
|
+
.map(timelineEntryForEvent)
|
|
223
|
+
.filter(Boolean)
|
|
224
|
+
.map((entry, index) => ({ ...entry, order: index * 2 + 1, sortAt: Date.parse(entry.at || "") || 0 }));
|
|
225
|
+
return [...chatItems, ...eventItems]
|
|
226
|
+
.sort((left, right) => (left.sortAt || 0) - (right.sortAt || 0) || left.order - right.order)
|
|
227
|
+
.map(({ sortAt: _sortAt, order: _order, ...entry }) => entry);
|
|
228
|
+
}
|
|
229
|
+
|
|
105
230
|
async function loadStoredRun(sessionId) {
|
|
106
231
|
const meta = db.getSession(sessionId);
|
|
107
232
|
if (!meta) return null;
|
|
@@ -606,11 +731,16 @@ function deriveChatFromState(state, meta) {
|
|
|
606
731
|
|
|
607
732
|
async function loadChat(sessionId) {
|
|
608
733
|
const store = sessionStore(sessionId);
|
|
609
|
-
const [state, meta] = await Promise.all([
|
|
734
|
+
const [state, meta, events] = await Promise.all([
|
|
735
|
+
store.loadState(),
|
|
736
|
+
Promise.resolve(db.getSession(sessionId)),
|
|
737
|
+
store.loadEvents().catch(() => []),
|
|
738
|
+
]);
|
|
610
739
|
|
|
611
740
|
if (!state && !meta) return null;
|
|
612
741
|
|
|
613
742
|
const inbox = await store.loadInbox().catch(() => []);
|
|
743
|
+
const chat = deriveChatFromState(state, meta);
|
|
614
744
|
return {
|
|
615
745
|
sessionId,
|
|
616
746
|
goal: state?.goal || meta?.goal || "",
|
|
@@ -619,7 +749,9 @@ async function loadChat(sessionId) {
|
|
|
619
749
|
model: state?.model || meta?.model || "",
|
|
620
750
|
status: meta?.status || "",
|
|
621
751
|
inbox,
|
|
622
|
-
chat
|
|
752
|
+
chat,
|
|
753
|
+
events: events.slice(-120),
|
|
754
|
+
timeline: sessionTimelineFromChatAndEvents(chat, events).slice(-220),
|
|
623
755
|
};
|
|
624
756
|
}
|
|
625
757
|
|