Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
98ad383
orchestrate: kill-tree descends into handed-off subplanner branches l…
deanrie Oct 16, 2026
b0346af
fix(orchestrate): fail kill-tree when cancellation fails
rudycelekli Oct 16, 2026
e3b7785
fix(orchestrate): cancel completed inspection timers
rudycelekli Oct 16, 2026
6e2e839
fix(orchestrate): paginate Andon reason replies
rudycelekli Oct 16, 2026
2e1fe73
fix(orchestrate): paginate root planner lookup
rudycelekli Oct 16, 2026
478ef44
fix(orchestrate): keep empty canonical verification authoritative
rudycelekli Oct 16, 2026
e51644f
fix(orchestrate): prefer the canonical handoff PR section
rudycelekli Oct 16, 2026
49f4192
fix(orchestrate): recognize empty PR headings at end of handoff
rudycelekli Oct 16, 2026
223bbe8
fix(orchestrate): preserve invalid required comment queues
rudycelekli Oct 16, 2026
bb32daf
fix(orchestrate): parse exponent-form measurement values
rudycelekli Oct 16, 2026
a38d696
test(orchestrate): cover relative exponent comparisons
rudycelekli Oct 16, 2026
f5be174
fix(orchestrate): compare fractional measurements relatively
rudycelekli Oct 16, 2026
f8fb6d5
fix(orchestrate): regex measurement parser returns the wrong value un…
deanrie Oct 22, 2026
126c881
fix(orchestrate): read a handoff section value written as a list item…
deanrie Oct 23, 2026
6607aca
fix(orchestrate): redact mid-line local paths and env-style secret as…
deanrie Oct 23, 2026
35af403
fix(orchestrate): serialise the required-comment retry queue across c…
deanrie Oct 27, 2026
e892e6a
fix(orchestrate): do not wait on a dead SSE stream after the watchdog…
deanrie Oct 26, 2026
8a3e024
style(orchestrate): biome format and import order for the bundled files
deanrie Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,104 @@ describe("SlackReactionAndonSource", () => {
expect(calls).toEqual([{ limit: 20, latest: expect.any(String) }]);
});

test("Finds the newest reason beyond the first reply page", async () => {
const cursors: (string | undefined)[] = [];
const latestBounds: (string | undefined)[] = [];
const source = new SlackReactionAndonSource(
{
...slackWithReactions([{ name: "rotating_light", users: ["U123"] }]),
async getThreadReplies(args) {
cursors.push(args.cursor);
latestBounds.push(args.latest);
return args.cursor === undefined
? {
messages: [
{
ts: "111.222",
text: "🚨 ANDON RAISED by parent: ignore parent",
},
{
ts: "111.300",
text: "🚨 ANDON RAISED by operator: old reason",
},
],
nextCursor: "page-two",
}
: {
messages: [
{
ts: "111.400",
text: ":rotating_light: ANDON RAISED by operator: newest reason",
},
{ ts: "111.500", text: "unrelated newest reply" },
],
};
},
},
{ channel: "C123", ts: "111.222" }
);
await expect(source.snapshot()).resolves.toMatchObject({
active: true,
reason: "newest reason",
});
expect(cursors).toEqual([undefined, "page-two"]);
expect(latestBounds[0]).toBe(latestBounds[1]);
});

test("Applies the recent reply window across pages", async () => {
const source = new SlackReactionAndonSource(
{
...slackWithReactions([{ name: "rotating_light", users: ["U123"] }]),
async getThreadReplies(args) {
return args.cursor === undefined
? {
messages: [
{
ts: "111.300",
text: "🚨 ANDON RAISED by operator: outside recent window",
},
],
nextCursor: "page-two",
}
: {
messages: Array.from({ length: 20 }, (_, i) => ({
ts: `112.${100 + i}`,
text: "unrelated",
})),
};
},
},
{ channel: "C123", ts: "111.222" }
);
const state = await source.snapshot();
expect(state.active).toBe(true);
expect(state).not.toHaveProperty("reason", "outside recent window");
});

test("Keeps Andon active when a later reply page fails", async () => {
const source = new SlackReactionAndonSource(
{
...slackWithReactions([{ name: "rotating_light", users: ["U123"] }]),
async getThreadReplies(args) {
if (args.cursor) throw new Error("slack_replies_unavailable");
return {
messages: [
{
ts: "111.300",
text: "🚨 ANDON RAISED by operator: incomplete history",
},
],
nextCursor: "page-two",
};
},
},
{ channel: "C123", ts: "111.222" }
);
const state = await source.snapshot();
expect(state.active).toBe(true);
expect(state).not.toHaveProperty("reason", "incomplete history");
});

