feat(hub): raise agent turns/time limits and notify teachers on failure

Defaults and silo env go to 150 turns / 1800s wall clock. Run completion
appends a clear Feishu notice for max-turns, timeout, and other failures
(partial answer kept). Startup process-restart kills notify the bound chat.
Release v0.0.38.
This commit is contained in:
2026-07-20 12:07:45 +00:00
parent 34c4908237
commit 6cefb2a938
11 changed files with 267 additions and 26 deletions
+64
View File
@@ -0,0 +1,64 @@
import { describe, expect, it } from "vitest";
import {
appendTeacherNotice,
isMaxTurnsError,
teacherFacingRunOutcome,
} from "../../src/feishu/runOutcomeNotice.js";
describe("teacherFacingRunOutcome", () => {
it("explains max-turn stops with the configured ceiling", () => {
const outcome = teacherFacingRunOutcome({
wallTimeExceeded: false,
interrupted: false,
resultStatus: "failed",
resultError: "Claude Code returned an error result: Reached maximum number of turns (25)",
maxTurns: 150,
maxRunSeconds: 1800,
hasPartialText: true,
});
expect(outcome.isError).toBe(true);
expect(outcome.notice).toContain("150");
expect(outcome.notice).toContain("最大步骤数");
expect(outcome.notice).toContain("部分结果");
});
it("explains wall-clock timeouts", () => {
const outcome = teacherFacingRunOutcome({
wallTimeExceeded: true,
interrupted: false,
resultStatus: "interrupted",
resultError: undefined,
maxTurns: 150,
maxRunSeconds: 1800,
hasPartialText: false,
});
expect(outcome.isError).toBe(true);
expect(outcome.notice).toContain("1800");
expect(outcome.notice).toContain("超时");
});
it("is quiet on successful completion", () => {
expect(
teacherFacingRunOutcome({
wallTimeExceeded: false,
interrupted: false,
resultStatus: "completed",
resultError: undefined,
maxTurns: 150,
maxRunSeconds: 1800,
hasPartialText: true,
}),
).toEqual({ isError: false, notice: undefined });
});
it("appendTeacherNotice joins body and notice", () => {
expect(appendTeacherNotice("hello", "bye")).toBe("hello\n\nbye");
expect(appendTeacherNotice("", "only")).toBe("only");
});
it("detects max-turn SDK wording", () => {
expect(isMaxTurnsError("Reached maximum number of turns (25)")).toBe(true);
expect(isMaxTurnsError("result_error_max_turns")).toBe(true);
expect(isMaxTurnsError("network glitch")).toBe(false);
});
});