| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import type { AgentMemory } from "@g1t/contracts"; |
| 5 | |
| 6 | import { |
| 7 | isLive, |
| 8 | kindLabel, |
| 9 | memoryGroups, |
| 10 | meterTone, |
| 11 | monthDays, |
| 12 | reposFrom, |
| 13 | routineInWords, |
| 14 | scheduleFrom, |
| 15 | scheduleInWords, |
| 16 | sessionRows, |
| 17 | sessionStatus, |
| 18 | shareOf, |
| 19 | timeField, |
| 20 | topSlices, |
| 21 | untilLabel, |
| 22 | versionChanges, |
| 23 | whereLabel, |
| 24 | } from "./agents/format.ts"; |
| 25 | |
| 26 | test("a session's status reads as its card in chat does", () => { |
| 27 | assert.deepEqual(sessionStatus("working"), { label: "Working", tone: "accent", moving: true }); |
| 28 | assert.deepEqual(sessionStatus("waiting"), { label: "Waiting on helpers", tone: "info", moving: true }); |
| 29 | assert.equal(sessionStatus("needs_approval").label, "Needs approval"); |
| 30 | assert.equal(sessionStatus("failed").tone, "danger"); |
| 31 | assert.equal(sessionStatus("something new").label, "something new"); |
| 32 | assert.ok(isLive("needs_approval")); |
| 33 | assert.ok(!isLive("done")); |
| 34 | }); |
| 35 | |
| 36 | test("kinds and places read in words", () => { |
| 37 | assert.equal(kindLabel("helper"), "Helper"); |
| 38 | assert.equal(kindLabel("subagent"), "Subagent"); |
| 39 | assert.equal(whereLabel({ channel_kind: "channel", channel_name: "releases" }), "#releases"); |
| 40 | assert.equal(whereLabel({ channel_kind: "dm", channel_name: null }), "A direct message"); |
| 41 | }); |
| 42 | |
| 43 | test("spend against a cap warns at 75% and stops at 100%", () => { |
| 44 | assert.equal(shareOf(5, null), null); |
| 45 | assert.equal(shareOf(5, 0), null); |
| 46 | assert.equal(shareOf(50, 100), 0.5); |
| 47 | assert.equal(meterTone(null), "accent"); |
| 48 | assert.equal(meterTone(0.5), "accent"); |
| 49 | assert.equal(meterTone(0.75), "warn"); |
| 50 | assert.equal(meterTone(1.2), "danger"); |
| 51 | }); |
| 52 | |
| 53 | test("a tree of sessions puts each child under its parent, oldest first", () => { |
| 54 | const s = (id: string, parent: string | null, at: string) => ({ id, parent_id: parent, created_at: at }); |
| 55 | const rows = sessionRows([s("root", null, "1"), s("b", "root", "3"), s("a", "root", "2"), s("a1", "a", "4"), s("orphan", "gone", "5")]); |
| 56 | assert.deepEqual( |
| 57 | rows.map((r) => `${r.session.id}:${r.depth}`), |
| 58 | ["root:0", "a:1", "a1:2", "b:1", "orphan:0"], |
| 59 | ); |
| 60 | // A cycle still shows each once. |
| 61 | const cycle = sessionRows([s("x", "y", "1"), s("y", "x", "2")]); |
| 62 | assert.equal(cycle.length, 2); |
| 63 | }); |
| 64 | |
| 65 | test("a schedule reads as a sentence", () => { |
| 66 | assert.equal(scheduleInWords({ every: "weekday", hour: 9, minute: 0, weekday: 1 }), "Every weekday at 09:00 UTC"); |
| 67 | assert.equal(scheduleInWords({ every: "week", hour: 17, minute: 30, weekday: 1 }), "Every Monday at 17:30 UTC"); |
| 68 | assert.equal(scheduleInWords({ every: "hour", hour: 0, minute: 15, weekday: 0 }), "Every hour at :15"); |
| 69 | assert.equal(scheduleInWords({ every: "hour", hour: 0, minute: 0, weekday: 0 }), "Every hour, on the hour"); |
| 70 | assert.equal(scheduleInWords({ every: "day", hour: 7, minute: 5, weekday: 0 }), "Every day at 07:05 UTC"); |
| 71 | }); |
| 72 | |
| 73 | test("a routine reads as its schedule, its events and its repositories", () => { |
| 74 | const labels = { pull_ready: "A pull request is ready for review", pull_merged: "A pull request is merged" }; |
| 75 | assert.equal( |
| 76 | routineInWords({ schedule: null, events: ["pull_ready"], repos: ["acme/web"] }, labels), |
| 77 | "When a pull request is ready for review · acme/web", |
| 78 | ); |
| 79 | assert.equal( |
| 80 | routineInWords({ schedule: { every: "day", hour: 9, minute: 0, weekday: 0 }, events: ["pull_ready", "pull_merged"], repos: [] }, labels), |
| 81 | "Every day at 09:00 UTC · When a pull request is ready for review, or a pull request is merged · Any repository", |
| 82 | ); |
| 83 | assert.equal(routineInWords({ schedule: null, events: [], repos: [] }, labels), "Never: it has no schedule or events"); |
| 84 | }); |
| 85 | |
| 86 | test("the routine dialog's fields become a schedule and back", () => { |
| 87 | assert.deepEqual(scheduleFrom("week", "17:30", "5"), { every: "week", hour: 17, minute: 30, weekday: 5 }); |
| 88 | assert.deepEqual(scheduleFrom("day", "", ""), { every: "day", hour: 9, minute: 0, weekday: 1 }); |
| 89 | assert.equal(scheduleFrom("never", "09:00", "1"), null); |
| 90 | assert.equal(scheduleFrom("day", "25:00", "1"), null); |
| 91 | assert.equal(timeField({ hour: 7, minute: 5 }), "07:05"); |
| 92 | }); |
| 93 | |
| 94 | test("repositories are typed as a list", () => { |
| 95 | assert.deepEqual(reposFrom("Acme/Web, acme/api acme/web"), ["acme/web", "acme/api"]); |
| 96 | assert.deepEqual(reposFrom(" "), []); |
| 97 | assert.equal(reposFrom("web"), null); |
| 98 | }); |
| 99 | |
| 100 | test("memories group by scope, pinned first", () => { |
| 101 | const m = (id: string, scope: AgentMemory["scope"], pinned: boolean, at: string) => |
| 102 | ({ id, scope, pinned, updated_at: at }) as AgentMemory; |
| 103 | const groups = memoryGroups([m("a", "person", false, "2"), m("b", "workspace", false, "3"), m("c", "workspace", true, "1"), m("d", "person", false, "4")]); |
| 104 | assert.deepEqual( |
| 105 | groups.map((g) => [g.scope, g.memories.map((x) => x.id)]), |
| 106 | [ |
| 107 | ["workspace", ["c", "b"]], |
| 108 | ["person", ["d", "a"]], |
| 109 | ], |
| 110 | ); |
| 111 | }); |
| 112 | |
| 113 | test("a month's days run to today, with spend or 0", () => { |
| 114 | const now = new Date("2026-10-04T12:00:00Z"); |
| 115 | assert.deepEqual(monthDays("2026-10", [{ day: "2026-10-02", micros: 5 }], now), [ |
| 116 | { day: "2026-10-01", micros: 0 }, |
| 117 | { day: "2026-10-02", micros: 5 }, |
| 118 | { day: "2026-10-03", micros: 0 }, |
| 119 | { day: "2026-10-04", micros: 0 }, |
| 120 | ]); |
| 121 | assert.equal(monthDays("2026-09", [], now).length, 30); |
| 122 | }); |
| 123 | |
| 124 | test("past five, the rest fold into one", () => { |
| 125 | const slices = Array.from({ length: 8 }, (_, i) => ({ key: `k${i}`, label: `L${i}`, micros: 10 - i, count: 1 })); |
| 126 | const top = topSlices(slices, 5); |
| 127 | assert.equal(top.length, 6); |
| 128 | assert.deepEqual(top[5], { key: "__rest", label: "3 more", micros: 5 + 4 + 3, count: 3 }); |
| 129 | assert.equal(topSlices(slices.slice(0, 6), 5).length, 6); |
| 130 | }); |
| 131 | |
| 132 | test("until reads short", () => { |
| 133 | const now = Date.parse("2026-10-04T12:00:00Z"); |
| 134 | assert.equal(untilLabel("2026-10-04T12:00:30Z", now), "now"); |
| 135 | assert.equal(untilLabel("2026-10-04T12:20:00Z", now), "in 20m"); |
| 136 | assert.equal(untilLabel("2026-10-04T15:00:00Z", now), "in 3h"); |
| 137 | assert.equal(untilLabel("2026-10-06T12:00:00Z", now), "in 2d"); |
| 138 | }); |
| 139 | |
| 140 | test("a version says what changed from the one before", () => { |
| 141 | assert.equal(versionChanges(null, { instructions: "a" }), "Created"); |
| 142 | assert.equal(versionChanges({ instructions: "a", budget: { monthly_micros: 1 } }, { instructions: "b", budget: { monthly_micros: 1 } }), "Changed job"); |
| 143 | assert.equal( |
| 144 | versionChanges({ personality_preset: "crisp", personality: "", routing: {} }, { personality_preset: "terse", personality: "x", routing: { floor: "fast" } }), |
| 145 | "Changed personality, models", |
| 146 | ); |
| 147 | assert.equal(versionChanges({ capacity: 3 }, { capacity: 3 }), "Saved with no changes"); |
| 148 | }); |