test("Keeps Andon active when reason reply fetch fails", async () => {
const source = new SlackReactionAndonSource(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { describe, expect, test } from "bun:test";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import type { SlackAdapter, SlackMessageRef } from "../adapters/types.ts";
import {
commentRetryQueuePath,
postOrQueueComment,
} from "../core/comment-retry-queue.ts";

const TEST_SLACK_CHANNEL = "C123TEST";

// Slack is down for everyone. The first post hangs for a moment so that a
// second caller reads and writes the queue file while the first is waiting.
function slowThenFailingSlack(): SlackAdapter {
let calls = 0;
const notUsed = async (): Promise<never> => {
throw new Error("not used");
};
return {
postRunKickoff: notUsed,
lookupFirstNameByEmail: async () => undefined,
postInThread: notUsed,
editThreadMessage: notUsed,
uploadFileToThread: notUsed,
getReactions: notUsed,
getThreadReplies: notUsed,
async postCommentInThread(): Promise<SlackMessageRef> {
calls++;
if (calls === 1) await new Promise(resolve => setTimeout(resolve, 60));
throw new Error("slack_unavailable");
},
addReaction: notUsed,
removeReaction: notUsed,
};
}

describe("required comment retry queue under concurrent callers", () => {
// Two required comments posted at the same time, as two local workers (or the
// orchestrator and a worker) do at a handoff.
test("Neither entry is lost and neither caller reports a comment it never delivered", async () => {
const workspace = mkdtempSync(join(tmpdir(), "orch-comment-queue-lock-"));
try {
const slack = slowThenFailingSlack();
const destination = `slack:${TEST_SLACK_CHANNEL}:111.222`;
const first = postOrQueueComment({
destinations: { slack },
workspace,
destination,
body: "first worker is done",
sender: "worker-1",
criticality: "required",
});
await new Promise(resolve => setTimeout(resolve, 10));
const second = postOrQueueComment({
destinations: { slack },
workspace,
destination,
body: "second worker is done",
sender: "worker-2",
criticality: "required",
});
const results = await Promise.all([first, second]);

const queue = JSON.parse(
readFileSync(commentRetryQueuePath(workspace), "utf8")
) as { body: string }[];
expect(queue.map(entry => entry.body).sort()).toEqual([
"first worker is done",
"second worker is done",
]);
expect(results).toEqual(["queued", "queued"]);
} finally {
rmSync(workspace, { recursive: true, force: true });
}
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, expect, test } from "bun:test";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import {
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

Expand Down Expand Up @@ -392,3 +398,97 @@ describe("required comment retry queue", () => {
}
});
});

describe("invalid persisted required comment queues", () => {
const valid = {
id: "persisted-required-comment",
destination: `slack:${TEST_SLACK_CHANNEL}:111.222`,
body: "keep this required comment",
sender: "agent",
attempts: 0,
createdAt: "1970-01-01T00:00:00.000Z",
nextAttemptAt: "1970-01-01T00:00:00.000Z",
};
for (const [name, content] of [
["malformed JSON", '[{"body":"private fixture content"}'],
["non-array JSON", JSON.stringify({ pending: [valid] })],
[
"invalid entry beside a valid comment",
JSON.stringify([valid, { id: "broken" }]),
],
]) {
for (const operation of ["enqueue", "drain"] as const) {
test(`${operation} preserves ${name} without posting`, async () => {
const workspace = mkdtempSync(
join(tmpdir(), "orch-comment-corruption-")
);
const posts: SlackPost[] = [];
const queuePath = commentRetryQueuePath(workspace);
try {
writeFileSync(queuePath, content);
const destinations = { slack: slackAdapter({ posts }) };
const run =
operation === "drain"
? drainCommentRetryQueue({ workspace, destinations })
: postOrQueueComment({
workspace,
destinations,
destination: valid.destination,
body: "new required comment",
sender: "agent",
criticality: "required",
});
await expect(run).rejects.toThrow(/comment queue/);
expect(readFileSync(queuePath, "utf8")).toBe(content);
expect(posts).toEqual([]);
} finally {
rmSync(workspace, { recursive: true, force: true });
}
});
}
}

test("read failures preserve the queue path without posting", async () => {
const workspace = mkdtempSync(join(tmpdir(), "orch-comment-read-error-"));
const posts: SlackPost[] = [];
try {
mkdirSync(commentRetryQueuePath(workspace));
await expect(
postOrQueueComment({
workspace,
destinations: { slack: slackAdapter({ posts }) },
destination: valid.destination,
body: "new comment",
sender: "agent",
criticality: "required",
})
).rejects.toThrow(/comment queue/);
expect(posts).toEqual([]);
} finally {
rmSync(workspace, { recursive: true, force: true });
}
});

test("a valid empty queue can enqueue and deliver normally", async () => {
const workspace = mkdtempSync(join(tmpdir(), "orch-comment-empty-"));
const posts: SlackPost[] = [];
try {
writeFileSync(commentRetryQueuePath(workspace), "[]");
const result = await postOrQueueComment({
workspace,
destinations: { slack: slackAdapter({ posts }) },
destination: valid.destination,
body: "new comment",
sender: "agent",
criticality: "required",
});
expect(result).toBe("posted");
expect(posts.map(post => post.text)).toEqual(["new comment"]);
expect(
JSON.parse(readFileSync(commentRetryQueuePath(workspace), "utf8"))
).toEqual([]);
} finally {
rmSync(workspace, { recursive: true, force: true });
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { describe, expect, test } from "bun:test";
import { parseHandoffBranch, resolveRunBranch } from "../core/handoff.ts";

describe("parseHandoffBranch / resolveRunBranch", () => {
test("A branch written as a one-item list is read", () => {
const body = ["## Branch", "- `agent/listed-abc1`", "## What I did"].join(
"\n"
);
expect(parseHandoffBranch(body)).toBe("agent/listed-abc1");
});

test("Worker template wraps branch value in single backticks", () => {
const body = [
"## Status",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, test } from "bun:test";
import { parseHandoffVerification } from "../core/handoff.ts";

describe("canonical verification remains authoritative when empty", () => {
test("empty canonical verdict cannot inherit legacy pass", () => {
expect(
parseHandoffVerification("## Verification\n\n## Verdict\npass\n")
).toBeNull();
});
test("empty terminal canonical section cannot inherit earlier legacy pass", () => {
expect(
parseHandoffVerification("## Verdict\npass\n\n## Verification\n")
).toBeNull();
});
test("legacy-only verdict remains supported", () => {
expect(parseHandoffVerification("## Verdict\npass\n")).toBe(
"type-check-only"
);
});
test("recognized canonical verdict retains precedence", () => {
expect(
parseHandoffVerification(
"## Verification\nverifier-blocked\n\n## Verdict\npass\n"
)
).toBe("verifier-blocked");
});
});
Loading