os-dpt 0.0.1 → 0.2.0
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/server/dist/client/assets/{highlighted-body-OFNGDK62-dz6tQnhA.js → highlighted-body-OFNGDK62-DVn0rkjL.js} +1 -1
- package/server/dist/client/assets/index-4qMZFL9L.js +251 -0
- package/server/dist/client/assets/index-DweMyov5.css +1 -0
- package/server/dist/client/index.html +3 -3
- package/server/dist/index.mjs +146 -34
- package/server/dist/client/assets/index-CdhII6ua.js +0 -242
- package/server/dist/client/assets/index-CtMhPW5q.css +0 -1
package/server/dist/index.mjs
CHANGED
|
@@ -1531,17 +1531,20 @@ async function streamAssistantMessage(p) {
|
|
|
1531
1531
|
}
|
|
1532
1532
|
},
|
|
1533
1533
|
async (span) => {
|
|
1534
|
-
const stream = client.messages.stream(
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1534
|
+
const stream = client.messages.stream(
|
|
1535
|
+
{
|
|
1536
|
+
model,
|
|
1537
|
+
max_tokens: maxTokens,
|
|
1538
|
+
system: cachedSystem(p.system),
|
|
1539
|
+
messages: withConversationCacheBreakpoint(p.messages),
|
|
1540
|
+
tools: p.tools,
|
|
1541
|
+
// One tool_use per assistant turn so the loop can cleanly handle the
|
|
1542
|
+
// ask_user_question pause without leaving sibling tool_use blocks
|
|
1543
|
+
// without matching tool_results.
|
|
1544
|
+
tool_choice: { type: "auto", disable_parallel_tool_use: true }
|
|
1545
|
+
},
|
|
1546
|
+
{ signal: p.signal }
|
|
1547
|
+
);
|
|
1545
1548
|
if (p.onTextDelta) {
|
|
1546
1549
|
stream.on("text", (delta) => p.onTextDelta?.(delta));
|
|
1547
1550
|
}
|
|
@@ -2018,6 +2021,7 @@ function costFromUsage(model, usage) {
|
|
|
2018
2021
|
|
|
2019
2022
|
// server/agent/prompt.ts
|
|
2020
2023
|
function buildSystemPrompt(session) {
|
|
2024
|
+
if (session.meta.mode === "quick-edit") return buildQuickEditPrompt(session);
|
|
2021
2025
|
const { worksheetSlug, connectionId } = session.meta;
|
|
2022
2026
|
const bound = !!worksheetSlug;
|
|
2023
2027
|
const lines = [
|
|
@@ -2059,7 +2063,7 @@ function buildSystemPrompt(session) {
|
|
|
2059
2063
|
"- run_sql results are capped to 50 rows by default; add LIMIT or aggregation if you need a broader view.",
|
|
2060
2064
|
"- run_sql executes whatever SQL you pass, including DDL and DML, against the role the user connected with. Treat exploration as read-only \u2014 use SELECT. Before any INSERT/UPDATE/DELETE/TRUNCATE/CREATE/DROP/ALTER, call ask_user_question to confirm.",
|
|
2061
2065
|
"- ask_user_question pauses the loop entirely \u2014 only one question per call, and use it sparingly.",
|
|
2062
|
-
"- render_chart draws a chart inline in the chat. After run_sql, when a picture beats a table (trends \u2192 line/area, comparisons \u2192 bar, proportions \u2192 pie), call it with pre-aggregated rows. Pass the data inline, shaped to small row objects (e.g. {month, revenue}); don't dump raw wide rows."
|
|
2066
|
+
"- render_chart draws a chart inline in the chat. After run_sql, when a picture beats a table (trends \u2192 line/area, comparisons \u2192 bar, part-to-whole \u2192 stacked-bar, proportions \u2192 pie, stage drop-off \u2192 funnel), call it with pre-aggregated rows. Pass the data inline, shaped to small row objects (e.g. {month, revenue}); don't dump raw wide rows."
|
|
2063
2067
|
);
|
|
2064
2068
|
const ctx = [];
|
|
2065
2069
|
if (worksheetSlug) ctx.push(`- Active worksheet: ${worksheetSlug}`);
|
|
@@ -2074,6 +2078,33 @@ function buildSystemPrompt(session) {
|
|
|
2074
2078
|
}
|
|
2075
2079
|
return lines.join("\n");
|
|
2076
2080
|
}
|
|
2081
|
+
function buildQuickEditPrompt(session) {
|
|
2082
|
+
const { worksheetSlug, connectionId } = session.meta;
|
|
2083
|
+
const lines = [
|
|
2084
|
+
"You are os-dpt's inline SQL-editing agent. The user prompts you from a small floating box inside their SQL editor. Your ONLY deliverable is SQL staged into their worksheet via write_sql \u2014 the user never reads chat output in this mode, they watch the editor update.",
|
|
2085
|
+
"",
|
|
2086
|
+
"Output rules (hard requirements):",
|
|
2087
|
+
"- Emit NO prose. No preamble, no explanation, no closing summary \u2014 no text blocks at all.",
|
|
2088
|
+
"- Anything worth flagging (an assumption you made, a caveat, a data-quality quirk) goes in a SQL comment inside the query itself.",
|
|
2089
|
+
"- Always finish by calling write_sql with the COMPLETE worksheet contents (drafts are overwrites, not patches), then end the turn.",
|
|
2090
|
+
"",
|
|
2091
|
+
"Method:",
|
|
2092
|
+
"- Each user message ends with the worksheet's current contents. Treat the request as an edit to that SQL unless it clearly asks for something new; preserve parts of the worksheet the request doesn't touch.",
|
|
2093
|
+
"- Check get_context first when the request depends on schema, business terms, or conventions you have not verified this session; call get_schema when tables or columns are uncertain. Do not guess names.",
|
|
2094
|
+
"- Verify your SQL with run_sql before staging it. If it errors, fix and re-run until it works. Treat verification as read-only \u2014 SELECT only; never run DDL or DML in this mode.",
|
|
2095
|
+
"- If no connection is bound or verification is impossible, stage your best SQL with a leading '-- not verified' comment instead of stopping.",
|
|
2096
|
+
"- You cannot ask the user questions in this mode. Make the most reasonable assumption and record it as a SQL comment.",
|
|
2097
|
+
"- Persist durable knowledge via update_context the moment you learn it: table/column meanings \u2192 schemas.md; corrections or errors that revealed a fact \u2192 feedback.md; standing rules and business terms \u2192 conventions.md.",
|
|
2098
|
+
"- run_sql results are capped to 50 rows by default; add LIMIT or aggregation if you need a broader view."
|
|
2099
|
+
];
|
|
2100
|
+
const ctx = [];
|
|
2101
|
+
if (worksheetSlug) ctx.push(`- Active worksheet: ${worksheetSlug}`);
|
|
2102
|
+
if (connectionId) ctx.push(`- Active connection: ${connectionId}`);
|
|
2103
|
+
if (ctx.length > 0) {
|
|
2104
|
+
lines.push("", "Current bindings:", ...ctx);
|
|
2105
|
+
}
|
|
2106
|
+
return lines.join("\n");
|
|
2107
|
+
}
|
|
2077
2108
|
|
|
2078
2109
|
// server/agent/session.ts
|
|
2079
2110
|
import { promises as fs14 } from "node:fs";
|
|
@@ -2085,6 +2116,7 @@ function freshMeta(input) {
|
|
|
2085
2116
|
const now = nowIso();
|
|
2086
2117
|
return {
|
|
2087
2118
|
id: crypto4.randomUUID(),
|
|
2119
|
+
mode: input.mode ?? "chat",
|
|
2088
2120
|
createdAt: now,
|
|
2089
2121
|
updatedAt: now,
|
|
2090
2122
|
title: input.title ?? null,
|
|
@@ -2102,6 +2134,9 @@ function ensureUsageFields(session) {
|
|
|
2102
2134
|
if (!Array.isArray(session.meta.usage)) session.meta.usage = [];
|
|
2103
2135
|
if (!session.meta.totals) session.meta.totals = emptyTotals();
|
|
2104
2136
|
if (typeof session.meta.standalone !== "boolean") session.meta.standalone = false;
|
|
2137
|
+
if (session.meta.mode !== "chat" && session.meta.mode !== "quick-edit") {
|
|
2138
|
+
session.meta.mode = "chat";
|
|
2139
|
+
}
|
|
2105
2140
|
if (typeof session.meta.titleGenerated !== "boolean") {
|
|
2106
2141
|
session.meta.titleGenerated = session.meta.title != null;
|
|
2107
2142
|
}
|
|
@@ -2141,6 +2176,19 @@ async function deleteChat(id) {
|
|
|
2141
2176
|
assertSafeChatId(id);
|
|
2142
2177
|
await fs14.rm(paths.chat(id), { force: true });
|
|
2143
2178
|
}
|
|
2179
|
+
var QUICK_EDIT_MAX_TURNS = 8;
|
|
2180
|
+
var QUICK_EDIT_KEEP_TURNS = 4;
|
|
2181
|
+
function trimQuickEditHistory(session) {
|
|
2182
|
+
if (session.meta.mode !== "quick-edit") return;
|
|
2183
|
+
const turnStarts = [];
|
|
2184
|
+
session.messages.forEach((m, i) => {
|
|
2185
|
+
if (m.role === "user" && typeof m.content === "string") turnStarts.push(i);
|
|
2186
|
+
});
|
|
2187
|
+
if (turnStarts.length <= QUICK_EDIT_MAX_TURNS) return;
|
|
2188
|
+
session.messages = session.messages.slice(
|
|
2189
|
+
turnStarts[turnStarts.length - QUICK_EDIT_KEEP_TURNS]
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2144
2192
|
async function appendMessage(session, message) {
|
|
2145
2193
|
session.messages.push(message);
|
|
2146
2194
|
session.meta.updatedAt = nowIso();
|
|
@@ -2242,6 +2290,32 @@ import { promises as fs16 } from "node:fs";
|
|
|
2242
2290
|
function isContextFile(v) {
|
|
2243
2291
|
return CONTEXT_FILES.includes(v);
|
|
2244
2292
|
}
|
|
2293
|
+
var DETAIL_CONTEXT_LINES = 8;
|
|
2294
|
+
function toLines(text) {
|
|
2295
|
+
return text === "" ? [] : text.replace(/\n$/, "").split("\n");
|
|
2296
|
+
}
|
|
2297
|
+
function detailSnapshots(before, after) {
|
|
2298
|
+
const a = toLines(before);
|
|
2299
|
+
const b = toLines(after);
|
|
2300
|
+
let prefix = 0;
|
|
2301
|
+
while (prefix < a.length && prefix < b.length && a[prefix] === b[prefix]) prefix++;
|
|
2302
|
+
let suffix = 0;
|
|
2303
|
+
while (suffix < a.length - prefix && suffix < b.length - prefix && a[a.length - 1 - suffix] === b[b.length - 1 - suffix]) {
|
|
2304
|
+
suffix++;
|
|
2305
|
+
}
|
|
2306
|
+
const leading = Math.max(0, prefix - DETAIL_CONTEXT_LINES);
|
|
2307
|
+
const trailing = Math.max(0, suffix - DETAIL_CONTEXT_LINES);
|
|
2308
|
+
if (leading === 0 && trailing === 0) return { before, after };
|
|
2309
|
+
let fences = 0;
|
|
2310
|
+
for (let i = 0; i < leading; i++) {
|
|
2311
|
+
if (/^\s{0,3}(```|~~~)/.test(a[i])) fences++;
|
|
2312
|
+
}
|
|
2313
|
+
return {
|
|
2314
|
+
before: a.slice(leading, a.length - trailing).join("\n"),
|
|
2315
|
+
after: b.slice(leading, b.length - trailing).join("\n"),
|
|
2316
|
+
trimmed: { leading, trailing, ...fences % 2 === 1 ? { inFence: true } : {} }
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2245
2319
|
var updateContextTool = {
|
|
2246
2320
|
name: "update_context",
|
|
2247
2321
|
description: "Write to one of the agent's context files (schemas.md, conventions.md, feedback.md). Call this the moment you learn something durable \u2014 without waiting to be asked: a clarified schema fact or value set, a project convention, a user correction, a data-quality quirk, or an error pattern from running SQL. Saving is cheap and git-tracked; prefer over-saving to losing a finding. Prefer 'append' for new findings; use 'replace' only when restructuring an entire file. Writes are scoped to the data source bound to this chat; with no connection bound they go to the workspace-level (unassigned) docs.",
|
|
@@ -2290,16 +2364,16 @@ var updateContextTool = {
|
|
|
2290
2364
|
};
|
|
2291
2365
|
}
|
|
2292
2366
|
const target = paths.contextFile(input.file, connectionId);
|
|
2367
|
+
let current = "";
|
|
2368
|
+
try {
|
|
2369
|
+
current = await fs16.readFile(target, "utf8");
|
|
2370
|
+
} catch (err) {
|
|
2371
|
+
if (err.code !== "ENOENT") throw err;
|
|
2372
|
+
}
|
|
2293
2373
|
let next;
|
|
2294
2374
|
if (input.mode === "replace") {
|
|
2295
2375
|
next = input.content.endsWith("\n") ? input.content : input.content + "\n";
|
|
2296
2376
|
} else {
|
|
2297
|
-
let current = "";
|
|
2298
|
-
try {
|
|
2299
|
-
current = await fs16.readFile(target, "utf8");
|
|
2300
|
-
} catch (err) {
|
|
2301
|
-
if (err.code !== "ENOENT") throw err;
|
|
2302
|
-
}
|
|
2303
2377
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2304
2378
|
const block = `## ${stamp}
|
|
2305
2379
|
|
|
@@ -2311,7 +2385,8 @@ ${input.content.trim()}
|
|
|
2311
2385
|
return {
|
|
2312
2386
|
toolResult: `Wrote ${next.length} bytes to context/${input.file}.md (${input.mode})`,
|
|
2313
2387
|
isError: false,
|
|
2314
|
-
uiSummary: `Updated context/${input.file}.md (${input.mode})
|
|
2388
|
+
uiSummary: `Updated context/${input.file}.md (${input.mode})`,
|
|
2389
|
+
detail: { file: `${input.file}.md`, mode: input.mode, ...detailSnapshots(current, next) }
|
|
2315
2390
|
};
|
|
2316
2391
|
}
|
|
2317
2392
|
};
|
|
@@ -2550,9 +2625,13 @@ var runSqlTool = {
|
|
|
2550
2625
|
description: "Execute SQL against the active database connection and return the result. Use this to validate queries you've written and to inspect data. If the query errors, capture what you learned by calling update_context against feedback.md so future runs avoid the same mistake. Row results are capped (default 50, max 500) \u2014 add LIMIT or aggregation if you need a wider view. IMPORTANT: this tool executes the SQL verbatim against whatever role the connection uses. Connections default to read-only, which rejects anything but a single read statement (SELECT/WITH/SHOW/EXPLAIN/TABLE/VALUES). On a read-write connection it will also run DDL (CREATE/DROP/ALTER) and DML (INSERT/UPDATE/DELETE/TRUNCATE), so before running anything that mutates data or schema, call ask_user_question to confirm.",
|
|
2551
2626
|
input_schema: {
|
|
2552
2627
|
type: "object",
|
|
2553
|
-
required: ["sql"],
|
|
2628
|
+
required: ["sql", "name"],
|
|
2554
2629
|
properties: {
|
|
2555
2630
|
sql: { type: "string", description: "SQL to execute." },
|
|
2631
|
+
name: {
|
|
2632
|
+
type: "string",
|
|
2633
|
+
description: 'A short human-readable name for this query (3\u20136 words) describing what it does, e.g. "Monthly revenue by region". Shown in the UI next to the query.'
|
|
2634
|
+
},
|
|
2556
2635
|
connection_id: {
|
|
2557
2636
|
type: "string",
|
|
2558
2637
|
description: "Connection UUID. Defaults to the chat's bound connection."
|
|
@@ -2623,7 +2702,7 @@ var runSqlTool = {
|
|
|
2623
2702
|
};
|
|
2624
2703
|
|
|
2625
2704
|
// server/agent/tools/render_chart.ts
|
|
2626
|
-
var CHART_TYPES = ["bar", "line", "area", "pie"];
|
|
2705
|
+
var CHART_TYPES = ["bar", "stacked-bar", "line", "area", "pie", "funnel"];
|
|
2627
2706
|
var MAX_ROWS2 = 500;
|
|
2628
2707
|
var MAX_SERIES = 8;
|
|
2629
2708
|
function parseSeries(raw) {
|
|
@@ -2664,7 +2743,7 @@ function parseData(raw) {
|
|
|
2664
2743
|
}
|
|
2665
2744
|
var renderChartTool = {
|
|
2666
2745
|
name: "render_chart",
|
|
2667
|
-
description: `Render a chart inline in the chat to visualize query results. Supply the data rows directly (shaped from your run_sql results) along with the chart spec \u2014 the chart is self-contained. Use this after run_sql when a visualization communicates the answer better than a table: trends over time (line/area), category comparisons (bar),
|
|
2746
|
+
description: `Render a chart inline in the chat to visualize query results. Supply the data rows directly (shaped from your run_sql results) along with the chart spec \u2014 the chart is self-contained. Use this after run_sql when a visualization communicates the answer better than a table: trends over time (line/area), category comparisons (bar), part-to-whole across categories (stacked-bar), proportions (pie), or stage-by-stage drop-off like a conversion pipeline (funnel). Keep data small and pre-aggregated (cap ${MAX_ROWS2} rows). Each data row is an object keyed by column name, e.g. \`[{ "month": "Jan", "revenue": 120 }, \u2026]\`. \`x\` names the category column; \`series\` names the numeric column(s) to plot. For pie and funnel charts only the first series is used; for funnel, order rows from the widest stage to the narrowest. Pass \`sourceQuery\` \u2014 the \`name\` you gave the run_sql call the data came from \u2014 so the chart links back to its query.`,
|
|
2668
2747
|
input_schema: {
|
|
2669
2748
|
type: "object",
|
|
2670
2749
|
required: ["type", "x", "series", "data"],
|
|
@@ -2672,7 +2751,7 @@ var renderChartTool = {
|
|
|
2672
2751
|
type: {
|
|
2673
2752
|
type: "string",
|
|
2674
2753
|
enum: CHART_TYPES,
|
|
2675
|
-
description: "Chart kind: bar, line, area, or
|
|
2754
|
+
description: "Chart kind: bar, stacked-bar, line, area, pie, or funnel."
|
|
2676
2755
|
},
|
|
2677
2756
|
title: { type: "string", description: "Optional title shown above the chart." },
|
|
2678
2757
|
x: {
|
|
@@ -2695,6 +2774,10 @@ var renderChartTool = {
|
|
|
2695
2774
|
type: "array",
|
|
2696
2775
|
description: "Row objects to chart, keyed by column name.",
|
|
2697
2776
|
items: { type: "object" }
|
|
2777
|
+
},
|
|
2778
|
+
sourceQuery: {
|
|
2779
|
+
type: "string",
|
|
2780
|
+
description: "The `name` of the run_sql call whose results this chart plots. Lets the UI link the chart back to its source query."
|
|
2698
2781
|
}
|
|
2699
2782
|
}
|
|
2700
2783
|
},
|
|
@@ -2727,7 +2810,8 @@ var renderChartTool = {
|
|
|
2727
2810
|
x: input.x,
|
|
2728
2811
|
series,
|
|
2729
2812
|
data,
|
|
2730
|
-
...typeof input.title === "string" && input.title.trim() !== "" ? { title: input.title } : {}
|
|
2813
|
+
...typeof input.title === "string" && input.title.trim() !== "" ? { title: input.title } : {},
|
|
2814
|
+
...typeof input.sourceQuery === "string" && input.sourceQuery.trim() !== "" ? { sourceQuery: input.sourceQuery.trim() } : {}
|
|
2731
2815
|
};
|
|
2732
2816
|
const seriesLabel = series.map((s) => s.label ?? s.key).join(", ");
|
|
2733
2817
|
return {
|
|
@@ -2756,8 +2840,14 @@ function findTool(name) {
|
|
|
2756
2840
|
return TOOL_BY_NAME[name];
|
|
2757
2841
|
}
|
|
2758
2842
|
function anthropicToolDefs(opts = {}) {
|
|
2759
|
-
const { worksheetBound = true } = opts;
|
|
2760
|
-
|
|
2843
|
+
const { worksheetBound = true, mode = "chat" } = opts;
|
|
2844
|
+
const withheld = /* @__PURE__ */ new Set();
|
|
2845
|
+
if (!worksheetBound) withheld.add("write_sql");
|
|
2846
|
+
if (mode === "quick-edit") {
|
|
2847
|
+
withheld.add("render_chart");
|
|
2848
|
+
withheld.add("ask_user_question");
|
|
2849
|
+
}
|
|
2850
|
+
return TOOLS.filter((t) => !withheld.has(t.name)).map((t) => ({
|
|
2761
2851
|
name: t.name,
|
|
2762
2852
|
description: t.description,
|
|
2763
2853
|
input_schema: t.input_schema
|
|
@@ -2820,7 +2910,7 @@ async function runAgentTurn(opts) {
|
|
|
2820
2910
|
);
|
|
2821
2911
|
}
|
|
2822
2912
|
async function runTurn(opts) {
|
|
2823
|
-
const { session, emit } = opts;
|
|
2913
|
+
const { session, emit, signal } = opts;
|
|
2824
2914
|
let apiKey;
|
|
2825
2915
|
try {
|
|
2826
2916
|
apiKey = await getAnthropicKey();
|
|
@@ -2829,8 +2919,12 @@ async function runTurn(opts) {
|
|
|
2829
2919
|
return;
|
|
2830
2920
|
}
|
|
2831
2921
|
const system = buildSystemPrompt(session);
|
|
2832
|
-
const tools = anthropicToolDefs({
|
|
2922
|
+
const tools = anthropicToolDefs({
|
|
2923
|
+
worksheetBound: !!session.meta.worksheetSlug,
|
|
2924
|
+
mode: session.meta.mode
|
|
2925
|
+
});
|
|
2833
2926
|
for (let step = 0; step < MAX_STEPS; step += 1) {
|
|
2927
|
+
if (signal?.aborted) return;
|
|
2834
2928
|
let final;
|
|
2835
2929
|
let model;
|
|
2836
2930
|
let usage;
|
|
@@ -2841,6 +2935,7 @@ async function runTurn(opts) {
|
|
|
2841
2935
|
system,
|
|
2842
2936
|
tools,
|
|
2843
2937
|
messages: session.messages,
|
|
2938
|
+
signal,
|
|
2844
2939
|
onTextDelta: (delta) => {
|
|
2845
2940
|
textEmitChain = textEmitChain.then(
|
|
2846
2941
|
() => Promise.resolve(emit({ type: "text_delta", text: delta }))
|
|
@@ -2852,6 +2947,7 @@ async function runTurn(opts) {
|
|
|
2852
2947
|
model = result.model;
|
|
2853
2948
|
usage = result.usage;
|
|
2854
2949
|
} catch (err) {
|
|
2950
|
+
if (signal?.aborted) return;
|
|
2855
2951
|
await emit({ type: "error", message: err.message });
|
|
2856
2952
|
return;
|
|
2857
2953
|
}
|
|
@@ -2892,6 +2988,15 @@ async function runTurn(opts) {
|
|
|
2892
2988
|
});
|
|
2893
2989
|
continue;
|
|
2894
2990
|
}
|
|
2991
|
+
if (signal?.aborted) {
|
|
2992
|
+
resultBlocks.push({
|
|
2993
|
+
type: "tool_result",
|
|
2994
|
+
tool_use_id: block.id,
|
|
2995
|
+
content: "Canceled: the user aborted this run before the tool executed.",
|
|
2996
|
+
is_error: true
|
|
2997
|
+
});
|
|
2998
|
+
continue;
|
|
2999
|
+
}
|
|
2895
3000
|
await emit({
|
|
2896
3001
|
type: "tool_start",
|
|
2897
3002
|
toolUseId: block.id,
|
|
@@ -2940,7 +3045,8 @@ async function runTurn(opts) {
|
|
|
2940
3045
|
toolUseId: block.id,
|
|
2941
3046
|
name: tool.name,
|
|
2942
3047
|
ok: !execution.isError,
|
|
2943
|
-
summary: execution.uiSummary
|
|
3048
|
+
summary: execution.uiSummary,
|
|
3049
|
+
...execution.detail ? { detail: execution.detail } : {}
|
|
2944
3050
|
});
|
|
2945
3051
|
resultBlocks.push({
|
|
2946
3052
|
type: "tool_result",
|
|
@@ -2976,7 +3082,7 @@ async function runTurn(opts) {
|
|
|
2976
3082
|
});
|
|
2977
3083
|
}
|
|
2978
3084
|
async function resumeWithAnswer(opts) {
|
|
2979
|
-
const { session, userAnswer, emit } = opts;
|
|
3085
|
+
const { session, userAnswer, emit, signal } = opts;
|
|
2980
3086
|
const pending = session.meta.pending;
|
|
2981
3087
|
if (!pending) {
|
|
2982
3088
|
await emit({ type: "error", message: "No pending question to resume." });
|
|
@@ -2993,7 +3099,7 @@ async function resumeWithAnswer(opts) {
|
|
|
2993
3099
|
}
|
|
2994
3100
|
await persistSession(session);
|
|
2995
3101
|
await clearPending(session);
|
|
2996
|
-
await runAgentTurn({ session, emit });
|
|
3102
|
+
await runAgentTurn({ session, emit, signal });
|
|
2997
3103
|
}
|
|
2998
3104
|
|
|
2999
3105
|
// server/api/agent.ts
|
|
@@ -3055,7 +3161,8 @@ app6.post("/sessions", async (c) => {
|
|
|
3055
3161
|
worksheetSlug: body.worksheetSlug ?? null,
|
|
3056
3162
|
connectionId: body.connectionId ?? null,
|
|
3057
3163
|
title: body.title ?? null,
|
|
3058
|
-
standalone: body.standalone ?? false
|
|
3164
|
+
standalone: body.standalone ?? false,
|
|
3165
|
+
mode: body.mode === "quick-edit" ? "quick-edit" : "chat"
|
|
3059
3166
|
});
|
|
3060
3167
|
return c.json(session, 201);
|
|
3061
3168
|
});
|
|
@@ -3149,7 +3256,9 @@ app6.post("/sessions/:id/messages", async (c) => {
|
|
|
3149
3256
|
);
|
|
3150
3257
|
}
|
|
3151
3258
|
return streamSSE(c, async (stream) => {
|
|
3259
|
+
const signal = c.req.raw.signal;
|
|
3152
3260
|
const emit = async (event) => {
|
|
3261
|
+
if (signal.aborted) return;
|
|
3153
3262
|
await stream.writeSSE({ data: JSON.stringify(event) });
|
|
3154
3263
|
};
|
|
3155
3264
|
try {
|
|
@@ -3166,11 +3275,12 @@ app6.post("/sessions/:id/messages", async (c) => {
|
|
|
3166
3275
|
});
|
|
3167
3276
|
return;
|
|
3168
3277
|
}
|
|
3278
|
+
trimQuickEditHistory(session);
|
|
3169
3279
|
await appendMessage(session, { role: "user", content: message });
|
|
3170
3280
|
if (!session.meta.title) {
|
|
3171
3281
|
await setTitle(session, message.slice(0, 60));
|
|
3172
3282
|
}
|
|
3173
|
-
await runAgentTurn({ session, emit });
|
|
3283
|
+
await runAgentTurn({ session, emit, signal });
|
|
3174
3284
|
});
|
|
3175
3285
|
} catch (err) {
|
|
3176
3286
|
await emit({ type: "error", message: err.message });
|
|
@@ -3190,7 +3300,9 @@ app6.post("/sessions/:id/respond", async (c) => {
|
|
|
3190
3300
|
return c.json({ error: "No pending question to answer." }, 409);
|
|
3191
3301
|
}
|
|
3192
3302
|
return streamSSE(c, async (stream) => {
|
|
3303
|
+
const signal = c.req.raw.signal;
|
|
3193
3304
|
const emit = async (event) => {
|
|
3305
|
+
if (signal.aborted) return;
|
|
3194
3306
|
await stream.writeSSE({ data: JSON.stringify(event) });
|
|
3195
3307
|
};
|
|
3196
3308
|
try {
|
|
@@ -3207,7 +3319,7 @@ app6.post("/sessions/:id/respond", async (c) => {
|
|
|
3207
3319
|
});
|
|
3208
3320
|
return;
|
|
3209
3321
|
}
|
|
3210
|
-
await resumeWithAnswer({ session, userAnswer: answer, emit });
|
|
3322
|
+
await resumeWithAnswer({ session, userAnswer: answer, emit, signal });
|
|
3211
3323
|
});
|
|
3212
3324
|
} catch (err) {
|
|
3213
3325
|
await emit({ type: "error", message: err.message });
|