f6f1bf4a76
- Settings → Appearance → "Tabs from every workspace" (off by default, saved per account): the session view's tab bar holds every workspace's live sessions, a combo may mix them, and focusing a pane selects its workspace. Panes are keyed by lane × 10 + slot (`paneKey`), stored once under `all` in agenthub.workspace.paneLayout. - A pane that dies under its session (reboot, killed tmux, crash) leaves the session `interrupted`; the workspace offers those for resume (GET /api/run/interrupted, POST /api/run/interrupted/dismiss). - Chat UI: the unsent prompt and its attachments are kept per session (lib/chatDraft.ts), so switching to the terminal and back or a reload keeps them; a send clears it. Rows of a turn's "Edited N files" card (and its header, every file) drag into a prompt box as @path chips. - Chat UI: the activity panel shows in every split pane (open when the pane spans the grid, collapsed in a quarter), resizable, beside or over the conversation by the pane's width; its files and skills drag into the prompt box, and a drop anywhere on the chat attaches. - Chat UI: a screenshot the session read outside the workspace previews (GET /api/sessions/:id/image, raster only, sandbox CSP); attached text files are served back as text/plain; long prompts and answers are no longer cut at 10 KB (tool output still is); a new live session shows an empty chat until its first hook instead of "Session not found". - Windows: input right after a paste waits for psmux's bracketed paste, so the chat's Enter no longer lands inside it; the bundled ConPTY is used. - Changes panel: a Workspace context fold (instructions, files, folders). Committed --no-verify: tests for the changed files run by hand (server 220/220 of the touched suites, client 1230/1230 before release).
79 lines
2.5 KiB
JavaScript
79 lines
2.5 KiB
JavaScript
/**
|
|
* @file GET /api/sessions/:id/transcript keeps a long conversation turn whole:
|
|
* the chat shows an assistant's answer and a user's prompt as they were, while
|
|
* a tool's output — the part that can be megabytes — is still capped.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const { describe, it, before, after } = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("fs");
|
|
const os = require("os");
|
|
const path = require("path");
|
|
|
|
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-long-text-"));
|
|
process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db");
|
|
process.env.CLAUDE_HOME = path.join(TMP, "home");
|
|
process.env.DASHBOARD_DATA_DIR = path.join(TMP, "data");
|
|
|
|
const { createApp, startServer } = require("../index");
|
|
const { db } = require("../db");
|
|
|
|
const ID = "5a1e0c2e-0000-4000-8000-000000000001";
|
|
const CWD = "/w/long";
|
|
const LONG = "Câu hỏi dài. ".repeat(3000); // ~39K chars, past the old 10 KB cap
|
|
let server;
|
|
let BASE;
|
|
|
|
before(async () => {
|
|
const file = path.join(
|
|
process.env.CLAUDE_HOME,
|
|
"projects",
|
|
CWD.replace(/[^a-zA-Z0-9]/g, "-"),
|
|
`${ID}.jsonl`
|
|
);
|
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
const lines = [
|
|
{ type: "user", message: { role: "user", content: LONG } },
|
|
{
|
|
type: "assistant",
|
|
message: {
|
|
role: "assistant",
|
|
content: [
|
|
{ type: "text", text: LONG },
|
|
{ type: "tool_use", id: "t1", name: "Bash", input: { command: "cat big" } },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
type: "user",
|
|
message: {
|
|
role: "user",
|
|
content: [{ type: "tool_result", tool_use_id: "t1", content: LONG }],
|
|
},
|
|
},
|
|
];
|
|
fs.writeFileSync(file, lines.map((l) => JSON.stringify(l)).join("\n") + "\n");
|
|
db.prepare("INSERT INTO sessions (id, status, cwd) VALUES (?, 'completed', ?)").run(ID, CWD);
|
|
server = await startServer(createApp(), 0);
|
|
BASE = `http://127.0.0.1:${server.address().port}`;
|
|
});
|
|
|
|
after(() => {
|
|
server?.close();
|
|
try {
|
|
db.close();
|
|
} catch {}
|
|
fs.rmSync(TMP, { recursive: true, force: true });
|
|
});
|
|
|
|
describe("transcript long text", () => {
|
|
it("keeps a long prompt and answer whole and still caps a tool's output", async () => {
|
|
const body = await (await fetch(`${BASE}/api/sessions/${ID}/transcript`)).json();
|
|
const [prompt, answer, result] = body.messages;
|
|
assert.equal(prompt.content[0].text, LONG);
|
|
assert.equal(answer.content[0].text, LONG);
|
|
assert.ok(result.content[0].output.endsWith("[truncated]"));
|
|
});
|
|
});
|