Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: an editor for an agent's AGENTS.md that saves as a diff

The instructions row of the agent detail panel gets an edit button that
opens the agent's AGENTS.md, as the harness last published it, in a text
box. Save sends a unified diff against the revision that was loaded; when
the agent changed the file meanwhile the editor keeps the operator's text,
shows the agent's version and offers to save over it or take it. An unsaved
edit survives closing the dialog.

The diff is built in the browser (LCS over lines, three lines of context,
git's no-newline marker) and was checked against git apply on 600 random
before/after pairs.
This commit is contained in:
iris 2026-10-18 12:40:34 +02:00 • committed by mara
commit b5d2e310ba
7 changed files with 664 additions and 0 deletions

View file

@ -0,0 +1,79 @@
// The AGENTS.md editor's diff builder. Run by `npm test`, which Node runs with
// its built-in test runner and type stripping, so the `.ts` module is
// imported as it is. The expected strings are what `git diff` writes for the
// same two texts, and `git apply` reads back.
import assert from "node:assert/strict";
import { test } from "node:test";
import { unifiedDiff } from "../src/agent/detail/unified-diff.ts";
test("the same text has no diff", () => {
assert.equal(unifiedDiff("a\nb\n", "a\nb\n"), "");
assert.equal(unifiedDiff("", ""), "");
});
test("a changed middle line carries three lines of context", () => {
const before = "1\n2\n3\n4\n5\n6\n7\n8\n9\n";
const after = "1\n2\n3\n4\nfive\n6\n7\n8\n9\n";
assert.equal(
unifiedDiff(before, after),
"--- a/AGENTS.md\n+++ b/AGENTS.md\n" +
"@@ -2,7 +2,7 @@\n 2\n 3\n 4\n-5\n+five\n 6\n 7\n 8\n",
);
});
test("two far apart changes make two hunks, near ones merge", () => {
const lines = Array.from({ length: 20 }, (_, i) => `l${i + 1}`);
const far = [...lines];
far[1] = "X";
far[17] = "Y";
const farDiff = unifiedDiff(lines.join("\n") + "\n", far.join("\n") + "\n");
assert.equal(farDiff.match(/^@@/gm)?.length, 2);
const near = [...lines];
near[1] = "X";
near[6] = "Y";
const nearDiff = unifiedDiff(lines.join("\n") + "\n", near.join("\n") + "\n");
assert.equal(nearDiff.match(/^@@/gm)?.length, 1);
});
test("writing into an empty file adds every line", () => {
assert.equal(
unifiedDiff("", "hello\nworld\n"),
"--- a/AGENTS.md\n+++ b/AGENTS.md\n@@ -0,0 +1,2 @@\n+hello\n+world\n",
);
});
test("emptying a file removes every line", () => {
assert.equal(
unifiedDiff("hello\n", ""),
"--- a/AGENTS.md\n+++ b/AGENTS.md\n@@ -1 +0,0 @@\n-hello\n",
);
});
test("a missing final newline is marked as git marks it", () => {
assert.equal(
unifiedDiff("a\nb", "a\nb\n"),
"--- a/AGENTS.md\n+++ b/AGENTS.md\n@@ -1,2 +1,2 @@\n a\n" +
"-b\n\\ No newline at end of file\n+b\n",
);
assert.equal(
unifiedDiff("a\nb\n", "a\nc"),
"--- a/AGENTS.md\n+++ b/AGENTS.md\n@@ -1,2 +1,2 @@\n a\n" +
"-b\n+c\n\\ No newline at end of file\n",
);
});
test("a pure insertion keeps the lines around it as context", () => {
assert.equal(
unifiedDiff("a\nc\n", "a\nb\nc\n"),
"--- a/AGENTS.md\n+++ b/AGENTS.md\n@@ -1,2 +1,3 @@\n a\n+b\n c\n",
);
});
test("the file name is the one asked for", () => {
assert.ok(
unifiedDiff("a\n", "b\n", "CLAUDE.md").startsWith(
"--- a/CLAUDE.md\n+++ b/CLAUDE.md\n",
),
);
});