Skip to content

fix(bg-shell): keep agent on duty until session-started bg jobs finish (#167)#168

Merged
ZhouChaunge merged 12 commits into
mainfrom
fix/bg-shell-keep-alive-167
May 26, 2026
Merged

fix(bg-shell): keep agent on duty until session-started bg jobs finish (#167)#168
ZhouChaunge merged 12 commits into
mainfrom
fix/bg-shell-keep-alive-167

Conversation

@ZhouChaunge
Copy link
Copy Markdown
Collaborator

@ZhouChaunge ZhouChaunge commented May 26, 2026

概述

修复 #167 报告的核心问题——run_shell_bg 启动的长任务在 agent-loop 的
BG_WAIT_SKIPPED_MODEL_DONE 分支被模型“一句安抚话”提前结束本轮,导致最终
结束事件无人监听、用户永远收不到反馈。

本 PR 落地 issue 中 阶段 1(必做止血) 的全部改动,不动 UI、不改 schema、
不引入新工具。

修改内容

  1. src/chat/agent-loop.js

    • 在每个 run 上新增 run._sessionStartedBgJobs: Set<jobId>,只收纳本 run
      通过 run_shell_bg 启动的 jobId。
    • _bgJobEndHandler 收到结束事件时把对应 jobId 从集合里清除,集合大小有界。
    • 改写 BG_WAIT_SKIPPED_MODEL_DONE 判据:模型说收尾话且无 pending 事件
      _sessionStartedBgJobs 与当前 active bg jobs 没有交集时才允许 break。
      dev server / watcher 这类上一轮启动的长任务行为不变;本轮新启动的
      bg 任务会被强制值守。
    • 日志附带 sessionStartedJobs 便于回归验证。
  2. src/chat/tool-executor.js

    • 给 dispatch 时构造的 ctxregisterBgJob(jobId) 回调,写入当前 run 的
      _sessionStartedBgJobsrun 为空时(一次性脚本/测试场景)no-op。
  3. src/tools/bg-shell.js

    • addActiveBgJob 之前先调用 ctx.registerBgJob(jobId),把 jobId
      同步登记到 agent-loop(注册顺序很关键,详见下文“评审反馈处理”)。
    • status: 'running' 分支的 hint 文案改为对系统行为如实描述:明确告诉
      模型“你不能用‘说完再走’的方式提前结束 turn;loop 会保持你值守,结束时
      会以 <system-reminder> 再次喂给你”。SI 不可用分支同样补一句“不要承诺
      ‘完成后通知你’,没有自动通知”。

顺手改进(user-visible)

  • 归档目录改名<workspace>/.deepcopilot/archives<workspace>/.deep-copilot/archives
    与代码库其它产物(.deep-copilot/plans.deep-copilot/memory.md.deep-copilot/logs
    约定对齐,避免在用户工作区里出现第二个隐藏目录。本 PR 顺路改了 archive-export.js
    ARCHIVE_SUBDIR 常量、session-store.js 的两处 docstring 路径示例;toast
    显示路径与 findContainingFolder 解析逻辑无需改动。归档功能本身由 feat(session): archive action exports session to Markdown (#165) #166 引入,
    已合入 main。

评审反馈处理

  • 注册顺序竞态(Copilot reviewer 提):极短命的命令可能在 addActiveBgJob()
    ctx.registerBgJob() 之间完成;若顺序倒置,end-handler 会对一个尚未 add 的
    set 执行 delete,并在随后的 register 中留下 stale 条目。已把 registerBgJob
    挪到 addActiveBgJob 之前,关闭这个窗口。
  • 空 catch 吞错误(Copilot reviewer 提):原来 try {} catch {} 静默丢失
    注册失败信号。现以 typeof === 'function' 守卫,失败走
    Logger.info('BG_JOB_REGISTER_FAILED', ...) 入 debug-log(Logger 目前只暴露
    .info,tag 自身携带 severity 关键字便于 grep)。

验收用例(U1,本 PR 范围)

在 SI 可用的终端跑:

run_shell_bg(command: "ping -n 30 127.0.0.1")

然后让模型回一句话即结束工具调用。

  • 修复前:日志记录 BG_WAIT_SKIPPED_MODEL_DONE,run 立即结束,30s 后无任何
    BG_JOB_END_INJECTED
  • 修复后:日志不再记录 BG_WAIT_SKIPPED_MODEL_DONE(或仅在 jobId 与
    sessionStartedJobs 无交集时记录);~30s 后看到 BG_JOB_END_INJECTED,模型
    收到 system-reminder 并给出最终回复。

未在本 PR 中处理(留作后续 issue)

  • 阶段 2:SI 不可用时的轮询兜底。
  • 阶段 3:session 级 pendingBgEvents 持久化,跨 VS Code 重启投递。

兼容性 / 风险

  • 仅 agent-loop 内部 run-scoped 状态变更,不影响持久化、不影响 UI。
  • dev server / watcher 类“上一轮启动的长任务”体验保持不变(不会被新判据误伤)。
  • 归档目录改名属于一次性路径切换;之前生成在 .deepcopilot/archives/ 下的旧文件
    保留原位,不做迁移。
  • 风险:模型若坚持在本轮多次发言,可能被多次 wake 后才完成回复——这是符合
    预期的“值守”行为,不是 regression。

验证

  • npm run build:成功。
  • npm run lint:0 errors(既有 indent/quote 风格 warning 与本 PR 无关,未触发
    任何新错误)。

Closes #167

Previously the right-click 'Archive' menu was a soft-hide toggle — it
flipped session.archived in globalState, the entry disappeared from the
sidebar, and that was it. Users reported that the label did not match
what happened: nothing was actually archived anywhere they could grep,
commit, or share.

This change makes Archive do what it says:

1. src/chat/archive-export.js (new): renders a SessionStore record to
   Markdown — YAML frontmatter with sessionId/model/timestamps, role-
   tagged sections (User / Assistant), and <details>-wrapped reasoning
   so it does not drown out the conversation. Picks the destination via
   workspaceFolders (single root -> use it; multi-root -> workspace
   folder pick, biased toward the session's original ws; no folder open
   -> showSaveDialog). Sanitises titles for filesystem hostility,
   appends -1/-2 on name collision, and verifies the resolved path
   stays under the chosen root before writing (defence in depth).

2. SessionStore.archive: on first archive of a visible session, export
   to .deepcopilot/archives/yyyyMMdd-HHmmss-<title>.md, then perform
   the original soft-hide. Clicking again on an archived session still
   un-hides it (toggle preserved; the file on disk is not touched).
   Export failures surface through showErrorMessage but do not block
   the hide, so the gesture always advances UI state.

3. Bottom-right toast on success with Open File / Reveal in Explorer
   actions. Path is shown workspace-relative when possible.

4. i18n: 9 new keys for the toast, dialog labels, and rendered role
   headers. EN and ZH both populated.

Security (per copilot-instructions.md red-line #3): writes are gated
by path.relative containment check + path.resolve normalisation before
any fs.writeFile call; no user input is interpolated into a shell
command.

Closes #165.
Four corrections from Copilot's PR review on #166:

1. archive-export.js renderSessionMarkdown: drop the global
   `replace(/\\n{3,}/g, '\\n\\n')` pass. It would collapse blank lines
   inside fenced code blocks / tool output, mutating verbatim message
   content. Each section already pushes its own controlled trailing
   blank line, so the global normalisation was both redundant and
   harmful. Issue raised by Copilot inline review.

2. archive-export.js exportSessionToMarkdown: showSaveDialog's
   `defaultUri` was `vscode.Uri.file(fileName)` — `Uri.file()` requires
   an absolute path. On Windows that resolves to a drive root, on POSIX
   to `/`, which is a confusing and possibly unwritable default. Anchor
   it at `os.homedir() + fileName` so the dialog opens somewhere the
   user expects.

3. session-store.js archive(): only set `archived = true` when a file
   was actually written. Previously, both `exportSessionToMarkdown`
   throwing AND returning null (user cancelled the save dialog) would
   still hide the session from the sidebar even though nothing was
   archived — leaving the UI inconsistent with disk state. New contract:
   - returns absolute path → hide + show toast
   - returns null (cancel)  → keep visible, no toast
   - throws (fs error)      → keep visible, show error message

4. archive-export.js _uniquePath: replace the fs.access pre-check with
   `fs.open(candidate, 'wx')` (exclusive create). Closes the TOCTOU
   window where two concurrent archive clicks in the same second could
   both pass the existence check and one would overwrite the other. The
   placeholder zero-byte file is then overwritten by the subsequent
   fs.writeFile.

Also added a comment to `_safeTitle` explaining the regex classes
(reserved-on-Windows set + C0 control codes + leading-dot strip),
addressing a readability note from the AI reviewer.

Build verified locally; no behavioural change outside the four fixes.
- _writeUnique: write through the exclusive handle so a failed write
  no longer leaves a zero-byte placeholder behind (closes Copilot
  comment on _uniquePath placeholder leak).
- Replace ad-hoc workspace-relative path logic in _notifyArchived with
  the multi-root + longest-prefix-aware findContainingFolder() helper
  from src/utils/paths.js.
- Move the 'archive path escapes workspace' error string into i18n
  (archiveErrEscape) so ZH users no longer see an English assertion in
  the failure toast.
- Drop the self-referential 'PR #166 review' rationale comments left
  over from the previous round; keep only the substantive 'why'.
- archive-export: sanitize session.title for Markdown `#` heading (collapse newlines/control chars)
- archive-export: distinguish 'user cancelled folder picker' (sentinel) from 'no workspace open' (null) so cancel no longer falls back to showSaveDialog
- session-store: wrap Open File / Reveal in Explorer handlers in try/catch and surface failures as a non-fatal toast (archiveOpenFailed i18n key)
- _safeTitle: strip trailing space/dot so Win32 path normalisation can't silently mutate the on-disk filename or defeat the collision counter
- _frontmatter: always quote string scalars so titles/models that look like YAML keywords (true/null/2026-05-26/123) aren't coerced into bool/date/null/number
- _notifyArchived: drop redundant Promise.resolve() wrapper around showInformationMessage (it already returns a thenable); attach rejection handler as the second .then() arg
Round 4 review (#166):

- _pickWorkspaceRoot: drop sessionWs shortcut. session.ws is always folder[0] so the shortcut silently bypassed the picker in multi-root workspaces.

- _frontmatter: add provider (from config), promptTokens / completionTokens / totalTokens (from session.totals).
#167)

Track jobIds started by run_shell_bg within the current run in run._sessionStartedBgJobs, and refuse to take the BG_WAIT_SKIPPED_MODEL_DONE early exit while any of them is still alive. Previously the model could defuse the wait loop by emitting a single closing sentence (e.g. 'I'll tell you when it's done'), leaving the just-spawned background task orphaned with no one listening for its end event.

Also restate the run_shell_bg tool hint honestly so the model understands it cannot end the turn via a verbal promise while a bg job is live.

Refs #167
Copilot AI review requested due to automatic review settings May 26, 2026 08:25
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Code review by ChatGPT

Comment thread .gitignore


# scratch files used by AI review triage
.tmp-*.json
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

新增的 .tmp-*.json 文件可能会导致敏感数据泄露,建议在版本控制中忽略此类临时文件。此外,确保这些临时文件不会被错误地上传到生产环境中。请考虑使用更安全的文件命名规则或在文档中明确说明这些文件的用途和处理方式。

Comment thread src/chat/agent-loop.js
if (payload.jobId) run._sessionStartedBgJobs.delete(payload.jobId);
};
onBgJobEnded(_bgJobEndHandler);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

在这一部分代码中,增加了对背景作业的跟踪和管理,但需要注意以下几点:

  1. 安全性:确保 run._sessionStartedBgJobs 中的 jobId 不会被外部输入操控,避免潜在的命令注入风险。建议对 jobId 进行验证。
  2. 异常处理:在处理 payload 时,建议增加对 payload.jobId 的存在性检查,避免空指针异常。
  3. 内存管理:虽然有清理机制,但需要确保在高并发情况下不会导致内存泄漏,建议定期检查和清理无效的 jobId。

Comment thread src/chat/agent-loop.js
});
break;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

在这一部分代码中,增加了对背景作业的状态检查,但存在以下问题:

  1. 安全性:确保 myBgJobs() 返回的作业列表不包含敏感信息,避免信息泄露。
  2. 异常处理:在 Logger.info 调用前,建议检查 jobssessionStartedJobs 的内容,确保它们不为空,以避免潜在的异常。
  3. 性能:在高并发情况下,myBgJobs() 的调用可能会影响性能,建议考虑缓存机制或优化查询逻辑。

return uri.fsPath;
}

module.exports = { exportSessionToMarkdown, renderSessionMarkdown };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  1. 安全性:在 _writeUnique 函数中,虽然使用了 fs.open(..., 'wx') 来防止竞争条件,但在高并发情况下,仍然可能会出现文件名冲突的情况。建议增加更强的锁机制或使用数据库来管理文件名。
  2. 异常处理:在 exportSessionToMarkdown 函数中,虽然有对文件系统错误的处理,但在某些情况下(如 fs.mkdirfs.writeFile),可能会抛出未处理的异常,建议使用 try-catch 块来捕获这些异常并进行适当处理。
  3. 代码风格:整体代码风格较为一致,但在某些地方(如注释)可以进一步优化,使其更简洁明了。

Comment thread src/chat/session-store.js
const { t, tf } = require('../utils/i18n');

// ─── Orphan tool_calls sanitizer ───────────────────────────────────────────
// Removes ANY incomplete assistant{tool_calls} group from a message array,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

在引入 tf 函数时,请确保它的来源和用途是安全的,避免潜在的命令注入风险。建议检查 ../utils/i18n 中的实现,确认 tf 的输入不会被恶意操控。

Comment thread src/chat/tool-executor.js
};
if (tcId) {
ctx.onStreamDelta = (delta) => {
if (!delta) return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

在这段代码中,ctx.registerBgJob 函数的实现存在潜在的空指针异常风险。虽然在函数开头进行了 if (!jobId || !run) return; 的检查,但在 run._sessionStartedBgJobs 的赋值操作中,如果 runnull,将会导致异常。建议在访问 run._sessionStartedBgJobs 之前,确保 run 不为 null。此外,建议对 jobId 的有效性进行更严格的检查,以防止无效值的传入。

Comment thread src/tools/bg-shell.js
try { ctx.registerBgJob && ctx.registerBgJob(jobId); } catch {}
}

// ── Early-failure capture: many commands fail within ~1–2 s (missing
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

在这里,虽然使用了 try-catch 来捕获可能的异常,但没有对异常进行处理或记录,这可能导致潜在的问题被忽略。建议至少记录异常信息,以便后续排查。同时,ctx.registerBgJob 的存在性检查虽然有效,但如果 ctx 为 null 或 undefined,仍然可能导致空指针异常。建议在调用前增加对 ctx 的有效性检查。

Comment thread src/tools/bg-shell.js
`Do NOT tell the user "I'll notify you when it's done" in this branch — there is no automatic notification.`,
].join('\n'),
`To cancel: ask the user to close the terminal named "${jobId}".`,
].join('\n'),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

