Public: anyone can read this; the crew can change it.

The terminal's System screen says how the forge is: the version and commit it runs, draining or not, the rollout and its incidents, as MCP `health` does #7304

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

torque/forge:torque/7296-the-terminal-s-system-screen-says-how-th → main 1 commit fork

  • Merged. torque/7296-the-terminal-s-system-screen-says-how-th was merged into main as 7ff8945.

Review

Sign in to review this change.
3 files changed +102 −5
src/tui/forge.ts +7 −2
@@ -68,7 +68,8 @@ import { resilienceLines, resilienceScreen } from "./screens/resilience.ts";
6868 import { forgeCeiling } from "../flow/ceiling.ts";
6969 import { flowLines, flowScreen } from "./screens/flow.ts";
7070 import { readHeld } from "../flow/held.ts";
71 import { memoryScreen, systemLines, systemScreen } from "./screens/system.ts";
71 import { forgeLines, memoryScreen, systemLines, systemScreen } from "./screens/system.ts";
72 import { readHealth } from "../actions/health.ts";
7273 import { grantable, READS_KEPT, recentReads } from "../core/telemetry.ts";
7374 import { noteRead, setTelemetryGrant, telemetryReader } from "../actions/telemetry.ts";
7475 import { queuesFor, systemFor } from "../system/telemetry.ts";
@@ -303,7 +304,11 @@ export function forgeCommands(session: Session): (ctx: Ctx) => Command[] {
303304 id: "system",
304305 title: "System",
305306 hint: "the machine now: CPU, memory, disks, processes",
306 run: () => ctx.push(systemScreen(ctx, systemLines(liveMachine().read()))),
307 run: () => ctx.push(systemScreen(ctx, [
308 ...forgeLines(readHealth(session.app, session.user)),
309 "",
310 ...systemLines(liveMachine().read()),
311 ])),
307312 });
308313 commands.push({
309314 id: "telemetry",
src/tui/screens/system.ts +29 −1
@@ -6,6 +6,11 @@
66 * It reads `live().read()`, the same view the page and the API serve.
77 * Read-only; Enter, Esc or q closes it.
88 *
9 * Above the machine, the forge itself (#7296): `forgeLines` reads
10 * `readHealth`, the answer MCP `health` gives the same user — version, short
11 * commit, serving or draining, and one more line only when a rollout is not
12 * simply fine or the platform reports incidents.
13 *
914 * `memoryScreen` is `/system`'s other half (#2151): the forge's RSS beside
1015 * its JS heap, an hour a row for the last 24 h, read through `memoryHours`
1116 * like the page. The gate is the page's (`canSeeMemory`): anyone but a forge
@@ -14,12 +19,14 @@
1419
1520 import type { Ctx, Screen } from "../app/app.ts";
1621 import type { SystemView } from "../../system/view.ts";
22 import type { readHealth } from "../../actions/health.ts";
23 import { rolloutLine, type ForgeHealth } from "../../kernel/guard.ts";
1724 import { canSeeMemory, memoryHours } from "../../boards/memory.ts";
1825 import { binding } from "../app/keys.ts";
1926 import type { Session } from "../session.ts";
2027 import { bytes, spark } from "../../system/format.ts";
2128 import { style } from "../term/style.ts";
22 import { pad, wrap } from "../term/width.ts";
29 import { pad, truncate, wrap } from "../term/width.ts";
2330 import { dialog } from "../ui/widgets.ts";
2431 import { Scroll, drawLines, hitScroll } from "./common.ts";
2532
@@ -49,6 +56,27 @@ export function duplicateLines(dups: SystemView["duplicates"], inner: number): s
4956 const capOf = (g: SystemView["groups"][number]): string =>
5057 g.capBytes === null ? "" : `${bytes(g.rssBytes)} of ${bytes(g.capBytes)} ${g.capBy === "kernel" ? "kernel" : "watched"}`;
5158
59 /**
60 * The forge as MCP `health` says it: the first line always, the second only
61 * when there is something to say. A stranger's answer carries no rollout or
62 * incidents, so it never has a second line.
63 */
64 export function forgeLines(h: ReturnType<typeof readHealth>, inner = WIDTH - 6): string[] {
65 const head = `forge ${h.version} · ${h.commit === null ? "no commit" : h.commit.slice(0, 7)} · ${h.draining ? "draining" : "serving"}`;
66 if (!("incidents" in h)) return [head];
67 const full = h as ForgeHealth;
68 const rollout = rolloutLine(full);
69 // `stuck` and `held` are the rollout's own words; the rest are incidents.
70 const others = full.incidents.filter((i) => i.key !== "stuck" && i.key !== "held");
71 const said = [
72 ...(rollout === null ? [] : [rollout]),
73 ...(others.length === 0
74 ? []
75 : [`${others.length} incident${others.length === 1 ? "" : "s"}: ${others.map((i) => `${i.key}: ${i.message}`).join("; ")}`]),
76 ];
77 return said.length === 0 ? [head] : [head, truncate(said.join(" · "), inner)];
78 }
79
5280 export function systemLines(v: SystemView): string[] {
5381 const inner = WIDTH - 6;
5482 const cores = v.cpu.cores.slice(0, CORES).map(
test/tui-system.test.ts +66 −2
@@ -4,15 +4,16 @@
44 * guest get neither.
55 */
66 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
7 import { mkdtemp, rm } from "node:fs/promises";
7 import { mkdtemp, rm, writeFile } from "node:fs/promises";
88 import { tmpdir } from "node:os";
99 import { join } from "node:path";
1010 import { createAgent } from "../src/actions/crew.ts";
11 import { readHealth } from "../src/actions/health.ts";
1112 import { createApp, type App } from "../src/app.ts";
1213 import { recordMemory } from "../src/boards/memory.ts";
1314 import { createUser, type User } from "../src/core/users.ts";
1415 import { createForgeApp, forgeCommands, Session } from "../src/tui/forge.ts";
15 import { memoryLines, memoryScreen } from "../src/tui/screens/system.ts";
16 import { forgeLines, memoryLines, memoryScreen } from "../src/tui/screens/system.ts";
1617 import { headlessTerminal } from "../src/tui/term/terminal.ts";
1718 import { quietLogs } from "./helpers/globals.ts";
1819 import { settleApp } from "./helpers/tui.ts";
@@ -99,3 +100,66 @@ describe("tui system memory", () => {
99100 }
100101 });
101102 });
103
104 describe("tui system: the forge's own state (#7296)", () => {
105 const SHA = "0123456789abcdef0123456789abcdef01234567";
106 const saved = ["FORGE_GUARD_DIR", "FORGE_VERSION", "FORGE_COMMIT"].map((k) => [k, process.env[k]] as const);
107 let guard: string;
108
109 beforeAll(async () => {
110 guard = await mkdtemp(join(tmpdir(), "forge-tui-system-guard-"));
111 process.env["FORGE_GUARD_DIR"] = guard;
112 process.env["FORGE_VERSION"] = "v9.8.7";
113 process.env["FORGE_COMMIT"] = SHA;
114 });
115
116 afterAll(async () => {
117 for (const [k, v] of saved) v === undefined ? delete process.env[k] : (process.env[k] = v);
118 await rm(guard, { recursive: true, force: true });
119 });
120
121 const seed = async (incidents: Record<string, string>) => {
122 await writeFile(join(guard, "status.json"), JSON.stringify({ incidents }));
123 await writeFile(join(guard, "checkedAt"), new Date().toISOString());
124 };
125
126 async function systemText(user: User): Promise<string> {
127 const terminal = headlessTerminal(120, 50);
128 const { app: tui, session } = createForgeApp(app, identity(user), { terminal, exitHooks: false });
129 tui.start();
130 forgeCommands(session)(tui.ctx).find((c) => c.id === "system")!.run();
131 await settleApp(tui);
132 const text = tui.screen.toText();
133 tui.stop?.();
134 return text;
135 }
136
137 test("all well: one line with the version, the short commit and serving; nothing more", async () => {
138 await seed({});
139 const lines = forgeLines(readHealth(app, carol));
140 expect(lines).toEqual(["forge 9.8.7 · 0123456 · serving"]);
141 const text = await systemText(carol);
142 expect(text).toContain("forge 9.8.7 · 0123456 · serving");
143 expect(text).not.toContain("incident");
144 });
145
146 test("a seeded incident adds exactly one line, in words", async () => {
147 await seed({ down: "the edge answered 502" });
148 const lines = forgeLines(readHealth(app, carol));
149 expect(lines).toEqual(["forge 9.8.7 · 0123456 · serving", "1 incident: down: the edge answered 502"]);
150 expect(await systemText(carol)).toContain("1 incident: down: the edge answered 502");
151 });
152
153 test("a blocked rollout and an incident share the one line", async () => {
154 await seed({ stuck: "quota refused", "oom:web": "killed" });
155 expect(forgeLines(readHealth(app, carol), 20)[1]).toBe("rollout blocked: qu…");
156 const lines = forgeLines(readHealth(app, carol));
157 expect(lines.length).toBe(2);
158 expect(lines[1]).toBe("rollout blocked: quota refused · 1 incident: oom:web: killed");
159 });
160
161 test("a stranger's answer, as MCP gives it, never has a second line", async () => {
162 await seed({ down: "the edge answered 502" });
163 expect(forgeLines(readHealth(app, undefined))).toEqual(["forge 9.8.7 · 0123456 · serving"]);
164 });
165 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs