toc-ocra 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/service/agent.mjs +224 -51
- package/dist/src/runs/run-supervisor.d.ts +5 -0
- package/dist/src/runs/run-supervisor.js +77 -12
- package/dist/src/runs/run-supervisor.js.map +1 -1
- package/dist/src/runs/task-attachments.d.ts +4 -0
- package/dist/src/runs/task-attachments.js +90 -0
- package/dist/src/runs/task-attachments.js.map +1 -0
- package/dist/src/runtime/agent-runtime.js +7 -0
- package/dist/src/runtime/agent-runtime.js.map +1 -1
- package/dist/src/transport/gateway-client.d.ts +1 -0
- package/dist/src/transport/gateway-client.js +31 -0
- package/dist/src/transport/gateway-client.js.map +1 -1
- package/dist/src/types/protocol.d.ts +8 -0
- package/dist/src/types/protocol.js +1 -0
- package/dist/src/types/protocol.js.map +1 -1
- package/dist/src/workspace/workspace-manager.js +1 -0
- package/dist/src/workspace/workspace-manager.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,14 @@ node dist/src/index.js run
|
|
|
73
73
|
- **服务端对账兜底**:心跳会上报本机正在运行的任务,服务端据此释放本机已经不存在的运行;Agent 崩溃重启时,会清理上次遗留的 CLI 进程。
|
|
74
74
|
- **加一个 CLI**:在 `src/providers/cli/` 新增一个文件,实现 `HeadlessProvider`(启动参数 + 解析每一行输出),再在 `src/providers/registry.ts` 加一行。
|
|
75
75
|
|
|
76
|
+
### 看板附件
|
|
77
|
+
|
|
78
|
+
任务链附件在 CLI 启动前由本机代理下载到本次实际工作目录的 `.echo/attachments/message-{message_id}/`,完成大小与 SHA-256 校验后,提示词中的附件引用会替换为本机绝对路径。下载目录必须属于已允许的 CLI 工作目录,不能通过符号链接跳出;未配置设备默认目录或工位工作目录时会拒绝附件任务。每条消息最多 16 个附件,准备过程可取消、可复用已校验文件,最长等待 15 分钟。
|
|
79
|
+
|
|
80
|
+
窗口工位使用同机代理的 `run.files.prepare` 先下载文件,然后由服务端转发路径;同机设备代理必须在线。更新后的代理通过心跳声明 `task_attachments.version=1`,服务端会拒绝把附件任务发给不支持此协议的旧代理。请更新并重启设备代理与对应后端后使用。
|
|
81
|
+
|
|
82
|
+
下载文件暂时保留,可手工清理;`.echo` 不进入设备目录扫描,并带有仅作用于该内部目录的 Git 忽略规则。视频也可下载为文件,实际解析取决于 CLI 的能力。
|
|
83
|
+
|
|
76
84
|
详见 [docs/architecture.md](docs/architecture.md)。
|
|
77
85
|
|
|
78
86
|
## 开发
|
package/dist/service/agent.mjs
CHANGED
|
@@ -7,7 +7,7 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/plugin-service.ts
|
|
9
9
|
import { existsSync as existsSync6 } from "node:fs";
|
|
10
|
-
import { delimiter as delimiter5, join as
|
|
10
|
+
import { delimiter as delimiter5, join as join10 } from "node:path";
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
@@ -15731,8 +15731,8 @@ var CliDetectionService = class {
|
|
|
15731
15731
|
var cliDetection = new CliDetectionService();
|
|
15732
15732
|
|
|
15733
15733
|
// src/runtime/agent-runtime.ts
|
|
15734
|
-
import { randomUUID as
|
|
15735
|
-
import { join as
|
|
15734
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
15735
|
+
import { join as join9 } from "node:path";
|
|
15736
15736
|
|
|
15737
15737
|
// src/exec/script-runner.ts
|
|
15738
15738
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -16717,8 +16717,8 @@ function commandOf(pid) {
|
|
|
16717
16717
|
|
|
16718
16718
|
// src/runs/run-supervisor.ts
|
|
16719
16719
|
import { spawn as spawn7 } from "node:child_process";
|
|
16720
|
-
import { mkdir as
|
|
16721
|
-
import { basename as
|
|
16720
|
+
import { mkdir as mkdir5, realpath as realpath2, stat as stat2, writeFile as writeFile5 } from "node:fs/promises";
|
|
16721
|
+
import { basename as basename3, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve3 } from "node:path";
|
|
16722
16722
|
import { createInterface as createInterface2 } from "node:readline";
|
|
16723
16723
|
|
|
16724
16724
|
// src/session/terminal-line-sanitizer.ts
|
|
@@ -16837,6 +16837,88 @@ function clipToCharacterBudget(rows, budget) {
|
|
|
16837
16837
|
return { lines: rows, limited: false };
|
|
16838
16838
|
}
|
|
16839
16839
|
|
|
16840
|
+
// src/runs/task-attachments.ts
|
|
16841
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
16842
|
+
import { constants as constants2 } from "node:fs";
|
|
16843
|
+
import { lstat, mkdir as mkdir4, open, realpath, rename as rename4, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
|
|
16844
|
+
import { basename as basename2, isAbsolute as isAbsolute2, join as join6, relative } from "node:path";
|
|
16845
|
+
function within(root, path) {
|
|
16846
|
+
const segment = relative(root, path);
|
|
16847
|
+
return segment === "" || !segment.startsWith("..") && !isAbsolute2(segment);
|
|
16848
|
+
}
|
|
16849
|
+
async function attachmentDirectory(workdir, messageId) {
|
|
16850
|
+
let directory = workdir;
|
|
16851
|
+
for (const segment of [".echo", "attachments", `message-${encodeURIComponent(messageId).slice(0, 180)}`]) {
|
|
16852
|
+
directory = join6(directory, segment);
|
|
16853
|
+
await mkdir4(directory, { mode: 448 }).catch((error51) => {
|
|
16854
|
+
if (error51.code !== "EEXIST") throw error51;
|
|
16855
|
+
});
|
|
16856
|
+
const metadata = await lstat(directory);
|
|
16857
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory() || !within(workdir, await realpath(directory))) {
|
|
16858
|
+
throw new Error("\u9644\u4EF6\u76EE\u5F55\u5FC5\u987B\u4F4D\u4E8E CLI \u5DE5\u4F5C\u76EE\u5F55\u5185\uFF0C\u4E14\u4E0D\u80FD\u662F\u7B26\u53F7\u94FE\u63A5");
|
|
16859
|
+
}
|
|
16860
|
+
if (segment === ".echo") await writeFile4(join6(directory, ".gitignore"), "*\n", { flag: "wx", mode: 384 }).catch((error51) => {
|
|
16861
|
+
if (error51.code !== "EEXIST") throw error51;
|
|
16862
|
+
});
|
|
16863
|
+
}
|
|
16864
|
+
return directory;
|
|
16865
|
+
}
|
|
16866
|
+
async function matches(path, size, sha256, signal2) {
|
|
16867
|
+
const metadata = await lstat(path).catch(() => void 0);
|
|
16868
|
+
if (!metadata?.isFile() || metadata.isSymbolicLink() || metadata.size !== size) return false;
|
|
16869
|
+
const file2 = await open(path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
|
|
16870
|
+
try {
|
|
16871
|
+
const hash2 = createHash2("sha256");
|
|
16872
|
+
let read = 0;
|
|
16873
|
+
for await (const chunk of file2.createReadStream({ autoClose: false })) {
|
|
16874
|
+
signal2.throwIfAborted();
|
|
16875
|
+
read += chunk.length;
|
|
16876
|
+
if (read > size) return false;
|
|
16877
|
+
hash2.update(chunk);
|
|
16878
|
+
}
|
|
16879
|
+
return hash2.digest("hex") === sha256;
|
|
16880
|
+
} finally {
|
|
16881
|
+
await file2.close();
|
|
16882
|
+
}
|
|
16883
|
+
}
|
|
16884
|
+
async function prepareTaskAttachments(payload, workdir, download, signal2) {
|
|
16885
|
+
const files = payload.task_attachments ?? [];
|
|
16886
|
+
if (!files.length) return payload.prompt;
|
|
16887
|
+
const directory = await attachmentDirectory(workdir, payload.message_id || payload.run_id);
|
|
16888
|
+
let prompt = payload.prompt;
|
|
16889
|
+
const paths = [];
|
|
16890
|
+
for (const file2 of files) {
|
|
16891
|
+
signal2.throwIfAborted();
|
|
16892
|
+
const token = `{{echo-file:${file2.file_id}}}`;
|
|
16893
|
+
if (file2.prompt_token !== token) throw new Error("\u9644\u4EF6\u8DEF\u5F84\u5360\u4F4D\u7B26\u65E0\u6548\uFF0C\u8BF7\u66F4\u65B0\u670D\u52A1\u5668");
|
|
16894
|
+
const characters = Array.from(basename2(file2.name.replace(/\\/g, "/")).replace(/[^\p{L}\p{N}._-]/gu, "_").replace(/\.+$/u, ""));
|
|
16895
|
+
while (Buffer.byteLength(characters.join("")) > 160) characters.shift();
|
|
16896
|
+
const name = characters.join("") || "attachment";
|
|
16897
|
+
const path = join6(directory, `${file2.file_id}-${name}`);
|
|
16898
|
+
if (!await matches(path, file2.size, file2.sha256, signal2)) {
|
|
16899
|
+
const temporary = join6(directory, `.download-${file2.file_id}-${randomUUID2()}.part`);
|
|
16900
|
+
try {
|
|
16901
|
+
await download(payload.run_id, file2.file_id, temporary, file2.size, file2.sha256, signal2);
|
|
16902
|
+
signal2.throwIfAborted();
|
|
16903
|
+
if (!within(workdir, await realpath(directory))) throw new Error("\u9644\u4EF6\u76EE\u5F55\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u8BD5");
|
|
16904
|
+
await rename4(temporary, path);
|
|
16905
|
+
} finally {
|
|
16906
|
+
await rm2(temporary, { force: true });
|
|
16907
|
+
}
|
|
16908
|
+
}
|
|
16909
|
+
if (!within(workdir, await realpath(path))) throw new Error("\u9644\u4EF6\u8DEF\u5F84\u4E0D\u5728 CLI \u5DE5\u4F5C\u76EE\u5F55\u5185");
|
|
16910
|
+
prompt = prompt.split(token).join(path);
|
|
16911
|
+
paths.push(`- ${JSON.stringify(path)}\uFF08${file2.mime}\uFF0C${file2.size} \u5B57\u8282\uFF09`);
|
|
16912
|
+
}
|
|
16913
|
+
if (prompt.includes("{{echo-file:")) throw new Error("\u9644\u4EF6\u8DEF\u5F84\u672A\u5B8C\u6574\u89E3\u6790");
|
|
16914
|
+
prompt += `
|
|
16915
|
+
|
|
16916
|
+
\u9644\u4EF6\u5DF2\u7531\u8BBE\u5907\u4EE3\u7406\u4E0B\u8F7D\u5230\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u3002\u8BF7\u76F4\u63A5\u8BFB\u53D6\u4EE5\u4E0B\u672C\u673A\u6587\u4EF6\uFF0C\u65E0\u9700\u4E0B\u8F7D\uFF1A
|
|
16917
|
+
${paths.join("\n")}`;
|
|
16918
|
+
if (Buffer.byteLength(prompt) > 512 * 1024) throw new Error("\u5305\u542B\u9644\u4EF6\u8DEF\u5F84\u7684\u6D88\u606F\u8FC7\u957F");
|
|
16919
|
+
return prompt;
|
|
16920
|
+
}
|
|
16921
|
+
|
|
16840
16922
|
// src/runs/run-supervisor.ts
|
|
16841
16923
|
var COMPLETION_EXIT_GRACE_MS = 15e3;
|
|
16842
16924
|
var RECENT_RUN_TTL_MS = 10 * 6e4;
|
|
@@ -16858,12 +16940,13 @@ var RunSupervisor = class {
|
|
|
16858
16940
|
/** Run ids whose finish report has not been acknowledged yet. */
|
|
16859
16941
|
unreported = /* @__PURE__ */ new Set();
|
|
16860
16942
|
chatRunIds = /* @__PURE__ */ new Set();
|
|
16943
|
+
preparing = /* @__PURE__ */ new Map();
|
|
16861
16944
|
get activeCount() {
|
|
16862
|
-
return this.active.size;
|
|
16945
|
+
return this.active.size + this.preparing.size;
|
|
16863
16946
|
}
|
|
16864
16947
|
/** Runs to list in the heartbeat: still running, or finished but not yet reported. */
|
|
16865
16948
|
activeRunIds() {
|
|
16866
|
-
return [.../* @__PURE__ */ new Set([...this.active.keys(), ...this.unreported])];
|
|
16949
|
+
return [.../* @__PURE__ */ new Set([...this.active.keys(), ...this.preparing.keys(), ...this.unreported])];
|
|
16867
16950
|
}
|
|
16868
16951
|
activeChatRunIds() {
|
|
16869
16952
|
return this.activeRunIds().filter((id) => this.chatRunIds.has(id));
|
|
@@ -16885,6 +16968,16 @@ var RunSupervisor = class {
|
|
|
16885
16968
|
* directory, unknown CLI) are reported as a failed run, never thrown.
|
|
16886
16969
|
*/
|
|
16887
16970
|
async start(payload) {
|
|
16971
|
+
if (this.preparing.has(payload.run_id)) return;
|
|
16972
|
+
const controller = new AbortController();
|
|
16973
|
+
this.preparing.set(payload.run_id, { workstationId: payload.workstation_id, controller });
|
|
16974
|
+
try {
|
|
16975
|
+
await this.startPrepared(payload, controller.signal);
|
|
16976
|
+
} finally {
|
|
16977
|
+
this.preparing.delete(payload.run_id);
|
|
16978
|
+
}
|
|
16979
|
+
}
|
|
16980
|
+
async startPrepared(payload, signal2) {
|
|
16888
16981
|
if (this.active.has(payload.run_id) || this.recent.has(payload.run_id)) {
|
|
16889
16982
|
this.logger.info("run-duplicate-ignored", { runId: payload.run_id });
|
|
16890
16983
|
return;
|
|
@@ -16895,20 +16988,32 @@ var RunSupervisor = class {
|
|
|
16895
16988
|
this.remember({ runId: payload.run_id, workstationId: payload.workstation_id, tail: new TerminalTailBuffer(), finishedAt: Date.now(), status: "failed", chat: !!payload.chat_session_id });
|
|
16896
16989
|
await this.report(payload.run_id, { status: "failed", error: error51, duration_ms: 0 });
|
|
16897
16990
|
};
|
|
16898
|
-
if ([...this.active.values()].some((run2) => run2.workstationId === payload.workstation_id)) {
|
|
16991
|
+
if ([...this.active.values()].some((run2) => run2.workstationId === payload.workstation_id) || [...this.preparing].some(([id, run2]) => id !== payload.run_id && run2.workstationId === payload.workstation_id)) {
|
|
16899
16992
|
return refuse("\u672C\u673A\u8BE5\u5DE5\u4F4D\u5DF2\u6709\u4EFB\u52A1\u5728\u8FD0\u884C");
|
|
16900
16993
|
}
|
|
16901
|
-
if (this.active.size
|
|
16994
|
+
if (this.active.size + this.preparing.size > this.config.maxConcurrentRuns) {
|
|
16902
16995
|
return refuse(`\u672C\u673A\u540C\u65F6\u8FD0\u884C\u7684\u4EFB\u52A1\u5DF2\u8FBE\u4E0A\u9650\uFF08${this.config.maxConcurrentRuns}\uFF09`);
|
|
16903
16996
|
}
|
|
16904
16997
|
let provider;
|
|
16905
16998
|
let workdir;
|
|
16906
16999
|
try {
|
|
16907
17000
|
provider = resolveHeadlessProvider(payload.agent_type);
|
|
17001
|
+
if (payload.task_attachments?.length && !payload.workdir) throw new Error("\u9644\u4EF6\u9700\u8981\u914D\u7F6E\u8BBE\u5907\u9ED8\u8BA4\u76EE\u5F55\u6216\u5DE5\u4F4D\u5DE5\u4F5C\u76EE\u5F55");
|
|
16908
17002
|
workdir = payload.chat_session_id && !payload.workdir ? await this.defaultChatWorkdir() : await this.validateWorkdir(payload.workdir || this.config.allowedRoots[0]);
|
|
16909
17003
|
} catch (error51) {
|
|
16910
17004
|
return refuse(errorMessage(error51));
|
|
16911
17005
|
}
|
|
17006
|
+
try {
|
|
17007
|
+
if (payload.task_attachments?.length) payload = { ...payload, prompt: await this.prepareTaskFiles(payload, signal2, workdir) };
|
|
17008
|
+
signal2.throwIfAborted();
|
|
17009
|
+
} catch (error51) {
|
|
17010
|
+
if (signal2.aborted) {
|
|
17011
|
+
this.remember({ runId: payload.run_id, workstationId: payload.workstation_id, tail: new TerminalTailBuffer(), finishedAt: Date.now(), status: "cancelled", chat: !!payload.chat_session_id });
|
|
17012
|
+
await this.report(payload.run_id, { status: "cancelled", error: "\u9644\u4EF6\u51C6\u5907\u5DF2\u53D6\u6D88", duration_ms: 0 });
|
|
17013
|
+
return;
|
|
17014
|
+
}
|
|
17015
|
+
return refuse(`\u9644\u4EF6\u4E0B\u8F7D\u5931\u8D25\uFF1A${errorMessage(error51)}`);
|
|
17016
|
+
}
|
|
16912
17017
|
const request = {
|
|
16913
17018
|
prompt: payload.prompt,
|
|
16914
17019
|
sessionId: payload.session_id,
|
|
@@ -16927,7 +17032,7 @@ var RunSupervisor = class {
|
|
|
16927
17032
|
}
|
|
16928
17033
|
if (!payload.prompt && !payload.attachments?.length) return refuse("\u6D88\u606F\u5185\u5BB9\u4E3A\u7A7A");
|
|
16929
17034
|
if ([...this.active.values()].some((run2) => run2.workstationId === payload.workstation_id)) return refuse("\u672C\u673A\u8BE5\u5DE5\u4F4D\u5DF2\u6709\u4EFB\u52A1\u5728\u8FD0\u884C");
|
|
16930
|
-
if (this.active.size
|
|
17035
|
+
if (this.active.size + this.preparing.size > this.config.maxConcurrentRuns) return refuse("\u672C\u673A\u540C\u65F6\u8FD0\u884C\u7684\u4EFB\u52A1\u5DF2\u8FBE\u4E0A\u9650");
|
|
16931
17036
|
const launch = provider.buildLaunch({
|
|
16932
17037
|
prompt: payload.prompt,
|
|
16933
17038
|
sessionId: payload.session_id,
|
|
@@ -16976,6 +17081,7 @@ var RunSupervisor = class {
|
|
|
16976
17081
|
timers: []
|
|
16977
17082
|
};
|
|
16978
17083
|
this.active.set(run.runId, run);
|
|
17084
|
+
this.preparing.delete(run.runId);
|
|
16979
17085
|
this.unreported.add(run.runId);
|
|
16980
17086
|
if (run.pid > 1) this.orphans?.add({ runId: run.runId, pid: run.pid, executable: launch.executable, startedAt: run.startedAt });
|
|
16981
17087
|
run.tail.append(`\u25CF ${provider.id} \u542F\u52A8\u4E8E ${workdir}${payload.session_id ? `\uFF08\u7EE7\u7EED\u4F1A\u8BDD ${payload.session_id}\uFF09` : ""}
|
|
@@ -16995,7 +17101,7 @@ var RunSupervisor = class {
|
|
|
16995
17101
|
run.tail.append(`${clean}
|
|
16996
17102
|
`);
|
|
16997
17103
|
});
|
|
16998
|
-
child.once("close", (code,
|
|
17104
|
+
child.once("close", (code, signal3) => void this.onExit(run, code, signal3, spawnError));
|
|
16999
17105
|
if (provider.startInput) {
|
|
17000
17106
|
child.once("spawn", () => {
|
|
17001
17107
|
try {
|
|
@@ -17026,16 +17132,15 @@ var RunSupervisor = class {
|
|
|
17026
17132
|
});
|
|
17027
17133
|
}
|
|
17028
17134
|
onStdout(run, rawLine) {
|
|
17029
|
-
const
|
|
17030
|
-
const parsed = run.provider.parseLine(line);
|
|
17135
|
+
const parsed = run.provider.parseLine(rawLine);
|
|
17031
17136
|
if (run.chat && !run.forwardingFailed) {
|
|
17032
|
-
for (const event of parsed.chatEvents ?? []) this.queueChatEvent(run, event);
|
|
17137
|
+
for (const event of parsed.chatEvents ?? []) this.queueChatEvent(run, sanitizeChatEvent(event));
|
|
17033
17138
|
if (parsed.sessionId) run.chatReady = true;
|
|
17034
17139
|
}
|
|
17035
|
-
for (const row of parsed.display) run.tail.append(`${row}
|
|
17140
|
+
for (const row of parsed.display) run.tail.append(`${sanitizeTerminalLine(row)}
|
|
17036
17141
|
`);
|
|
17037
|
-
if (parsed.message !== void 0) run.finalMessage = parsed.message;
|
|
17038
|
-
if (parsed.turnError) run.turnError = parsed.turnError;
|
|
17142
|
+
if (parsed.message !== void 0) run.finalMessage = sanitizeTerminalLine(parsed.message);
|
|
17143
|
+
if (parsed.turnError) run.turnError = sanitizeTerminalLine(parsed.turnError);
|
|
17039
17144
|
if (parsed.sessionId && parsed.sessionId !== run.sessionId) {
|
|
17040
17145
|
run.sessionId = parsed.sessionId;
|
|
17041
17146
|
void this.reporter.started(run.runId, { session_id: parsed.sessionId }).catch(() => void 0);
|
|
@@ -17131,16 +17236,16 @@ var RunSupervisor = class {
|
|
|
17131
17236
|
async prepareChatAttachments(payload) {
|
|
17132
17237
|
if (!payload.attachments?.length) return [];
|
|
17133
17238
|
if (!this.reporter.chatMedia) throw new Error("\u9644\u4EF6\u8F6C\u53D1\u672A\u914D\u7F6E");
|
|
17134
|
-
const directory =
|
|
17135
|
-
await
|
|
17239
|
+
const directory = join7(this.config.dataDir, "chat-media", encodeURIComponent(payload.chat_session_id ?? payload.run_id));
|
|
17240
|
+
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
17136
17241
|
const files = [];
|
|
17137
17242
|
for (const attachment of payload.attachments) {
|
|
17138
|
-
const name =
|
|
17139
|
-
const path =
|
|
17243
|
+
const name = basename3(attachment.name).replace(/[^\p{L}\p{N}._-]/gu, "_").slice(-120) || "attachment";
|
|
17244
|
+
const path = join7(directory, `${encodeURIComponent(attachment.media_id)}-${name}`);
|
|
17140
17245
|
const existing = await stat2(path).catch(() => void 0);
|
|
17141
17246
|
if (!existing?.isFile()) {
|
|
17142
17247
|
const data = await this.reporter.chatMedia(payload.run_id, attachment.media_id);
|
|
17143
|
-
await
|
|
17248
|
+
await writeFile5(path, data, { mode: 384 });
|
|
17144
17249
|
}
|
|
17145
17250
|
files.push({ path, name: attachment.name, kind: attachment.kind });
|
|
17146
17251
|
}
|
|
@@ -17153,7 +17258,14 @@ var RunSupervisor = class {
|
|
|
17153
17258
|
/** Stops a run: the whole process tree, then a `cancelled` report. */
|
|
17154
17259
|
async cancel(runId) {
|
|
17155
17260
|
const run = this.active.get(runId);
|
|
17156
|
-
if (!run)
|
|
17261
|
+
if (!run) {
|
|
17262
|
+
const preparing = this.preparing.get(runId);
|
|
17263
|
+
if (preparing) {
|
|
17264
|
+
preparing.controller.abort();
|
|
17265
|
+
return true;
|
|
17266
|
+
}
|
|
17267
|
+
return false;
|
|
17268
|
+
}
|
|
17157
17269
|
run.cancelRequested = true;
|
|
17158
17270
|
run.tail.append("\u25CF \u6536\u5230\u505C\u6B62\u6307\u4EE4\uFF0C\u6B63\u5728\u7ED3\u675F\u8FDB\u7A0B\u6811\n");
|
|
17159
17271
|
this.logger.info("run-cancel", { runId, pid: run.pid });
|
|
@@ -17176,6 +17288,7 @@ var RunSupervisor = class {
|
|
|
17176
17288
|
return { ...read, status: this.active.has(target.runId) ? "running" : "finished", run_id: target.runId };
|
|
17177
17289
|
}
|
|
17178
17290
|
async disposeAll() {
|
|
17291
|
+
for (const entry of this.preparing.values()) entry.controller.abort();
|
|
17179
17292
|
await Promise.all([...this.active.values()].map((run) => {
|
|
17180
17293
|
run.cancelRequested = true;
|
|
17181
17294
|
return killProcessTree(run.pid, 1e3);
|
|
@@ -17201,23 +17314,47 @@ var RunSupervisor = class {
|
|
|
17201
17314
|
if (!input) throw new Error("\u6CA1\u6709\u914D\u7F6E\u5141\u8BB8\u7684\u5DE5\u4F5C\u76EE\u5F55");
|
|
17202
17315
|
let candidate;
|
|
17203
17316
|
try {
|
|
17204
|
-
candidate = await
|
|
17317
|
+
candidate = await realpath2(isAbsolute3(input) ? input : resolve3(input));
|
|
17205
17318
|
} catch {
|
|
17206
17319
|
throw new Error(`\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${input}`);
|
|
17207
17320
|
}
|
|
17208
17321
|
for (const root of this.config.allowedRoots) {
|
|
17209
|
-
const allowed = await
|
|
17210
|
-
const segment =
|
|
17211
|
-
if (segment === "" || !segment.startsWith("..") && !
|
|
17322
|
+
const allowed = await realpath2(root).catch(() => resolve3(root));
|
|
17323
|
+
const segment = relative2(allowed, candidate);
|
|
17324
|
+
if (segment === "" || !segment.startsWith("..") && !isAbsolute3(segment)) return candidate;
|
|
17212
17325
|
}
|
|
17213
17326
|
throw new Error(`\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5728\u5141\u8BB8\u8303\u56F4\u5185\uFF1A${input}`);
|
|
17214
17327
|
}
|
|
17328
|
+
async prepareTaskFiles(payload, signal2 = AbortSignal.timeout(15 * 6e4), workdir) {
|
|
17329
|
+
if (!payload.workdir) throw new Error("\u8BF7\u5148\u914D\u7F6E\u8BBE\u5907\u9ED8\u8BA4\u76EE\u5F55\u6216\u5DE5\u4F4D\u5DE5\u4F5C\u76EE\u5F55");
|
|
17330
|
+
if (!this.reporter.taskFile) throw new Error("\u4EFB\u52A1\u9644\u4EF6\u4E0B\u8F7D\u672A\u914D\u7F6E");
|
|
17331
|
+
return prepareTaskAttachments(payload, workdir ?? await this.validateWorkdir(payload.workdir), this.reporter.taskFile, AbortSignal.any([signal2, AbortSignal.timeout(15 * 6e4)]));
|
|
17332
|
+
}
|
|
17215
17333
|
};
|
|
17216
17334
|
function errorMessage(error51) {
|
|
17217
17335
|
return error51 instanceof Error ? error51.message : String(error51);
|
|
17218
17336
|
}
|
|
17337
|
+
function sanitizeChatEvent(event) {
|
|
17338
|
+
const sanitize = (value) => {
|
|
17339
|
+
if (typeof value === "string") return sanitizeTerminalLine(value);
|
|
17340
|
+
if (Array.isArray(value)) return value.map(sanitize);
|
|
17341
|
+
if (value && typeof value === "object") {
|
|
17342
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, sanitize(item)]));
|
|
17343
|
+
}
|
|
17344
|
+
return value;
|
|
17345
|
+
};
|
|
17346
|
+
return {
|
|
17347
|
+
...event,
|
|
17348
|
+
...event.text !== void 0 ? { text: sanitizeTerminalLine(event.text) } : {},
|
|
17349
|
+
...event.item ? { item: sanitize(event.item) } : {}
|
|
17350
|
+
};
|
|
17351
|
+
}
|
|
17219
17352
|
|
|
17220
17353
|
// src/transport/gateway-client.ts
|
|
17354
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
17355
|
+
import { createWriteStream } from "node:fs";
|
|
17356
|
+
import { Readable, Transform } from "node:stream";
|
|
17357
|
+
import { pipeline } from "node:stream/promises";
|
|
17221
17358
|
var GatewayError = class extends Error {
|
|
17222
17359
|
constructor(message, status) {
|
|
17223
17360
|
super(message);
|
|
@@ -17299,19 +17436,46 @@ var GatewayClient = class {
|
|
|
17299
17436
|
runStarted(runId, body) {
|
|
17300
17437
|
return this.request("POST", `/runs/${encodeURIComponent(runId)}/started`, body, 15e3);
|
|
17301
17438
|
}
|
|
17439
|
+
async taskFile(runId, fileId, destination, size, sha256, signal2) {
|
|
17440
|
+
const response = await fetch(`${this.config.serverUrl}/api/agent/runs/${encodeURIComponent(runId)}/files/${encodeURIComponent(fileId)}`, {
|
|
17441
|
+
headers: { authorization: `Bearer ${this.config.token}` },
|
|
17442
|
+
redirect: "error",
|
|
17443
|
+
signal: AbortSignal.any([signal2, AbortSignal.timeout(15 * 6e4)])
|
|
17444
|
+
});
|
|
17445
|
+
if (!response.ok || !response.body) throw new GatewayError("\u65E0\u6CD5\u8BFB\u53D6\u4EFB\u52A1\u9644\u4EF6\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u5DF2\u5220\u9664\u53CA\u8BBE\u5907\u51ED\u636E", response.status);
|
|
17446
|
+
const declared = response.headers.get("content-length");
|
|
17447
|
+
if (declared !== null && Number(declared) !== size) {
|
|
17448
|
+
await response.body.cancel();
|
|
17449
|
+
throw new Error("\u9644\u4EF6\u5927\u5C0F\u4E0E\u4FDD\u5B58\u7684\u8BB0\u5F55\u4E0D\u4E00\u81F4");
|
|
17450
|
+
}
|
|
17451
|
+
const hash2 = createHash3("sha256");
|
|
17452
|
+
let received = 0;
|
|
17453
|
+
const verifier = new Transform({ transform(chunk, _encoding, callback) {
|
|
17454
|
+
received += chunk.length;
|
|
17455
|
+
if (received > size) {
|
|
17456
|
+
callback(new Error("\u9644\u4EF6\u4E0B\u8F7D\u8D85\u51FA\u8BB0\u5F55\u5927\u5C0F"));
|
|
17457
|
+
return;
|
|
17458
|
+
}
|
|
17459
|
+
hash2.update(chunk);
|
|
17460
|
+
callback(null, chunk);
|
|
17461
|
+
} });
|
|
17462
|
+
const target = createWriteStream(destination, { flags: "wx", mode: 384 });
|
|
17463
|
+
await pipeline(Readable.fromWeb(response.body), verifier, target, { signal: signal2 });
|
|
17464
|
+
if (received !== size || hash2.digest("hex") !== sha256) throw new Error("\u9644\u4EF6\u4E0B\u8F7D\u4E0D\u5B8C\u6574\u6216\u6821\u9A8C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
|
|
17465
|
+
}
|
|
17302
17466
|
runFinished(runId, body) {
|
|
17303
17467
|
return this.request("POST", `/runs/${encodeURIComponent(runId)}/finished`, body, 15e3);
|
|
17304
17468
|
}
|
|
17305
17469
|
};
|
|
17306
17470
|
|
|
17307
17471
|
// src/workspace/directory-browser.ts
|
|
17308
|
-
import { readdir, realpath as
|
|
17472
|
+
import { readdir, realpath as realpath3, stat as stat3 } from "node:fs/promises";
|
|
17309
17473
|
import { homedir as homedir5 } from "node:os";
|
|
17310
17474
|
import { dirname as dirname6, parse as parse3, resolve as resolve4 } from "node:path";
|
|
17311
17475
|
var MAX_ENTRIES = 1e3;
|
|
17312
17476
|
async function listDirectory(requested, allowedRoots) {
|
|
17313
17477
|
const raw = requested.trim().replace(/^~(?=$|[\\/])/, homedir5()) || homedir5();
|
|
17314
|
-
const directory = await
|
|
17478
|
+
const directory = await realpath3(resolve4(raw)).catch(() => {
|
|
17315
17479
|
throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${requested}`);
|
|
17316
17480
|
});
|
|
17317
17481
|
if (!(await stat3(directory)).isDirectory()) throw new Error("\u8FD9\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55");
|
|
@@ -17343,12 +17507,13 @@ async function listDirectory(requested, allowedRoots) {
|
|
|
17343
17507
|
|
|
17344
17508
|
// src/workspace/workspace-manager.ts
|
|
17345
17509
|
import { spawn as spawn8 } from "node:child_process";
|
|
17346
|
-
import { chmod as chmod3, mkdir as
|
|
17510
|
+
import { chmod as chmod3, mkdir as mkdir6, mkdtemp, opendir, realpath as realpath4, rm as rm3, stat as stat4, writeFile as writeFile6 } from "node:fs/promises";
|
|
17347
17511
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
17348
|
-
import { isAbsolute as
|
|
17512
|
+
import { isAbsolute as isAbsolute4, join as join8, relative as relative3, resolve as resolve5 } from "node:path";
|
|
17349
17513
|
var MAX_NODES = 2e3;
|
|
17350
17514
|
var MAX_DIRECTORY_ENTRIES = 12e3;
|
|
17351
17515
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
17516
|
+
".echo",
|
|
17352
17517
|
".git",
|
|
17353
17518
|
"node_modules",
|
|
17354
17519
|
".next",
|
|
@@ -17394,12 +17559,12 @@ var WorkspaceManager = class {
|
|
|
17394
17559
|
validateRootPath(value) {
|
|
17395
17560
|
const raw = value.trim().replace(/^~(?=$|[\\/])/, homedir6());
|
|
17396
17561
|
const root = resolve5(raw);
|
|
17397
|
-
if (!
|
|
17398
|
-
if (
|
|
17562
|
+
if (!isAbsolute4(raw)) throw new Error("\u6839\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
|
|
17563
|
+
if (relative3(root, resolve5(root, "..")) === "") throw new Error("\u4E0D\u80FD\u628A\u6587\u4EF6\u7CFB\u7EDF\u6839\u76EE\u5F55\u4F5C\u4E3A\u76EE\u5F55\u6839");
|
|
17399
17564
|
return root;
|
|
17400
17565
|
}
|
|
17401
17566
|
async syncTree(rootPath, request) {
|
|
17402
|
-
await
|
|
17567
|
+
await mkdir6(rootPath, { recursive: true });
|
|
17403
17568
|
await addAllowedRoot(this.config, rootPath);
|
|
17404
17569
|
const results = [];
|
|
17405
17570
|
const requestedPaths = /* @__PURE__ */ new Set();
|
|
@@ -17423,7 +17588,7 @@ var WorkspaceManager = class {
|
|
|
17423
17588
|
});
|
|
17424
17589
|
continue;
|
|
17425
17590
|
}
|
|
17426
|
-
await
|
|
17591
|
+
await mkdir6(target, { recursive: true });
|
|
17427
17592
|
results.push({
|
|
17428
17593
|
relative_path: relativePath,
|
|
17429
17594
|
status: "created",
|
|
@@ -17472,7 +17637,7 @@ var WorkspaceManager = class {
|
|
|
17472
17637
|
}
|
|
17473
17638
|
if (!metadata) {
|
|
17474
17639
|
if (!gitUrl) throw new Error("A missing repository needs a Git remote URL");
|
|
17475
|
-
await
|
|
17640
|
+
await mkdir6(resolve5(target, ".."), { recursive: true });
|
|
17476
17641
|
const cloneArgs = branch ? ["clone", "--branch", branch, "--single-branch", gitUrl, target] : ["clone", gitUrl, target];
|
|
17477
17642
|
await this.runGit(cloneArgs, rootPath, timeoutSeconds, false, gitEnvironment);
|
|
17478
17643
|
created = true;
|
|
@@ -17566,13 +17731,13 @@ var WorkspaceManager = class {
|
|
|
17566
17731
|
};
|
|
17567
17732
|
}
|
|
17568
17733
|
async scanTree(rootPath, maxDepth, scanRoots) {
|
|
17569
|
-
const resolvedRoot = await
|
|
17734
|
+
const resolvedRoot = await realpath4(rootPath);
|
|
17570
17735
|
const rootMetadata = await stat4(resolvedRoot);
|
|
17571
17736
|
if (!rootMetadata.isDirectory()) throw new Error("The workspace root is not a directory");
|
|
17572
17737
|
const nodes = [];
|
|
17573
17738
|
let entriesRead = 0;
|
|
17574
17739
|
const appendNode = async (target) => {
|
|
17575
|
-
const relativePath =
|
|
17740
|
+
const relativePath = relative3(resolvedRoot, target).replaceAll("\\", "/");
|
|
17576
17741
|
if (!relativePath || !this.isSafeRelativePath(relativePath)) return void 0;
|
|
17577
17742
|
const git = await this.readGitMetadata(target);
|
|
17578
17743
|
if (git) {
|
|
@@ -17656,8 +17821,8 @@ var WorkspaceManager = class {
|
|
|
17656
17821
|
if (result.exitCode !== 0 || !topLevel) return false;
|
|
17657
17822
|
try {
|
|
17658
17823
|
const [resolvedDirectory, resolvedTopLevel] = await Promise.all([
|
|
17659
|
-
|
|
17660
|
-
|
|
17824
|
+
realpath4(directory),
|
|
17825
|
+
realpath4(topLevel)
|
|
17661
17826
|
]);
|
|
17662
17827
|
return resolvedDirectory === resolvedTopLevel;
|
|
17663
17828
|
} catch {
|
|
@@ -17677,8 +17842,8 @@ var WorkspaceManager = class {
|
|
|
17677
17842
|
}
|
|
17678
17843
|
resolveWithinRoot(rootPath, relativePath) {
|
|
17679
17844
|
const target = resolve5(rootPath, relativePath);
|
|
17680
|
-
const relativeToRoot =
|
|
17681
|
-
if (relativeToRoot.startsWith("..") ||
|
|
17845
|
+
const relativeToRoot = relative3(rootPath, target);
|
|
17846
|
+
if (relativeToRoot.startsWith("..") || isAbsolute4(relativeToRoot)) {
|
|
17682
17847
|
throw new Error("A workspace path must remain under the configured workspace root");
|
|
17683
17848
|
}
|
|
17684
17849
|
return target;
|
|
@@ -17719,13 +17884,13 @@ var WorkspaceManager = class {
|
|
|
17719
17884
|
}
|
|
17720
17885
|
async createGitAuthEnvironment(auth) {
|
|
17721
17886
|
if (!auth) return void 0;
|
|
17722
|
-
const directory = await mkdtemp(
|
|
17887
|
+
const directory = await mkdtemp(join8(tmpdir(), "toc-ocra-git-auth-"));
|
|
17723
17888
|
const isWindows = process.platform === "win32";
|
|
17724
|
-
const scriptPath =
|
|
17889
|
+
const scriptPath = join8(directory, isWindows ? "askpass.cmd" : "askpass.sh");
|
|
17725
17890
|
let script;
|
|
17726
17891
|
if (isWindows) {
|
|
17727
|
-
const helperPath =
|
|
17728
|
-
await
|
|
17892
|
+
const helperPath = join8(directory, "askpass.cjs");
|
|
17893
|
+
await writeFile6(
|
|
17729
17894
|
helperPath,
|
|
17730
17895
|
'const prompt = process.argv.slice(2).join(" ");\nprocess.stdout.write(/username/i.test(prompt) ? process.env.ECHO_GIT_USERNAME ?? "" : process.env.ECHO_GIT_PASSWORD ?? "");\n',
|
|
17731
17896
|
{ mode: 448 }
|
|
@@ -17734,7 +17899,7 @@ var WorkspaceManager = class {
|
|
|
17734
17899
|
} else {
|
|
17735
17900
|
script = '#!/bin/sh\ncase "$1" in *Username*|*username*) printf "%s\\n" "$ECHO_GIT_USERNAME" ;; *) printf "%s\\n" "$ECHO_GIT_PASSWORD" ;; esac\n';
|
|
17736
17901
|
}
|
|
17737
|
-
await
|
|
17902
|
+
await writeFile6(scriptPath, script, { mode: 448 });
|
|
17738
17903
|
if (!isWindows) await chmod3(scriptPath, 448);
|
|
17739
17904
|
return {
|
|
17740
17905
|
env: {
|
|
@@ -17745,7 +17910,7 @@ var WorkspaceManager = class {
|
|
|
17745
17910
|
ECHO_GIT_USERNAME: auth.username,
|
|
17746
17911
|
ECHO_GIT_PASSWORD: auth.password
|
|
17747
17912
|
},
|
|
17748
|
-
cleanup: () =>
|
|
17913
|
+
cleanup: () => rm3(directory, { recursive: true, force: true })
|
|
17749
17914
|
};
|
|
17750
17915
|
}
|
|
17751
17916
|
message(error51) {
|
|
@@ -17770,6 +17935,7 @@ var RunStartPayloadSchema = external_exports.object({
|
|
|
17770
17935
|
prompt: external_exports.string().max(512 * 1024),
|
|
17771
17936
|
chat_session_id: external_exports.string().max(64).optional(),
|
|
17772
17937
|
attachments: external_exports.array(external_exports.object({ media_id: external_exports.string().min(1).max(64), name: external_exports.string().max(200), kind: external_exports.enum(["image", "file"]) })).max(16).optional(),
|
|
17938
|
+
task_attachments: external_exports.array(external_exports.object({ file_id: external_exports.uuid(), name: external_exports.string().min(1).max(255), mime: external_exports.string().max(255), size: external_exports.number().int().positive(), sha256: external_exports.string().regex(/^[a-f0-9]{64}$/), prompt_token: external_exports.string().max(100) })).max(16).nullish(),
|
|
17773
17939
|
session_id: external_exports.string().max(200).optional().default(""),
|
|
17774
17940
|
model: external_exports.string().max(100).optional().default(""),
|
|
17775
17941
|
reasoning_effort: external_exports.string().max(40).optional().default(""),
|
|
@@ -17840,13 +18006,14 @@ var AgentRuntime = class {
|
|
|
17840
18006
|
await this.retry(() => this.client.chatEvents(runId, events), 5);
|
|
17841
18007
|
},
|
|
17842
18008
|
chatMedia: (runId, mediaId) => this.client.chatMedia(runId, mediaId),
|
|
18009
|
+
taskFile: (runId, fileId, destination, size, sha256, signal2) => this.client.taskFile(runId, fileId, destination, size, sha256, signal2),
|
|
17843
18010
|
finished: async (runId, report) => {
|
|
17844
18011
|
this.pendingReports.set(runId, report);
|
|
17845
18012
|
await this.flushReport(runId);
|
|
17846
18013
|
}
|
|
17847
18014
|
};
|
|
17848
18015
|
this.workspaces = new WorkspaceManager(config2, this.logger);
|
|
17849
|
-
this.orphans = new OrphanRegistry(
|
|
18016
|
+
this.orphans = new OrphanRegistry(join9(config2.dataDir, chatOnly ? "chat-active-runs.json" : "active-runs.json"));
|
|
17850
18017
|
this.runs = new RunSupervisor(config2, this.logger, reporter, this.orphans);
|
|
17851
18018
|
}
|
|
17852
18019
|
config;
|
|
@@ -17859,7 +18026,7 @@ var AgentRuntime = class {
|
|
|
17859
18026
|
workspaces;
|
|
17860
18027
|
stopController = new AbortController();
|
|
17861
18028
|
startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17862
|
-
chatRunnerId =
|
|
18029
|
+
chatRunnerId = randomUUID3();
|
|
17863
18030
|
chatReady = false;
|
|
17864
18031
|
chatLeaseInitialized = false;
|
|
17865
18032
|
chatError;
|
|
@@ -18000,6 +18167,7 @@ var AgentRuntime = class {
|
|
|
18000
18167
|
return {
|
|
18001
18168
|
providers: snapshot.providers.map((p) => ({ id: p.id, installed: p.installed, runnable: p.available, version: p.version ?? "", path: p.resolvedPath ?? "" })),
|
|
18002
18169
|
cli_chat: { version: 1, providers: CHAT_PROVIDERS.filter((id) => snapshot.detectedIds.includes(id)) },
|
|
18170
|
+
task_attachments: { version: 1 },
|
|
18003
18171
|
detected_ids: snapshot.detectedIds,
|
|
18004
18172
|
refreshed_at: snapshot.refreshedAt
|
|
18005
18173
|
};
|
|
@@ -18053,6 +18221,11 @@ var AgentRuntime = class {
|
|
|
18053
18221
|
}
|
|
18054
18222
|
async execute(command) {
|
|
18055
18223
|
switch (command.type) {
|
|
18224
|
+
case "run.files.prepare": {
|
|
18225
|
+
const payload = RunStartPayloadSchema.parse(command.payload);
|
|
18226
|
+
const prompt = await this.runs.prepareTaskFiles(payload);
|
|
18227
|
+
return { success: true, result: { prompt } };
|
|
18228
|
+
}
|
|
18056
18229
|
case "chat.run.start":
|
|
18057
18230
|
case "run.start": {
|
|
18058
18231
|
const payload = RunStartPayloadSchema.parse(command.payload);
|
|
@@ -18387,7 +18560,7 @@ function preferSystemNode() {
|
|
|
18387
18560
|
const names = process.platform === "win32" ? ["node.exe"] : ["node"];
|
|
18388
18561
|
for (const directory of (process.env.PATH ?? "").split(delimiter5)) {
|
|
18389
18562
|
for (const name of names) {
|
|
18390
|
-
const candidate = directory &&
|
|
18563
|
+
const candidate = directory && join10(directory, name);
|
|
18391
18564
|
if (candidate && existsSync6(candidate)) {
|
|
18392
18565
|
process.env.TOC_OCRA_NODE_PATH = candidate;
|
|
18393
18566
|
return;
|
|
@@ -4,6 +4,7 @@ import type { ChatContentEvent } from '../providers/provider.js';
|
|
|
4
4
|
import { type TerminalTailRead } from '../session/terminal-tail-buffer.js';
|
|
5
5
|
import type { RunFinishedReport, RunStartPayload, RunStartedReport } from '../types/protocol.js';
|
|
6
6
|
import { OrphanRegistry } from './orphan-registry.js';
|
|
7
|
+
import { type TaskFileDownload } from './task-attachments.js';
|
|
7
8
|
/**
|
|
8
9
|
* Supervises headless CLI turns. One process per turn:
|
|
9
10
|
*
|
|
@@ -17,6 +18,7 @@ import { OrphanRegistry } from './orphan-registry.js';
|
|
|
17
18
|
*/
|
|
18
19
|
export declare const COMPLETION_EXIT_GRACE_MS = 15000;
|
|
19
20
|
export type RunReporter = {
|
|
21
|
+
taskFile?: TaskFileDownload;
|
|
20
22
|
chatEvents?(runId: string, events: Array<ChatContentEvent & {
|
|
21
23
|
sequence: number;
|
|
22
24
|
}>): Promise<void>;
|
|
@@ -38,6 +40,7 @@ export declare class RunSupervisor {
|
|
|
38
40
|
/** Run ids whose finish report has not been acknowledged yet. */
|
|
39
41
|
private readonly unreported;
|
|
40
42
|
private readonly chatRunIds;
|
|
43
|
+
private readonly preparing;
|
|
41
44
|
constructor(config: AgentConfig, logger: DiagnosticLogger, reporter: RunReporter, orphans?: OrphanRegistry | undefined);
|
|
42
45
|
get activeCount(): number;
|
|
43
46
|
/** Runs to list in the heartbeat: still running, or finished but not yet reported. */
|
|
@@ -56,6 +59,7 @@ export declare class RunSupervisor {
|
|
|
56
59
|
* directory, unknown CLI) are reported as a failed run, never thrown.
|
|
57
60
|
*/
|
|
58
61
|
start(payload: RunStartPayload): Promise<void>;
|
|
62
|
+
private startPrepared;
|
|
59
63
|
private onStdout;
|
|
60
64
|
private onExit;
|
|
61
65
|
private queueChatEvent;
|
|
@@ -72,4 +76,5 @@ export declare class RunSupervisor {
|
|
|
72
76
|
private remember;
|
|
73
77
|
private defaultChatWorkdir;
|
|
74
78
|
validateWorkdir(input: string | undefined): Promise<string>;
|
|
79
|
+
prepareTaskFiles(payload: RunStartPayload, signal?: AbortSignal, workdir?: string): Promise<string>;
|
|
75
80
|
}
|