Skip to content

feat(bash): add yield_time_ms and explicit accesses#1140

Open
starSumi wants to merge 1 commit into
MoonshotAI:mainfrom
starSumi:feat/tool-enhancements
Open

feat(bash): add yield_time_ms and explicit accesses#1140
starSumi wants to merge 1 commit into
MoonshotAI:mainfrom
starSumi:feat/tool-enhancements

Conversation

@starSumi

@starSumi starSumi commented Jun 26, 2026

Copy link
Copy Markdown

Related Issue

No related issue. This PR addresses the Bash tool behavior gaps described below.

Problem

Foreground Bash commands can yield partial output, but that only works when the agent has background task tools available. In profiles that enable Bash without TaskOutput or TaskStop, returning a task_id would strand the running command because the model cannot poll or stop it.

The branch also exposed sandbox override parameters before the execution layer could enforce them, which made the tool schema and prompt promise behavior that was not wired.

What changed

  • Keep yield_time_ms for normal Bash tool profiles, but only return a yielded task_id when background task tools are available.
  • In background-disabled profiles, update the Bash prompt so foreground commands wait for completion, timeout, detach, or cancellation instead of suggesting task polling.
  • Keep manual ! shell commands from auto-yielding after the Bash yield window; they continue waiting for completion, timeout, cancellation, or explicit detach.
  • Start and await output persistence when a foreground Bash call auto-yields, so later TaskOutput can point at a complete log file instead of only exposing the tail preview.
  • Persist and await yielded task metadata before returning a task_id, so resumed sessions can reconcile the running task even if the CLI exits immediately after yield.
  • Remove the sandbox override arguments from the Bash schema and prompt until sandbox permissions are connected through the execution path.
  • Keep the explicit shell access metadata from the original change.
  • Add a changeset for the CLI package.

Validation

  • pnpm exec vitest run packages/agent-core/test/tools/bash.test.ts --reporter=verbose
  • pnpm exec vitest run packages/agent-core/test/tools/bash.test.ts packages/agent-core/test/tools/grep.test.ts packages/agent-core/test/loop/tool-scheduler.test.ts --reporter=dot
  • pnpm exec vitest run packages/agent-core/test/agent/context.test.ts packages/agent-core/test/tools/bash.test.ts packages/agent-core/test/tools/grep.test.ts packages/agent-core/test/loop/tool-scheduler.test.ts --reporter=dot
  • pnpm exec vitest run packages/agent-core/test/tools/bash.test.ts packages/agent-core/test/agent/background/foreground-persistence.test.ts --reporter=dot
  • pnpm exec vitest run packages/agent-core/test/agent/context.test.ts packages/agent-core/test/tools/bash.test.ts packages/agent-core/test/tools/grep.test.ts packages/agent-core/test/loop/tool-scheduler.test.ts packages/agent-core/test/agent/background/foreground-persistence.test.ts --reporter=dot
  • pnpm --filter @moonshot-ai/agent-core typecheck
  • pnpm exec oxlint packages/agent-core/src/tools/builtin/shell/bash.ts packages/agent-core/src/agent/tool/index.ts packages/agent-core/src/agent/background/index.ts packages/agent-core/test/agent/context.test.ts packages/agent-core/test/tools/bash.test.ts
  • pnpm changeset status --since da63403207691856b9fe762953413b7205d5c903
  • git diff --check

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@changeset-bot