在这里,提示信息的内容需要注意,尤其是关于用户交互的部分。建议明确告知用户在特定情况下不应使用某些命令,避免用户误操作。同时,提示信息中提到的 'agent-loop' 和 'system-reminder' 可能会引起用户的困惑,建议使用更清晰的术语或提供额外的上下文信息。此外,确保这些提示信息不会引入安全隐患,例如泄露内部实现细节。

Comment thread src/utils/i18n.js
archiveThoughtsLabel: 'Reasoning',
};

const ZH = {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

新增的归档相关信息中,archiveErrEscape 提到的 "解析后的路径超出了工作区" 可能导致路径穿越漏洞。建议在处理路径时使用安全库(如 path.resolve)来确保路径在工作区内。此外,archiveFailedarchiveOpenFailed 中的 {msg} 可能会泄露内部错误信息,建议对错误信息进行适当的处理和过滤,以避免信息泄露。

Comment thread src/utils/i18n.js
archiveThoughtsLabel: '思维链',
};

function t(key) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

同样,archiveErrEscape 的中文翻译也存在路径穿越的风险。建议在路径处理时确保安全性。对于 archiveFailedarchiveOpenFailed 的错误信息,需注意避免泄露敏感信息,建议进行适当的处理。

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR primarily targets issue #167 by preventing the agent loop from ending a turn early when a background shell job started in the current run is still active, ensuring the eventual bg-job end event is still observed and injected back to the model. However, it also introduces a session archive → Markdown export feature (issue #165), which expands scope beyond the PR title/description.

