fixup: address CodeRabbit review on PR #386

Twelve actionable items (7 Major + 5 Minor) from the CodeRabbit review on
claude-code-best/claude-code#386:

- docs/internals/autonomy-jira.md: typo "due input close" → "due to input close".
- src/utils/autonomyRuns.ts:
  - selectPersistedAutonomyRuns no longer evicts active (queued/running) runs
    when the combined list exceeds AUTONOMY_RUNS_MAX. Active runs are kept in
    full and the inactive history is capped to the remaining budget so
    persisted ownership for live work survives.
  - isValidOwnerProcessId now allows pid <= 4_194_304 so a live run owned by
    the maximum Linux PID is not treated as stale.
- src/utils/autonomyAuthority.ts: maskCodeFencedLines tracks the active fence
  length and only closes the fence when a same-character run of equal-or-
  greater length appears with no trailing content, so a nested ```yaml inside
  an outer ```` block no longer leaks fake `tasks:` entries into the parser.
- src/cli/print.ts: late-shutdown branches in the cron and scheduled-task
  paths now call cancelQueuedAutonomyCommands({ commands: [command] }) instead
  of markAutonomyRunCancelled(...). Updating run state alone left the
  queue-side record orphaned for resume/recovery.
- src/utils/processUserInput/processSlashCommand.tsx: scheduled-task-result
  notification is enqueued before finalizeAutonomyRunCompleted (which queues
  follow-up autonomy commands) so both at priority: 'later' land in order and
  the next autonomy step can not run before the worker's output is observed.
- src/screens/REPL.tsx + src/utils/handlePromptSubmit.ts:
  - onQuery now returns Promise<boolean>: false from the concurrent-guard
    skip path, true otherwise. Other call sites use `void onQuery(...)` and
    are unaffected. handlePromptSubmit's onQuery prop type matches.
  - The autonomy-prompt callsite captures the executed flag, finalizes
    claim.claimedCommands as { type: 'completed' } only when onQuery actually
    ran, and runs the completed-finalize in its own try/catch so a failure
    there does not propagate into the outer catch and trigger a second
    finalize as { type: 'failed' } for the same commands.
  - Removed the unsafe `command.value as string` cast; createUserMessage
    already accepts `string | ContentBlockParam[]`.
  - createUserMessage mock in src/__tests__/handlePromptSubmit.test.ts now
    matches the new Promise<boolean> shape.
- packages/builtin-tools/src/tools/RemoteTriggerTool/__tests__/
  RemoteTriggerTool.test.ts:
  - Inline auth mock replaced with the shared tests/mocks/auth (added).
  - The full mock of src/constants/oauth.js is replaced by a narrow
    side-effect-only mock that overrides the env-reading helpers
    (getOauthConfig, fileSuffixForOauthConfig, MCP_CLIENT_METADATA_URL) and
    delegates pure data exports to the real module.
- tests/integration/dependency-overrides.test.ts:
  - mermaid does not export `./package.json` in its exports map, so
    require.resolve('mermaid/package.json') throws
    ERR_PACKAGE_PATH_NOT_EXPORTED in runtimes that honor exports semantics.
    The test now resolves the package entry and walks up to the package
    root via a small findPackageJson helper.
  - readFileSync from node:fs is replaced with `await Bun.file(...).text()`
    to match the project's Bun-API requirement.

Validation:
- bun run typecheck (clean).
- bun test → 3996 pass / 0 fail across 305 test files.

Targets PRs:
- amDosion/claude-code-bast#8 (fork-internal review)
- claude-code-best/claude-code#386 (upstream review, same head branch)
This commit is contained in:
unraid
2026-04-29 15:17:50 +08:00
parent f2e9af4927
commit 452a7e6a15
11 changed files with 151 additions and 84 deletions

View File

@@ -38,9 +38,9 @@ function createBaseParams() {
commands: [],
setUserInputOnProcessing: mock((_prompt?: string) => {}),
setAbortController: mock((_abortController: AbortController | null) => {}),
onQuery: mock(async () => undefined) as unknown as (
onQuery: mock(async () => true) as unknown as (
...args: unknown[]
) => Promise<void>,
) => Promise<boolean>,
setAppState: mock((_updater: unknown) => {}),
}
}

View File

@@ -2839,10 +2839,7 @@ function runHeadlessStreaming(
workload: WORKLOAD_CRON,
})
if (inputClosed) {
await markAutonomyRunCancelled(
command.autonomy!.runId,
command.autonomy!.rootDir,
)
await cancelQueuedAutonomyCommands({ commands: [command] })
return
}
enqueue({
@@ -2875,10 +2872,7 @@ function runHeadlessStreaming(
})
if (!command) return
if (inputClosed) {
await markAutonomyRunCancelled(
command.autonomy!.runId,
command.autonomy!.rootDir,
)
await cancelQueuedAutonomyCommands({ commands: [command] })
return
}
await markAutonomyRunFailed(
@@ -2899,10 +2893,7 @@ function runHeadlessStreaming(
})
if (!command) return
if (inputClosed) {
await markAutonomyRunCancelled(
command.autonomy!.runId,
command.autonomy!.rootDir,
)
await cancelQueuedAutonomyCommands({ commands: [command] })
return
}
enqueue({

View File

@@ -3474,7 +3474,7 @@ export function REPL({
onBeforeQueryCallback?: (input: string, newMessages: MessageType[]) => Promise<boolean>,
input?: string,
effort?: EffortValue,
): Promise<void> => {
): Promise<boolean> => {
// If this is a teammate, mark them as active when starting a turn
if (isAgentSwarmsEnabled()) {
const teamName = getTeamName();
@@ -3505,7 +3505,7 @@ export function REPL({
logEvent('tengu_concurrent_onquery_enqueued', {});
}
});
return;
return false;
}
try {
@@ -3538,7 +3538,7 @@ export function REPL({
if (onBeforeQueryCallback && input) {
const shouldProceed = await onBeforeQueryCallback(input, latestMessages);
if (!shouldProceed) {
return;
return true;
}
}
@@ -3687,6 +3687,7 @@ export function REPL({
}
}
}
return true;
},
[onQueryImpl, setAppState, resetLoadingState, queryGuard, mrOnBeforeQuery, mrOnTurnComplete],
);
@@ -4851,13 +4852,37 @@ export function REPL({
// Create a user message with the formatted content (includes XML wrapper)
const userMessage = createUserMessage({
content: command.value as string,
content: command.value,
isMeta: command.isMeta ? true : undefined,
origin: command.origin,
});
let executed = false;
try {
executed = (await onQuery([userMessage], newAbortController, true, [], mainLoopModel)) !== false;
} catch (error: unknown) {
try {
await finalizeAutonomyCommandsForTurn({
commands: claim.claimedCommands,
outcome: { type: 'failed', error },
currentDir: getCwd(),
priority: 'later',
});
} catch (finalizeError: unknown) {
logError(toError(finalizeError));
}
logError(toError(error));
return;
}
// Only finalize as completed when onQuery actually executed the turn
// (it returns false from the concurrent-guard path without running).
// Keep this finalize in its own try/catch so a failure here does not
// trigger a second finalize as `failed` for the same commands.
if (!executed) {
return;
}
try {
await onQuery([userMessage], newAbortController, true, [], mainLoopModel);
const nextCommands = await finalizeAutonomyCommandsForTurn({
commands: claim.claimedCommands,
outcome: { type: 'completed' },
@@ -4867,14 +4892,8 @@ export function REPL({
for (const nextCommand of nextCommands) {
enqueue(nextCommand);
}
} catch (error: unknown) {
await finalizeAutonomyCommandsForTurn({
commands: claim.claimedCommands,
outcome: { type: 'failed', error },
currentDir: getCwd(),
priority: 'later',
});
logError(toError(error));
} catch (finalizeError: unknown) {
logError(toError(finalizeError));
}
})().catch((error: unknown) => {
logError(toError(error));

View File

@@ -143,15 +143,24 @@ function mergeAgentsAuthority(files: AutonomyAuthorityFile[]): string | null {
function maskCodeFencedLines(lines: string[]): string[] {
const masked = lines.slice()
let activeFenceChar: '`' | '~' | null = null
let activeFenceLen = 0
for (let i = 0; i < masked.length; i++) {
const trimmed = masked[i]!.trim()
const fenceMatch = trimmed.match(/^(```+|~~~+)/)
const fenceMatch = trimmed.match(/^([`~])\1{2,}/)
if (fenceMatch) {
const fenceChar = fenceMatch[1]![0] as '`' | '~'
const fenceChar = fenceMatch[1]! as '`' | '~'
const fenceLen = fenceMatch[0]!.length
const trailing = trimmed.slice(fenceLen)
if (activeFenceChar === null) {
activeFenceChar = fenceChar
} else if (activeFenceChar === fenceChar) {
activeFenceLen = fenceLen
} else if (
activeFenceChar === fenceChar &&
fenceLen >= activeFenceLen &&
trailing.trim() === ''
) {
activeFenceChar = null
activeFenceLen = 0
}
masked[i] = ''
continue

View File

@@ -130,20 +130,18 @@ function isAutonomyRunActive(run: AutonomyRunRecord): boolean {
function selectPersistedAutonomyRuns(
runs: AutonomyRunRecord[],
): AutonomyRunRecord[] {
const retained = runs
.slice()
.map(cloneRunRecord)
.sort((left, right) => {
const leftActive = isAutonomyRunActive(left)
const rightActive = isAutonomyRunActive(right)
if (leftActive !== rightActive) {
return leftActive ? -1 : 1
}
return right.createdAt - left.createdAt
})
.slice(0, AUTONOMY_RUNS_MAX)
const cloned = runs.slice().map(cloneRunRecord)
const active = cloned
.filter(isAutonomyRunActive)
.sort((left, right) => right.createdAt - left.createdAt)
const history = cloned
.filter(run => !isAutonomyRunActive(run))
.sort((left, right) => right.createdAt - left.createdAt)
.slice(0, Math.max(0, AUTONOMY_RUNS_MAX - active.length))
return retained.sort((left, right) => right.createdAt - left.createdAt)
return [...active, ...history].sort(
(left, right) => right.createdAt - left.createdAt,
)
}
function normalizePersistedRunRecord(
@@ -260,7 +258,7 @@ function isValidOwnerProcessId(pid: number | undefined): pid is number {
typeof pid === 'number' &&
Number.isInteger(pid) &&
pid > 0 &&
pid < 4_194_304
pid <= 4_194_304
)
}
@@ -407,10 +405,7 @@ async function persistAutonomyRunRecord(
continue
}
if (isStaleActiveAutonomyRun(run)) {
const recovered = recoverStaleActiveAutonomyRun(
run,
record.createdAt,
)
const recovered = recoverStaleActiveAutonomyRun(run, record.createdAt)
runs[i] = recovered
recoveredStaleRuns.push(recovered)
staleRecoveriesApplied = true

View File

@@ -74,7 +74,7 @@ type BaseExecutionParams = {
onBeforeQuery?: (input: string, newMessages: Message[]) => Promise<boolean>,
input?: string,
effort?: EffortValue,
) => Promise<void>
) => Promise<boolean>
setAppState: (updater: (prev: AppState) => AppState) => void
onBeforeQuery?: (input: string, newMessages: Message[]) => Promise<boolean>
canUseTool?: CanUseToolFn

View File

@@ -260,8 +260,13 @@ async function executeForkedSlashCommand(
}
const resultText = extractResultText(agentMessages, 'Command completed');
logForDebugging(`Background forked command /${commandName} completed (agent ${agentId})`);
await finalizeDeferredAutonomyRunCompleted();
// Enqueue the worker's result before finalizing the autonomy run so the
// <scheduled-task-result> notification is observed before any follow-up
// autonomy commands the finalizer enqueues at the same priority. Without
// this ordering, both land at `priority: 'later'` and the next autonomy
// step can run before the main thread sees this worker's output.
enqueueResult(`<scheduled-task-result command="/${commandName}">\n${resultText}\n</scheduled-task-result>`);
await finalizeDeferredAutonomyRunCompleted();
})().catch(async err => {
logError(err);
enqueueResult(