changeset-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b06072

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a56e2ed10f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +360 to +363
const yieldMs = args.yield_time_ms ?? 10_000;
const yieldDeadline = sleep(yieldMs);
const completionOrDetach = this.backgroundManager.waitForForegroundRelease(taskId);
const release = await Promise.race([completionOrDetach, yieldDeadline.then(() => 'yielded' as const)]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not yield when task tools are unavailable

In profiles where Bash is available but TaskOutput/TaskStop are not active, such as the coder and explore subagent profiles, allowBackground is false, yet every foreground command that runs longer than the new default 10s yield now returns a task_id and tells the model to poll or stop it. Because those tools are not in the subagent's tool list and the task is not detached for automatic notification, the caller loses the command result for ordinary long commands like tests or builds. Gate this yield path on allowBackground, or use the existing detach/background result path only when task tools are actually available.

Useful? React with 👍 / 👎.

Comment on lines +92 to +96
sandbox_permissions: z
.enum(['use_default', 'with_additional_permissions', 'require_escalated'])
.optional()
.describe(
'Per-command sandbox override. Defaults to `use_default`; use `with_additional_permissions` with `additional_permissions`, or `require_escalated` for unsandboxed execution.',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire sandbox permissions before exposing them

These new arguments are accepted by the Bash schema, but the execution path never reads them; repo-wide search for sandbox_permissions only finds this schema and the prompt text. A call with sandbox_permissions: "require_escalated" therefore follows the same approval and spawn path as any other Bash command: justification is not required, no extra approval is forced, and additional_permissions is never applied. Please either connect these fields to the permission/execution layer or keep them out of the schema and prompt until they are supported.

Useful? React with 👍 / 👎.

@starSumi starSumi changed the title feat(bash): add yield_time_ms, sandbox_permissions, and explicit accesses feat(bash): add yield_time_ms and explicit accesses Jun 27, 2026
@starSumi starSumi force-pushed the feat/tool-enhancements branch from 1cab83f to ae0f660 Compare June 27, 2026 11:48
@starSumi

Copy link
Copy Markdown
Author

Follow-up pushed in ae0f660a:

  • Gated foreground yield_time_ms on background task tool availability, so profiles without TaskOutput/TaskStop wait for completion, timeout, detach, or cancellation instead of returning an unmanageable task_id.
  • Removed the sandbox override arguments from the Bash schema and prompt until the execution layer can enforce them.
  • Added regression tests for both review points and a patch changeset for @moonshot-ai/kimi-code.
  • Cleaned the PR branch to a single focused commit with no unrelated .momo file.

@starSumi

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae0f660a14

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const release = this.allowBackground
? await Promise.race([
completionOrDetach,
sleep(args.yield_time_ms ?? 10_000).then(() => 'yielded' as const),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don’t default-yield manual shell commands

When this BashTool is used by ToolManager.runShellCommand for the TUI ! path, that caller resolves Bash with only { command, timeout: SHELL_FOREGROUND_TIMEOUT_S } and only treats results starting with task_id: as backgrounded. With this new 10s default, any manual shell command that runs longer than 10 seconds returns through the yielded branch, but its handle is embedded in result.output as a <system>...task_id... message while runShellCommand reports only the streamed stdout/stderr and drops the handle, leaving the process running without a visible way for the user to poll or cancel it. Please make yielding opt-in for this path or teach the shell-command caller to handle yielded results.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7e275e1: manual ! shell commands now call Bash through an internal no-auto-yield execution path, so they wait for completion, timeout, cancellation, or explicit ctrl+b detach instead of returning a hidden yielded task_id. Added a regression test in packages/agent-core/test/agent/context.test.ts.

@starSumi starSumi force-pushed the feat/tool-enhancements branch from ae0f660 to 7e275e1 Compare June 27, 2026 21:19
@starSumi

Copy link
Copy Markdown
Author

Follow-up pushed in 7e275e1a for the new Codex P2 review:

  • Manual ! shell commands now use the Bash tool through an internal no-auto-yield path, so they keep waiting for completion, timeout, cancellation, or explicit ctrl+b detach instead of returning a hidden yielded task_id.
  • Normal model-visible Bash calls still support yield_time_ms.
  • Added a regression test covering manual shell commands running past the Bash yield window.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e275e1aa5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +351 to +354
if (release === 'yielded') {
// Command is still running after yield_time_ms. Return partial output
// with wall_time_seconds so the caller knows how long we waited.
collectForegroundOutput = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist logs when Bash auto-yields

When a foreground Bash call reaches yield_time_ms, this branch returns a task_id but does not detach the task or start output persistence, and then collectForegroundOutput is disabled. For a yielded command that emits more than TaskOutput's 32 KiB preview but less than the manager's 1 MiB spill threshold after the yield (for example, a long test ending with a 100 KiB log), TaskOutput has no output_path and only exposes the tail, so the head of the output is inaccessible to the model even though the result tells it to poll TaskOutput. Start persisting the task log when yielding.

Useful? React with 👍 / 👎.

@starSumi starSumi force-pushed the feat/tool-enhancements branch from 7e275e1 to 22aca6b Compare June 27, 2026 21:29
@starSumi

Copy link
Copy Markdown
Author

Follow-up pushed in 22aca6b4 for the latest Codex P2 review:

  • Foreground Bash auto-yield now starts output persistence before returning the yielded task_id.
  • Added a regression test that writes output before and after yield and verifies the persisted output.log contains the complete stream.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22aca6b425

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (release === 'yielded') {
// Command is still running after yield_time_ms. Return partial output
// with wall_time_seconds so the caller knows how long we waited.
this.backgroundManager.persistOutput(taskId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist yielded task metadata before returning task_id

When a foreground Bash call auto-yields, this only starts output.log persistence; unlike the detach path, it does not write the task JSON metadata before returning the task_id. In a persistent session, if the CLI/session restarts after this return but before the process reaches finalizeTask, BackgroundManager.loadFromDisk() has no task record to reconcile, so the returned task disappears and TaskOutput cannot recover or mark it lost. Please persist the live task metadata as part of the yield path before exposing the task id.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0aad1f1: persistOutput(task_id) now also persists the live task metadata before exposing the yielded task_id, and the regression test checks that tasks/<task_id>.json exists before the process finishes.

@starSumi starSumi force-pushed the feat/tool-enhancements branch from 22aca6b to 0aad1f1 Compare June 27, 2026 21:38
@starSumi

Copy link
Copy Markdown
Author

Follow-up pushed in 0aad1f16 for the latest Codex P2 review:

  • persistOutput(task_id) now also persists the live task metadata before returning a yielded task_id.
  • The yield regression test now checks that both output.log and tasks/<task_id>.json exist before the process finishes.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0aad1f16ed

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const entry = this.tasks.get(taskId);
if (entry === undefined) return;
this.startOutputPersist(entry);
void this.persistLive(entry);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Await task metadata persistence before yielding

When a foreground Bash call yields, this fire-and-forget write can still be pending after the tool result has returned the task_id. If the CLI exits or restarts immediately after that yield, tasks/<task_id>.json may never reach disk, so the returned id is not recoverable via loadFromDisk()/TaskOutput even though the command was handed back as a background-like task. Make this persistence path awaitable and await it before returning the yielded result.

Useful? React with 👍 / 👎.

@starSumi starSumi force-pushed the feat/tool-enhancements branch from 0aad1f1 to 8b06072 Compare June 27, 2026 21:48
@starSumi

Copy link
Copy Markdown
Author

Follow-up pushed in 8b06072b for the latest Codex P2 review:

  • BackgroundManager.persistOutput(task_id) is now awaitable and waits for the current output.log flush plus live task metadata persistence.
  • The Bash auto-yield path now awaits that persistence before returning the yielded task_id.
  • The yield regression test now immediately checks that tasks/<task_id>.json exists when the yield result is returned, and still verifies the complete persisted output stream.

Validation:

  • pnpm exec vitest run packages/agent-core/test/tools/bash.test.ts packages/agent-core/test/agent/background/foreground-persistence.test.ts --reporter=dot
  • pnpm exec vitest run packages/agent-core/test/agent/context.test.ts packages/agent-core/test/tools/bash.test.ts packages/agent-core/test/tools/grep.test.ts packages/agent-core/test/loop/tool-scheduler.test.ts packages/agent-core/test/agent/background/foreground-persistence.test.ts --reporter=dot
  • pnpm --filter @moonshot-ai/agent-core typecheck
  • pnpm exec oxlint packages/agent-core/src/tools/builtin/shell/bash.ts packages/agent-core/src/agent/tool/index.ts packages/agent-core/src/agent/background/index.ts packages/agent-core/test/agent/context.test.ts packages/agent-core/test/tools/bash.test.ts
  • pnpm changeset status --since da63403207691856b9fe762953413b7205d5c903
  • git diff --check

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8b06072b1f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +359 to +360
return {
isError: false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep exclusive Bash access after auto-yield

When this return path resolves, the shell process is still running, but the loop releases the call's ToolAccesses.all() lock as soon as the tool result promise settles (tool-call.ts schedules access only around that promise). In a provider batch such as Bash("sleep 1; echo x > a", yield_time_ms=250) followed by a Read/Edit of a, the later tool can start while the yielded command is still mutating the workspace, so it can observe stale state or race with the shell despite Bash declaring exclusive access. Please stop/skip the remaining batch when yielding, or otherwise keep the exclusive scheduling boundary until the process is actually detached/completed.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant