Make English the repository working language (#5)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-20 17:45:02 +08:00
committed by GitHub
parent e80cde0ea6
commit 0eaaa54e25
136 changed files with 1515 additions and 1352 deletions
+22 -16
View File
@@ -82,30 +82,31 @@ export function parseScheduleFile(name: string, raw: string): ScheduleParseResul
} catch (err) {
return {
ok: false,
error: `TOML 解析失败:${err instanceof Error ? err.message : String(err)}`,
error: `Failed to parse TOML: ${err instanceof Error ? err.message : String(err)}`,
};
}
if (parsed === null || typeof parsed !== "object")
return { ok: false, error: "内容不是 TOML 表" };
return { ok: false, error: "Content is not a TOML table" };
const t = parsed as Record<string, unknown>;
const prompt = t["prompt"];
if (typeof prompt !== "string" || prompt.trim() === "") {
return { ok: false, error: "缺少必填字段 prompt" };
return { ok: false, error: "Missing required field prompt" };
}
const enabled = t["enabled"] === undefined ? false : t["enabled"];
if (typeof enabled !== "boolean") return { ok: false, error: "enabled 必须是布尔值" };
if (typeof enabled !== "boolean") return { ok: false, error: "enabled must be a boolean" };
const startAt = parseInstant(t["start_at"]);
if (startAt === null) return { ok: false, error: "start_at 缺失或不是合法的 ISO 8601 时刻" };
if (startAt === null)
return { ok: false, error: "start_at is missing or not a valid ISO 8601 instant" };
let period: string | undefined;
let periodMs: number | undefined;
if (t["period"] !== undefined) {
if (typeof t["period"] !== "string") return { ok: false, error: "period 必须是字符串" };
if (typeof t["period"] !== "string") return { ok: false, error: "period must be a string" };
const ms = parsePeriod(t["period"]);
if (ms === null) return { ok: false, error: "period 必须形如 30m / 12h / 7d" };
if (ms < MIN_PERIOD_MS) return { ok: false, error: "period 低于下限 5m" };
if (ms === null) return { ok: false, error: "period must look like 30m / 12h / 7d" };
if (ms < MIN_PERIOD_MS) return { ok: false, error: "period is below the 5m minimum" };
period = t["period"].trim();
periodMs = ms;
}
@@ -113,41 +114,46 @@ export function parseScheduleFile(name: string, raw: string): ScheduleParseResul
let endAt: { ms: number; raw: string } | undefined;
if (t["end_at"] !== undefined) {
const parsedEnd = parseInstant(t["end_at"]);
if (parsedEnd === null) return { ok: false, error: "end_at 不是合法的 ISO 8601 时刻" };
if (parsedEnd.ms <= startAt.ms) return { ok: false, error: "end_at 必须晚于 start_at" };
if (parsedEnd === null) return { ok: false, error: "end_at is not a valid ISO 8601 instant" };
if (parsedEnd.ms <= startAt.ms)
return { ok: false, error: "end_at must be later than start_at" };
endAt = parsedEnd;
}
let sessionId: string | undefined;
if (t["session_id"] !== undefined) {
if (typeof t["session_id"] !== "string" || t["session_id"] === "") {
return { ok: false, error: "session_id 必须是非空字符串" };
return { ok: false, error: "session_id must be a non-empty string" };
}
sessionId = t["session_id"];
}
let workspace: string | undefined;
if (t["workspace"] !== undefined) {
if (typeof t["workspace"] !== "string" || t["workspace"] === "") {
return { ok: false, error: "workspace 必须是非空字符串" };
return { ok: false, error: "workspace must be a non-empty string" };
}
workspace = t["workspace"];
}
let modelId: string | undefined;
if (t["model_id"] !== undefined) {
if (typeof t["model_id"] !== "string" || t["model_id"] === "") {
return { ok: false, error: "model_id 必须是非空字符串" };
return { ok: false, error: "model_id must be a non-empty string" };
}
modelId = t["model_id"];
}
let provider: string | undefined;
if (t["provider"] !== undefined) {
if (typeof t["provider"] !== "string" || t["provider"] === "") {
return { ok: false, error: "provider 必须是非空字符串" };
return { ok: false, error: "provider must be a non-empty string" };
}
provider = t["provider"];
}
if (provider !== undefined && modelId === undefined) {
return { ok: false, error: "provider 仅与 model_id 成对使用(模型引用须成对给出)" };
return {
ok: false,
error:
"provider is only used together with model_id (a model reference must be given as a pair)",
};
}
if (
sessionId !== undefined &&
@@ -155,7 +161,7 @@ export function parseScheduleFile(name: string, raw: string): ScheduleParseResul
) {
return {
ok: false,
error: "目标二选一:workspace 与 provider / model_id 仅用于新建 Session 模式",
error: "Pick one target: workspace and provider / model_id are only for new-Session mode",
};
}
+34 -13
View File
@@ -52,9 +52,10 @@ import type { UsageContext } from "./usage-recorder.js";
/** 409 for when there's nothing to compact: give the specific reason rather than a one-size-fits-none message. */
function compactUnavailable(why: Exclude<CompactAvailability, "ok">): HttpError {
const messages: Record<typeof why, string> = {
unsupported: "该 Agent 未配置上下文压缩能力。",
empty: "当前上下文没有可压缩的内容(尚无已完成的对话轮次)。",
just_compacted: "上下文刚压缩过,此后还没有新的对话,无需再次压缩。",
unsupported: "This Agent does not have context compaction configured.",
empty: "The current context has nothing to compact (no completed conversation turns yet).",
just_compacted:
"The context was just compacted and there is no new conversation since; no need to compact again.",
};
return new HttpError(409, "nothing_to_compact", messages[why]);
}
@@ -129,7 +130,7 @@ export function createCoreSessionLoader(root: string): SessionLoader {
throw new HttpError(
409,
"workspace_missing",
`该 Session 的 Workspace 已不存在:${row.workspace},无法继续。请新建 Session。`,
`This Session's Workspace no longer exists: ${row.workspace}, so it cannot continue. Create a new Session.`,
);
}
try {
@@ -533,7 +534,11 @@ export class SessionManager {
private assertOpen(): void {
if (this.closed) {
throw new HttpError(503, "shutting_down", "服务端正在关停,暂不接受新任务。");
throw new HttpError(
503,
"shutting_down",
"Server is shutting down; not accepting new Tasks.",
);
}
}
@@ -541,23 +546,35 @@ export class SessionManager {
private assertAgentNotDeleting(sessionId: string): void {
const row = this.deps.sessions.findById(sessionId);
if (row && this.deletingAgents.has(agentKey(row.projectId, row.agentId))) {
throw new HttpError(409, "agent_deleting", "该 Agent 正在删除,暂不接受新任务。");
throw new HttpError(
409,
"agent_deleting",
"This Agent is being deleted; not accepting new Tasks.",
);
}
}
/** This Session is being deleted → 409 (guards against the entry/Trace being rebuilt and reviving it inside the deletion race window). */
private assertSessionNotDeleting(sessionId: string): void {
if (this.deletingSessions.has(sessionId)) {
throw new HttpError(409, "session_deleting", "该 Session 正在删除,暂不接受新任务。");
throw new HttpError(
409,
"session_deleting",
"This Session is being deleted; not accepting new Tasks.",
);
}
}
private assertIdle(entry: RuntimeEntry): void {
if (entry.status === "running") {
throw new HttpError(409, "task_in_progress", "该 Session 已有进行中的 Task。");
throw new HttpError(409, "task_in_progress", "This Session already has a Task in progress.");
}
if (entry.status === "compacting") {
throw new HttpError(409, "compacting", "该 Session 正在压缩上下文,暂不接受新输入。");
throw new HttpError(
409,
"compacting",
"This Session is compacting its context; not accepting new input.",
);
}
}
@@ -567,7 +584,11 @@ export class SessionManager {
if (existing) return existing;
const row = this.deps.sessions.findById(sessionId);
if (!row) {
throw new HttpError(404, "session_not_found", "Session 不存在或无权访问。");
throw new HttpError(
404,
"session_not_found",
"Session does not exist or you do not have access.",
);
}
const session = await this.deps.loader.load(row);
// The Session/Agent was marked for deletion while loading: discard the load result,
@@ -676,7 +697,7 @@ export class SessionManager {
}
} catch (err) {
this.log(
`[subagent] 子会话登记失败: ${err instanceof Error ? err.message : String(err)}`,
`[subagent] Failed to register child session: ${err instanceof Error ? err.message : String(err)}`,
);
this.deps.errors?.record({
source: "subagent",
@@ -702,7 +723,7 @@ export class SessionManager {
try {
await this.deps.recorder.record(ctx, msg);
} catch (err) {
this.log(`[usage] 落库失败: ${err instanceof Error ? err.message : String(err)}`);
this.log(`[usage] Insert failed: ${err instanceof Error ? err.message : String(err)}`);
this.deps.errors?.record({ source: "usage", err, ctx, code: "usage_insert_failed" });
}
}
@@ -710,7 +731,7 @@ export class SessionManager {
// The SDK doesn't normally throw (errors are converged into the message stream);
// this is a defensive record here to avoid crashing the runtime.
this.log(
`[session] 运行异常: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`,
`[session] Run failed: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`,
);
this.deps.errors?.record({ source: "session", err, ctx, code: "session_run_failed" });
} finally {
@@ -75,12 +75,16 @@ type LlmFailure = "failed" | "timeout" | "malformed";
/** LLM failure state → error code, classification, and fallback message (used when the abort reason isn't available). */
const LLM_FAILURES: Record<LlmFailure, { code: string; kind: ErrorKind; text: string }> = {
failed: { code: "llm_failed", kind: "unexpected", text: "LLM 请求失败(不可重试)。" },
timeout: { code: "llm_timeout", kind: "expected", text: "LLM 请求超时(引擎重连重试)。" },
failed: { code: "llm_failed", kind: "unexpected", text: "LLM request failed (not retryable)." },
timeout: {
code: "llm_timeout",
kind: "expected",
text: "LLM request timed out (the engine reconnects and retries).",
},
malformed: {
code: "llm_malformed",
kind: "expected",
text: "LLM 响应无法解析(引擎重连重试)。",
text: "LLM response could not be parsed (the engine reconnects and retries).",
},
};
@@ -113,7 +117,7 @@ function originKey(msg: OmniMessage): string {
* drop the reason.
*/
function toolFailureText(output: string): string {
if (!output) return "工具执行失败(无输出)。";
if (!output) return "Tool execution failed (no output).";
if (output.length <= MESSAGE_MAX) return output;
return `…${output.slice(output.length - (MESSAGE_MAX - 1))}`;
}
@@ -71,7 +71,7 @@ export class TitleGenerator implements TitleNotifier {
this.inflight.add(ctx.sessionId);
void this.generate(ctx, session, req)
.catch((err: unknown) => {
this.log(`[title] 生成失败: ${err instanceof Error ? err.message : String(err)}`);
this.log(`[title] Generation failed: ${err instanceof Error ? err.message : String(err)}`);
this.deps.errors?.record({ source: "title", err, ctx, code: "title_failed" });
})
.finally(() => {
@@ -97,7 +97,9 @@ export class TitleGenerator implements TitleNotifier {
try {
await this.deps.recorder.record(ctx, tokenUsage(emptyTokenCounts(), res.usage));
} catch (err) {
this.log(`[title] 用量落库失败: ${err instanceof Error ? err.message : String(err)}`);
this.log(
`[title] Usage insert failed: ${err instanceof Error ? err.message : String(err)}`,
);
this.deps.errors?.record({
source: "title",
err,
@@ -109,7 +111,7 @@ export class TitleGenerator implements TitleNotifier {
} catch (err) {
// A model request error (rate limit / timeout / network, etc.) shouldn't leave the
// title permanently missing: log it and fall through to the fallback.
this.log(`[title] 模型请求失败: ${err instanceof Error ? err.message : String(err)}`);
this.log(`[title] Model request failed: ${err instanceof Error ? err.message : String(err)}`);
this.deps.errors?.record({ source: "title", err, ctx, code: "title_llm_failed" });
}
// When the LLM produces no usable title (failure / empty result), truncate the fallback material's first line — this guarantees a title is always generated.