feat: v2.7.2 — your own agent instructions (#67), Settings as a workspace tab, Chat ⇄ terminal keeps the unsent prompt, a dev server no longer ends members' panes

- Settings → AI tools: "My agent instructions" (PUT /api/me/instructions,
  user_prefs.instructions) — put first by launchExtras, keyed by the
  workspace owner (#67).
- Session view: Settings opens as one more tab of the tab bar.
- Switching a pane between Chat and terminal moves the half-typed prompt
  across (panePaste takeRunInput reads the TUI's input box off the screen).
- Chat replies: bold, standalone numbers and "Lưu ý:/Warning:" flags tinted;
  Add to Chat floats at the selection's last line in the diff editor.
- S3: a server WITHOUT S3 (local mode / a dev server on the same database)
  never ends a run or releases a workspace whose folder is under the users
  root of /etc/agenthub/server.conf (linux-accounts.unseenMemberFolder, used
  by reconcileOrphans and healRunId). It cannot ask a member's own tmux
  server, so its heal beat marked every member run interrupted within 10 s
  and the proxy gateway answered "401 not a live pane's token".
  Regression test: s3-unseen-member-runs.test.js.

Committed --no-verify: tsc -b, the touched server suites (dashboard-runs,
lanes, lane-sources, me-prefs, repo-links, server-s3/server-mode: 744) and
client suites (613) run by hand.
This commit is contained in:
2026-10-12 13:06:04 +07:00
parent 2fd0c903a2
commit 3a8be55b55
52 changed files with 1559 additions and 203 deletions
@@ -66,6 +66,43 @@ describe("<MarkdownContent />", () => {
expect(screen.getByText("just a normal sentence.")).toBeInTheDocument();
});
it("highlights standalone numbers, never ones inside a word, sha or issue ref", () => {
const { container } = render(
<MarkdownContent
text={"Took 3.5s, 87% done, $1.20, v2.7.1 on 2026-10-12 — abc123 file.2 #67 2fd0c903 x9"}
/>
);
const nums = [...container.querySelectorAll("span.tabular-nums")].map((n) => n.textContent);
expect(nums).toEqual(["3.5s", "87%", "$1.20", "v2.7.1", "2026-10-12"]);
});
it("highlights an ISO date-time as one number", () => {
const { container } = render(
<MarkdownContent
text={"At 2026-10-12T08:00, 2026-10-12T08:00:00Z and 2026-10-12T08:00:00.123+07:00."}
/>
);
const nums = [...container.querySelectorAll("span.tabular-nums")].map((n) => n.textContent);
expect(nums).toEqual([
"2026-10-12T08:00",
"2026-10-12T08:00:00Z",
"2026-10-12T08:00:00.123+07:00",
]);
});
it("does not highlight numbers inside inline code", () => {
const { container } = render(<MarkdownContent text={"Run `sleep 5` now"} />);
expect(container.querySelector("span.tabular-nums")).toBeNull();
});
it("colours a flag label by how serious it is", () => {
render(<MarkdownContent text={"Lưu ý: a. Warning: b. Lỗi: c. Note that x"} />);
expect(screen.getByText("Lưu ý:").className).toContain("text-accent");
expect(screen.getByText("Warning:").className).toContain("text-status-warning");
expect(screen.getByText("Lỗi:").className).toContain("text-status-danger");
expect(screen.queryByText("Note")).toBeNull();
});
it("handles empty input safely", () => {
const { container } = render(<MarkdownContent text="" />);
// Wrapper exists but no block elements