@konglx/rotom 2.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +417 -0
- package/bin/mesh-master.sh +439 -0
- package/bin/rotom +29 -0
- package/bin/rotom-link.sh +136 -0
- package/bin/rotom-send-with-status +57 -0
- package/bin/rotom-up.sh +428 -0
- package/dist/cli/ask.js +62 -0
- package/dist/cli/common.js +321 -0
- package/dist/cli/config.js +65 -0
- package/dist/cli/directory.js +17 -0
- package/dist/cli/executor.js +58 -0
- package/dist/cli/fed.js +91 -0
- package/dist/cli/group.js +273 -0
- package/dist/cli/identity.js +62 -0
- package/dist/cli/init.js +268 -0
- package/dist/cli/issue.js +202 -0
- package/dist/cli/join.js +170 -0
- package/dist/cli/link.js +47 -0
- package/dist/cli/master.js +51 -0
- package/dist/cli/memory.js +307 -0
- package/dist/cli/note.js +68 -0
- package/dist/cli/repo.js +77 -0
- package/dist/cli/rotom.js +277 -0
- package/dist/cli/routes.js +118 -0
- package/dist/cli/run.js +45 -0
- package/dist/cli/schedule.js +237 -0
- package/dist/cli/skill.js +173 -0
- package/dist/cli/team.js +106 -0
- package/dist/executor/claude-code-hook.cjs +80 -0
- package/dist/executor/cli-executor.js +8 -0
- package/dist/executor/executors/claude-code.js +780 -0
- package/dist/executor/executors/codex.js +719 -0
- package/dist/executor/executors/hermes-cli.js +855 -0
- package/dist/executor/executors/openclaw.js +467 -0
- package/dist/executor/executors/pi.js +514 -0
- package/dist/executor/index.js +269 -0
- package/dist/executor/jsonrpc-transport.js +125 -0
- package/dist/executor/process-runner.js +101 -0
- package/dist/executor/reasoning-status.js +83 -0
- package/dist/executor/repo-cache.js +502 -0
- package/dist/executor/session-store.js +188 -0
- package/dist/executor/worker-chat.js +257 -0
- package/dist/executor/worker-connection.js +89 -0
- package/dist/executor/worker-issue.js +264 -0
- package/dist/executor/worker.js +877 -0
- package/dist/link/pending-requests.js +72 -0
- package/dist/link/server.js +233 -0
- package/dist/link/visibility-store.js +58 -0
- package/dist/master/api/agents.js +333 -0
- package/dist/master/api/artifacts.js +271 -0
- package/dist/master/api/domains.js +64 -0
- package/dist/master/api/groups.js +635 -0
- package/dist/master/api/guidance-templates.js +147 -0
- package/dist/master/api/index.js +89 -0
- package/dist/master/api/issues-patrol.js +172 -0
- package/dist/master/api/issues.js +663 -0
- package/dist/master/api/links-patrol.js +168 -0
- package/dist/master/api/links.js +114 -0
- package/dist/master/api/memory.js +259 -0
- package/dist/master/api/messages.js +157 -0
- package/dist/master/api/notes.js +77 -0
- package/dist/master/api/schedule-patterns.js +133 -0
- package/dist/master/api/schedules.js +272 -0
- package/dist/master/api/sessions.js +158 -0
- package/dist/master/api/share.js +269 -0
- package/dist/master/api/skills.js +190 -0
- package/dist/master/api/teams.js +122 -0
- package/dist/master/api/uploads.js +245 -0
- package/dist/master/auth.js +134 -0
- package/dist/master/dashboard/animations/calico-dozing.apng +0 -0
- package/dist/master/dashboard/animations/calico-error.apng +0 -0
- package/dist/master/dashboard/animations/calico-happy.apng +0 -0
- package/dist/master/dashboard/animations/calico-notification.apng +0 -0
- package/dist/master/dashboard/animations/calico-sleeping.apng +0 -0
- package/dist/master/dashboard/animations/calico-thinking.apng +0 -0
- package/dist/master/dashboard/animations/calico-waking.apng +0 -0
- package/dist/master/dashboard/assets/ApprovalCard-C38VV6ko.css +1 -0
- package/dist/master/dashboard/assets/ApprovalCard-CHPh2dmE.js +17 -0
- package/dist/master/dashboard/assets/ArtifactPanel-P_2gAP7v.js +1 -0
- package/dist/master/dashboard/assets/ArtifactPanel-aGHySny5.css +1 -0
- package/dist/master/dashboard/assets/css.worker-DaIe3gwK.js +84 -0
- package/dist/master/dashboard/assets/editor.worker-BCzxt1at.js +12 -0
- package/dist/master/dashboard/assets/html.worker-CKrFyw_2.js +461 -0
- package/dist/master/dashboard/assets/index-CChrTn81.css +32 -0
- package/dist/master/dashboard/assets/index-Dhu4SN1z.js +181 -0
- package/dist/master/dashboard/assets/json.worker-B7c_PmGb.js +49 -0
- package/dist/master/dashboard/assets/markdown-CeN5IgdF.js +29 -0
- package/dist/master/dashboard/assets/monaco-core-DyX1CsEw.css +1 -0
- package/dist/master/dashboard/assets/monaco-core-oQiQUisy.js +833 -0
- package/dist/master/dashboard/assets/monaco-setup-CiOPQdmo.js +1 -0
- package/dist/master/dashboard/assets/react-vendor-C8IxlyCR.js +67 -0
- package/dist/master/dashboard/assets/ts.worker-BhkL8olL.js +51334 -0
- package/dist/master/dashboard/assets/useMonaco-ILb4vyPh.js +12 -0
- package/dist/master/dashboard/assets/vite-preload-CxJPbCTl.js +1 -0
- package/dist/master/dashboard/debug-auth.html +197 -0
- package/dist/master/dashboard/favicon.ico +0 -0
- package/dist/master/dashboard/index.html +20 -0
- package/dist/master/dashboard/rotom-avatar.png +0 -0
- package/dist/master/db/agent-sessions.js +60 -0
- package/dist/master/db/agent-visibility.js +64 -0
- package/dist/master/db/agents.js +119 -0
- package/dist/master/db/ask-bridges.js +157 -0
- package/dist/master/db/build-update.js +59 -0
- package/dist/master/db/core.js +82 -0
- package/dist/master/db/domains.js +80 -0
- package/dist/master/db/groups.js +316 -0
- package/dist/master/db/guidance-templates.js +58 -0
- package/dist/master/db/index.js +12 -0
- package/dist/master/db/internal.js +45 -0
- package/dist/master/db/issues-patrol.js +81 -0
- package/dist/master/db/issues.js +373 -0
- package/dist/master/db/links.js +221 -0
- package/dist/master/db/master-node.js +43 -0
- package/dist/master/db/memory.js +272 -0
- package/dist/master/db/messages.js +210 -0
- package/dist/master/db/notes.js +55 -0
- package/dist/master/db/schedule-patterns.js +56 -0
- package/dist/master/db/schedules.js +135 -0
- package/dist/master/db/skills.js +144 -0
- package/dist/master/db/team.js +88 -0
- package/dist/master/db/types.js +10 -0
- package/dist/master/db.js +12 -0
- package/dist/master/embedded.js +133 -0
- package/dist/master/federation/client.js +283 -0
- package/dist/master/federation/identity.js +133 -0
- package/dist/master/federation/manager.js +267 -0
- package/dist/master/federation/publisher.js +87 -0
- package/dist/master/federation/self-publisher.js +69 -0
- package/dist/master/federation/server.js +487 -0
- package/dist/master/group-paths.js +208 -0
- package/dist/master/offline-queue.js +38 -0
- package/dist/master/opc-bootstrap.js +245 -0
- package/dist/master/patrol-terminal.js +275 -0
- package/dist/master/repo-scan.js +188 -0
- package/dist/master/router.js +214 -0
- package/dist/master/scheduler-handlers.js +510 -0
- package/dist/master/scheduler.js +201 -0
- package/dist/master/server.js +203 -0
- package/dist/master/services/link-collector.js +82 -0
- package/dist/master/services/link-patrol-bootstrap.js +50 -0
- package/dist/master/services/memory-extract-prompt.js +34 -0
- package/dist/master/services/patrol-bootstrap.js +63 -0
- package/dist/master/share-tokens.js +56 -0
- package/dist/master/terminal-hub.js +300 -0
- package/dist/master/uploads.js +108 -0
- package/dist/master/util/fs.js +100 -0
- package/dist/master/util/paths.js +50 -0
- package/dist/master/util/persona.js +10 -0
- package/dist/master/ws-hub/connection.js +928 -0
- package/dist/master/ws-hub/conversation.js +290 -0
- package/dist/master/ws-hub/directory.js +70 -0
- package/dist/master/ws-hub/dispatch-enrich.js +34 -0
- package/dist/master/ws-hub/hub.js +136 -0
- package/dist/master/ws-hub/index.js +9 -0
- package/dist/master/ws-hub/internal.js +35 -0
- package/dist/master/ws-hub/routing.js +295 -0
- package/dist/master/ws-hub/sessions.js +130 -0
- package/dist/master/ws-hub.js +11 -0
- package/dist/shared/agent-profile.js +44 -0
- package/dist/shared/constants.js +55 -0
- package/dist/shared/dedup.js +33 -0
- package/dist/shared/group-context.js +62 -0
- package/dist/shared/json-codec.js +33 -0
- package/dist/shared/logger.js +136 -0
- package/dist/shared/mention.js +22 -0
- package/dist/shared/network.js +40 -0
- package/dist/shared/parse.js +18 -0
- package/dist/shared/prompt-composer.js +171 -0
- package/dist/shared/protocol/client-messages.js +8 -0
- package/dist/shared/protocol/enums.js +6 -0
- package/dist/shared/protocol/federation.js +62 -0
- package/dist/shared/protocol/guards.js +87 -0
- package/dist/shared/protocol/server-messages.js +8 -0
- package/dist/shared/protocol/types.js +8 -0
- package/dist/shared/protocol.js +19 -0
- package/dist/shared/readonly-allowlist.js +122 -0
- package/dist/shared/rotom-cli-prompt.js +23 -0
- package/dist/shared/skill-context.js +19 -0
- package/dist/shared/skill-md.js +43 -0
- package/dist/shared/slash-commands.js +50 -0
- package/dist/shared/time.js +80 -0
- package/dist/shared/title.js +46 -0
- package/dist/shared/url-extractor.js +99 -0
- package/migrations/001-schema.sql +942 -0
- package/package.json +68 -0
- package/scripts/fix-node-pty-perms.mjs +46 -0
- package/skill/rotom-a2a-communicate/SKILL.md +257 -0
- package/skill/rotom-bus-host/SKILL.md +78 -0
- package/skill/rotom-bus-host/scripts/poll-replies.sh +148 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IssueHandler — issue execution lifecycle for ExecutorWorker.
|
|
3
|
+
*
|
|
4
|
+
* Owns the per-issue CLI execution loop, including abort/cancel/interrupt
|
|
5
|
+
* semantics, the append-queue (pendingAppends), and the approval gate that
|
|
6
|
+
* gates writes under r_allow policy. State maps (activeTasks,
|
|
7
|
+
* pendingApprovals, pendingAppends) live on the worker so the WS router and
|
|
8
|
+
* other handlers can see them.
|
|
9
|
+
*/
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { composePrompt } from "../shared/prompt-composer.js";
|
|
12
|
+
import { isReadonlyCommand } from "../shared/readonly-allowlist.js";
|
|
13
|
+
import { parseSlashCommand } from "../shared/slash-commands.js";
|
|
14
|
+
import { createLogger } from "../shared/logger.js";
|
|
15
|
+
const log = createLogger("mesh-executor-worker-issue", { stream: "stderr" });
|
|
16
|
+
export class IssueHandler {
|
|
17
|
+
worker;
|
|
18
|
+
constructor(worker) {
|
|
19
|
+
this.worker = worker;
|
|
20
|
+
}
|
|
21
|
+
async executeIssue(issueId, title, description, cwd, slashCommand, approvalPolicy, repoCtx) {
|
|
22
|
+
// title 现在是 description 的截断,不再拼进 body——否则会重复用户输入。
|
|
23
|
+
// body 直接用 description,仅在 /plan 模式剥掉前缀。cleanTitle 仅用于 header 展示。
|
|
24
|
+
let cleanTitle = title;
|
|
25
|
+
if (slashCommand) {
|
|
26
|
+
const parsed = parseSlashCommand(title);
|
|
27
|
+
if (parsed?.known && parsed.command === slashCommand) {
|
|
28
|
+
cleanTitle = parsed.stripped || title;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
let body = (description || "").trim();
|
|
32
|
+
if (slashCommand && body) {
|
|
33
|
+
const parsed = parseSlashCommand(body);
|
|
34
|
+
if (parsed?.known && parsed.command === slashCommand) {
|
|
35
|
+
body = parsed.stripped || body;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// 注入 issue 执行上下文,让 agent 知道自己已在 issue 中,避免重复建 issue。
|
|
39
|
+
const issueHeader = `[当前群活跃 issue]\n` +
|
|
40
|
+
`- #${issueId.slice(0, 8)} in_progress "${cleanTitle}" by ${this.worker.config.name}\n` +
|
|
41
|
+
`提示:你正在执行此 issue,工作目录 **可写**,直接按任务描述动手即可。` +
|
|
42
|
+
`**不要为此任务再创建新 issue。**\n`;
|
|
43
|
+
const bodyWithContext = `${issueHeader}\n${body}`;
|
|
44
|
+
// issue 模式没有 conversation(group_basic 层无法渲染,因为 worker 这边没有
|
|
45
|
+
// activeIssues 数据);只拼 rotom-cli + agent-role + cwd + task 四层。
|
|
46
|
+
const composed = composePrompt({
|
|
47
|
+
mode: "issue",
|
|
48
|
+
agentName: this.worker.config.name,
|
|
49
|
+
agentProfile: this.worker.agentProfile,
|
|
50
|
+
group: null,
|
|
51
|
+
cwd,
|
|
52
|
+
body: bodyWithContext,
|
|
53
|
+
approvalPolicy,
|
|
54
|
+
});
|
|
55
|
+
this.runIssueExecution(issueId, composed.final, cwd, undefined, slashCommand, approvalPolicy, composed, repoCtx);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Core issue execution loop — shared by first-run (executeIssue) and
|
|
59
|
+
* continuation (handleIssueContinue). When resumeSessionId is provided the
|
|
60
|
+
* CLI gets --resume / thread/resume so the conversation picks up where it
|
|
61
|
+
* left off.
|
|
62
|
+
*
|
|
63
|
+
* repoCtx 用于 issue 完成后可选的 worktree 清理(当前实现不在终态自动清理,
|
|
64
|
+
* 留给 issue delete/cancel 路径触发;此处仅保存上下文备用)。
|
|
65
|
+
*/
|
|
66
|
+
async runIssueExecution(issueId, prompt, cwd, resumeSessionId, slashCommand, approvalPolicy, composedPrompt, repoCtx) {
|
|
67
|
+
if (this.worker.activeTasks.size >= this.worker.maxConcurrent) {
|
|
68
|
+
log.info(this.worker.tag, `At capacity (${this.worker.maxConcurrent}), skip ${issueId}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (this.worker.activeTasks.has(issueId))
|
|
72
|
+
return;
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const task = { aborted: false, controller };
|
|
75
|
+
this.worker.activeTasks.set(issueId, task);
|
|
76
|
+
const cliName = this.worker.config.cliTool || "cli";
|
|
77
|
+
this.worker.sendUpdate(issueId, "in_progress", `${resumeSessionId ? "Resuming" : "Starting"} with ${cliName}...`, undefined, cwd);
|
|
78
|
+
// 本轮结束后用于喂给 append 续跑的 sessionId — 优先用本轮新产出的,
|
|
79
|
+
// fall back 到入参 resumeSessionId(本轮没拿到新 session 时,比如 codex 早退)。
|
|
80
|
+
let lastSessionId = resumeSessionId;
|
|
81
|
+
// 写策略跟随 issue.approval_policy 入参;master 端 normalizeApprovalPolicy
|
|
82
|
+
// 已把 undefined / 脏值收敛成有效值,,这里再兜一次底:undefined 当 rw_allow
|
|
83
|
+
// 走(写需审批),符合 protocol 默认。PreToolUse hook 始终挂载:避免 claude
|
|
84
|
+
// 自己的权限提示因 stdin 关闭而卡死。r_allow 下 hook 走 issue_approval_request
|
|
85
|
+
// 挂起等用户决策,rw_allow 下本地立即 accept 不阻塞 CLI。
|
|
86
|
+
const effectivePolicy = approvalPolicy ?? "rw_allow";
|
|
87
|
+
const onApprovalRequest = (req) => {
|
|
88
|
+
const approvalId = randomUUID();
|
|
89
|
+
if (effectivePolicy === "rw_allow") {
|
|
90
|
+
// rw_allow:立即放行,本地 decision=accept。不发 issue_approval_request
|
|
91
|
+
// 给 master —— 否则 Dashboard 会渲染成"待确认"卡片,误导用户以为
|
|
92
|
+
// agent 被阻塞,实际底层 claude 已经继续跑。审计/可见性可从 master
|
|
93
|
+
// 日志 + claude session jsonl 拉(都是 fullOutput 的一部分)。
|
|
94
|
+
return Promise.resolve({ decision: "accept" });
|
|
95
|
+
}
|
|
96
|
+
// r_allow + 只读 Bash:命中内置白名单直接 accept,不弹 dashboard 卡片。
|
|
97
|
+
// 安全契约(fail-closed):复合命令(管道/重定向/&&/;/`/$()/\\)及前导 env
|
|
98
|
+
// 赋值一律不命中,详见 src/shared/readonly-allowlist.ts。file_change/plan/
|
|
99
|
+
// ask 不查白名单,继续走 dashboard。
|
|
100
|
+
if (req.kind === "exec" && isReadonlyCommand(req.command)) {
|
|
101
|
+
log.info(this.worker.tag, `auto-approve readonly exec: ${req.command}`);
|
|
102
|
+
return Promise.resolve({ decision: "accept" });
|
|
103
|
+
}
|
|
104
|
+
// r_allow:Dashboard 侧能看到请求记录,挂起等待用户 Accept/Deny
|
|
105
|
+
this.worker.send({
|
|
106
|
+
type: "issue_approval_request",
|
|
107
|
+
issueId,
|
|
108
|
+
approvalId,
|
|
109
|
+
kind: req.kind,
|
|
110
|
+
summary: req.summary,
|
|
111
|
+
command: req.command,
|
|
112
|
+
cwd: req.cwd,
|
|
113
|
+
files: req.files,
|
|
114
|
+
plan: req.plan,
|
|
115
|
+
diff: req.diff,
|
|
116
|
+
questions: req.questions,
|
|
117
|
+
});
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
this.worker.pendingApprovals.set(approvalId, { issueId, resolve });
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
// result 提到 try 外声明,让 finally 能访问 result.usage 给 flushIssueUsage
|
|
123
|
+
// 做终态覆盖。execute 抛错时 result 为 undefined,flush 用累积值兜底。
|
|
124
|
+
let result;
|
|
125
|
+
try {
|
|
126
|
+
result = await this.worker.executor.execute(prompt, cwd, (chunk) => {
|
|
127
|
+
if (task.aborted)
|
|
128
|
+
return;
|
|
129
|
+
this.worker.sendUpdate(issueId, "in_progress", chunk, undefined, cwd);
|
|
130
|
+
}, {
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
env: this.worker.agentEnv(),
|
|
133
|
+
kind: "issue",
|
|
134
|
+
sessionId: resumeSessionId,
|
|
135
|
+
slashCommand,
|
|
136
|
+
approvalPolicy: effectivePolicy,
|
|
137
|
+
onApprovalRequest,
|
|
138
|
+
onTodos: (todos) => {
|
|
139
|
+
if (task.aborted)
|
|
140
|
+
return;
|
|
141
|
+
// 单独走 issue_todos_update WS 消息,不混进 issue_update 的 progress
|
|
142
|
+
// 事件流——后者会产生 [tool:exec] 等气泡,todos 不该走那条路。
|
|
143
|
+
this.worker.send({ type: "issue_todos_update", issueId, todos });
|
|
144
|
+
},
|
|
145
|
+
onUsage: (increment) => {
|
|
146
|
+
if (task.aborted)
|
|
147
|
+
return;
|
|
148
|
+
// executor 给单轮增量,worker 内部 sum 成累积值并 1s 节流推送。
|
|
149
|
+
// 不调 onUsage 的 backend(codex/hermes/openclaw)→ 本回调不被触发,
|
|
150
|
+
// IssueStatusBar 自然降级到终态 issue.usage。
|
|
151
|
+
this.worker.reportIssueUsage(issueId, increment);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
// 不管后续走 abort / completed / failed 哪条分支,先把本轮 sessionId
|
|
155
|
+
// 抓回来 —— finally 的队列续跑要用它做 --resume,丢了就起新会话丢上下文。
|
|
156
|
+
// 之前的 bug:interrupt 走 `if (task.aborted) return` 早返回,这行被跳过,
|
|
157
|
+
// 队列续跑 lastSessionId=undefined → claude 起新会话,前一轮工作全丢。
|
|
158
|
+
if (result?.sessionId)
|
|
159
|
+
lastSessionId = result.sessionId;
|
|
160
|
+
if (task.aborted) {
|
|
161
|
+
// 中断/取消事件已由 /interrupt 或 /cancel API 在落 issue_event 的同时
|
|
162
|
+
// 推 WS 给 worker,worker 不再二次 sendUpdate —— 否则 master 会再落一条
|
|
163
|
+
// progress 事件,把 "[interrupted] 当前步骤已中断..." 这种系统话塞进
|
|
164
|
+
// agent 的对话气泡(被 groupEvents 当成 mergeable progress 合并)。
|
|
165
|
+
// sessionId 已在上面抓回,finally 块会用它做 --resume 续跑队列。
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const artifacts = this.extractArtifacts(result.fullOutput);
|
|
169
|
+
// Persist sessionId + cliTool so the master can feed them back for
|
|
170
|
+
// continuation (POST /issues/:id/continue → issue_continue WS).
|
|
171
|
+
const sessionMeta = { artifacts };
|
|
172
|
+
if (result.sessionId) {
|
|
173
|
+
sessionMeta.sessionId = result.sessionId;
|
|
174
|
+
}
|
|
175
|
+
else if (resumeSessionId) {
|
|
176
|
+
// Resume was requested but executor returned no session (e.g. claude
|
|
177
|
+
// "No conversation found"). Clear the stale session_id in DB so the
|
|
178
|
+
// next continuation starts fresh instead of retrying the dead session.
|
|
179
|
+
sessionMeta.sessionId = null;
|
|
180
|
+
}
|
|
181
|
+
sessionMeta.cliTool = this.worker.cliTool;
|
|
182
|
+
if (result.usage)
|
|
183
|
+
sessionMeta.usage = result.usage;
|
|
184
|
+
if (result.model)
|
|
185
|
+
sessionMeta.model = result.model;
|
|
186
|
+
if (result.exitCode === 0) {
|
|
187
|
+
this.worker.sendUpdate(issueId, "completed", result.fullOutput, sessionMeta, cwd, composedPrompt);
|
|
188
|
+
log.info(this.worker.tag, `Issue done: ${issueId} (exit=0, session=${result.sessionId ?? "none"})`);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.worker.sendUpdate(issueId, "failed", `Exit ${result.exitCode}\n${result.fullOutput}`, sessionMeta, cwd, composedPrompt);
|
|
192
|
+
log.info(this.worker.tag, `Issue failed: ${issueId} (exit=${result.exitCode})`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
if (task.aborted) {
|
|
197
|
+
// 走异常路径的中断/取消(理论上 executor 都走 resolve,catch 是兜底)。
|
|
198
|
+
// 同 try 块,事件已由 API 落库,worker 不再二次 sendUpdate。
|
|
199
|
+
log.info(this.worker.tag, `Issue aborted via exception: ${issueId} (interrupted=${!!task.interrupted})`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
this.worker.sendUpdate(issueId, "failed", err.message, undefined, cwd);
|
|
203
|
+
log.error(this.worker.tag, `Issue error: ${issueId}`, err.message);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
// 强制 flush 累积 usage:覆盖所有路径(正常完成/abort/catch)。override
|
|
208
|
+
// 用 result.usage(终态汇总值,口径与 reload 后 issue.usage 一致),保证
|
|
209
|
+
// 最后一次推送与 DB 终态值对齐,避免数字回退。result 为 undefined 时
|
|
210
|
+
// (execute 抛错)用累积值兜底。
|
|
211
|
+
this.worker.flushIssueUsage(issueId, result?.usage);
|
|
212
|
+
this.worker.activeTasks.delete(issueId);
|
|
213
|
+
for (const [approvalId, p] of this.worker.pendingApprovals) {
|
|
214
|
+
if (p.issueId !== issueId)
|
|
215
|
+
continue;
|
|
216
|
+
this.worker.pendingApprovals.delete(approvalId);
|
|
217
|
+
p.resolve({ decision: "deny" });
|
|
218
|
+
}
|
|
219
|
+
// 取消的任务丢弃排队 append(用户主动放弃,不该再触发续跑);
|
|
220
|
+
// 正常 / 失败 / 中断才消费队列起新一轮。中断(interrupted)与取消的
|
|
221
|
+
// 区别:中断保留 session 并消费队列(对齐 codex ESC + flush steers),
|
|
222
|
+
// 取消则整体作废。
|
|
223
|
+
const queued = this.worker.pendingAppends.get(issueId);
|
|
224
|
+
this.worker.pendingAppends.delete(issueId);
|
|
225
|
+
const shouldConsumeQueue = !task.aborted || !!task.interrupted;
|
|
226
|
+
if (queued && queued.length > 0 && shouldConsumeQueue) {
|
|
227
|
+
const merged = queued.join("\n\n");
|
|
228
|
+
log.info(this.worker.tag, `Issue append consuming queue: ${issueId} (count=${queued.length}, session=${lastSessionId ?? "(none)"}, interrupted=${!!task.interrupted})`);
|
|
229
|
+
// setImmediate 避免在 finally 同步链上递归调起新一轮。
|
|
230
|
+
// 队列消费继承本轮的 effectivePolicy;append 自己的 ws 消息此时已经在
|
|
231
|
+
// 队列里被吃掉,没机会再传策略,沿用本轮是正确做法(用户切换策略后
|
|
232
|
+
// 新的 issue_append 会重新走 ws → effectivePolicy 才会刷新)。
|
|
233
|
+
setImmediate(() => {
|
|
234
|
+
this.runIssueExecution(issueId, merged, cwd, lastSessionId, slashCommand, effectivePolicy);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
else if (task.aborted && task.interrupted) {
|
|
238
|
+
// 中断 + 队列空:issue 没有新一轮要跑,转 paused(待继续)状态。
|
|
239
|
+
// 否则 master 上 issue.status 会一直停在 in_progress,Dashboard
|
|
240
|
+
// loading 转不停。session_id 已在 issue 表里,用户下次 append 时
|
|
241
|
+
// worker 走 idle 分支用 --resume 续跑。
|
|
242
|
+
// 不传 content:避免把状态变更伪装成 agent 对话气泡(参考上面
|
|
243
|
+
// task.aborted 早返回路径的同款注释)。
|
|
244
|
+
log.info(this.worker.tag, `Issue interrupted idle → paused: ${issueId} (session=${lastSessionId ?? "(none)"})`);
|
|
245
|
+
this.worker.sendUpdate(issueId, "paused", undefined, undefined, cwd);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
extractArtifacts(output) {
|
|
250
|
+
const artifacts = [];
|
|
251
|
+
const patterns = [
|
|
252
|
+
/(?:created|modified|wrote|updated)\s+`?([^\s`]+\.\w+)`?/gi,
|
|
253
|
+
/(?:Editing|Creating)\s+([^\s:]+\.\w+)/gi,
|
|
254
|
+
];
|
|
255
|
+
for (const p of patterns) {
|
|
256
|
+
let m;
|
|
257
|
+
while ((m = p.exec(output)) !== null) {
|
|
258
|
+
if (!artifacts.includes(m[1]))
|
|
259
|
+
artifacts.push(m[1]);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return artifacts;
|
|
263
|
+
}
|
|
264
|
+
}
|