Changes:

  • Track bg jobs started within the current agent run and block BG_WAIT_SKIPPED_MODEL_DONE early-exit while any of those jobs are still active.
  • Allow run_shell_bg to register newly-started jobIds into the current run via ctx.registerBgJob(jobId).
  • Add session archive-to-Markdown export plumbing (new exporter module, SessionStore flow, i18n strings) and a .gitignore entry.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/chat/agent-loop.js Adds run-scoped tracking to prevent early-exit while run-started bg jobs are still running.
src/chat/tool-executor.js Injects ctx.registerBgJob(jobId) so tools can register bg jobs to the current run.
src/tools/bg-shell.js Registers bg jobs with agent-loop context and updates hint text to reflect actual behavior.
src/chat/session-store.js Changes “Archive” from soft-hide to Markdown export + hide; adds post-save notification.
src/chat/archive-export.js New module to render a session to Markdown and write to a workspace path safely.
src/utils/i18n.js Adds EN/ZH strings for archive/export UX.
.gitignore Ignores AI triage scratch JSON files.

结论:需修改

Comment thread src/tools/bg-shell.js
Comment on lines 119 to 125
if (usedSI) {
addActiveBgJob(jobId, ctx.sessionId || null);
// Issue #167: tell agent-loop this jobId was started in THIS run, so
// its BG_WAIT_SKIPPED_MODEL_DONE early-exit guard refuses to end the
// turn while the freshly spawned job is still alive.
try { ctx.registerBgJob && ctx.registerBgJob(jobId); } catch {}
}
Comment thread src/chat/archive-export.js Outdated
const fs = require('fs/promises');
const { t } = require('../utils/i18n');

