@xdevops/issue-auto-finish 1.0.86 → 1.0.87
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/dist/AIRunnerRegistry-II3WWSFN.js +31 -0
- package/dist/PtyRunner-6UGI5STW.js +22 -0
- package/dist/TerminalManager-RT2N7N5R.js +8 -0
- package/dist/ai-runner/AIRunner.d.ts +9 -1
- package/dist/ai-runner/AIRunner.d.ts.map +1 -1
- package/dist/ai-runner/AIRunnerRegistry.d.ts +37 -1
- package/dist/ai-runner/AIRunnerRegistry.d.ts.map +1 -1
- package/dist/ai-runner/PtyRunner.d.ts +114 -0
- package/dist/ai-runner/PtyRunner.d.ts.map +1 -0
- package/dist/ai-runner/index.d.ts +3 -1
- package/dist/ai-runner/index.d.ts.map +1 -1
- package/dist/{ai-runner-SVUNA3FX.js → ai-runner-HLA44WI6.js} +12 -3
- package/dist/{analyze-SXXPE5XL.js → analyze-ZIXNC5GN.js} +10 -8
- package/dist/{analyze-SXXPE5XL.js.map → analyze-ZIXNC5GN.js.map} +1 -1
- package/dist/{braindump-4E5SDMSZ.js → braindump-56WAY2RD.js} +10 -8
- package/dist/{braindump-4E5SDMSZ.js.map → braindump-56WAY2RD.js.map} +1 -1
- package/dist/{chunk-ICXB2WP5.js → chunk-2MESXJEZ.js} +3 -3
- package/dist/{chunk-P4O4ZXEC.js → chunk-2YQHKXLL.js} +40 -19
- package/dist/chunk-2YQHKXLL.js.map +1 -0
- package/dist/chunk-AVGZH64A.js +211 -0
- package/dist/chunk-AVGZH64A.js.map +1 -0
- package/dist/{chunk-OUPJMHAL.js → chunk-IP3QTP5A.js} +1026 -764
- package/dist/chunk-IP3QTP5A.js.map +1 -0
- package/dist/chunk-KC5S66OZ.js +177 -0
- package/dist/chunk-KC5S66OZ.js.map +1 -0
- package/dist/{chunk-4QV6D34Y.js → chunk-M5C2WILQ.js} +8 -6
- package/dist/{chunk-4QV6D34Y.js.map → chunk-M5C2WILQ.js.map} +1 -1
- package/dist/{chunk-FWEW5E3B.js → chunk-NZHKAPU6.js} +35 -5
- package/dist/chunk-NZHKAPU6.js.map +1 -0
- package/dist/{chunk-KTYPZTF4.js → chunk-O3WEV5W3.js} +10 -2
- package/dist/chunk-O3WEV5W3.js.map +1 -0
- package/dist/{chunk-QO5VTSMI.js → chunk-QZZGIZWC.js} +455 -202
- package/dist/chunk-QZZGIZWC.js.map +1 -0
- package/dist/{chunk-4LFNFRCL.js → chunk-SAMTXC4A.js} +91 -214
- package/dist/chunk-SAMTXC4A.js.map +1 -0
- package/dist/chunk-U237JSLB.js +1 -0
- package/dist/chunk-U237JSLB.js.map +1 -0
- package/dist/chunk-U6GWFTKA.js +657 -0
- package/dist/chunk-U6GWFTKA.js.map +1 -0
- package/dist/{chunk-HOFYJEJ4.js → chunk-UBQLXQ7I.js} +11 -11
- package/dist/cli/setup/env-metadata.d.ts.map +1 -1
- package/dist/cli.js +8 -7
- package/dist/cli.js.map +1 -1
- package/dist/{config-QLINHCHD.js → config-WTRSZLOC.js} +4 -3
- package/dist/config-WTRSZLOC.js.map +1 -0
- package/dist/config-schema.d.ts +17 -1
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/errors/PhaseAbortedError.d.ts +3 -3
- package/dist/errors/PhaseAbortedError.d.ts.map +1 -1
- package/dist/errors-S3BWYA4I.js +43 -0
- package/dist/errors-S3BWYA4I.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -11
- package/dist/{init-TDKDC6YP.js → init-QQDXGTPB.js} +7 -6
- package/dist/{init-TDKDC6YP.js.map → init-QQDXGTPB.js.map} +1 -1
- package/dist/lib.js +9 -7
- package/dist/lib.js.map +1 -1
- package/dist/orchestrator/IssueProcessingContext.d.ts +39 -21
- package/dist/orchestrator/IssueProcessingContext.d.ts.map +1 -1
- package/dist/orchestrator/PipelineOrchestrator.d.ts +10 -1
- package/dist/orchestrator/PipelineOrchestrator.d.ts.map +1 -1
- package/dist/orchestrator/steps/PhaseLoopStep.d.ts +1 -1
- package/dist/orchestrator/steps/PhaseLoopStep.d.ts.map +1 -1
- package/dist/orchestrator/steps/SetupStep.d.ts.map +1 -1
- package/dist/persistence/PlanPersistence.d.ts +7 -1
- package/dist/persistence/PlanPersistence.d.ts.map +1 -1
- package/dist/phases/BasePhase.d.ts +31 -42
- package/dist/phases/BasePhase.d.ts.map +1 -1
- package/dist/phases/BuildPhase.d.ts.map +1 -1
- package/dist/phases/PhaseFactory.d.ts +2 -3
- package/dist/phases/PhaseFactory.d.ts.map +1 -1
- package/dist/phases/PhaseOutcome.d.ts +42 -0
- package/dist/phases/PhaseOutcome.d.ts.map +1 -0
- package/dist/phases/PlanPhase.d.ts +1 -1
- package/dist/phases/PlanPhase.d.ts.map +1 -1
- package/dist/phases/ReleasePhase.d.ts +8 -18
- package/dist/phases/ReleasePhase.d.ts.map +1 -1
- package/dist/phases/UatPhase.d.ts +7 -24
- package/dist/phases/UatPhase.d.ts.map +1 -1
- package/dist/phases/VerifyPhase.d.ts +4 -4
- package/dist/phases/VerifyPhase.d.ts.map +1 -1
- package/dist/poller/IssuePoller.d.ts.map +1 -1
- package/dist/prompts/release-templates.d.ts.map +1 -1
- package/dist/prompts/templates.d.ts.map +1 -1
- package/dist/{restart-4NSHDOX3.js → restart-BMILTP5X.js} +6 -5
- package/dist/{restart-4NSHDOX3.js.map → restart-BMILTP5X.js.map} +1 -1
- package/dist/run.js +14 -11
- package/dist/run.js.map +1 -1
- package/dist/settings/ExperimentalSettings.d.ts +1 -1
- package/dist/settings/ExperimentalSettings.d.ts.map +1 -1
- package/dist/start-6QRW6IJI.js +15 -0
- package/dist/start-6QRW6IJI.js.map +1 -0
- package/dist/terminal/TerminalManager.d.ts +62 -0
- package/dist/terminal/TerminalManager.d.ts.map +1 -0
- package/dist/terminal/TerminalWebSocket.d.ts +9 -0
- package/dist/terminal/TerminalWebSocket.d.ts.map +1 -0
- package/dist/tracker/ExecutableTask.d.ts +4 -2
- package/dist/tracker/ExecutableTask.d.ts.map +1 -1
- package/dist/tracker/IssueState.d.ts +11 -1
- package/dist/tracker/IssueState.d.ts.map +1 -1
- package/dist/tracker/IssueTracker.d.ts +19 -1
- package/dist/tracker/IssueTracker.d.ts.map +1 -1
- package/dist/web/WebServer.d.ts +4 -0
- package/dist/web/WebServer.d.ts.map +1 -1
- package/dist/web/routes/terminal.d.ts +11 -0
- package/dist/web/routes/terminal.d.ts.map +1 -0
- package/dist/webhook/CommandExecutor.d.ts.map +1 -1
- package/package.json +7 -1
- package/src/web/frontend/dist/assets/index-COYziOhv.css +1 -0
- package/src/web/frontend/dist/assets/index-D_oTMuJU.js +151 -0
- package/src/web/frontend/dist/index.html +2 -2
- package/dist/chunk-4LFNFRCL.js.map +0 -1
- package/dist/chunk-DADQSKPL.js +0 -1
- package/dist/chunk-FWEW5E3B.js.map +0 -1
- package/dist/chunk-KTYPZTF4.js.map +0 -1
- package/dist/chunk-OUPJMHAL.js.map +0 -1
- package/dist/chunk-P4O4ZXEC.js.map +0 -1
- package/dist/chunk-QO5VTSMI.js.map +0 -1
- package/dist/start-XZIBPLC2.js +0 -14
- package/src/web/frontend/dist/assets/index-BWVpNmFm.js +0 -133
- package/src/web/frontend/dist/assets/index-C7lorIa0.css +0 -1
- /package/dist/{ai-runner-SVUNA3FX.js.map → AIRunnerRegistry-II3WWSFN.js.map} +0 -0
- /package/dist/{chunk-DADQSKPL.js.map → PtyRunner-6UGI5STW.js.map} +0 -0
- /package/dist/{config-QLINHCHD.js.map → TerminalManager-RT2N7N5R.js.map} +0 -0
- /package/dist/{start-XZIBPLC2.js.map → ai-runner-HLA44WI6.js.map} +0 -0
- /package/dist/{chunk-ICXB2WP5.js.map → chunk-2MESXJEZ.js.map} +0 -0
- /package/dist/{chunk-HOFYJEJ4.js.map → chunk-UBQLXQ7I.js.map} +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-GF2RRYHB.js";
|
|
4
|
+
|
|
5
|
+
// src/terminal/TerminalManager.ts
|
|
6
|
+
import * as pty from "node-pty";
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
import path from "path";
|
|
9
|
+
var logger2 = logger.child("TerminalManager");
|
|
10
|
+
var SENSITIVE_PATTERNS = [
|
|
11
|
+
/_TOKEN$/i,
|
|
12
|
+
/_SECRET$/i,
|
|
13
|
+
/_KEY$/i,
|
|
14
|
+
/_PASSWORD$/i,
|
|
15
|
+
/^PRIVATE_TOKEN$/i,
|
|
16
|
+
/^GONGFENG_PRIVATE_TOKEN$/i
|
|
17
|
+
];
|
|
18
|
+
function sanitizeEnv() {
|
|
19
|
+
const { CLAUDECODE: _cc, ...env } = process.env;
|
|
20
|
+
const sanitized = {};
|
|
21
|
+
for (const [key, value] of Object.entries(env)) {
|
|
22
|
+
if (value === void 0) continue;
|
|
23
|
+
if (SENSITIVE_PATTERNS.some((p) => p.test(key))) continue;
|
|
24
|
+
sanitized[key] = value;
|
|
25
|
+
}
|
|
26
|
+
return sanitized;
|
|
27
|
+
}
|
|
28
|
+
var TerminalManager = class {
|
|
29
|
+
sessions = /* @__PURE__ */ new Map();
|
|
30
|
+
idleCheckTimer = null;
|
|
31
|
+
options;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.options = options;
|
|
34
|
+
this.startIdleCheck();
|
|
35
|
+
}
|
|
36
|
+
// ---- Lifecycle -----------------------------------------------------------
|
|
37
|
+
create(opts) {
|
|
38
|
+
if (this.sessions.size >= this.options.maxSessions) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Maximum terminal sessions (${this.options.maxSessions}) reached. Destroy an existing session first.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
this.validateWorkDir(opts.workDir);
|
|
44
|
+
const id = randomUUID();
|
|
45
|
+
const cols = opts.cols ?? 80;
|
|
46
|
+
const rows = opts.rows ?? 24;
|
|
47
|
+
const command = opts.command ?? "claude";
|
|
48
|
+
const args = opts.args ?? [];
|
|
49
|
+
const ptyProcess = pty.spawn(command, args, {
|
|
50
|
+
name: "xterm-256color",
|
|
51
|
+
cols,
|
|
52
|
+
rows,
|
|
53
|
+
cwd: opts.workDir,
|
|
54
|
+
env: sanitizeEnv()
|
|
55
|
+
});
|
|
56
|
+
const session = {
|
|
57
|
+
id,
|
|
58
|
+
pty: ptyProcess,
|
|
59
|
+
issueIid: opts.issueIid,
|
|
60
|
+
workDir: opts.workDir,
|
|
61
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
62
|
+
lastActivityAt: /* @__PURE__ */ new Date(),
|
|
63
|
+
cols,
|
|
64
|
+
rows,
|
|
65
|
+
managed: opts.managed ?? false
|
|
66
|
+
};
|
|
67
|
+
this.sessions.set(id, session);
|
|
68
|
+
logger2.info("Terminal session created", { id, pid: ptyProcess.pid, workDir: opts.workDir, issueIid: opts.issueIid });
|
|
69
|
+
return this.toInfo(session);
|
|
70
|
+
}
|
|
71
|
+
get(id) {
|
|
72
|
+
return this.sessions.get(id);
|
|
73
|
+
}
|
|
74
|
+
list() {
|
|
75
|
+
return Array.from(this.sessions.values()).map((s) => this.toInfo(s));
|
|
76
|
+
}
|
|
77
|
+
write(id, data) {
|
|
78
|
+
const session = this.sessions.get(id);
|
|
79
|
+
if (!session) throw new Error(`Session ${id} not found`);
|
|
80
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
81
|
+
session.pty.write(data);
|
|
82
|
+
}
|
|
83
|
+
resize(id, cols, rows) {
|
|
84
|
+
const session = this.sessions.get(id);
|
|
85
|
+
if (!session) throw new Error(`Session ${id} not found`);
|
|
86
|
+
session.cols = cols;
|
|
87
|
+
session.rows = rows;
|
|
88
|
+
session.pty.resize(cols, rows);
|
|
89
|
+
}
|
|
90
|
+
onData(id, callback) {
|
|
91
|
+
const session = this.sessions.get(id);
|
|
92
|
+
if (!session) throw new Error(`Session ${id} not found`);
|
|
93
|
+
const disposable = session.pty.onData((data) => {
|
|
94
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
95
|
+
callback(data);
|
|
96
|
+
});
|
|
97
|
+
return { dispose: () => disposable.dispose() };
|
|
98
|
+
}
|
|
99
|
+
onExit(id, callback) {
|
|
100
|
+
const session = this.sessions.get(id);
|
|
101
|
+
if (!session) throw new Error(`Session ${id} not found`);
|
|
102
|
+
session.pty.onExit(({ exitCode, signal }) => {
|
|
103
|
+
logger2.info("Terminal PTY exited", { id, exitCode, signal });
|
|
104
|
+
this.sessions.delete(id);
|
|
105
|
+
callback(exitCode, signal);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
destroy(id) {
|
|
109
|
+
const session = this.sessions.get(id);
|
|
110
|
+
if (!session) return;
|
|
111
|
+
logger2.info("Destroying terminal session", { id, pid: session.pty.pid });
|
|
112
|
+
try {
|
|
113
|
+
session.pty.kill();
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
this.sessions.delete(id);
|
|
117
|
+
}
|
|
118
|
+
destroyAll() {
|
|
119
|
+
if (this.idleCheckTimer) {
|
|
120
|
+
clearInterval(this.idleCheckTimer);
|
|
121
|
+
this.idleCheckTimer = null;
|
|
122
|
+
}
|
|
123
|
+
for (const id of [...this.sessions.keys()]) {
|
|
124
|
+
this.destroy(id);
|
|
125
|
+
}
|
|
126
|
+
logger2.info("All terminal sessions destroyed");
|
|
127
|
+
}
|
|
128
|
+
findByWorkDir(workDir) {
|
|
129
|
+
for (const session of this.sessions.values()) {
|
|
130
|
+
if (session.workDir === workDir) return session;
|
|
131
|
+
}
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
// ---- Internal ------------------------------------------------------------
|
|
135
|
+
validateWorkDir(workDir) {
|
|
136
|
+
const resolved = path.resolve(workDir);
|
|
137
|
+
const allowed = this.options.allowedBaseDirs.some(
|
|
138
|
+
(base) => resolved.startsWith(path.resolve(base))
|
|
139
|
+
);
|
|
140
|
+
if (!allowed) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Working directory "${workDir}" is not under any allowed base directory`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
startIdleCheck() {
|
|
147
|
+
this.idleCheckTimer = setInterval(() => {
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
for (const [id, session] of this.sessions) {
|
|
150
|
+
if (session.managed) continue;
|
|
151
|
+
const idleMs = now - session.lastActivityAt.getTime();
|
|
152
|
+
if (idleMs >= this.options.idleTimeoutMs) {
|
|
153
|
+
logger2.info("Destroying idle terminal session", { id, idleMs });
|
|
154
|
+
this.destroy(id);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}, 6e4);
|
|
158
|
+
this.idleCheckTimer.unref();
|
|
159
|
+
}
|
|
160
|
+
toInfo(session) {
|
|
161
|
+
return {
|
|
162
|
+
id: session.id,
|
|
163
|
+
issueIid: session.issueIid,
|
|
164
|
+
workDir: session.workDir,
|
|
165
|
+
createdAt: session.createdAt.toISOString(),
|
|
166
|
+
lastActivityAt: session.lastActivityAt.toISOString(),
|
|
167
|
+
cols: session.cols,
|
|
168
|
+
rows: session.rows,
|
|
169
|
+
pid: session.pty.pid
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
TerminalManager
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=chunk-KC5S66OZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/terminal/TerminalManager.ts"],"sourcesContent":["import * as pty from 'node-pty';\nimport type { IPty } from 'node-pty';\nimport { randomUUID } from 'node:crypto';\nimport path from 'node:path';\nimport { logger as rootLogger } from '../logger.js';\n\nconst logger = rootLogger.child('TerminalManager');\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TerminalSession {\n id: string;\n pty: IPty;\n issueIid?: number;\n workDir: string;\n createdAt: Date;\n lastActivityAt: Date;\n cols: number;\n rows: number;\n /** true = PtyRunner 管理生命周期,WS 断开不销毁、idle check 不回收 */\n managed: boolean;\n}\n\nexport interface TerminalSessionInfo {\n id: string;\n issueIid?: number;\n workDir: string;\n createdAt: string;\n lastActivityAt: string;\n cols: number;\n rows: number;\n pid: number;\n}\n\nexport interface TerminalManagerOptions {\n idleTimeoutMs: number;\n maxSessions: number;\n allowedBaseDirs: string[];\n}\n\nexport interface CreateSessionOptions {\n workDir: string;\n issueIid?: number;\n cols?: number;\n rows?: number;\n /** PTY 执行的命令,默认 'claude' */\n command?: string;\n /** 命令参数,默认 [] */\n args?: string[];\n /** true = PtyRunner 管理生命周期 */\n managed?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Sensitive env-var patterns to strip before spawning PTY\n// ---------------------------------------------------------------------------\n\nconst SENSITIVE_PATTERNS = [\n /_TOKEN$/i,\n /_SECRET$/i,\n /_KEY$/i,\n /_PASSWORD$/i,\n /^PRIVATE_TOKEN$/i,\n /^GONGFENG_PRIVATE_TOKEN$/i,\n];\n\nfunction sanitizeEnv(): Record<string, string> {\n const { CLAUDECODE: _cc, ...env } = process.env;\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(env)) {\n if (value === undefined) continue;\n if (SENSITIVE_PATTERNS.some((p) => p.test(key))) continue;\n sanitized[key] = value;\n }\n return sanitized;\n}\n\n// ---------------------------------------------------------------------------\n// TerminalManager\n// ---------------------------------------------------------------------------\n\nexport class TerminalManager {\n private sessions = new Map<string, TerminalSession>();\n private idleCheckTimer: ReturnType<typeof setInterval> | null = null;\n private readonly options: TerminalManagerOptions;\n\n constructor(options: TerminalManagerOptions) {\n this.options = options;\n this.startIdleCheck();\n }\n\n // ---- Lifecycle -----------------------------------------------------------\n\n create(opts: CreateSessionOptions): TerminalSessionInfo {\n if (this.sessions.size >= this.options.maxSessions) {\n throw new Error(\n `Maximum terminal sessions (${this.options.maxSessions}) reached. Destroy an existing session first.`,\n );\n }\n\n this.validateWorkDir(opts.workDir);\n\n const id = randomUUID();\n const cols = opts.cols ?? 80;\n const rows = opts.rows ?? 24;\n const command = opts.command ?? 'claude';\n const args = opts.args ?? [];\n\n const ptyProcess = pty.spawn(command, args, {\n name: 'xterm-256color',\n cols,\n rows,\n cwd: opts.workDir,\n env: sanitizeEnv(),\n });\n\n const session: TerminalSession = {\n id,\n pty: ptyProcess,\n issueIid: opts.issueIid,\n workDir: opts.workDir,\n createdAt: new Date(),\n lastActivityAt: new Date(),\n cols,\n rows,\n managed: opts.managed ?? false,\n };\n\n this.sessions.set(id, session);\n logger.info('Terminal session created', { id, pid: ptyProcess.pid, workDir: opts.workDir, issueIid: opts.issueIid });\n\n return this.toInfo(session);\n }\n\n get(id: string): TerminalSession | undefined {\n return this.sessions.get(id);\n }\n\n list(): TerminalSessionInfo[] {\n return Array.from(this.sessions.values()).map((s) => this.toInfo(s));\n }\n\n write(id: string, data: string): void {\n const session = this.sessions.get(id);\n if (!session) throw new Error(`Session ${id} not found`);\n session.lastActivityAt = new Date();\n session.pty.write(data);\n }\n\n resize(id: string, cols: number, rows: number): void {\n const session = this.sessions.get(id);\n if (!session) throw new Error(`Session ${id} not found`);\n session.cols = cols;\n session.rows = rows;\n session.pty.resize(cols, rows);\n }\n\n onData(id: string, callback: (data: string) => void): { dispose: () => void } {\n const session = this.sessions.get(id);\n if (!session) throw new Error(`Session ${id} not found`);\n const disposable = session.pty.onData((data) => {\n session.lastActivityAt = new Date();\n callback(data);\n });\n return { dispose: () => disposable.dispose() };\n }\n\n onExit(id: string, callback: (exitCode: number, signal?: number) => void): void {\n const session = this.sessions.get(id);\n if (!session) throw new Error(`Session ${id} not found`);\n session.pty.onExit(({ exitCode, signal }) => {\n logger.info('Terminal PTY exited', { id, exitCode, signal });\n this.sessions.delete(id);\n callback(exitCode, signal);\n });\n }\n\n destroy(id: string): void {\n const session = this.sessions.get(id);\n if (!session) return;\n logger.info('Destroying terminal session', { id, pid: session.pty.pid });\n try {\n session.pty.kill();\n } catch {\n // Already exited\n }\n this.sessions.delete(id);\n }\n\n destroyAll(): void {\n if (this.idleCheckTimer) {\n clearInterval(this.idleCheckTimer);\n this.idleCheckTimer = null;\n }\n for (const id of [...this.sessions.keys()]) {\n this.destroy(id);\n }\n logger.info('All terminal sessions destroyed');\n }\n\n findByWorkDir(workDir: string): TerminalSession | undefined {\n for (const session of this.sessions.values()) {\n if (session.workDir === workDir) return session;\n }\n return undefined;\n }\n\n // ---- Internal ------------------------------------------------------------\n\n private validateWorkDir(workDir: string): void {\n const resolved = path.resolve(workDir);\n const allowed = this.options.allowedBaseDirs.some((base) =>\n resolved.startsWith(path.resolve(base)),\n );\n if (!allowed) {\n throw new Error(\n `Working directory \"${workDir}\" is not under any allowed base directory`,\n );\n }\n }\n\n private startIdleCheck(): void {\n this.idleCheckTimer = setInterval(() => {\n const now = Date.now();\n for (const [id, session] of this.sessions) {\n if (session.managed) continue; // Runner-managed sessions are not auto-reaped\n const idleMs = now - session.lastActivityAt.getTime();\n if (idleMs >= this.options.idleTimeoutMs) {\n logger.info('Destroying idle terminal session', { id, idleMs });\n this.destroy(id);\n }\n }\n }, 60_000);\n // Don't prevent process exit\n this.idleCheckTimer.unref();\n }\n\n private toInfo(session: TerminalSession): TerminalSessionInfo {\n return {\n id: session.id,\n issueIid: session.issueIid,\n workDir: session.workDir,\n createdAt: session.createdAt.toISOString(),\n lastActivityAt: session.lastActivityAt.toISOString(),\n cols: session.cols,\n rows: session.rows,\n pid: session.pty.pid,\n };\n }\n}\n"],"mappings":"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","names":["logger"]}
|
|
@@ -4,14 +4,16 @@ import {
|
|
|
4
4
|
GitOperations,
|
|
5
5
|
braindumpTaskToExecutableTask,
|
|
6
6
|
eventBus
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2YQHKXLL.js";
|
|
8
8
|
import {
|
|
9
|
-
AIOutputParseError,
|
|
10
|
-
BatchNotFoundError,
|
|
11
|
-
TaskNotFoundError,
|
|
12
9
|
createAIRunner,
|
|
13
10
|
isShuttingDown
|
|
14
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-SAMTXC4A.js";
|
|
12
|
+
import {
|
|
13
|
+
AIOutputParseError,
|
|
14
|
+
BatchNotFoundError,
|
|
15
|
+
TaskNotFoundError
|
|
16
|
+
} from "./chunk-AVGZH64A.js";
|
|
15
17
|
import {
|
|
16
18
|
logger
|
|
17
19
|
} from "./chunk-GF2RRYHB.js";
|
|
@@ -655,4 +657,4 @@ export {
|
|
|
655
657
|
BraindumpOrchestrator,
|
|
656
658
|
BraindumpTracker
|
|
657
659
|
};
|
|
658
|
-
//# sourceMappingURL=chunk-
|
|
660
|
+
//# sourceMappingURL=chunk-M5C2WILQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/braindump/BraindumpOrchestrator.ts","../src/braindump/prompts/split-prompt.ts","../src/braindump/TaskSplitter.ts","../src/braindump/MergeQueue.ts","../src/braindump/prompts/task-prompt.ts","../src/braindump/BraindumpTracker.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport { Config, AIRunnerMode } from '../config.js';\nimport { GitOperations } from '../git/GitOperations.js';\nimport { AsyncMutex } from '../utils/AsyncMutex.js';\nimport { createAIRunner, type AIRunner, type StreamEvent } from '../ai-runner/index.js';\nimport { eventBus } from '../events/EventBus.js';\nimport { isShuttingDown } from '../shutdown/ShutdownSignal.js';\nimport { logger as rootLogger } from '../logger.js';\nimport {\n BatchStatus,\n TaskStatus,\n type BraindumpBatch,\n type BraindumpTask,\n type SplitResult,\n} from './BraindumpState.js';\nimport { BraindumpTracker } from './BraindumpTracker.js';\nimport { TaskSplitter } from './TaskSplitter.js';\nimport { MergeQueue } from './MergeQueue.js';\nimport { buildTaskPrompt } from './prompts/task-prompt.js';\n\nconst logger = rootLogger.child('BraindumpOrchestrator');\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport interface BatchCreateOptions {\n targetBranch?: string;\n maxConcurrent?: number;\n defaultAiRunnerMode?: AIRunnerMode;\n}\n\nexport class BraindumpOrchestrator {\n private config: Config;\n private mainGit: GitOperations;\n private mainGitMutex: AsyncMutex;\n private defaultAiRunner: AIRunner;\n private tracker: BraindumpTracker;\n private mergeQueue: MergeQueue;\n private activeBatches = new Set<string>();\n\n constructor(\n config: Config,\n mainGit: GitOperations,\n mainGitMutex: AsyncMutex,\n defaultAiRunner: AIRunner,\n tracker: BraindumpTracker,\n ) {\n this.config = config;\n this.mainGit = mainGit;\n this.mainGitMutex = mainGitMutex;\n this.defaultAiRunner = defaultAiRunner;\n this.tracker = tracker;\n\n this.mergeQueue = new MergeQueue({\n mainGit,\n mainGitMutex,\n aiRunner: defaultAiRunner,\n tracker,\n worktreeBaseDir: config.project.worktreeBaseDir || config.project.gitRootDir,\n projectSubDir: config.project.projectSubDir,\n phaseTimeoutMs: config.braindump.taskTimeoutMs,\n maxConflictAttempts: config.braindump.maxConflictAttempts,\n });\n }\n\n getTracker(): BraindumpTracker {\n return this.tracker;\n }\n\n /** Replace the default AI runner (used by config hot-reload) */\n setAIRunner(runner: AIRunner): void {\n this.defaultAiRunner = runner;\n logger.info('BraindumpOrchestrator AIRunner replaced via hot-reload');\n }\n\n // ── Batch lifecycle ──\n\n createBatch(rawInput: string, options?: BatchCreateOptions): BraindumpBatch {\n const id = crypto.randomUUID();\n const targetBranch = options?.targetBranch || this.config.project.baseBranch;\n const batchShort = id.slice(0, 8);\n\n const batch: BraindumpBatch = {\n id,\n rawInput,\n targetBranch,\n integrationBranch: `braindump/${batchShort}/integration`,\n status: BatchStatus.Draft,\n tasks: [],\n maxConcurrent: options?.maxConcurrent || this.config.braindump.maxConcurrent,\n defaultAiRunnerMode: options?.defaultAiRunnerMode || this.config.ai.mode,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n return this.tracker.createBatch(batch);\n }\n\n async splitBatch(\n batchId: string,\n onEvent?: (event: StreamEvent) => void,\n ): Promise<BraindumpBatch> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n this.tracker.updateBatchStatus(batchId, BatchStatus.Splitting);\n\n try {\n const splitter = new TaskSplitter(this.defaultAiRunner);\n const result: SplitResult = await splitter.split(\n batch.rawInput,\n this.config.project.workDir,\n this.config.braindump.splitTimeoutMs,\n onEvent,\n );\n\n const batchShort = batchId.slice(0, 8);\n const tasks: BraindumpTask[] = result.tasks.map((t, i) => ({\n id: crypto.randomUUID(),\n index: i,\n title: t.title,\n description: t.description,\n dependsOn: t.dependsOn.map(depIdx => {\n // Convert index-based dependencies to task IDs (we'll set them below)\n return String(depIdx);\n }),\n branchName: `braindump/${batchShort}/task-${i}`,\n status: TaskStatus.Pending,\n attempts: 0,\n }));\n\n // Resolve index-based dependsOn to actual task IDs\n for (const task of tasks) {\n task.dependsOn = task.dependsOn\n .map(depIdxStr => {\n const depIdx = parseInt(depIdxStr, 10);\n return tasks[depIdx]?.id;\n })\n .filter((id): id is string => !!id);\n\n // Mark tasks with dependencies as blocked\n if (task.dependsOn.length > 0) {\n task.status = TaskStatus.Blocked;\n }\n }\n\n this.tracker.updateBatch(batchId, {\n tasks,\n status: BatchStatus.WaitingConfirm,\n });\n\n eventBus.emitTyped('braindump:split:done', { batchId, taskCount: tasks.length });\n logger.info('Batch split completed', { batchId, taskCount: tasks.length });\n\n return this.tracker.getBatch(batchId)!;\n } catch (err) {\n const errorMsg = (err as Error).message;\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, errorMsg);\n throw err;\n }\n }\n\n confirmBatch(batchId: string, tasks?: BraindumpTask[]): void {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n if (batch.status !== BatchStatus.WaitingConfirm) {\n throw new Error(`Batch ${batchId} is not waiting for confirmation (status: ${batch.status})`);\n }\n\n if (tasks) {\n this.tracker.updateBatch(batchId, { tasks });\n }\n eventBus.emitTyped('braindump:confirmed', { batchId });\n }\n\n async executeBatch(batchId: string): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n if (this.activeBatches.has(batchId)) {\n throw new Error(`Batch ${batchId} is already being executed`);\n }\n\n this.activeBatches.add(batchId);\n this.tracker.updateBatchStatus(batchId, BatchStatus.Running);\n\n try {\n // 1. Create integration branch from target\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n const exists = await this.mainGit.branchExists(batch.integrationBranch);\n if (!exists) {\n await this.mainGit.createBranch(batch.integrationBranch, batch.targetBranch);\n await this.mainGit.push(batch.integrationBranch);\n // Switch back to original branch\n await this.mainGit.checkout(this.config.project.baseBranch);\n }\n });\n\n // 2. Scheduling loop\n const runningTasks = new Map<string, Promise<void>>();\n\n while (!isShuttingDown()) {\n const currentBatch = this.tracker.getBatch(batchId);\n if (!currentBatch || currentBatch.status === BatchStatus.Failed) break;\n\n // Check if all tasks are in terminal state\n const allTerminal = currentBatch.tasks.every(\n t => t.status === TaskStatus.Merged || t.status === TaskStatus.Failed,\n );\n if (allTerminal) break;\n\n // Unblock tasks whose dependencies are met\n this.unblockReadyTasks(batchId);\n\n // Get ready tasks (pending + not running)\n const ready = currentBatch.tasks.filter(\n t => t.status === TaskStatus.Pending && !runningTasks.has(t.id),\n );\n const available = currentBatch.maxConcurrent - runningTasks.size;\n\n // Start new tasks\n for (const task of ready.slice(0, available)) {\n const promise = this.executeTask(batchId, task).finally(() => {\n runningTasks.delete(task.id);\n });\n runningTasks.set(task.id, promise);\n }\n\n await sleep(2000);\n }\n\n // Wait for remaining running tasks\n if (runningTasks.size > 0) {\n await Promise.allSettled([...runningTasks.values()]);\n }\n\n // Determine final batch status\n const finalBatch = this.tracker.getBatch(batchId)!;\n const allMerged = finalBatch.tasks.every(t => t.status === TaskStatus.Merged);\n const anyFailed = finalBatch.tasks.some(t => t.status === TaskStatus.Failed);\n\n if (allMerged) {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Completed);\n eventBus.emitTyped('braindump:completed', { batchId });\n logger.info('Batch completed successfully', { batchId });\n } else if (anyFailed) {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, 'Some tasks failed');\n eventBus.emitTyped('braindump:failed', { batchId });\n logger.warn('Batch completed with failures', { batchId });\n }\n } catch (err) {\n const errorMsg = (err as Error).message;\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, errorMsg);\n eventBus.emitTyped('braindump:failed', { batchId, error: errorMsg });\n logger.error('Batch execution failed', { batchId, error: errorMsg });\n } finally {\n this.activeBatches.delete(batchId);\n }\n }\n\n async cancelBatch(batchId: string): Promise<void> {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, 'Cancelled by user');\n this.activeBatches.delete(batchId);\n }\n\n async retryFailed(batchId: string): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n for (const task of batch.tasks) {\n if (task.status === TaskStatus.Failed) {\n task.status = TaskStatus.Pending;\n task.lastError = undefined;\n }\n }\n this.tracker.updateBatch(batchId, { tasks: batch.tasks });\n await this.executeBatch(batchId);\n }\n\n recoverInterrupted(): number {\n return this.tracker.recoverInterrupted();\n }\n\n // ── Private ──\n\n private unblockReadyTasks(batchId: string): void {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) return;\n\n for (const task of batch.tasks) {\n if (task.status !== TaskStatus.Blocked) continue;\n\n const allDepsMet = task.dependsOn.every(depId => {\n const dep = batch.tasks.find(t => t.id === depId);\n return dep && dep.status === TaskStatus.Merged;\n });\n\n if (allDepsMet) {\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Pending);\n }\n }\n }\n\n private async executeTask(batchId: string, task: BraindumpTask): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) return;\n\n logger.info('Starting task execution', { batchId, taskId: task.id, title: task.title });\n\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Running, {\n startedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:started', { batchId, taskId: task.id, title: task.title });\n\n const batchShort = batchId.slice(0, 8);\n const worktreeBaseDir = this.config.project.worktreeBaseDir || this.config.project.gitRootDir;\n const taskWorktreeDir = `${worktreeBaseDir}/braindump-${batchShort}-task-${task.index}`;\n const taskWorkDir = this.config.project.projectSubDir\n ? `${taskWorktreeDir}/${this.config.project.projectSubDir}`\n : taskWorktreeDir;\n\n try {\n // 1. Create worktree from integration branch\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n const worktrees = await this.mainGit.worktreeList();\n if (!worktrees.includes(taskWorktreeDir)) {\n await this.mainGit.worktreeAdd(\n taskWorktreeDir,\n task.branchName,\n `origin/${batch.integrationBranch}`,\n );\n }\n });\n\n // 2. Run AI\n const aiRunner = this.getRunnerForTask(task);\n const prompt = buildTaskPrompt(task.title, task.description);\n\n await aiRunner.run({\n prompt,\n workDir: taskWorkDir,\n timeoutMs: this.config.braindump.taskTimeoutMs,\n idleTimeoutMs: this.config.ai.idleTimeoutMs,\n onStreamEvent: (event: StreamEvent) => {\n eventBus.emitTyped('agent:output', {\n batchId,\n taskId: task.id,\n phase: 'task-execute',\n event,\n });\n },\n });\n\n // 3. Commit changes\n const wtGit = new GitOperations(taskWorktreeDir);\n if (await wtGit.hasChanges()) {\n await wtGit.add(['.']);\n await wtGit.commit(`braindump: ${task.title}`);\n await wtGit.push(task.branchName);\n }\n\n // 4. Mark done\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Done, {\n completedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:completed', { batchId, taskId: task.id });\n logger.info('Task execution completed', { batchId, taskId: task.id });\n\n // 5. Enqueue for merge\n await this.mergeQueue.enqueue(batchId, {\n ...task,\n status: TaskStatus.Done,\n completedAt: new Date().toISOString(),\n });\n } catch (err) {\n const errorMsg = (err as Error).message;\n logger.error('Task execution failed', { batchId, taskId: task.id, error: errorMsg });\n this.tracker.markTaskFailed(batchId, task.id, errorMsg);\n }\n }\n\n private getRunnerForTask(task: BraindumpTask): AIRunner {\n const mode = task.aiRunnerMode || this.config.ai.mode;\n if (mode === this.config.ai.mode) return this.defaultAiRunner;\n return createAIRunner({\n mode,\n binary: this.config.ai.binary,\n phaseTimeoutMs: this.config.braindump.taskTimeoutMs,\n nvmNodeVersion: this.config.ai.nvmNodeVersion,\n model: this.config.ai.model,\n });\n }\n}\n","/**\n * Build the prompt for AI task splitting.\n * The AI should browse the codebase, analyze the user's input,\n * and output a structured JSON with independent tasks.\n */\nexport function buildSplitPrompt(rawInput: string): string {\n return `你是一个项目管理专家和代码架构师。用户输入了一段包含多个想法/特性/任务的文本内容。\n\n你的任务:\n1. 先浏览项目代码库,了解项目结构和技术栈\n2. 分析用户输入的内容,识别出所有独立的特性或任务\n3. 为每个任务生成清晰的标题、详细描述和依赖关系\n4. 标注哪些任务可以并行执行(没有依赖关系的任务)\n\n## 用户输入\n\n${rawInput}\n\n## 输出要求\n\n请严格按以下 JSON 格式输出(用 \\`\\`\\`json 包裹):\n\n\\`\\`\\`json\n{\n \"tasks\": [\n {\n \"title\": \"简洁的任务标题\",\n \"description\": \"详细的任务描述,包含:\\\\n1. 需要修改哪些文件\\\\n2. 具体要做什么改动\\\\n3. 验收标准\",\n \"dependsOn\": [],\n \"estimatedComplexity\": \"low\"\n },\n {\n \"title\": \"另一个任务\",\n \"description\": \"这个任务依赖第一个任务的完成\",\n \"dependsOn\": [0],\n \"estimatedComplexity\": \"medium\"\n }\n ]\n}\n\\`\\`\\`\n\n## 注意事项\n\n- \\`dependsOn\\` 中填写的是任务在数组中的索引(从 0 开始),表示本任务依赖哪些任务先完成\n- 如果两个任务修改不同文件且逻辑独立,它们之间不应有依赖关系(可并行)\n- 如果两个任务修改相同文件或存在逻辑先后顺序,应设置依赖关系\n- \\`estimatedComplexity\\` 可选值:low(简单改动)、medium(中等复杂度)、high(复杂重构)\n- 每个任务的描述要足够详细,让 AI Agent 能独立完成该任务\n- 任务粒度适中:不要太大(一个任务不应包含多个独立变更),也不要太小(一行代码的改动不需要单独一个任务)`;\n}\n","import type { AIRunner, RunResult, StreamEvent } from '../ai-runner/index.js';\nimport type { SplitResult } from './BraindumpState.js';\nimport { buildSplitPrompt } from './prompts/split-prompt.js';\nimport { AIOutputParseError } from '../errors/index.js';\nimport { logger as rootLogger } from '../logger.js';\n\nconst logger = rootLogger.child('TaskSplitter');\n\nexport class TaskSplitter {\n constructor(private aiRunner: AIRunner) {}\n\n async split(\n rawInput: string,\n workDir: string,\n timeoutMs: number,\n onEvent?: (event: StreamEvent) => void,\n ): Promise<SplitResult> {\n logger.info('Starting task split', { inputLength: rawInput.length });\n\n const prompt = buildSplitPrompt(rawInput);\n const result: RunResult = await this.aiRunner.run({\n prompt,\n workDir,\n timeoutMs,\n onStreamEvent: onEvent,\n });\n\n return this.parseSplitOutput(result.output);\n }\n\n private parseSplitOutput(output: string): SplitResult {\n // Extract JSON block from AI output (wrapped in ```json ... ```)\n const jsonMatch = output.match(/```json\\s*\\n([\\s\\S]*?)\\n\\s*```/);\n if (!jsonMatch) {\n // Try parsing the entire output as JSON\n try {\n return this.validateResult(JSON.parse(output));\n } catch {\n throw new AIOutputParseError(\n 'Failed to parse task split output: no JSON block found in AI response',\n output,\n );\n }\n }\n\n try {\n return this.validateResult(JSON.parse(jsonMatch[1]));\n } catch (err) {\n throw new AIOutputParseError(`Failed to parse task split JSON: ${(err as Error).message}`, jsonMatch[1]);\n }\n }\n\n private validateResult(raw: unknown): SplitResult {\n if (!raw || typeof raw !== 'object') {\n throw new AIOutputParseError('Split result is not an object');\n }\n\n const obj = raw as Record<string, unknown>;\n if (!Array.isArray(obj.tasks)) {\n throw new AIOutputParseError('Split result missing \"tasks\" array');\n }\n\n const tasks = obj.tasks.map((t: Record<string, unknown>, i: number) => ({\n title: String(t.title || `Task ${i + 1}`),\n description: String(t.description || ''),\n dependsOn: Array.isArray(t.dependsOn)\n ? t.dependsOn.map(Number).filter(n => !isNaN(n))\n : [],\n estimatedComplexity: (['low', 'medium', 'high'].includes(String(t.estimatedComplexity))\n ? String(t.estimatedComplexity)\n : 'medium') as 'low' | 'medium' | 'high',\n }));\n\n logger.info('Parsed split result', { taskCount: tasks.length });\n return { tasks };\n }\n}\n","import { GitOperations } from '../git/GitOperations.js';\nimport { ConflictResolver } from '../git/ConflictResolver.js';\nimport { AsyncMutex } from '../utils/AsyncMutex.js';\nimport type { AIRunner } from '../ai-runner/index.js';\nimport { eventBus } from '../events/EventBus.js';\nimport { logger as rootLogger } from '../logger.js';\nimport type { BraindumpTask } from './BraindumpState.js';\nimport { TaskStatus } from './BraindumpState.js';\nimport { BraindumpTracker } from './BraindumpTracker.js';\n\nconst logger = rootLogger.child('MergeQueue');\n\ninterface MergeRequest {\n batchId: string;\n task: BraindumpTask;\n resolve: () => void;\n reject: (err: Error) => void;\n}\n\n/**\n * Serial merge queue: tasks are merged one at a time into the integration branch.\n * This ensures each merge sees the latest integration state and minimizes conflicts.\n */\nexport class MergeQueue {\n private queue: MergeRequest[] = [];\n private processing = false;\n private mainGit: GitOperations;\n private mainGitMutex: AsyncMutex;\n private conflictResolver: ConflictResolver;\n private tracker: BraindumpTracker;\n private worktreeBaseDir: string;\n private projectSubDir: string;\n private phaseTimeoutMs: number;\n private maxConflictAttempts: number;\n\n constructor(opts: {\n mainGit: GitOperations;\n mainGitMutex: AsyncMutex;\n aiRunner: AIRunner;\n tracker: BraindumpTracker;\n worktreeBaseDir: string;\n projectSubDir: string;\n phaseTimeoutMs: number;\n maxConflictAttempts: number;\n }) {\n this.mainGit = opts.mainGit;\n this.mainGitMutex = opts.mainGitMutex;\n this.conflictResolver = new ConflictResolver(opts.aiRunner);\n this.tracker = opts.tracker;\n this.worktreeBaseDir = opts.worktreeBaseDir;\n this.projectSubDir = opts.projectSubDir;\n this.phaseTimeoutMs = opts.phaseTimeoutMs;\n this.maxConflictAttempts = opts.maxConflictAttempts;\n }\n\n /** Enqueue a completed task for merging. Returns a promise that resolves when merge is done. */\n enqueue(batchId: string, task: BraindumpTask): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.queue.push({ batchId, task, resolve, reject });\n this.processNext();\n });\n }\n\n get pendingCount(): number {\n return this.queue.length;\n }\n\n private async processNext(): Promise<void> {\n if (this.processing || this.queue.length === 0) return;\n this.processing = true;\n\n // Sort by task index to ensure deterministic merge order\n this.queue.sort((a, b) => a.task.index - b.task.index);\n const req = this.queue.shift()!;\n\n try {\n await this.mergeTask(req.batchId, req.task);\n req.resolve();\n } catch (err) {\n req.reject(err as Error);\n } finally {\n this.processing = false;\n // Process next item if any\n if (this.queue.length > 0) {\n this.processNext();\n }\n }\n }\n\n private async mergeTask(batchId: string, task: BraindumpTask): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n logger.info('Merging task into integration branch', {\n batchId, taskId: task.id, taskIndex: task.index, branch: task.branchName,\n });\n\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Merging);\n eventBus.emitTyped('braindump:task:merging', { batchId, taskId: task.id });\n\n const taskWorktreeDir = this.getTaskWorktreeDir(batchId, task.index);\n const taskWorkDir = this.projectSubDir\n ? `${taskWorktreeDir}/${this.projectSubDir}`\n : taskWorktreeDir;\n\n try {\n const wtGit = new GitOperations(taskWorktreeDir);\n\n // 1. Checkout the task branch\n await wtGit.checkout(task.branchName);\n\n // 2. Rebase onto integration branch (with AI conflict resolution)\n await this.conflictResolver.resolve({\n wtGit,\n targetRef: `origin/${batch.integrationBranch}`,\n workDir: taskWorkDir,\n branchName: task.branchName,\n contextId: task.id,\n phaseTimeoutMs: this.phaseTimeoutMs,\n maxAttempts: this.maxConflictAttempts,\n onEvent: (event) => {\n eventBus.emitTyped('agent:output', {\n batchId,\n taskId: task.id,\n phase: 'merge-conflict-resolve',\n event,\n });\n },\n });\n\n // 3. Force push the rebased task branch\n await wtGit.forcePush(task.branchName);\n\n // 4. Merge into integration branch (in main repo under mutex)\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n await this.mainGit.checkout(batch.integrationBranch);\n try {\n await this.mainGit.mergeFF(task.branchName);\n } catch {\n // If ff not possible, do a regular merge\n await this.mainGit.merge(task.branchName, `merge: braindump task #${task.index} - ${task.title}`);\n }\n await this.mainGit.push(batch.integrationBranch);\n });\n\n // 5. Update status\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Merged, {\n mergedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:merged', { batchId, taskId: task.id });\n\n logger.info('Task merged successfully', { batchId, taskId: task.id });\n } catch (err) {\n const errorMsg = (err as Error).message;\n logger.error('Task merge failed', { batchId, taskId: task.id, error: errorMsg });\n\n // Try to abort any in-progress rebase\n try {\n const wtGit = new GitOperations(taskWorktreeDir);\n if (await wtGit.isRebaseInProgress()) {\n await wtGit.rebaseAbort();\n }\n } catch { /* ignore */ }\n\n this.tracker.markTaskFailed(batchId, task.id, `Merge failed: ${errorMsg}`);\n throw err;\n }\n }\n\n private getTaskWorktreeDir(batchId: string, taskIndex: number): string {\n const batchShort = batchId.slice(0, 8);\n return `${this.worktreeBaseDir}/braindump-${batchShort}-task-${taskIndex}`;\n }\n}\n","/**\n * Build the prompt for executing a single braindump task.\n */\nexport function buildTaskPrompt(taskTitle: string, taskDescription: string): string {\n return `你需要完成以下任务:\n\n## 任务:${taskTitle}\n\n${taskDescription}\n\n## 要求\n\n1. 仔细阅读相关代码,理解现有架构\n2. 按照任务描述进行代码修改\n3. 确保修改不会破坏现有功能\n4. 遵循项目现有的代码风格和规范\n5. 完成后确保代码可以通过 lint 检查`;\n}\n","import { BatchStatus, TaskStatus, type BraindumpBatch, type BraindumpTask } from './BraindumpState.js';\nimport { BaseTracker } from '../tracker/BaseTracker.js';\nimport { type ExecutableTask, braindumpTaskToExecutableTask } from '../tracker/ExecutableTask.js';\nimport { BatchNotFoundError, TaskNotFoundError } from '../errors/index.js';\nimport { logger as rootLogger } from '../logger.js';\nimport { eventBus } from '../events/EventBus.js';\n\nconst logger = rootLogger.child('BraindumpTracker');\n\nexport class BraindumpTracker extends BaseTracker<BraindumpBatch> {\n constructor(dataDir: string) {\n super(dataDir, 'braindump-tracker.json', 'batches', 'braindump-tracker');\n }\n\n // ── CRUD ──\n\n getBatch(batchId: string): BraindumpBatch | undefined {\n return this.getByKey(batchId);\n }\n\n getAll(): BraindumpBatch[] {\n return this.getAllRecords();\n }\n\n createBatch(batch: BraindumpBatch): BraindumpBatch {\n this.setRecord(batch.id, batch);\n this.save();\n logger.info('Batch created', { batchId: batch.id });\n eventBus.emitTyped('braindump:created', { batchId: batch.id });\n return batch;\n }\n\n updateBatch(batchId: string, updates: Partial<BraindumpBatch>): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n Object.assign(batch, updates, { updatedAt: new Date().toISOString() });\n this.save();\n }\n\n deleteBatch(batchId: string): boolean {\n if (!this.deleteByKey(batchId)) return false;\n logger.info('Batch deleted', { batchId });\n return true;\n }\n\n // ── Status helpers ──\n\n updateBatchStatus(batchId: string, status: BatchStatus, error?: string): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n batch.status = status;\n batch.updatedAt = new Date().toISOString();\n if (error !== undefined) batch.lastError = error;\n if (status === BatchStatus.Completed) batch.completedAt = new Date().toISOString();\n this.save();\n }\n\n updateTaskStatus(batchId: string, taskId: string, status: TaskStatus, extra?: Partial<BraindumpTask>): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n const task = batch.tasks.find(t => t.id === taskId);\n if (!task) throw new TaskNotFoundError(taskId);\n task.status = status;\n if (extra) Object.assign(task, extra);\n batch.updatedAt = new Date().toISOString();\n this.save();\n }\n\n markTaskFailed(batchId: string, taskId: string, error: string): void {\n const batch = this.collection[batchId];\n if (!batch) return;\n const task = batch.tasks.find(t => t.id === taskId);\n if (!task) return;\n task.status = TaskStatus.Failed;\n task.lastError = error;\n task.attempts += 1;\n batch.updatedAt = new Date().toISOString();\n this.save();\n logger.warn('Task marked failed', { batchId, taskId, error, attempts: task.attempts });\n eventBus.emitTyped('braindump:task:failed', { batchId, taskId, error });\n }\n\n // ── Recovery ──\n\n /** Recover batches interrupted by service restart. Returns count of recovered batches. */\n recoverInterrupted(): number {\n const IN_PROGRESS_TASK_STATES = new Set([TaskStatus.Running, TaskStatus.Merging, TaskStatus.ConflictResolving]);\n let count = 0;\n\n for (const batch of this.getAllRecords()) {\n if (batch.status !== BatchStatus.Running && batch.status !== BatchStatus.Merging) continue;\n\n let recovered = false;\n for (const task of batch.tasks) {\n if (IN_PROGRESS_TASK_STATES.has(task.status)) {\n task.status = TaskStatus.Failed;\n task.lastError = 'Interrupted by service restart';\n task.attempts += 1;\n recovered = true;\n }\n }\n\n if (recovered) {\n // If any task is still pending/blocked/done, keep batch running for retry\n const hasRecoverable = batch.tasks.some(\n t => t.status === TaskStatus.Pending || t.status === TaskStatus.Blocked || t.status === TaskStatus.Done,\n );\n if (!hasRecoverable) {\n batch.status = BatchStatus.Failed;\n batch.lastError = 'Interrupted by service restart';\n }\n batch.updatedAt = new Date().toISOString();\n count++;\n }\n }\n\n if (count > 0) {\n this.save();\n logger.info('Recovered interrupted braindump batches', { count });\n }\n return count;\n }\n\n /** 将指定批次的所有任务投影为 ExecutableTask[] */\n toExecutableTasks(batchId: string): ExecutableTask[] {\n const batch = this.getByKey(batchId);\n if (!batch) return [];\n return batch.tasks.map((task) =>\n braindumpTaskToExecutableTask(task, batch),\n );\n }\n}\n"],"mappings":"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","names":["logger","logger","logger","logger"]}
|
|
1
|
+
{"version":3,"sources":["../src/braindump/BraindumpOrchestrator.ts","../src/braindump/prompts/split-prompt.ts","../src/braindump/TaskSplitter.ts","../src/braindump/MergeQueue.ts","../src/braindump/prompts/task-prompt.ts","../src/braindump/BraindumpTracker.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport { Config, AIRunnerMode } from '../config.js';\nimport { GitOperations } from '../git/GitOperations.js';\nimport { AsyncMutex } from '../utils/AsyncMutex.js';\nimport { createAIRunner, type AIRunner, type StreamEvent } from '../ai-runner/index.js';\nimport { eventBus } from '../events/EventBus.js';\nimport { isShuttingDown } from '../shutdown/ShutdownSignal.js';\nimport { logger as rootLogger } from '../logger.js';\nimport {\n BatchStatus,\n TaskStatus,\n type BraindumpBatch,\n type BraindumpTask,\n type SplitResult,\n} from './BraindumpState.js';\nimport { BraindumpTracker } from './BraindumpTracker.js';\nimport { TaskSplitter } from './TaskSplitter.js';\nimport { MergeQueue } from './MergeQueue.js';\nimport { buildTaskPrompt } from './prompts/task-prompt.js';\n\nconst logger = rootLogger.child('BraindumpOrchestrator');\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport interface BatchCreateOptions {\n targetBranch?: string;\n maxConcurrent?: number;\n defaultAiRunnerMode?: AIRunnerMode;\n}\n\nexport class BraindumpOrchestrator {\n private config: Config;\n private mainGit: GitOperations;\n private mainGitMutex: AsyncMutex;\n private defaultAiRunner: AIRunner;\n private tracker: BraindumpTracker;\n private mergeQueue: MergeQueue;\n private activeBatches = new Set<string>();\n\n constructor(\n config: Config,\n mainGit: GitOperations,\n mainGitMutex: AsyncMutex,\n defaultAiRunner: AIRunner,\n tracker: BraindumpTracker,\n ) {\n this.config = config;\n this.mainGit = mainGit;\n this.mainGitMutex = mainGitMutex;\n this.defaultAiRunner = defaultAiRunner;\n this.tracker = tracker;\n\n this.mergeQueue = new MergeQueue({\n mainGit,\n mainGitMutex,\n aiRunner: defaultAiRunner,\n tracker,\n worktreeBaseDir: config.project.worktreeBaseDir || config.project.gitRootDir,\n projectSubDir: config.project.projectSubDir,\n phaseTimeoutMs: config.braindump.taskTimeoutMs,\n maxConflictAttempts: config.braindump.maxConflictAttempts,\n });\n }\n\n getTracker(): BraindumpTracker {\n return this.tracker;\n }\n\n /** Replace the default AI runner (used by config hot-reload) */\n setAIRunner(runner: AIRunner): void {\n this.defaultAiRunner = runner;\n logger.info('BraindumpOrchestrator AIRunner replaced via hot-reload');\n }\n\n // ── Batch lifecycle ──\n\n createBatch(rawInput: string, options?: BatchCreateOptions): BraindumpBatch {\n const id = crypto.randomUUID();\n const targetBranch = options?.targetBranch || this.config.project.baseBranch;\n const batchShort = id.slice(0, 8);\n\n const batch: BraindumpBatch = {\n id,\n rawInput,\n targetBranch,\n integrationBranch: `braindump/${batchShort}/integration`,\n status: BatchStatus.Draft,\n tasks: [],\n maxConcurrent: options?.maxConcurrent || this.config.braindump.maxConcurrent,\n defaultAiRunnerMode: options?.defaultAiRunnerMode || this.config.ai.mode,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n return this.tracker.createBatch(batch);\n }\n\n async splitBatch(\n batchId: string,\n onEvent?: (event: StreamEvent) => void,\n ): Promise<BraindumpBatch> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n this.tracker.updateBatchStatus(batchId, BatchStatus.Splitting);\n\n try {\n const splitter = new TaskSplitter(this.defaultAiRunner);\n const result: SplitResult = await splitter.split(\n batch.rawInput,\n this.config.project.workDir,\n this.config.braindump.splitTimeoutMs,\n onEvent,\n );\n\n const batchShort = batchId.slice(0, 8);\n const tasks: BraindumpTask[] = result.tasks.map((t, i) => ({\n id: crypto.randomUUID(),\n index: i,\n title: t.title,\n description: t.description,\n dependsOn: t.dependsOn.map(depIdx => {\n // Convert index-based dependencies to task IDs (we'll set them below)\n return String(depIdx);\n }),\n branchName: `braindump/${batchShort}/task-${i}`,\n status: TaskStatus.Pending,\n attempts: 0,\n }));\n\n // Resolve index-based dependsOn to actual task IDs\n for (const task of tasks) {\n task.dependsOn = task.dependsOn\n .map(depIdxStr => {\n const depIdx = parseInt(depIdxStr, 10);\n return tasks[depIdx]?.id;\n })\n .filter((id): id is string => !!id);\n\n // Mark tasks with dependencies as blocked\n if (task.dependsOn.length > 0) {\n task.status = TaskStatus.Blocked;\n }\n }\n\n this.tracker.updateBatch(batchId, {\n tasks,\n status: BatchStatus.WaitingConfirm,\n });\n\n eventBus.emitTyped('braindump:split:done', { batchId, taskCount: tasks.length });\n logger.info('Batch split completed', { batchId, taskCount: tasks.length });\n\n return this.tracker.getBatch(batchId)!;\n } catch (err) {\n const errorMsg = (err as Error).message;\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, errorMsg);\n throw err;\n }\n }\n\n confirmBatch(batchId: string, tasks?: BraindumpTask[]): void {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n if (batch.status !== BatchStatus.WaitingConfirm) {\n throw new Error(`Batch ${batchId} is not waiting for confirmation (status: ${batch.status})`);\n }\n\n if (tasks) {\n this.tracker.updateBatch(batchId, { tasks });\n }\n eventBus.emitTyped('braindump:confirmed', { batchId });\n }\n\n async executeBatch(batchId: string): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n if (this.activeBatches.has(batchId)) {\n throw new Error(`Batch ${batchId} is already being executed`);\n }\n\n this.activeBatches.add(batchId);\n this.tracker.updateBatchStatus(batchId, BatchStatus.Running);\n\n try {\n // 1. Create integration branch from target\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n const exists = await this.mainGit.branchExists(batch.integrationBranch);\n if (!exists) {\n await this.mainGit.createBranch(batch.integrationBranch, batch.targetBranch);\n await this.mainGit.push(batch.integrationBranch);\n // Switch back to original branch\n await this.mainGit.checkout(this.config.project.baseBranch);\n }\n });\n\n // 2. Scheduling loop\n const runningTasks = new Map<string, Promise<void>>();\n\n while (!isShuttingDown()) {\n const currentBatch = this.tracker.getBatch(batchId);\n if (!currentBatch || currentBatch.status === BatchStatus.Failed) break;\n\n // Check if all tasks are in terminal state\n const allTerminal = currentBatch.tasks.every(\n t => t.status === TaskStatus.Merged || t.status === TaskStatus.Failed,\n );\n if (allTerminal) break;\n\n // Unblock tasks whose dependencies are met\n this.unblockReadyTasks(batchId);\n\n // Get ready tasks (pending + not running)\n const ready = currentBatch.tasks.filter(\n t => t.status === TaskStatus.Pending && !runningTasks.has(t.id),\n );\n const available = currentBatch.maxConcurrent - runningTasks.size;\n\n // Start new tasks\n for (const task of ready.slice(0, available)) {\n const promise = this.executeTask(batchId, task).finally(() => {\n runningTasks.delete(task.id);\n });\n runningTasks.set(task.id, promise);\n }\n\n await sleep(2000);\n }\n\n // Wait for remaining running tasks\n if (runningTasks.size > 0) {\n await Promise.allSettled([...runningTasks.values()]);\n }\n\n // Determine final batch status\n const finalBatch = this.tracker.getBatch(batchId)!;\n const allMerged = finalBatch.tasks.every(t => t.status === TaskStatus.Merged);\n const anyFailed = finalBatch.tasks.some(t => t.status === TaskStatus.Failed);\n\n if (allMerged) {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Completed);\n eventBus.emitTyped('braindump:completed', { batchId });\n logger.info('Batch completed successfully', { batchId });\n } else if (anyFailed) {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, 'Some tasks failed');\n eventBus.emitTyped('braindump:failed', { batchId });\n logger.warn('Batch completed with failures', { batchId });\n }\n } catch (err) {\n const errorMsg = (err as Error).message;\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, errorMsg);\n eventBus.emitTyped('braindump:failed', { batchId, error: errorMsg });\n logger.error('Batch execution failed', { batchId, error: errorMsg });\n } finally {\n this.activeBatches.delete(batchId);\n }\n }\n\n async cancelBatch(batchId: string): Promise<void> {\n this.tracker.updateBatchStatus(batchId, BatchStatus.Failed, 'Cancelled by user');\n this.activeBatches.delete(batchId);\n }\n\n async retryFailed(batchId: string): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n for (const task of batch.tasks) {\n if (task.status === TaskStatus.Failed) {\n task.status = TaskStatus.Pending;\n task.lastError = undefined;\n }\n }\n this.tracker.updateBatch(batchId, { tasks: batch.tasks });\n await this.executeBatch(batchId);\n }\n\n recoverInterrupted(): number {\n return this.tracker.recoverInterrupted();\n }\n\n // ── Private ──\n\n private unblockReadyTasks(batchId: string): void {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) return;\n\n for (const task of batch.tasks) {\n if (task.status !== TaskStatus.Blocked) continue;\n\n const allDepsMet = task.dependsOn.every(depId => {\n const dep = batch.tasks.find(t => t.id === depId);\n return dep && dep.status === TaskStatus.Merged;\n });\n\n if (allDepsMet) {\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Pending);\n }\n }\n }\n\n private async executeTask(batchId: string, task: BraindumpTask): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) return;\n\n logger.info('Starting task execution', { batchId, taskId: task.id, title: task.title });\n\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Running, {\n startedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:started', { batchId, taskId: task.id, title: task.title });\n\n const batchShort = batchId.slice(0, 8);\n const worktreeBaseDir = this.config.project.worktreeBaseDir || this.config.project.gitRootDir;\n const taskWorktreeDir = `${worktreeBaseDir}/braindump-${batchShort}-task-${task.index}`;\n const taskWorkDir = this.config.project.projectSubDir\n ? `${taskWorktreeDir}/${this.config.project.projectSubDir}`\n : taskWorktreeDir;\n\n try {\n // 1. Create worktree from integration branch\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n const worktrees = await this.mainGit.worktreeList();\n if (!worktrees.includes(taskWorktreeDir)) {\n await this.mainGit.worktreeAdd(\n taskWorktreeDir,\n task.branchName,\n `origin/${batch.integrationBranch}`,\n );\n }\n });\n\n // 2. Run AI\n const aiRunner = this.getRunnerForTask(task);\n const prompt = buildTaskPrompt(task.title, task.description);\n\n await aiRunner.run({\n prompt,\n workDir: taskWorkDir,\n timeoutMs: this.config.braindump.taskTimeoutMs,\n idleTimeoutMs: this.config.ai.idleTimeoutMs,\n onStreamEvent: (event: StreamEvent) => {\n eventBus.emitTyped('agent:output', {\n batchId,\n taskId: task.id,\n phase: 'task-execute',\n event,\n });\n },\n });\n\n // 3. Commit changes\n const wtGit = new GitOperations(taskWorktreeDir);\n if (await wtGit.hasChanges()) {\n await wtGit.add(['.']);\n await wtGit.commit(`braindump: ${task.title}`);\n await wtGit.push(task.branchName);\n }\n\n // 4. Mark done\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Done, {\n completedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:completed', { batchId, taskId: task.id });\n logger.info('Task execution completed', { batchId, taskId: task.id });\n\n // 5. Enqueue for merge\n await this.mergeQueue.enqueue(batchId, {\n ...task,\n status: TaskStatus.Done,\n completedAt: new Date().toISOString(),\n });\n } catch (err) {\n const errorMsg = (err as Error).message;\n logger.error('Task execution failed', { batchId, taskId: task.id, error: errorMsg });\n this.tracker.markTaskFailed(batchId, task.id, errorMsg);\n }\n }\n\n private getRunnerForTask(task: BraindumpTask): AIRunner {\n const mode = task.aiRunnerMode || this.config.ai.mode;\n if (mode === this.config.ai.mode) return this.defaultAiRunner;\n return createAIRunner({\n mode,\n binary: this.config.ai.binary,\n phaseTimeoutMs: this.config.braindump.taskTimeoutMs,\n nvmNodeVersion: this.config.ai.nvmNodeVersion,\n model: this.config.ai.model,\n });\n }\n}\n","/**\n * Build the prompt for AI task splitting.\n * The AI should browse the codebase, analyze the user's input,\n * and output a structured JSON with independent tasks.\n */\nexport function buildSplitPrompt(rawInput: string): string {\n return `你是一个项目管理专家和代码架构师。用户输入了一段包含多个想法/特性/任务的文本内容。\n\n你的任务:\n1. 先浏览项目代码库,了解项目结构和技术栈\n2. 分析用户输入的内容,识别出所有独立的特性或任务\n3. 为每个任务生成清晰的标题、详细描述和依赖关系\n4. 标注哪些任务可以并行执行(没有依赖关系的任务)\n\n## 用户输入\n\n${rawInput}\n\n## 输出要求\n\n请严格按以下 JSON 格式输出(用 \\`\\`\\`json 包裹):\n\n\\`\\`\\`json\n{\n \"tasks\": [\n {\n \"title\": \"简洁的任务标题\",\n \"description\": \"详细的任务描述,包含:\\\\n1. 需要修改哪些文件\\\\n2. 具体要做什么改动\\\\n3. 验收标准\",\n \"dependsOn\": [],\n \"estimatedComplexity\": \"low\"\n },\n {\n \"title\": \"另一个任务\",\n \"description\": \"这个任务依赖第一个任务的完成\",\n \"dependsOn\": [0],\n \"estimatedComplexity\": \"medium\"\n }\n ]\n}\n\\`\\`\\`\n\n## 注意事项\n\n- \\`dependsOn\\` 中填写的是任务在数组中的索引(从 0 开始),表示本任务依赖哪些任务先完成\n- 如果两个任务修改不同文件且逻辑独立,它们之间不应有依赖关系(可并行)\n- 如果两个任务修改相同文件或存在逻辑先后顺序,应设置依赖关系\n- \\`estimatedComplexity\\` 可选值:low(简单改动)、medium(中等复杂度)、high(复杂重构)\n- 每个任务的描述要足够详细,让 AI Agent 能独立完成该任务\n- 任务粒度适中:不要太大(一个任务不应包含多个独立变更),也不要太小(一行代码的改动不需要单独一个任务)`;\n}\n","import type { AIRunner, RunResult, StreamEvent } from '../ai-runner/index.js';\nimport type { SplitResult } from './BraindumpState.js';\nimport { buildSplitPrompt } from './prompts/split-prompt.js';\nimport { AIOutputParseError } from '../errors/index.js';\nimport { logger as rootLogger } from '../logger.js';\n\nconst logger = rootLogger.child('TaskSplitter');\n\nexport class TaskSplitter {\n constructor(private aiRunner: AIRunner) {}\n\n async split(\n rawInput: string,\n workDir: string,\n timeoutMs: number,\n onEvent?: (event: StreamEvent) => void,\n ): Promise<SplitResult> {\n logger.info('Starting task split', { inputLength: rawInput.length });\n\n const prompt = buildSplitPrompt(rawInput);\n const result: RunResult = await this.aiRunner.run({\n prompt,\n workDir,\n timeoutMs,\n onStreamEvent: onEvent,\n });\n\n return this.parseSplitOutput(result.output);\n }\n\n private parseSplitOutput(output: string): SplitResult {\n // Extract JSON block from AI output (wrapped in ```json ... ```)\n const jsonMatch = output.match(/```json\\s*\\n([\\s\\S]*?)\\n\\s*```/);\n if (!jsonMatch) {\n // Try parsing the entire output as JSON\n try {\n return this.validateResult(JSON.parse(output));\n } catch {\n throw new AIOutputParseError(\n 'Failed to parse task split output: no JSON block found in AI response',\n output,\n );\n }\n }\n\n try {\n return this.validateResult(JSON.parse(jsonMatch[1]));\n } catch (err) {\n throw new AIOutputParseError(`Failed to parse task split JSON: ${(err as Error).message}`, jsonMatch[1]);\n }\n }\n\n private validateResult(raw: unknown): SplitResult {\n if (!raw || typeof raw !== 'object') {\n throw new AIOutputParseError('Split result is not an object');\n }\n\n const obj = raw as Record<string, unknown>;\n if (!Array.isArray(obj.tasks)) {\n throw new AIOutputParseError('Split result missing \"tasks\" array');\n }\n\n const tasks = obj.tasks.map((t: Record<string, unknown>, i: number) => ({\n title: String(t.title || `Task ${i + 1}`),\n description: String(t.description || ''),\n dependsOn: Array.isArray(t.dependsOn)\n ? t.dependsOn.map(Number).filter(n => !isNaN(n))\n : [],\n estimatedComplexity: (['low', 'medium', 'high'].includes(String(t.estimatedComplexity))\n ? String(t.estimatedComplexity)\n : 'medium') as 'low' | 'medium' | 'high',\n }));\n\n logger.info('Parsed split result', { taskCount: tasks.length });\n return { tasks };\n }\n}\n","import { GitOperations } from '../git/GitOperations.js';\nimport { ConflictResolver } from '../git/ConflictResolver.js';\nimport { AsyncMutex } from '../utils/AsyncMutex.js';\nimport type { AIRunner } from '../ai-runner/index.js';\nimport { eventBus } from '../events/EventBus.js';\nimport { logger as rootLogger } from '../logger.js';\nimport type { BraindumpTask } from './BraindumpState.js';\nimport { TaskStatus } from './BraindumpState.js';\nimport { BraindumpTracker } from './BraindumpTracker.js';\n\nconst logger = rootLogger.child('MergeQueue');\n\ninterface MergeRequest {\n batchId: string;\n task: BraindumpTask;\n resolve: () => void;\n reject: (err: Error) => void;\n}\n\n/**\n * Serial merge queue: tasks are merged one at a time into the integration branch.\n * This ensures each merge sees the latest integration state and minimizes conflicts.\n */\nexport class MergeQueue {\n private queue: MergeRequest[] = [];\n private processing = false;\n private mainGit: GitOperations;\n private mainGitMutex: AsyncMutex;\n private conflictResolver: ConflictResolver;\n private tracker: BraindumpTracker;\n private worktreeBaseDir: string;\n private projectSubDir: string;\n private phaseTimeoutMs: number;\n private maxConflictAttempts: number;\n\n constructor(opts: {\n mainGit: GitOperations;\n mainGitMutex: AsyncMutex;\n aiRunner: AIRunner;\n tracker: BraindumpTracker;\n worktreeBaseDir: string;\n projectSubDir: string;\n phaseTimeoutMs: number;\n maxConflictAttempts: number;\n }) {\n this.mainGit = opts.mainGit;\n this.mainGitMutex = opts.mainGitMutex;\n this.conflictResolver = new ConflictResolver(opts.aiRunner);\n this.tracker = opts.tracker;\n this.worktreeBaseDir = opts.worktreeBaseDir;\n this.projectSubDir = opts.projectSubDir;\n this.phaseTimeoutMs = opts.phaseTimeoutMs;\n this.maxConflictAttempts = opts.maxConflictAttempts;\n }\n\n /** Enqueue a completed task for merging. Returns a promise that resolves when merge is done. */\n enqueue(batchId: string, task: BraindumpTask): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.queue.push({ batchId, task, resolve, reject });\n this.processNext();\n });\n }\n\n get pendingCount(): number {\n return this.queue.length;\n }\n\n private async processNext(): Promise<void> {\n if (this.processing || this.queue.length === 0) return;\n this.processing = true;\n\n // Sort by task index to ensure deterministic merge order\n this.queue.sort((a, b) => a.task.index - b.task.index);\n const req = this.queue.shift()!;\n\n try {\n await this.mergeTask(req.batchId, req.task);\n req.resolve();\n } catch (err) {\n req.reject(err as Error);\n } finally {\n this.processing = false;\n // Process next item if any\n if (this.queue.length > 0) {\n this.processNext();\n }\n }\n }\n\n private async mergeTask(batchId: string, task: BraindumpTask): Promise<void> {\n const batch = this.tracker.getBatch(batchId);\n if (!batch) throw new Error(`Batch ${batchId} not found`);\n\n logger.info('Merging task into integration branch', {\n batchId, taskId: task.id, taskIndex: task.index, branch: task.branchName,\n });\n\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Merging);\n eventBus.emitTyped('braindump:task:merging', { batchId, taskId: task.id });\n\n const taskWorktreeDir = this.getTaskWorktreeDir(batchId, task.index);\n const taskWorkDir = this.projectSubDir\n ? `${taskWorktreeDir}/${this.projectSubDir}`\n : taskWorktreeDir;\n\n try {\n const wtGit = new GitOperations(taskWorktreeDir);\n\n // 1. Checkout the task branch\n await wtGit.checkout(task.branchName);\n\n // 2. Rebase onto integration branch (with AI conflict resolution)\n await this.conflictResolver.resolve({\n wtGit,\n targetRef: `origin/${batch.integrationBranch}`,\n workDir: taskWorkDir,\n branchName: task.branchName,\n contextId: task.id,\n phaseTimeoutMs: this.phaseTimeoutMs,\n maxAttempts: this.maxConflictAttempts,\n onEvent: (event) => {\n eventBus.emitTyped('agent:output', {\n batchId,\n taskId: task.id,\n phase: 'merge-conflict-resolve',\n event,\n });\n },\n });\n\n // 3. Force push the rebased task branch\n await wtGit.forcePush(task.branchName);\n\n // 4. Merge into integration branch (in main repo under mutex)\n await this.mainGitMutex.runExclusive(async () => {\n await this.mainGit.fetch();\n await this.mainGit.checkout(batch.integrationBranch);\n try {\n await this.mainGit.mergeFF(task.branchName);\n } catch {\n // If ff not possible, do a regular merge\n await this.mainGit.merge(task.branchName, `merge: braindump task #${task.index} - ${task.title}`);\n }\n await this.mainGit.push(batch.integrationBranch);\n });\n\n // 5. Update status\n this.tracker.updateTaskStatus(batchId, task.id, TaskStatus.Merged, {\n mergedAt: new Date().toISOString(),\n });\n eventBus.emitTyped('braindump:task:merged', { batchId, taskId: task.id });\n\n logger.info('Task merged successfully', { batchId, taskId: task.id });\n } catch (err) {\n const errorMsg = (err as Error).message;\n logger.error('Task merge failed', { batchId, taskId: task.id, error: errorMsg });\n\n // Try to abort any in-progress rebase\n try {\n const wtGit = new GitOperations(taskWorktreeDir);\n if (await wtGit.isRebaseInProgress()) {\n await wtGit.rebaseAbort();\n }\n } catch { /* ignore */ }\n\n this.tracker.markTaskFailed(batchId, task.id, `Merge failed: ${errorMsg}`);\n throw err;\n }\n }\n\n private getTaskWorktreeDir(batchId: string, taskIndex: number): string {\n const batchShort = batchId.slice(0, 8);\n return `${this.worktreeBaseDir}/braindump-${batchShort}-task-${taskIndex}`;\n }\n}\n","/**\n * Build the prompt for executing a single braindump task.\n */\nexport function buildTaskPrompt(taskTitle: string, taskDescription: string): string {\n return `你需要完成以下任务:\n\n## 任务:${taskTitle}\n\n${taskDescription}\n\n## 要求\n\n1. 仔细阅读相关代码,理解现有架构\n2. 按照任务描述进行代码修改\n3. 确保修改不会破坏现有功能\n4. 遵循项目现有的代码风格和规范\n5. 完成后确保代码可以通过 lint 检查`;\n}\n","import { BatchStatus, TaskStatus, type BraindumpBatch, type BraindumpTask } from './BraindumpState.js';\nimport { BaseTracker } from '../tracker/BaseTracker.js';\nimport { type ExecutableTask, braindumpTaskToExecutableTask } from '../tracker/ExecutableTask.js';\nimport { BatchNotFoundError, TaskNotFoundError } from '../errors/index.js';\nimport { logger as rootLogger } from '../logger.js';\nimport { eventBus } from '../events/EventBus.js';\n\nconst logger = rootLogger.child('BraindumpTracker');\n\nexport class BraindumpTracker extends BaseTracker<BraindumpBatch> {\n constructor(dataDir: string) {\n super(dataDir, 'braindump-tracker.json', 'batches', 'braindump-tracker');\n }\n\n // ── CRUD ──\n\n getBatch(batchId: string): BraindumpBatch | undefined {\n return this.getByKey(batchId);\n }\n\n getAll(): BraindumpBatch[] {\n return this.getAllRecords();\n }\n\n createBatch(batch: BraindumpBatch): BraindumpBatch {\n this.setRecord(batch.id, batch);\n this.save();\n logger.info('Batch created', { batchId: batch.id });\n eventBus.emitTyped('braindump:created', { batchId: batch.id });\n return batch;\n }\n\n updateBatch(batchId: string, updates: Partial<BraindumpBatch>): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n Object.assign(batch, updates, { updatedAt: new Date().toISOString() });\n this.save();\n }\n\n deleteBatch(batchId: string): boolean {\n if (!this.deleteByKey(batchId)) return false;\n logger.info('Batch deleted', { batchId });\n return true;\n }\n\n // ── Status helpers ──\n\n updateBatchStatus(batchId: string, status: BatchStatus, error?: string): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n batch.status = status;\n batch.updatedAt = new Date().toISOString();\n if (error !== undefined) batch.lastError = error;\n if (status === BatchStatus.Completed) batch.completedAt = new Date().toISOString();\n this.save();\n }\n\n updateTaskStatus(batchId: string, taskId: string, status: TaskStatus, extra?: Partial<BraindumpTask>): void {\n const batch = this.collection[batchId];\n if (!batch) throw new BatchNotFoundError(batchId);\n const task = batch.tasks.find(t => t.id === taskId);\n if (!task) throw new TaskNotFoundError(taskId);\n task.status = status;\n if (extra) Object.assign(task, extra);\n batch.updatedAt = new Date().toISOString();\n this.save();\n }\n\n markTaskFailed(batchId: string, taskId: string, error: string): void {\n const batch = this.collection[batchId];\n if (!batch) return;\n const task = batch.tasks.find(t => t.id === taskId);\n if (!task) return;\n task.status = TaskStatus.Failed;\n task.lastError = error;\n task.attempts += 1;\n batch.updatedAt = new Date().toISOString();\n this.save();\n logger.warn('Task marked failed', { batchId, taskId, error, attempts: task.attempts });\n eventBus.emitTyped('braindump:task:failed', { batchId, taskId, error });\n }\n\n // ── Recovery ──\n\n /** Recover batches interrupted by service restart. Returns count of recovered batches. */\n recoverInterrupted(): number {\n const IN_PROGRESS_TASK_STATES = new Set([TaskStatus.Running, TaskStatus.Merging, TaskStatus.ConflictResolving]);\n let count = 0;\n\n for (const batch of this.getAllRecords()) {\n if (batch.status !== BatchStatus.Running && batch.status !== BatchStatus.Merging) continue;\n\n let recovered = false;\n for (const task of batch.tasks) {\n if (IN_PROGRESS_TASK_STATES.has(task.status)) {\n task.status = TaskStatus.Failed;\n task.lastError = 'Interrupted by service restart';\n task.attempts += 1;\n recovered = true;\n }\n }\n\n if (recovered) {\n // If any task is still pending/blocked/done, keep batch running for retry\n const hasRecoverable = batch.tasks.some(\n t => t.status === TaskStatus.Pending || t.status === TaskStatus.Blocked || t.status === TaskStatus.Done,\n );\n if (!hasRecoverable) {\n batch.status = BatchStatus.Failed;\n batch.lastError = 'Interrupted by service restart';\n }\n batch.updatedAt = new Date().toISOString();\n count++;\n }\n }\n\n if (count > 0) {\n this.save();\n logger.info('Recovered interrupted braindump batches', { count });\n }\n return count;\n }\n\n /** 将指定批次的所有任务投影为 ExecutableTask[] */\n toExecutableTasks(batchId: string): ExecutableTask[] {\n const batch = this.getByKey(batchId);\n if (!batch) return [];\n return batch.tasks.map((task) =>\n braindumpTaskToExecutableTask(task, batch),\n );\n }\n}\n"],"mappings":"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","names":["logger","logger","logger","logger"]}
|
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
getBinaryEnvKey,
|
|
9
9
|
getDefaultBinary
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-SAMTXC4A.js";
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
13
13
|
import { config as loadDotenv } from "dotenv";
|
|
14
14
|
import path2 from "path";
|
|
15
15
|
import fs from "fs";
|
|
16
|
+
import os from "os";
|
|
16
17
|
import { fileURLToPath } from "url";
|
|
17
18
|
|
|
18
19
|
// src/config-schema.ts
|
|
@@ -33,6 +34,15 @@ function envPort(defaultValue) {
|
|
|
33
34
|
function envMs(defaultValue) {
|
|
34
35
|
return z.coerce.number().int().min(1e3, "Duration must be >= 1000ms").optional().default(Number(defaultValue));
|
|
35
36
|
}
|
|
37
|
+
function parsePtyPhaseAgents(raw) {
|
|
38
|
+
if (!raw) return {};
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const pair of raw.split(",")) {
|
|
41
|
+
const [phase, agent] = pair.split(":").map((s) => s.trim());
|
|
42
|
+
if (phase && agent) result[phase] = agent;
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
36
46
|
var envSchema = z.object({
|
|
37
47
|
// --- Required ---
|
|
38
48
|
GONGFENG_API_URL: z.string().url("GONGFENG_API_URL must be a valid URL"),
|
|
@@ -93,7 +103,7 @@ var envSchema = z.object({
|
|
|
93
103
|
E2E_UAT_VENDOR_DIR: z.string().optional().default(""),
|
|
94
104
|
E2E_UAT_CONFIG_FILE: z.string().optional().default(""),
|
|
95
105
|
E2E_PYTHON_BIN: z.string().optional().default("python3"),
|
|
96
|
-
E2E_AI_RUNNER_MODE: z.string().optional()
|
|
106
|
+
E2E_AI_RUNNER_MODE: z.string().optional(),
|
|
97
107
|
E2E_AI_MODEL: z.string().optional(),
|
|
98
108
|
// --- Preview ---
|
|
99
109
|
PREVIEW_ENABLED: envBoolean("false"),
|
|
@@ -159,6 +169,16 @@ var envSchema = z.object({
|
|
|
159
169
|
VERIFY_FIX_LOOP_ENABLED: envBoolean("true"),
|
|
160
170
|
VERIFY_FIX_MAX_ITERATIONS: envInt("3", { min: 1, max: 10 }),
|
|
161
171
|
VERIFY_TODOLIST_CHECK_ENABLED: envBoolean("true"),
|
|
172
|
+
// --- Terminal (交互式终端) ---
|
|
173
|
+
TERMINAL_ENABLED: envBoolean("false"),
|
|
174
|
+
TERMINAL_IDLE_TIMEOUT_MS: envMs("1800000"),
|
|
175
|
+
TERMINAL_MAX_SESSIONS: envInt("5", { min: 1 }),
|
|
176
|
+
// --- PTY Runner ---
|
|
177
|
+
PTY_IDLE_DETECT_MS: envMs("30000"),
|
|
178
|
+
/** Default AI agent for PTY mode (must have a PtyProfile in registry) */
|
|
179
|
+
PTY_DEFAULT_AGENT: z.string().optional().default("claude-internal"),
|
|
180
|
+
/** Per-phase agent overrides, format: "plan:claude-internal,build:codebuddy" */
|
|
181
|
+
PTY_PHASE_AGENTS: z.string().optional().default(""),
|
|
162
182
|
// --- Analytics (运营分析) ---
|
|
163
183
|
ANALYTICS_ENABLED: envBoolean("false"),
|
|
164
184
|
ANALYTICS_TARGET_API_URL: z.string().url().optional(),
|
|
@@ -275,7 +295,7 @@ function transformEnvToConfig(env, dirname) {
|
|
|
275
295
|
uatVendorDir: env.E2E_UAT_VENDOR_DIR,
|
|
276
296
|
uatConfigFile: env.E2E_UAT_CONFIG_FILE,
|
|
277
297
|
pythonBin: env.E2E_PYTHON_BIN,
|
|
278
|
-
aiRunnerMode: env.E2E_AI_RUNNER_MODE,
|
|
298
|
+
aiRunnerMode: env.E2E_AI_RUNNER_MODE ?? env.AI_RUNNER_MODE,
|
|
279
299
|
aiModel: env.E2E_AI_MODEL
|
|
280
300
|
},
|
|
281
301
|
preview: {
|
|
@@ -356,6 +376,16 @@ function transformEnvToConfig(env, dirname) {
|
|
|
356
376
|
cacheTtlMs: env.ANALYTICS_CACHE_TTL_MS,
|
|
357
377
|
skillAutoDiscover: env.ANALYTICS_SKILL_AUTO_DISCOVER
|
|
358
378
|
},
|
|
379
|
+
terminal: {
|
|
380
|
+
enabled: env.TERMINAL_ENABLED,
|
|
381
|
+
idleTimeoutMs: env.TERMINAL_IDLE_TIMEOUT_MS,
|
|
382
|
+
maxSessions: env.TERMINAL_MAX_SESSIONS
|
|
383
|
+
},
|
|
384
|
+
pty: {
|
|
385
|
+
idleDetectMs: env.PTY_IDLE_DETECT_MS,
|
|
386
|
+
defaultAgent: env.PTY_DEFAULT_AGENT,
|
|
387
|
+
phaseAgents: parsePtyPhaseAgents(env.PTY_PHASE_AGENTS)
|
|
388
|
+
},
|
|
359
389
|
workspace: {
|
|
360
390
|
configPath: env.WORKSPACE_CONFIG_PATH
|
|
361
391
|
},
|
|
@@ -384,7 +414,7 @@ ${lines.join("\n")}`
|
|
|
384
414
|
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
385
415
|
function resolveConfigFilePath(configPath) {
|
|
386
416
|
if (configPath) return configPath;
|
|
387
|
-
if (process.env.IAF_CONFIG_PATH) return process.env.IAF_CONFIG_PATH;
|
|
417
|
+
if (process.env.IAF_CONFIG_PATH) return process.env.IAF_CONFIG_PATH.replace(/^~(?=$|\/)/, os.homedir());
|
|
388
418
|
const localEnv = path2.resolve(__dirname, "../.env");
|
|
389
419
|
if (fs.existsSync(localEnv)) return localEnv;
|
|
390
420
|
const globalEnv = path2.join(getGlobalDir(), ".env");
|
|
@@ -428,4 +458,4 @@ export {
|
|
|
428
458
|
resetDotenvCache,
|
|
429
459
|
reloadConfig
|
|
430
460
|
};
|
|
431
|
-
//# sourceMappingURL=chunk-
|
|
461
|
+
//# sourceMappingURL=chunk-NZHKAPU6.js.map
|