This commit is contained in:
oogway 2026-01-31 00:13:13 +08:00 committed by GitHub
commit 5dc856bf2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 102 additions and 9 deletions

15
pnpm-lock.yaml generated
View File

@ -172,6 +172,13 @@ importers:
zod:
specifier: ^4.3.6
version: 4.3.6
optionalDependencies:
'@napi-rs/canvas':
specifier: ^0.1.88
version: 0.1.88
node-llama-cpp:
specifier: 3.15.0
version: 3.15.0(typescript@5.9.3)
devDependencies:
'@grammyjs/types':
specifier: ^3.23.0
@ -254,13 +261,6 @@ importers:
wireit:
specifier: ^0.14.12
version: 0.14.12
optionalDependencies:
'@napi-rs/canvas':
specifier: ^0.1.88
version: 0.1.88
node-llama-cpp:
specifier: 3.15.0
version: 3.15.0(typescript@5.9.3)
extensions/bluebubbles: {}
@ -1328,7 +1328,6 @@ packages:
'@lancedb/lancedb@0.23.0':
resolution: {integrity: sha512-aYrIoEG24AC+wILCL57Ius/Y4yU+xFHDPKLvmjzzN4byAjzeIGF0TC86S5RBt4Ji+dxS7yIWV5Q/gE5/fybIFQ==}
engines: {node: '>= 18'}
cpu: [x64, arm64]
os: [darwin, linux, win32]
peerDependencies:
apache-arrow: '>=15.0.0 <=18.1.0'

View File

@ -351,6 +351,7 @@ export async function compactEmbeddedPiSessionDirect(
const sessionLock = await acquireSessionWriteLock({
sessionFile: params.sessionFile,
timeoutMs: params.config?.agents?.defaults?.sessionWriteLockTimeoutMs,
});
try {
await prewarmSessionFile(params.sessionFile);

View File

@ -386,6 +386,7 @@ export async function runEmbeddedAttempt(
const sessionLock = await acquireSessionWriteLock({
sessionFile: params.sessionFile,
timeoutMs: params.config?.agents?.defaults?.sessionWriteLockTimeoutMs,
});
let sessionManager: ReturnType<typeof guardSessionManager> | undefined;

View File

@ -107,7 +107,9 @@ export async function acquireSessionWriteLock(params: {
release: () => Promise<void>;
}> {
registerCleanupHandlers();
const timeoutMs = params.timeoutMs ?? 10_000;
// Default timeout increased from 10s to 60s to prevent premature termination
// when multiple subagents compete for session file locks (see issue #4355)
const timeoutMs = params.timeoutMs ?? 60_000;
const staleMs = params.staleMs ?? 30 * 60 * 1000;
const sessionFile = path.resolve(params.sessionFile);
const sessionDir = path.dirname(sessionFile);

View File

@ -0,0 +1,87 @@
import { describe, expect, it } from "vitest";
import type { AgentDefaultsConfig } from "./types.agent-defaults.js";
import { AgentDefaultsSchema } from "./zod-schema.agent-defaults.js";
/**
* Helper to resolve session write lock timeout from config.
* Matches the pattern in session-write-lock.ts where default is 60_000ms.
*/
function resolveSessionWriteLockTimeout(config: {
agents?: { defaults?: { sessionWriteLockTimeoutMs?: number } };
}): number {
return config.agents?.defaults?.sessionWriteLockTimeoutMs ?? 60_000;
}
describe("sessionWriteLockTimeoutMs config", () => {
it("uses default timeout when unset", () => {
const config = {};
expect(resolveSessionWriteLockTimeout(config)).toBe(60_000);
});
it("uses default timeout when agents.defaults is empty", () => {
const config = { agents: { defaults: {} } };
expect(resolveSessionWriteLockTimeout(config)).toBe(60_000);
});
it("uses custom timeout from config", () => {
const config = {
agents: {
defaults: {
sessionWriteLockTimeoutMs: 30_000,
},
},
};
expect(resolveSessionWriteLockTimeout(config)).toBe(30_000);
});
it("accepts large timeout values", () => {
const config = {
agents: {
defaults: {
sessionWriteLockTimeoutMs: 120_000,
},
},
};
expect(resolveSessionWriteLockTimeout(config)).toBe(120_000);
});
it("config type includes sessionWriteLockTimeoutMs field", () => {
const config: AgentDefaultsConfig = {
sessionWriteLockTimeoutMs: 45_000,
};
expect(config.sessionWriteLockTimeoutMs).toBe(45_000);
});
it("validates positive integer timeout via zod schema", () => {
const validConfig = { sessionWriteLockTimeoutMs: 30_000 };
const result = AgentDefaultsSchema.safeParse(validConfig);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.sessionWriteLockTimeoutMs).toBe(30_000);
}
});
it("rejects negative timeout values", () => {
const invalidConfig = { sessionWriteLockTimeoutMs: -1000 };
const result = AgentDefaultsSchema.safeParse(invalidConfig);
expect(result.success).toBe(false);
});
it("rejects zero timeout value", () => {
const invalidConfig = { sessionWriteLockTimeoutMs: 0 };
const result = AgentDefaultsSchema.safeParse(invalidConfig);
expect(result.success).toBe(false);
});
it("rejects non-integer timeout values", () => {
const invalidConfig = { sessionWriteLockTimeoutMs: 1000.5 };
const result = AgentDefaultsSchema.safeParse(invalidConfig);
expect(result.success).toBe(false);
});
it("allows undefined/optional timeout", () => {
const config = {};
const result = AgentDefaultsSchema.safeParse(config);
expect(result.success).toBe(true);
});
});

View File

@ -196,6 +196,8 @@ export type AgentDefaultsConfig = {
};
/** Max concurrent agent runs across all conversations. Default: 1 (sequential). */
maxConcurrent?: number;
/** Timeout (ms) for acquiring per-session write lock. Default: 10000. */
sessionWriteLockTimeoutMs?: number;
/** Sub-agent defaults (spawned via sessions_spawn). */
subagents?: {
/** Max concurrent sub-agent runs (global lane: "subagent"). Default: 1. */

View File

@ -123,6 +123,7 @@ export const AgentDefaultsSchema = z
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
humanDelay: HumanDelaySchema.optional(),
timeoutSeconds: z.number().int().positive().optional(),
sessionWriteLockTimeoutMs: z.number().int().positive().optional(),
mediaMaxMb: z.number().positive().optional(),
typingIntervalSeconds: z.number().int().positive().optional(),
typingMode: z