Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/claude/__tests__/executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ vi.mock('../../cron/init.js', () => ({
getCronScheduler: vi.fn(() => null),
}));

import { ClaudeExecutor } from '../executor.js';
import { ClaudeExecutor, buildWorkspaceSystemPrompt } from '../executor.js';

// ============================================================
// Helpers
Expand Down Expand Up @@ -581,4 +581,21 @@ describe('ClaudeExecutor', () => {
expect(opts.env).toBeUndefined();
});
});

describe('buildWorkspaceSystemPrompt — platform constraints', () => {
it('includes platform execution constraints section', () => {
const prompt = buildWorkspaceSystemPrompt('/tmp/work');
expect(prompt).toContain('平台执行约束');
expect(prompt).toContain('子进程生命周期');
expect(prompt).toContain('不要用 Monitor 等待超过 5 分钟的后台任务结果');
expect(prompt).toContain('权限交互');
});

it('omits cron guidance when cron is disabled', () => {
// config mock has cron.enabled = false
const prompt = buildWorkspaceSystemPrompt('/tmp/work');
expect(prompt).not.toContain('manage_cron');
expect(prompt).toContain('建议用户稍后手动追问检查结果');
});
});
});
39 changes: 34 additions & 5 deletions src/claude/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ export function listKnownOrgs(cacheDir: string): string[] {
}

/** 构建工作区管理系统提示词(注入实际目录路径 + 可用项目列表) */
function buildWorkspaceSystemPrompt(workingDir?: string, options?: { isRestart?: boolean }): string {
export function buildWorkspaceSystemPrompt(workingDir?: string, options?: { isRestart?: boolean }): string {
const projectsDir = config.claude.defaultWorkDir;
const cacheDir = config.repoCache.dir;
const workspacesDir = config.workspace.baseDir;
Expand Down Expand Up @@ -465,7 +465,30 @@ gh search issues --repo owner/repo -- "keyword"

# 错误 ✗ — 不要 cd 到主仓库或其他目录执行 gh
cd /some/other/dir && gh pr view 123
\`\`\``;
\`\`\`

## 平台执行约束

你运行在 Feishu Bot 环境中,通过 Claude Agent SDK 的 \`query()\` API 执行。以下约束源于该执行模型,与目标仓库无关:

### 子进程生命周期

每次对话是一次独立的 query,返回结果后子进程(包括所有后台任务)会被回收。这意味着:

- **不要用 Monitor 等待超过 5 分钟的后台任务结果** — 你返回 result 后 Monitor 会随子进程一起终止
- **不要期望"返回后继续工作"** — 一旦你给出最终回复,本次执行即结束

### 长时间任务的正确模式

对于需要 5 分钟以上才能完成的后台任务(如跑批、大规模测试、编译):

1. 用 \`nohup ... &\` 或 \`... &\` 启动任务,确保脱离子进程
${config.cron.enabled ? `2. 用 cron 工具(\`manage_cron\`)设一个一次性定时检查(\`schedule_kind: "at"\`),在预计完成时间触发
3. 立即返回结果,告知用户任务已启动、预计完成时间、以及会自动回来检查` : `2. 立即返回结果,告知用户任务已启动、预计完成时间,建议用户稍后手动追问检查结果`}

### 权限交互

Bot 场景无法弹窗授权。所有需要的命令权限必须通过 \`.claude/settings.json\` 或 \`.claude/settings.local.json\` 白名单预配置。如果遇到权限拒绝,不要重试,告知用户需要更新白名单。`;

const selfRepoGuide = (workingDir && isServiceOwnRepo(workingDir)) ? (() => {
const rt = detectRuntime();
Expand Down Expand Up @@ -946,7 +969,8 @@ export class ClaudeExecutor {
: { type: 'preset', preset: 'claude_code', append: promptAppend },

// 加载项目设置 (CLAUDE.md 等);路由 agent 传 [] 避免加载
settingSources: settingSourcesOverride ?? ['user', 'project'],
// 'local' 加载 .claude/settings.local.json(优先级最高,覆盖 project)
settingSources: settingSourcesOverride ?? ['user', 'project', 'local'],

// MCP 服务器:工作区管理工具 + 飞书工具 (空对象等同于无 MCP 服务器)
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
Expand Down Expand Up @@ -984,7 +1008,8 @@ export class ClaudeExecutor {

try {
// 遍历 SDK 流式消息
for await (const message of q) {
// label 用于在收到 result 后跳出循环(result 是 SDK 协议的终止消息)
messageLoop: for await (const message of q) {
// 每收到消息重置 idle 计时器
resetIdleTimer(`msg:${message.type}${'subtype' in message ? ':' + (message as Record<string, unknown>).subtype : ''}`);

Expand Down Expand Up @@ -1123,7 +1148,7 @@ export class ClaudeExecutor {

case 'result':
resultMessage = message;
break;
break messageLoop;

default:
// tool_progress, stream_event 等其他消息类型 — 记录以便诊断 idle timeout 间隙
Expand All @@ -1132,6 +1157,10 @@ export class ClaudeExecutor {
break;
}
}

// 提前 break(收到 result)时子进程可能仍在运行(如有 background task),
// 主动 close 确保不泄漏;正常迭代结束时 close 是幂等的 no-op
q.close();
} catch (err) {
clearTimeout(idleTimer);
if (hardTimer) clearTimeout(hardTimer);
Expand Down
Loading