feat: v2.7.8 — Artifacts hub, clone related repos as extra folders, quieter resume prompt
- Artifacts (/artifacts): claude.ai pages sessions publish (captured from the Artifact tool's hook), pinned workspace files, skills and plugins, each kept as a snapshot; shared ones appear in a Hub and a skill or plugin installs into the viewer's own account (server/lib/artifacts.js, /api/artifacts, docs/plans/artifacts/). - Workspace extra folders: clone one or more related repos beside the workspace and add them in one step (POST /api/lanes/:id/sources/clone). - A pane that ended by itself while tmux kept running is no longer offered for resume (tmux.serverStartedAt in reconcileOrphans). - Changes panel: a folder that is not a git repo opens on Files, and its files open from there. - Plus the parallel sessions' work in this tree: File Explorer button in the panel header, user delete, RSA SSH keys, chat/terminal and pet fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3186,6 +3186,48 @@ describe("GET /api/sessions/:id/file", () => {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
assert.equal((await fetch(url(report))).status, 404);
|
||||
});
|
||||
|
||||
it("serves a file the session's Bash or reply named as a whole path (issue #76)", async () => {
|
||||
const sid = "named-file-" + Date.now();
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ah-named-file-"));
|
||||
const report = path.join(dir, "REPORT_ssh_ec2.md");
|
||||
const out = path.join(dir, "out.txt");
|
||||
const chart = path.join(dir, "chart.png");
|
||||
const stem = path.join(dir, "REPORT");
|
||||
const unread = path.join(dir, "locked.md");
|
||||
fs.writeFileSync(report, "# Audit");
|
||||
fs.writeFileSync(out, "generated");
|
||||
fs.writeFileSync(chart, "PNGBYTES");
|
||||
fs.writeFileSync(stem, "stem");
|
||||
fs.writeFileSync(unread, "locked");
|
||||
fs.chmodSync(unread, 0o000);
|
||||
await post("/api/hooks/event", { hook_type: "SessionStart", data: { session_id: sid } });
|
||||
await post("/api/hooks/event", {
|
||||
hook_type: "PostToolUse",
|
||||
data: {
|
||||
session_id: sid,
|
||||
tool_name: "Bash",
|
||||
tool_input: { command: `python gen.py -o ${out} && plot ${chart} ${unread}` },
|
||||
tool_response: { stdout: "done", stderr: "" },
|
||||
},
|
||||
});
|
||||
await post("/api/hooks/event", {
|
||||
hook_type: "Stop",
|
||||
data: { session_id: sid, last_assistant_message: `Báo cáo: \`${report}\`.` },
|
||||
});
|
||||
const url = (p) => `/api/sessions/${sid}/file?path=${encodeURIComponent(p)}`;
|
||||
|
||||
assert.deepEqual((await fetch(url(report))).body, { text: "# Audit", truncated: false });
|
||||
assert.deepEqual((await fetch(url(out))).body, { text: "generated", truncated: false });
|
||||
const img = await fetch(`/api/sessions/${sid}/image?path=${encodeURIComponent(chart)}`);
|
||||
assert.equal(img.status, 200);
|
||||
// Only a WHOLE path: `<dir>/REPORT` is a prefix of the named report, not named itself.
|
||||
assert.equal((await fetch(url(stem))).status, 404);
|
||||
// Named, but its account could not read it (root sees through the mode).
|
||||
if (process.getuid?.() !== 0) assert.equal((await fetch(url(unread))).status, 404);
|
||||
fs.chmodSync(unread, 0o600);
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/sessions/:id/image", () => {
|
||||
|
||||
Reference in New Issue
Block a user