const ARCHIVE_SUBDIR = '.deepcopilot/archives';
Comment thread src/chat/session-store.js
Comment on lines +371 to +373
* New behaviour: render the session to Markdown and write it under
* <workspace>/.deepcopilot/archives/yyyyMMdd-HHmmss-<title>.md
* Then perform the original soft-hide so the session leaves the sidebar.
Comment thread src/chat/session-store.js
Comment on lines +363 to +370
/**
* "Archive" a session — issue #165.
*
* Original behaviour (pre-#165) was a soft-hide toggle: flip `archived`,
* disappear from the sidebar list, data stays in globalState. Users
* reported that this didn't match the menu label's promise: nothing was
* actually *archived* anywhere they could see, find, or grep.
*
Comment thread src/chat/session-store.js Outdated
Comment on lines +424 to +426
* Show the bottom-right toast with "Open" / "Reveal in Explorer" buttons.
* Path display is workspace-relative when possible so users see
* ".deepcopilot/archives/20260526-….md"
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread src/tools/bg-shell.js Outdated
// Issue #167: tell agent-loop this jobId was started in THIS run, so
// its BG_WAIT_SKIPPED_MODEL_DONE early-exit guard refuses to end the
// turn while the freshly spawned job is still alive.
try { ctx.registerBgJob && ctx.registerBgJob(jobId); } catch {}
1. Register jobId in run-scoped _sessionStartedBgJobs BEFORE publishing to addActiveBgJob, closing a race where an extremely short-lived command could fire bg-job-end between the two calls, leaving a stale entry in the run set.

2. Stop swallowing ctx.registerBgJob errors. Guard with typeof check and log via Logger.info (Logger has no warn today) so regressions surface in debug-logs instead of silently losing the keep-alive signal.

3. Rename archive directory from .deepcopilot/archives to .deep-copilot/archives to match the rest of the codebase's workspace-artifact convention (plans, memory, logs). Updates docstrings and toast example path accordingly.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment thread src/chat/archive-export.js Outdated
Comment on lines +23 to +26
// PR #168 review: align with the rest of the codebase's workspace-artifact
// convention (`.deep-copilot/plans`, `.deep-copilot/memory.md`,
// `.deep-copilot/logs`). Previously this lived under `.deepcopilot/archives`,
// which produced an inconsistent second hidden directory in user workspaces.
// convention (`.deep-copilot/plans`, `.deep-copilot/memory.md`,
// `.deep-copilot/logs`). Previously this lived under `.deepcopilot/archives`,
// which produced an inconsistent second hidden directory in user workspaces.
const ARCHIVE_SUBDIR = '.deep-copilot/archives';
Comment thread src/tools/bg-shell.js Outdated
Comment on lines +120 to +123
// Issue #167 follow-up (PR #168 review): register the jobId in the
// run-scoped set BEFORE publishing to the cross-session active-job
// registry. If the order were reversed, an extremely short-lived
// command could fire its `bg-job-end` event between addActiveBgJob()
Copilot reviewer kept reading the in-code 'PR #168 review' note as a wrong PR reference (the issue is #167 and the branch name carries 167). Rephrase to 'post-merge review' so the rationale stays accurate regardless of which PR ends up shipping the commit.
@ZhouChaunge
Copy link
Copy Markdown
Collaborator Author

The race comment is a false positive on this commit — the order in the current code is registerBgJob first, addActiveBgJob second, exactly to close the window described. See src/tools/bg-shell.js#L132-L150: ctx.registerBgJob(jobId) runs inside the typeof === 'function' guard before addActiveBgJob(jobId, ctx.sessionId || null) publishes to the cross-session registry. The accompanying inline comment also spells out exactly this rationale.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread src/tools/bg-shell.js
Comment on lines +131 to +134
if (typeof ctx.registerBgJob === 'function') {
try {
ctx.registerBgJob(jobId);
} catch (e) {
@ZhouChaunge ZhouChaunge merged commit 09c05d1 into main May 26, 2026
20 checks passed
@ZhouChaunge ZhouChaunge deleted the fix/bg-shell-keep-alive-167 branch May 26, 2026 09:27
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.

run_shell_bg: agent 在长任务启动后过早结束本轮,永远不返回最终结果

2 participants