aws-runtime-bridge 1.9.45 → 1.9.50
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/adapter/AcodeSdkAdapter.d.ts +6 -0
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/AcodeSdkAdapter.js +3 -1
- package/dist/adapter/CodexSdkAdapter.js +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +1 -1
- package/dist/index.js +4 -0
- package/dist/routes/file-diff-rollback.d.ts +56 -0
- package/dist/routes/file-diff-rollback.d.ts.map +1 -0
- package/dist/routes/file-diff-rollback.js +278 -0
- package/dist/routes/task-context.d.ts +8 -0
- package/dist/routes/task-context.d.ts.map +1 -0
- package/dist/routes/task-context.js +78 -0
- package/dist/services/workspaceTaskChangeWatcher.d.ts +87 -0
- package/dist/services/workspaceTaskChangeWatcher.d.ts.map +1 -0
- package/dist/services/workspaceTaskChangeWatcher.js +483 -0
- package/package/acode/dist/built-in-file-tools.d.ts +9 -1
- package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
- package/package/acode/dist/built-in-file-tools.js +86 -5
- package/package/acode/dist/mcpToolNames.js +1 -1
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +1 -1
- package/package/acode/dist/types.d.ts +6 -0
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +3 -7
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.js +10 -16
- package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.js +7 -2
- package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +4 -21
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +0 -4
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +44 -55
- package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.js +0 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +0 -11
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package.json +3 -1
|
@@ -43,6 +43,12 @@ type ACodeRuntimeConstructor = new (config: {
|
|
|
43
43
|
memory?: string;
|
|
44
44
|
env?: Record<string, string>;
|
|
45
45
|
subAgentManager?: SubAgentManager;
|
|
46
|
+
onFileChange?: (record: {
|
|
47
|
+
filePath: string;
|
|
48
|
+
operation: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
49
|
+
beforeContent: string | null;
|
|
50
|
+
afterContent: string | null;
|
|
51
|
+
}) => void | Promise<void>;
|
|
46
52
|
}) => ACodeRuntimeInstance;
|
|
47
53
|
export declare function toAcodeEsmImportSpecifier(modulePath: string): string;
|
|
48
54
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AcodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/AcodeSdkAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"AcodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/AcodeSdkAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,6 +21,7 @@ import { getInstanceAiConfigDebugLogger } from "../utils/instance-ai-config-debu
|
|
|
21
21
|
import { redactForLog } from "../services/instance-ai-config-service.js";
|
|
22
22
|
import { subAgentManager } from "../services/sub-agent-manager-instance.js";
|
|
23
23
|
import { resolveAcodeProviderConfig, } from "./instance-ai-config-resolver.js";
|
|
24
|
+
import { recordAgentFileChange } from "../services/workspaceTaskChangeWatcher.js";
|
|
24
25
|
import { isMessagePollingIdleCommand, isPollMessageToolDescriptor, } from "./acode-tool-names.js";
|
|
25
26
|
import { IdlePollLoop } from "./idle-poll-loop.js";
|
|
26
27
|
import { getCanonicalMcpDetail, getToolActionInfo } from "./types.js";
|
|
@@ -58,7 +59,7 @@ const ACODE_SUBMIT_TIMEOUT_MS = 120000; // 120秒(2分钟)
|
|
|
58
59
|
// 兜底超时:mcp_status 缓存能力后,等待 skill_status 的最长时间。
|
|
59
60
|
// 超时后单独发出 MCP 能力清单,避免 skill_status 事件缺失导致"暂无能力清单"。
|
|
60
61
|
const CAPABILITIES_FALLBACK_MS = 3000;
|
|
61
|
-
const ACODE_MESSAGE_POLLING_IDLE_PROMPT = "系统提醒:当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒;收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理;完成任务后调用 submit_task_result 提交结果,然后继续 poll_message。";
|
|
62
|
+
const ACODE_MESSAGE_POLLING_IDLE_PROMPT = "系统提醒:当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒;收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理;完成任务后调用 submit_task_result 提交结果,然后继续 poll_message。 调用 submit_task_result 时请一并传入 changeLog(本次处理的文件/内容变更日志,包含 operation 与 path),用于 AI 回退。";
|
|
62
63
|
function getAcodePackageEntryPath() {
|
|
63
64
|
return path.resolve(getReleasedAcodeEntryPath());
|
|
64
65
|
}
|
|
@@ -659,6 +660,7 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
659
660
|
...(config.subAgentModel ? { subAgentModel: config.subAgentModel } : {}),
|
|
660
661
|
...(config.subAgentEffort ? { subAgentEffort: config.subAgentEffort } : {}),
|
|
661
662
|
...(config.instanceAiConfig?.effort ? { effort: config.instanceAiConfig.effort } : {}),
|
|
663
|
+
onFileChange: (record) => recordAgentFileChange(record),
|
|
662
664
|
});
|
|
663
665
|
const debugLogger = getInstanceAiConfigDebugLogger();
|
|
664
666
|
void debugLogger.log({
|
|
@@ -822,7 +822,7 @@ export class CodexSdkAdapter extends EventEmitter {
|
|
|
822
822
|
toIdlePrompt(command) {
|
|
823
823
|
const trimmed = command.trim();
|
|
824
824
|
if (this.isMessagePollingIdleCommand(trimmed)) {
|
|
825
|
-
return '系统提醒:当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒;收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理;完成任务后调用 submit_task_result 提交结果,然后继续 poll_message。';
|
|
825
|
+
return '系统提醒:当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒;收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理;完成任务后调用 submit_task_result 提交结果,然后继续 poll_message。 调用 submit_task_result 时请一并传入 changeLog(本次处理的文件/内容变更日志,包含 operation 与 path),用于 AI 回退。';
|
|
826
826
|
}
|
|
827
827
|
return trimmed;
|
|
828
828
|
}
|
|
@@ -865,7 +865,7 @@ export class OpencodeSdkAdapter extends EventEmitter {
|
|
|
865
865
|
toIdlePrompt(command) {
|
|
866
866
|
const trimmed = command.trim();
|
|
867
867
|
if (this.isMessagePollingIdleCommand(trimmed)) {
|
|
868
|
-
return '系统提醒:当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒;收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理;完成任务后调用 submit_task_result 提交结果,然后继续 poll_message。';
|
|
868
|
+
return '系统提醒:当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒;收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理;完成任务后调用 submit_task_result 提交结果,然后继续 poll_message。 调用 submit_task_result 时请一并传入 changeLog(本次处理的文件/内容变更日志,包含 operation 与 path),用于 AI 回退。';
|
|
869
869
|
}
|
|
870
870
|
return trimmed;
|
|
871
871
|
}
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,8 @@ import { fileBrowserRouter } from "./routes/file-browser.js";
|
|
|
26
26
|
import { aiSourcesRouter } from "./routes/ai-sources.js";
|
|
27
27
|
import { processesRouter } from "./routes/processes.js";
|
|
28
28
|
import { systemMetricsRouter } from "./routes/system-metrics.js";
|
|
29
|
+
import { taskContextRouter } from "./routes/task-context.js";
|
|
30
|
+
import { fileDiffRollbackRouter } from "./routes/file-diff-rollback.js";
|
|
29
31
|
import { attachPtyWebSocketServer, closeAllPtySessions, ptyRouter, ptySessions, } from "./routes/pty.js";
|
|
30
32
|
import { runtimeBindingRouter, logRuntimeBindingStartupState, } from "./routes/runtime-binding.js";
|
|
31
33
|
import { dashboardRouter } from "./routes/dashboard.js";
|
|
@@ -309,6 +311,8 @@ app.use("/runtime", memoryRouter);
|
|
|
309
311
|
app.use("/runtime", acodeTodosRouter);
|
|
310
312
|
app.use("/runtime", subAgentsRouter);
|
|
311
313
|
app.use("/runtime", aiSourcesRouter);
|
|
314
|
+
app.use("/runtime", taskContextRouter);
|
|
315
|
+
app.use("/runtime", fileDiffRollbackRouter);
|
|
312
316
|
app.use("/pty", ptyRouter);
|
|
313
317
|
app.use("/", dashboardRouter);
|
|
314
318
|
app.use("/api/file-browser", fileBrowserRouter);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件差异回滚路由
|
|
3
|
+
*
|
|
4
|
+
* 根据变更记录的反向操作执行文件回滚。
|
|
5
|
+
* 与全量快照还原不同的是,只还原被修改过的文件。
|
|
6
|
+
*
|
|
7
|
+
* 核心流程(两步走):
|
|
8
|
+
* 1. 内存预检:在内存中按修改时间逆序模拟所有反向操作,验证完整性。不触碰文件系统。
|
|
9
|
+
* - CREATE:检查文件是否存在,准备删除
|
|
10
|
+
* - UPDATE:读取 diff patch,读取当前文件内容,在内存中应用反向 patch 验证是否成功
|
|
11
|
+
* - DELETE:读取删除前的完整内容,准备重建
|
|
12
|
+
* 2. 原子执行:预检全部通过后,批量写入文件系统;任一预检失败则直接返回错误,
|
|
13
|
+
* 由调用方决定是否使用快照进行完整覆盖。
|
|
14
|
+
*
|
|
15
|
+
* 反向操作映射:
|
|
16
|
+
* CREATE → DELETE(删除创建的文件)
|
|
17
|
+
* UPDATE → 应用反向 diff patch(精确撤销修改的部分,保留用户后续改动)
|
|
18
|
+
* DELETE → 重新创建被删除的文件(根据 cacheKey 从 bridge 本地缓存读取)
|
|
19
|
+
*/
|
|
20
|
+
export declare const fileDiffRollbackRouter: import("express-serve-static-core").Router;
|
|
21
|
+
export interface ReverseOperation {
|
|
22
|
+
/** 原始操作类型 */
|
|
23
|
+
originalOperation: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
24
|
+
/** 文件路径(相对于工作区) */
|
|
25
|
+
relativePath: string;
|
|
26
|
+
/** bridge 本地缓存 key,UPDATE/DELETE 时用于读取变更前内容或 diff */
|
|
27
|
+
cacheKey: string | null;
|
|
28
|
+
/** 原始创建时间(用于日志与排序) */
|
|
29
|
+
createdAt: string;
|
|
30
|
+
}
|
|
31
|
+
export interface DiffRollbackRequest {
|
|
32
|
+
workspacePath: string;
|
|
33
|
+
operations: ReverseOperation[];
|
|
34
|
+
}
|
|
35
|
+
/** 单条操作的执行状态 */
|
|
36
|
+
export interface OperationDetail {
|
|
37
|
+
relativePath: string;
|
|
38
|
+
originalOperation: string;
|
|
39
|
+
/** 当前阶段状态 */
|
|
40
|
+
status: 'preview_passed' | 'preview_failed' | 'success' | 'failed' | 'skipped';
|
|
41
|
+
/** 计划执行的操作描述 */
|
|
42
|
+
action?: string;
|
|
43
|
+
/** 失败或跳过的原因 */
|
|
44
|
+
reason?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface DiffRollbackResult {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
restored: number;
|
|
49
|
+
skipped: number;
|
|
50
|
+
errors: string[];
|
|
51
|
+
/** 当 ok=false 且未执行写盘时为 true */
|
|
52
|
+
previewFailed?: boolean;
|
|
53
|
+
/** 每条操作的详细状态 */
|
|
54
|
+
details: OperationDetail[];
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=file-diff-rollback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-diff-rollback.d.ts","sourceRoot":"","sources":["../../src/routes/file-diff-rollback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAWH,eAAO,MAAM,sBAAsB,4CAAW,CAAC;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,aAAa;IACb,iBAAiB,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClD,mBAAmB;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,gBAAgB;AAChB,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa;IACb,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC/E,gBAAgB;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,+BAA+B;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB;IAChB,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B"}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件差异回滚路由
|
|
3
|
+
*
|
|
4
|
+
* 根据变更记录的反向操作执行文件回滚。
|
|
5
|
+
* 与全量快照还原不同的是,只还原被修改过的文件。
|
|
6
|
+
*
|
|
7
|
+
* 核心流程(两步走):
|
|
8
|
+
* 1. 内存预检:在内存中按修改时间逆序模拟所有反向操作,验证完整性。不触碰文件系统。
|
|
9
|
+
* - CREATE:检查文件是否存在,准备删除
|
|
10
|
+
* - UPDATE:读取 diff patch,读取当前文件内容,在内存中应用反向 patch 验证是否成功
|
|
11
|
+
* - DELETE:读取删除前的完整内容,准备重建
|
|
12
|
+
* 2. 原子执行:预检全部通过后,批量写入文件系统;任一预检失败则直接返回错误,
|
|
13
|
+
* 由调用方决定是否使用快照进行完整覆盖。
|
|
14
|
+
*
|
|
15
|
+
* 反向操作映射:
|
|
16
|
+
* CREATE → DELETE(删除创建的文件)
|
|
17
|
+
* UPDATE → 应用反向 diff patch(精确撤销修改的部分,保留用户后续改动)
|
|
18
|
+
* DELETE → 重新创建被删除的文件(根据 cacheKey 从 bridge 本地缓存读取)
|
|
19
|
+
*/
|
|
20
|
+
import { promises as fs } from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { Router } from 'express';
|
|
23
|
+
import * as Diff from 'diff';
|
|
24
|
+
import { createLogger } from '../utils/logger.js';
|
|
25
|
+
import { validateToken } from '../middleware/auth.js';
|
|
26
|
+
import { readBeforeContentFromCache, readDiffFromCache } from '../services/workspaceTaskChangeWatcher.js';
|
|
27
|
+
const log = createLogger('file-diff-rollback');
|
|
28
|
+
export const fileDiffRollbackRouter = Router();
|
|
29
|
+
/**
|
|
30
|
+
* 内存预检:在内存中按时间逆序模拟所有反向操作,生成执行计划。
|
|
31
|
+
* 不实际触碰文件系统。
|
|
32
|
+
*
|
|
33
|
+
* 关键:使用内存文件状态跟踪(fileContentMap),每条回滚操作基于前一条执行后的内容,
|
|
34
|
+
* 确保同一文件多次 UPDATE 时链式回滚正确(D→C→B→A)。
|
|
35
|
+
*/
|
|
36
|
+
async function previewRollback(workspacePath, operations) {
|
|
37
|
+
const errors = [];
|
|
38
|
+
const plan = [];
|
|
39
|
+
const details = [];
|
|
40
|
+
const normalizedWorkspace = path.resolve(String(workspacePath).trim());
|
|
41
|
+
// 按 createdAt 逆序排列(最新的变更最先回滚)
|
|
42
|
+
const sortedOps = [...operations].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
43
|
+
// 内存中的文件内容跟踪:key = relativePath,value = 当前内容(null = 文件不存在)
|
|
44
|
+
// 首次访问某文件时从磁盘读取,后续操作基于内存中的最新状态
|
|
45
|
+
const fileContentMap = new Map();
|
|
46
|
+
const fileExistedMap = new Map();
|
|
47
|
+
for (const op of sortedOps) {
|
|
48
|
+
const fullPath = path.join(normalizedWorkspace, op.relativePath);
|
|
49
|
+
const relativePath = op.relativePath.replace(/\\/g, '/');
|
|
50
|
+
// 安全检查:路径不能跳出工作区
|
|
51
|
+
const resolvedPath = path.resolve(fullPath);
|
|
52
|
+
if (!resolvedPath.startsWith(normalizedWorkspace)) {
|
|
53
|
+
const reason = `Path escape detected: ${relativePath}`;
|
|
54
|
+
errors.push(reason);
|
|
55
|
+
details.push({ relativePath, originalOperation: op.originalOperation, status: 'preview_failed', reason });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// 获取当前内存中的文件状态(首次从磁盘读取)
|
|
59
|
+
if (!fileContentMap.has(relativePath)) {
|
|
60
|
+
let diskContent = null;
|
|
61
|
+
let diskExists = false;
|
|
62
|
+
try {
|
|
63
|
+
diskContent = await fs.readFile(fullPath, 'utf-8');
|
|
64
|
+
diskExists = true;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
diskExists = false;
|
|
68
|
+
}
|
|
69
|
+
fileContentMap.set(relativePath, diskContent);
|
|
70
|
+
fileExistedMap.set(relativePath, diskExists);
|
|
71
|
+
}
|
|
72
|
+
switch (op.originalOperation) {
|
|
73
|
+
case 'CREATE': {
|
|
74
|
+
// 逆操作:删除文件
|
|
75
|
+
plan.push({ type: 'DELETE', fullPath, relativePath, originalOperation: 'CREATE' });
|
|
76
|
+
details.push({ relativePath, originalOperation: 'CREATE', status: 'preview_passed', action: '删除文件' });
|
|
77
|
+
// 更新内存状态:文件已删除
|
|
78
|
+
fileContentMap.set(relativePath, null);
|
|
79
|
+
fileExistedMap.set(relativePath, false);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case 'UPDATE': {
|
|
83
|
+
if (!op.cacheKey) {
|
|
84
|
+
const reason = `缺少 cacheKey,无法读取 diff 补丁`;
|
|
85
|
+
errors.push(`UPDATE rollback failed: ${reason} for ${relativePath}`);
|
|
86
|
+
details.push({ relativePath, originalOperation: 'UPDATE', status: 'preview_failed', reason });
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
const currentContent = fileContentMap.get(relativePath) ?? null;
|
|
90
|
+
if (currentContent === null) {
|
|
91
|
+
const reason = `文件当前不存在,无法应用反向 diff 补丁`;
|
|
92
|
+
errors.push(`UPDATE rollback failed: ${reason} for ${relativePath}`);
|
|
93
|
+
details.push({ relativePath, originalOperation: 'UPDATE', status: 'preview_failed', reason });
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
// 尝试用 diff patch 精确回滚
|
|
97
|
+
const patch = await readDiffFromCache(normalizedWorkspace, op.cacheKey);
|
|
98
|
+
let rollbackContent = null;
|
|
99
|
+
let rollbackMethod = 'beforeContent 兜底';
|
|
100
|
+
if (patch !== null) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = Diff.parsePatch(patch);
|
|
103
|
+
const reversed = Diff.reversePatch(parsed);
|
|
104
|
+
const result = Diff.applyPatch(currentContent, reversed);
|
|
105
|
+
if (result !== false) {
|
|
106
|
+
rollbackContent = result;
|
|
107
|
+
rollbackMethod = '反向 diff 补丁';
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
log.warn(`Diff patch failed for ${relativePath}, falling back to beforeContent`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
log.warn(`Diff patch error for ${relativePath}: ${err.message}, falling back to beforeContent`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// diff patch 失败时,用 beforeContent 兜底
|
|
118
|
+
if (rollbackContent === null) {
|
|
119
|
+
const beforeContent = await readBeforeContentFromCache(normalizedWorkspace, op.cacheKey);
|
|
120
|
+
if (beforeContent === null) {
|
|
121
|
+
const reason = `diff 补丁应用失败且本地缓存中找不到 beforeContent (cacheKey=${op.cacheKey})`;
|
|
122
|
+
errors.push(`UPDATE rollback failed: ${reason} for ${relativePath}`);
|
|
123
|
+
details.push({ relativePath, originalOperation: 'UPDATE', status: 'preview_failed', reason });
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
rollbackContent = beforeContent;
|
|
127
|
+
}
|
|
128
|
+
plan.push({ type: 'WRITE', fullPath, relativePath, originalOperation: 'UPDATE', content: rollbackContent });
|
|
129
|
+
details.push({ relativePath, originalOperation: 'UPDATE', status: 'preview_passed', action: rollbackMethod });
|
|
130
|
+
// 更新内存状态:文件内容已回滚
|
|
131
|
+
fileContentMap.set(relativePath, rollbackContent);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case 'DELETE': {
|
|
135
|
+
if (!op.cacheKey) {
|
|
136
|
+
const reason = `缺少 cacheKey,无法读取删除前的文件内容`;
|
|
137
|
+
errors.push(`DELETE rollback failed: ${reason} for ${relativePath}`);
|
|
138
|
+
details.push({ relativePath, originalOperation: 'DELETE', status: 'preview_failed', reason });
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
const content = await readBeforeContentFromCache(normalizedWorkspace, op.cacheKey);
|
|
142
|
+
if (content === null) {
|
|
143
|
+
const reason = `本地缓存中找不到删除前的文件内容 (cacheKey=${op.cacheKey})`;
|
|
144
|
+
errors.push(`DELETE rollback failed: ${reason} for ${relativePath}`);
|
|
145
|
+
details.push({ relativePath, originalOperation: 'DELETE', status: 'preview_failed', reason });
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
plan.push({ type: 'WRITE', fullPath, relativePath, originalOperation: 'DELETE', content });
|
|
149
|
+
details.push({ relativePath, originalOperation: 'DELETE', status: 'preview_passed', action: '重建文件' });
|
|
150
|
+
// 更新内存状态:文件已重建
|
|
151
|
+
fileContentMap.set(relativePath, content);
|
|
152
|
+
fileExistedMap.set(relativePath, true);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
default: {
|
|
156
|
+
const reason = `未知的操作类型 "${op.originalOperation}"`;
|
|
157
|
+
errors.push(`${reason} for ${relativePath}`);
|
|
158
|
+
details.push({ relativePath, originalOperation: op.originalOperation, status: 'preview_failed', reason });
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { ok: errors.length === 0, errors, plan, details };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 批量执行回滚计划。
|
|
167
|
+
* 返回每条操作的执行状态详情。
|
|
168
|
+
*/
|
|
169
|
+
async function executePlan(plan, previewDetails) {
|
|
170
|
+
let restored = 0;
|
|
171
|
+
let skipped = 0;
|
|
172
|
+
const errors = [];
|
|
173
|
+
// 以预检 details 为基础,更新执行阶段状态
|
|
174
|
+
const details = previewDetails.map((d) => ({ ...d }));
|
|
175
|
+
for (const item of plan) {
|
|
176
|
+
const detail = details.find((d) => d.relativePath === item.relativePath && d.status === 'preview_passed');
|
|
177
|
+
try {
|
|
178
|
+
if (item.type === 'DELETE') {
|
|
179
|
+
await fs.unlink(item.fullPath);
|
|
180
|
+
log.info(`Rollback executed: deleted created file ${item.relativePath}`);
|
|
181
|
+
if (detail) {
|
|
182
|
+
detail.status = 'success';
|
|
183
|
+
detail.action = '删除文件';
|
|
184
|
+
}
|
|
185
|
+
restored++;
|
|
186
|
+
}
|
|
187
|
+
else if (item.type === 'WRITE') {
|
|
188
|
+
await fs.mkdir(path.dirname(item.fullPath), { recursive: true });
|
|
189
|
+
await fs.writeFile(item.fullPath, item.content, 'utf-8');
|
|
190
|
+
log.info(`Rollback executed: restored file ${item.relativePath}`);
|
|
191
|
+
if (detail) {
|
|
192
|
+
detail.status = 'success';
|
|
193
|
+
detail.action = item.originalOperation === 'UPDATE' ? '应用反向 diff 补丁' : '重建文件';
|
|
194
|
+
}
|
|
195
|
+
restored++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
const reason = `${err.message}`;
|
|
200
|
+
const msg = `Rollback failed for ${item.relativePath}: ${reason}`;
|
|
201
|
+
errors.push(msg);
|
|
202
|
+
log.warn(msg);
|
|
203
|
+
if (detail) {
|
|
204
|
+
detail.status = 'failed';
|
|
205
|
+
detail.reason = reason;
|
|
206
|
+
}
|
|
207
|
+
skipped++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { restored, skipped, errors, details };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 差异回滚预检接口
|
|
214
|
+
* POST /runtime/file-diff-rollback/preview
|
|
215
|
+
*
|
|
216
|
+
* 仅做内存模拟,不触碰文件系统。
|
|
217
|
+
* 返回每条操作的预检状态(preview_passed / preview_failed / skipped)及原因。
|
|
218
|
+
*/
|
|
219
|
+
fileDiffRollbackRouter.post('/file-diff-rollback/preview', validateToken, async (req, res) => {
|
|
220
|
+
const { workspacePath, operations } = req.body;
|
|
221
|
+
if (!workspacePath || !Array.isArray(operations)) {
|
|
222
|
+
res.status(400).json({ error: 'workspacePath and operations (array) are required' });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const { ok, errors, plan, details } = await previewRollback(workspacePath, operations);
|
|
226
|
+
res.json({
|
|
227
|
+
ok,
|
|
228
|
+
errors,
|
|
229
|
+
plan: plan.map((p) => ({ type: p.type, relativePath: p.relativePath })),
|
|
230
|
+
totalOperations: operations.length,
|
|
231
|
+
plannedOperations: plan.length,
|
|
232
|
+
details,
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
/**
|
|
236
|
+
* 执行文件差异回滚
|
|
237
|
+
* POST /runtime/file-diff-rollback/execute
|
|
238
|
+
*
|
|
239
|
+
* 第一步:内存预检(previewRollback)。
|
|
240
|
+
* 第二步:若预检全部通过,则批量原子写入;若任一预检失败,直接返回错误,不写盘。
|
|
241
|
+
*
|
|
242
|
+
* 调用方在收到 previewFailed=true 时,可提示用户选择使用文件快照进行完整覆盖。
|
|
243
|
+
* 无论成功失败,都返回每条操作的详细状态。
|
|
244
|
+
*/
|
|
245
|
+
fileDiffRollbackRouter.post('/file-diff-rollback/execute', validateToken, async (req, res) => {
|
|
246
|
+
const { workspacePath, operations } = req.body;
|
|
247
|
+
if (!workspacePath || !Array.isArray(operations)) {
|
|
248
|
+
res.status(400).json({ error: 'workspacePath and operations (array) are required' });
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// ── 第一步:内存预检 ──
|
|
252
|
+
const preview = await previewRollback(workspacePath, operations);
|
|
253
|
+
if (!preview.ok) {
|
|
254
|
+
const result = {
|
|
255
|
+
ok: false,
|
|
256
|
+
restored: 0,
|
|
257
|
+
skipped: operations.length,
|
|
258
|
+
errors: preview.errors,
|
|
259
|
+
previewFailed: true,
|
|
260
|
+
details: preview.details,
|
|
261
|
+
};
|
|
262
|
+
log.warn(`Diff rollback preview failed, no files touched: errors=${preview.errors.length}`);
|
|
263
|
+
res.json(result);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// ── 第二步:批量原子执行 ──
|
|
267
|
+
const execResult = await executePlan(preview.plan, preview.details);
|
|
268
|
+
const result = {
|
|
269
|
+
ok: execResult.errors.length === 0,
|
|
270
|
+
restored: execResult.restored,
|
|
271
|
+
skipped: execResult.skipped,
|
|
272
|
+
errors: execResult.errors,
|
|
273
|
+
previewFailed: false,
|
|
274
|
+
details: execResult.details,
|
|
275
|
+
};
|
|
276
|
+
log.info(`Diff rollback completed: restored=${execResult.restored}, skipped=${execResult.skipped}, errors=${execResult.errors.length}`);
|
|
277
|
+
res.json(result);
|
|
278
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-context.d.ts","sourceRoot":"","sources":["../../src/routes/task-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAcH,eAAO,MAAM,iBAAiB,4CAAW,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 任务上下文路由
|
|
3
|
+
*
|
|
4
|
+
* 提供调度中心设置/清除 Agent 当前活跃任务上下文的能力。
|
|
5
|
+
* 文件变更监听服务据此将变更自动归属到正确的任务。
|
|
6
|
+
*/
|
|
7
|
+
import { Router } from 'express';
|
|
8
|
+
import { createLogger } from '../utils/logger.js';
|
|
9
|
+
import { validateToken } from '../middleware/auth.js';
|
|
10
|
+
import { setTaskContext, clearTaskContext, getTaskContext, startWatching, stopWatching, } from '../services/workspaceTaskChangeWatcher.js';
|
|
11
|
+
const log = createLogger('task-context');
|
|
12
|
+
export const taskContextRouter = Router();
|
|
13
|
+
/**
|
|
14
|
+
* 设置活跃任务上下文 + 启动工作区文件监听
|
|
15
|
+
* POST /runtime/task-context
|
|
16
|
+
*
|
|
17
|
+
* Body:
|
|
18
|
+
* {
|
|
19
|
+
* agentId: string,
|
|
20
|
+
* taskInstanceId: string,
|
|
21
|
+
* taskNodeId: string,
|
|
22
|
+
* tenantId: string,
|
|
23
|
+
* workspacePath: string,
|
|
24
|
+
* projectName: string
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
taskContextRouter.post('/task-context', validateToken, async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const { agentId, taskInstanceId, taskNodeId, tenantId, workspacePath, projectName } = req.body || {};
|
|
30
|
+
if (!agentId || !taskInstanceId || !taskNodeId || !tenantId || !workspacePath) {
|
|
31
|
+
res.status(400).json({ error: 'agentId, taskInstanceId, taskNodeId, tenantId, workspacePath are required' });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// 启动工作区监听
|
|
35
|
+
await startWatching(String(workspacePath).trim());
|
|
36
|
+
// 设置任务上下文
|
|
37
|
+
setTaskContext({
|
|
38
|
+
agentId: String(agentId).trim(),
|
|
39
|
+
taskInstanceId: String(taskInstanceId).trim(),
|
|
40
|
+
taskNodeId: String(taskNodeId).trim(),
|
|
41
|
+
tenantId: String(tenantId).trim(),
|
|
42
|
+
workspacePath: String(workspacePath).trim(),
|
|
43
|
+
projectName: String(projectName || '').trim(),
|
|
44
|
+
});
|
|
45
|
+
log.info(`Task context set via API: agent=${agentId}, taskInstance=${taskInstanceId}`);
|
|
46
|
+
res.json({ ok: true });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const err = error;
|
|
50
|
+
log.error(`Failed to set task context: ${err.message}`);
|
|
51
|
+
res.status(500).json({ error: err.message });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* 清除活跃任务上下文 + 停止工作区文件监听
|
|
56
|
+
* DELETE /runtime/task-context
|
|
57
|
+
*/
|
|
58
|
+
taskContextRouter.delete('/task-context', validateToken, async (_req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
await stopWatching();
|
|
61
|
+
clearTaskContext();
|
|
62
|
+
log.info('Task context cleared via API');
|
|
63
|
+
res.json({ ok: true });
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const err = error;
|
|
67
|
+
log.error(`Failed to clear task context: ${err.message}`);
|
|
68
|
+
res.status(500).json({ error: err.message });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* 获取当前任务上下文
|
|
73
|
+
* GET /runtime/task-context
|
|
74
|
+
*/
|
|
75
|
+
taskContextRouter.get('/task-context', validateToken, async (_req, res) => {
|
|
76
|
+
const ctx = getTaskContext();
|
|
77
|
+
res.json({ ok: true, context: ctx });
|
|
78
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工作区文件变更精确记录服务
|
|
3
|
+
*
|
|
4
|
+
* **仅通过 Agent 文件操作工具拦截记录变更**(write_file/edit_file/delete_file),
|
|
5
|
+
* 不再使用 fs.watch 全量监听(无法区分 Agent 修改与用户手动修改)。
|
|
6
|
+
*
|
|
7
|
+
* - recordAgentFileChange() 由工具 execute 成功后直接调用
|
|
8
|
+
* - 变更内容(diff/beforeContent)持久化到 bridge 本地 .agentswork/change-cache
|
|
9
|
+
* - 变更元数据 + cacheKey 通过 HTTP 上报到 mcp-server
|
|
10
|
+
* - 差异回滚时从数据库读取记录、从 bridge 缓存读取 diff
|
|
11
|
+
*/
|
|
12
|
+
export interface TaskContext {
|
|
13
|
+
agentId: string;
|
|
14
|
+
taskInstanceId: string;
|
|
15
|
+
taskNodeId: string;
|
|
16
|
+
tenantId: string;
|
|
17
|
+
workspacePath: string;
|
|
18
|
+
projectName: string;
|
|
19
|
+
}
|
|
20
|
+
export interface FileChangeRecord {
|
|
21
|
+
workspacePath: string;
|
|
22
|
+
filePath: string;
|
|
23
|
+
operation: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
24
|
+
beforeContent: string | null;
|
|
25
|
+
afterContent: string | null;
|
|
26
|
+
beforeHash: string | null;
|
|
27
|
+
afterHash: string | null;
|
|
28
|
+
timestamp: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 设置当前活跃任务上下文。
|
|
32
|
+
* 调用时机:Agent 通过 startTaskNode 开始处理任务节点时,由 server 回调通知。
|
|
33
|
+
*/
|
|
34
|
+
export declare function setTaskContext(ctx: TaskContext): void;
|
|
35
|
+
/**
|
|
36
|
+
* 清除当前任务上下文。
|
|
37
|
+
* 调用时机:Agent 提交任务结果后,或任务被中断/取消时。
|
|
38
|
+
*/
|
|
39
|
+
export declare function clearTaskContext(): void;
|
|
40
|
+
/**
|
|
41
|
+
* 获取当前活跃任务上下文。
|
|
42
|
+
*/
|
|
43
|
+
export declare function getTaskContext(): TaskContext | null;
|
|
44
|
+
/**
|
|
45
|
+
* 检查是否有活跃任务上下文。
|
|
46
|
+
*/
|
|
47
|
+
export declare function hasActiveTask(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* 设置工作区路径(不再启动 fs.watch,仅记录路径供工具拦截上报使用)。
|
|
50
|
+
* 如果已有活跃的 watcher 则会先关闭。
|
|
51
|
+
*/
|
|
52
|
+
export declare function startWatching(targetWorkspacePath: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* 停止工作区监听(清理状态)。
|
|
55
|
+
*/
|
|
56
|
+
export declare function stopWatching(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* 从 bridge 本地缓存读取变更前的文件内容(供 DELETE 回滚使用)。
|
|
59
|
+
*/
|
|
60
|
+
export declare function readBeforeContentFromCache(workspacePath: string, cacheKey: string): Promise<string | null>;
|
|
61
|
+
/**
|
|
62
|
+
* 从 bridge 本地缓存读取 diff patch(供 UPDATE 精确回滚使用)。
|
|
63
|
+
*/
|
|
64
|
+
export declare function readDiffFromCache(workspacePath: string, cacheKey: string): Promise<string | null>;
|
|
65
|
+
/**
|
|
66
|
+
* 获取正在监控的文件变更数量(用于测试)。
|
|
67
|
+
*/
|
|
68
|
+
export declare function getPendingChangeCountForTest(): number;
|
|
69
|
+
/**
|
|
70
|
+
* 判断工作区是否正在被监听。
|
|
71
|
+
*/
|
|
72
|
+
export declare function isWatching(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* 获取当前工作区路径。
|
|
75
|
+
*/
|
|
76
|
+
export declare function getWorkspaceRoot(): string;
|
|
77
|
+
/**
|
|
78
|
+
* 由 Agent 文件操作工具直接调用,精确记录变更并上报。
|
|
79
|
+
* 仅在工具 execute 成功后调用,确保记录的都是实际发生的变更。
|
|
80
|
+
*/
|
|
81
|
+
export declare function recordAgentFileChange(record: {
|
|
82
|
+
filePath: string;
|
|
83
|
+
operation: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
84
|
+
beforeContent: string | null;
|
|
85
|
+
afterContent: string | null;
|
|
86
|
+
}): Promise<void>;
|
|
87
|
+
//# sourceMappingURL=workspaceTaskChangeWatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceTaskChangeWatcher.d.ts","sourceRoot":"","sources":["../../src/services/workspaceTaskChangeWatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAsCH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAkCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAOrD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAMvC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,WAAW,GAAG,IAAI,CAEnD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,mBAAmB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB9E;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAalD;AA8CD;;GAEG;AACH,wBAAsB,0BAA0B,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOhH;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOvG;AAwPD;;GAEG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAID;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CAkChB"}
|