toc-ocra 0.2.5 → 0.2.7
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/service/agent.mjs +507 -43
- package/dist/src/cli-chat-worker.d.ts +1 -0
- package/dist/src/cli-chat-worker.js +24 -0
- package/dist/src/cli-chat-worker.js.map +1 -0
- package/dist/src/providers/chat/claude-stream-json.d.ts +6 -0
- package/dist/src/providers/chat/claude-stream-json.js +125 -0
- package/dist/src/providers/chat/claude-stream-json.js.map +1 -0
- package/dist/src/providers/chat/codex-app-server.d.ts +9 -0
- package/dist/src/providers/chat/codex-app-server.js +146 -0
- package/dist/src/providers/chat/codex-app-server.js.map +1 -0
- package/dist/src/providers/chat/index.d.ts +5 -0
- package/dist/src/providers/chat/index.js +11 -0
- package/dist/src/providers/chat/index.js.map +1 -0
- package/dist/src/providers/provider.d.ts +10 -0
- package/dist/src/providers/provider.js.map +1 -1
- package/dist/src/runs/run-supervisor.d.ts +12 -0
- package/dist/src/runs/run-supervisor.js +152 -6
- package/dist/src/runs/run-supervisor.js.map +1 -1
- package/dist/src/runtime/agent-runtime.d.ts +6 -1
- package/dist/src/runtime/agent-runtime.js +77 -24
- package/dist/src/runtime/agent-runtime.js.map +1 -1
- package/dist/src/transport/gateway-client.d.ts +6 -0
- package/dist/src/transport/gateway-client.js +24 -0
- package/dist/src/transport/gateway-client.js.map +1 -1
- package/dist/src/types/protocol.d.ts +9 -0
- package/dist/src/types/protocol.js +3 -1
- package/dist/src/types/protocol.js.map +1 -1
- package/package.json +1 -1
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 join9 } from "node:path";
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
@@ -15731,7 +15731,8 @@ var CliDetectionService = class {
|
|
|
15731
15731
|
var cliDetection = new CliDetectionService();
|
|
15732
15732
|
|
|
15733
15733
|
// src/runtime/agent-runtime.ts
|
|
15734
|
-
import {
|
|
15734
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
15735
|
+
import { join as join8 } from "node:path";
|
|
15735
15736
|
|
|
15736
15737
|
// src/exec/script-runner.ts
|
|
15737
15738
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -15833,6 +15834,268 @@ function executeScript(command, workdir, timeoutSeconds) {
|
|
|
15833
15834
|
});
|
|
15834
15835
|
}
|
|
15835
15836
|
|
|
15837
|
+
// src/providers/chat/codex-app-server.ts
|
|
15838
|
+
function codexChatProvider(request, attachments) {
|
|
15839
|
+
let write;
|
|
15840
|
+
let threadId = request.sessionId;
|
|
15841
|
+
let hasStartedTurn = false;
|
|
15842
|
+
const send = (message) => write?.(`${JSON.stringify(message)}
|
|
15843
|
+
`);
|
|
15844
|
+
const options = {
|
|
15845
|
+
cwd: request.workdir,
|
|
15846
|
+
approvalPolicy: "never",
|
|
15847
|
+
sandbox: "danger-full-access",
|
|
15848
|
+
...request.model ? { model: request.model } : {}
|
|
15849
|
+
};
|
|
15850
|
+
const threadRequest = () => send({
|
|
15851
|
+
id: 2,
|
|
15852
|
+
method: threadId ? "thread/resume" : "thread/start",
|
|
15853
|
+
params: { ...options, ...threadId ? { threadId } : { ephemeral: false } }
|
|
15854
|
+
});
|
|
15855
|
+
const startTurn = () => {
|
|
15856
|
+
if (hasStartedTurn) return;
|
|
15857
|
+
hasStartedTurn = true;
|
|
15858
|
+
const input = [];
|
|
15859
|
+
if (request.prompt) input.push({ type: "text", text: request.prompt });
|
|
15860
|
+
for (const attachment of attachments) {
|
|
15861
|
+
if (attachment.kind === "image") input.push({ type: "localImage", path: attachment.path });
|
|
15862
|
+
else input.push({ type: "text", text: `\u9644\u4EF6\u6587\u4EF6\uFF1A${attachment.name}
|
|
15863
|
+
\u672C\u673A\u8DEF\u5F84\uFF1A${attachment.path}` });
|
|
15864
|
+
}
|
|
15865
|
+
send({ id: 3, method: "turn/start", params: {
|
|
15866
|
+
threadId,
|
|
15867
|
+
input,
|
|
15868
|
+
cwd: request.workdir,
|
|
15869
|
+
approvalPolicy: "never",
|
|
15870
|
+
sandboxPolicy: { type: "dangerFullAccess" },
|
|
15871
|
+
...request.model ? { model: request.model } : {},
|
|
15872
|
+
...request.reasoningEffort ? { effort: request.reasoningEffort } : {}
|
|
15873
|
+
} });
|
|
15874
|
+
};
|
|
15875
|
+
const empty = () => ({ display: [] });
|
|
15876
|
+
return {
|
|
15877
|
+
id: "codex",
|
|
15878
|
+
buildLaunch() {
|
|
15879
|
+
return resolveExecutable("codex", ["app-server", "--listen", "stdio://"], providerEnvironment(), process.env.TOC_OCRA_CODEX_PATH);
|
|
15880
|
+
},
|
|
15881
|
+
startInput(writer) {
|
|
15882
|
+
write = writer;
|
|
15883
|
+
send({ id: 1, method: "initialize", params: {
|
|
15884
|
+
clientInfo: { name: "echo_cli_chat", title: "Echo CLI Chat", version: "1.0" },
|
|
15885
|
+
capabilities: { experimentalApi: true }
|
|
15886
|
+
} });
|
|
15887
|
+
},
|
|
15888
|
+
parseLine(line) {
|
|
15889
|
+
const message = tryJson(line);
|
|
15890
|
+
if (!message) return empty();
|
|
15891
|
+
if (message.id !== void 0 && !message.method) {
|
|
15892
|
+
const error51 = message.error;
|
|
15893
|
+
if (error51) return { display: [], turnError: str(error51.message) ?? "Codex \u4F1A\u8BDD\u8BF7\u6C42\u5931\u8D25", endProcess: true };
|
|
15894
|
+
if (message.id === 1) {
|
|
15895
|
+
send({ method: "initialized" });
|
|
15896
|
+
threadRequest();
|
|
15897
|
+
return empty();
|
|
15898
|
+
}
|
|
15899
|
+
if (message.id === 2) {
|
|
15900
|
+
const result = message.result;
|
|
15901
|
+
const thread = result?.thread;
|
|
15902
|
+
const id = str(thread?.id);
|
|
15903
|
+
if (!id) return { display: [], turnError: "Codex \u672A\u8FD4\u56DE\u539F\u751F\u4F1A\u8BDD ID", endProcess: true };
|
|
15904
|
+
threadId = id;
|
|
15905
|
+
startTurn();
|
|
15906
|
+
return { display: [`\u25CF \u4F1A\u8BDD ${id}`], sessionId: id };
|
|
15907
|
+
}
|
|
15908
|
+
return empty();
|
|
15909
|
+
}
|
|
15910
|
+
const params = message.params ?? {};
|
|
15911
|
+
if (params.threadId && params.threadId !== threadId) return empty();
|
|
15912
|
+
if (message.id !== void 0) {
|
|
15913
|
+
send({ id: message.id, error: { code: -32601, message: "\u5F53\u524D\u804A\u5929\u5C1A\u672A\u63A5\u5165\u4EA4\u4E92\u5F0F\u6743\u9650\u6216\u95EE\u7B54" } });
|
|
15914
|
+
return { display: [], turnError: "CLI \u6B63\u5728\u8BF7\u6C42\u4EA4\u4E92\u786E\u8BA4\uFF0C\u5F53\u524D\u804A\u5929\u5C1A\u672A\u63A5\u5165\u6B64\u64CD\u4F5C\u3002", endProcess: true };
|
|
15915
|
+
}
|
|
15916
|
+
const itemId = str(params.itemId) ?? "";
|
|
15917
|
+
switch (message.method) {
|
|
15918
|
+
case "item/agentMessage/delta":
|
|
15919
|
+
case "item/reasoning/textDelta":
|
|
15920
|
+
case "item/reasoning/summaryTextDelta": {
|
|
15921
|
+
const text = typeof params.delta === "string" ? params.delta : "";
|
|
15922
|
+
return { display: [], chatEvents: itemId && text ? [{ event: message.method === "item/agentMessage/delta" ? "text_delta" : "thought_delta", item_id: itemId, text }] : [] };
|
|
15923
|
+
}
|
|
15924
|
+
case "turn/plan/updated": {
|
|
15925
|
+
const plan = Array.isArray(params.plan) ? params.plan : [];
|
|
15926
|
+
return { display: [], chatEvents: [{ event: "item", item: {
|
|
15927
|
+
itemId: "plan",
|
|
15928
|
+
type: "plan",
|
|
15929
|
+
entries: plan.map((entry) => ({ content: String(entry.step ?? ""), status: String(entry.status ?? "pending") }))
|
|
15930
|
+
} }] };
|
|
15931
|
+
}
|
|
15932
|
+
case "item/started":
|
|
15933
|
+
case "item/completed": {
|
|
15934
|
+
const item = params.item ?? {};
|
|
15935
|
+
const id = str(item.id);
|
|
15936
|
+
if (!id || item.type === "userMessage") return empty();
|
|
15937
|
+
const completed = message.method === "item/completed";
|
|
15938
|
+
const status = completed ? item.status === "failed" ? "failed" : "completed" : "in_progress";
|
|
15939
|
+
let normalized;
|
|
15940
|
+
switch (item.type) {
|
|
15941
|
+
case "agentMessage":
|
|
15942
|
+
normalized = { itemId: id, type: "text", text: String(item.text ?? ""), status };
|
|
15943
|
+
break;
|
|
15944
|
+
case "reasoning": {
|
|
15945
|
+
const summary = Array.isArray(item.summary) ? item.summary.map(String).join("\n\n") : "";
|
|
15946
|
+
normalized = { itemId: id, type: "thought", text: summary, status };
|
|
15947
|
+
break;
|
|
15948
|
+
}
|
|
15949
|
+
case "plan":
|
|
15950
|
+
normalized = { itemId: id, type: "text", text: String(item.text ?? ""), status };
|
|
15951
|
+
break;
|
|
15952
|
+
case "commandExecution":
|
|
15953
|
+
normalized = { itemId: id, type: "tool_call", name: "shell", kind: "execute", title: String(item.command ?? "\u6267\u884C\u547D\u4EE4"), input: String(item.command ?? ""), output: clip(String(item.aggregatedOutput ?? ""), 64e3), text: clip(String(item.aggregatedOutput ?? ""), 64e3), status };
|
|
15954
|
+
break;
|
|
15955
|
+
case "fileChange":
|
|
15956
|
+
normalized = { itemId: id, type: "tool_call", name: "file_change", kind: "edit", title: "\u4FEE\u6539\u6587\u4EF6", input: clip(JSON.stringify(item.changes ?? []), 64e3), text: clip(JSON.stringify(item.changes ?? []), 64e3), status };
|
|
15957
|
+
break;
|
|
15958
|
+
case "mcpToolCall":
|
|
15959
|
+
normalized = { itemId: id, type: "tool_call", name: String(item.tool ?? "MCP"), kind: "call", title: String(item.tool ?? "\u5DE5\u5177\u8C03\u7528"), input: clip(JSON.stringify(item.arguments ?? {}), 64e3), ...completed ? { output: clip(JSON.stringify(item.result ?? item.error ?? {}), 64e3) } : {}, text: clip(JSON.stringify(item.result ?? item.arguments ?? {}), 64e3), status };
|
|
15960
|
+
break;
|
|
15961
|
+
default:
|
|
15962
|
+
normalized = { itemId: id, type: "tool_call", name: String(item.type ?? "tool"), title: String(item.type ?? "\u6267\u884C\u8FC7\u7A0B"), status };
|
|
15963
|
+
break;
|
|
15964
|
+
}
|
|
15965
|
+
if (completed && normalized.type === "thought" && !normalized.text) return empty();
|
|
15966
|
+
return {
|
|
15967
|
+
display: [],
|
|
15968
|
+
chatEvents: [{ event: "item", item: normalized }],
|
|
15969
|
+
...completed && item.type === "agentMessage" ? { message: String(item.text ?? "") } : {}
|
|
15970
|
+
};
|
|
15971
|
+
}
|
|
15972
|
+
case "turn/completed": {
|
|
15973
|
+
const turn = params.turn ?? {};
|
|
15974
|
+
const error51 = turn.error ?? {};
|
|
15975
|
+
if (turn.status !== "completed") return { display: [], turnError: str(error51.message) ?? (turn.status === "interrupted" ? "\u6267\u884C\u5DF2\u4E2D\u65AD" : "Codex \u672C\u8F6E\u6267\u884C\u5931\u8D25"), endProcess: true };
|
|
15976
|
+
return { display: ["\u25CF \u672C\u8F6E\u5B8C\u6210"], turnCompleted: true, endProcess: true };
|
|
15977
|
+
}
|
|
15978
|
+
default:
|
|
15979
|
+
return empty();
|
|
15980
|
+
}
|
|
15981
|
+
}
|
|
15982
|
+
};
|
|
15983
|
+
}
|
|
15984
|
+
|
|
15985
|
+
// src/providers/chat/claude-stream-json.ts
|
|
15986
|
+
var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
15987
|
+
var textContent = (value) => typeof value === "string" ? value : Array.isArray(value) ? value.map((part) => str(record2(part).text) ?? "").filter(Boolean).join("\n") : JSON.stringify(value ?? "");
|
|
15988
|
+
function claudeChatProvider(attachments) {
|
|
15989
|
+
let messageID = "";
|
|
15990
|
+
let messageSequence = 0;
|
|
15991
|
+
let hasText = false;
|
|
15992
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
15993
|
+
const tools = /* @__PURE__ */ new Map();
|
|
15994
|
+
const asItem = (block, status, output) => ({ event: "item", item: {
|
|
15995
|
+
itemId: block.id,
|
|
15996
|
+
type: block.type,
|
|
15997
|
+
status,
|
|
15998
|
+
...block.type === "tool_call" ? { name: block.name, title: block.name, input: clip(block.partialInput || JSON.stringify(block.input), 64e3), ...output !== void 0 ? { output: clip(output, 64e3), text: clip(output, 64e3) } : {} } : { text: block.text }
|
|
15999
|
+
} });
|
|
16000
|
+
return {
|
|
16001
|
+
id: "claude",
|
|
16002
|
+
buildLaunch(request) {
|
|
16003
|
+
const files = attachments.map((file2) => `\u9644\u4EF6\u6587\u4EF6\uFF1A${file2.name}
|
|
16004
|
+
\u672C\u673A\u8DEF\u5F84\uFF1A${file2.path}`).join("\n\n");
|
|
16005
|
+
const launch = claudeProvider.buildLaunch({ ...request, prompt: [request.prompt, files].filter(Boolean).join("\n\n") });
|
|
16006
|
+
launch.args.push("--include-partial-messages");
|
|
16007
|
+
return launch;
|
|
16008
|
+
},
|
|
16009
|
+
parseLine(line) {
|
|
16010
|
+
const envelope = tryJson(line);
|
|
16011
|
+
if (!envelope) return { display: line.trim() ? [line] : [] };
|
|
16012
|
+
const sessionId = str(envelope.session_id);
|
|
16013
|
+
const base = sessionId ? { sessionId } : {};
|
|
16014
|
+
const events = [];
|
|
16015
|
+
if (envelope.parent_tool_use_id) return { ...base, display: [] };
|
|
16016
|
+
if (envelope.type === "stream_event") {
|
|
16017
|
+
const event = record2(envelope.event);
|
|
16018
|
+
const index = Number(event.index ?? 0);
|
|
16019
|
+
if (event.type === "message_start") {
|
|
16020
|
+
messageID = str(record2(event.message).id) ?? `message:${++messageSequence}`;
|
|
16021
|
+
blocks.clear();
|
|
16022
|
+
} else if (event.type === "content_block_start") {
|
|
16023
|
+
if (!messageID) messageID = `message:${++messageSequence}`;
|
|
16024
|
+
const part = record2(event.content_block);
|
|
16025
|
+
const type = part.type === "tool_use" ? "tool_call" : part.type === "thinking" ? "thought" : "text";
|
|
16026
|
+
if (!["tool_use", "thinking", "text"].includes(String(part.type))) return { ...base, display: [] };
|
|
16027
|
+
const block = { id: type === "tool_call" ? str(part.id) ?? `${messageID}:${index}` : `${messageID}:${index}`, type, name: str(part.name) ?? "tool", text: String(part.text ?? part.thinking ?? ""), input: part.input ?? {}, partialInput: "" };
|
|
16028
|
+
blocks.set(index, block);
|
|
16029
|
+
if (type === "tool_call") tools.set(block.id, block);
|
|
16030
|
+
events.push(asItem(block, "in_progress"));
|
|
16031
|
+
} else if (event.type === "content_block_delta") {
|
|
16032
|
+
const block = blocks.get(index);
|
|
16033
|
+
const delta = record2(event.delta);
|
|
16034
|
+
if (!block) return { ...base, display: [] };
|
|
16035
|
+
if (delta.type === "text_delta" || delta.type === "thinking_delta") {
|
|
16036
|
+
const text = String(delta.text ?? delta.thinking ?? "");
|
|
16037
|
+
block.text += text;
|
|
16038
|
+
if (delta.type === "text_delta") hasText ||= !!text;
|
|
16039
|
+
events.push({ event: delta.type === "text_delta" ? "text_delta" : "thought_delta", item_id: block.id, text });
|
|
16040
|
+
} else if (delta.type === "input_json_delta") {
|
|
16041
|
+
block.partialInput += String(delta.partial_json ?? "");
|
|
16042
|
+
if (block.partialInput.length > 64e3) block.partialInput = block.partialInput.slice(0, 64e3);
|
|
16043
|
+
events.push(asItem(block, "in_progress"));
|
|
16044
|
+
}
|
|
16045
|
+
} else if (event.type === "content_block_stop") {
|
|
16046
|
+
const block = blocks.get(index);
|
|
16047
|
+
if (block && block.type !== "tool_call") events.push(asItem(block, "completed"));
|
|
16048
|
+
}
|
|
16049
|
+
} else if (envelope.type === "assistant") {
|
|
16050
|
+
const message = record2(envelope.message);
|
|
16051
|
+
const id = str(message.id) || messageID || `message:${++messageSequence}`;
|
|
16052
|
+
const parts = Array.isArray(message.content) ? message.content : [];
|
|
16053
|
+
parts.forEach((value, index) => {
|
|
16054
|
+
const part = record2(value);
|
|
16055
|
+
if (!["text", "thinking", "tool_use"].includes(String(part.type))) return;
|
|
16056
|
+
const type = part.type === "tool_use" ? "tool_call" : part.type === "thinking" ? "thought" : "text";
|
|
16057
|
+
const block = { id: type === "tool_call" ? str(part.id) ?? `${id}:${index}` : `${id}:${index}`, type, name: str(part.name) ?? "tool", text: String(part.text ?? part.thinking ?? ""), input: part.input ?? {}, partialInput: "" };
|
|
16058
|
+
if (type === "tool_call") tools.set(block.id, block);
|
|
16059
|
+
if (type === "text") hasText ||= !!block.text;
|
|
16060
|
+
events.push(asItem(block, type === "tool_call" ? "in_progress" : "completed"));
|
|
16061
|
+
});
|
|
16062
|
+
} else if (envelope.type === "user") {
|
|
16063
|
+
const message = record2(envelope.message);
|
|
16064
|
+
for (const value of Array.isArray(message.content) ? message.content : []) {
|
|
16065
|
+
const part = record2(value);
|
|
16066
|
+
if (part.type !== "tool_result") continue;
|
|
16067
|
+
const id = str(part.tool_use_id);
|
|
16068
|
+
if (!id) continue;
|
|
16069
|
+
const block = tools.get(id) ?? { id, type: "tool_call", name: "tool", text: "", input: {}, partialInput: "" };
|
|
16070
|
+
events.push(asItem(block, part.is_error === true ? "failed" : "completed", textContent(part.content)));
|
|
16071
|
+
}
|
|
16072
|
+
} else if (envelope.type === "result") {
|
|
16073
|
+
const text = str(envelope.result) ?? "";
|
|
16074
|
+
if (envelope.is_error === true) {
|
|
16075
|
+
const errors = Array.isArray(envelope.errors) ? envelope.errors.map(String).join("\n") : "";
|
|
16076
|
+
return { ...base, display: [], turnError: text || errors || "Claude \u672C\u8F6E\u6267\u884C\u5931\u8D25", endProcess: true };
|
|
16077
|
+
}
|
|
16078
|
+
if (!hasText && text) events.push({ event: "item", item: { itemId: "answer", type: "text", text, status: "completed" } });
|
|
16079
|
+
return { ...base, display: ["\u25CF \u672C\u8F6E\u5B8C\u6210"], message: text, turnCompleted: true, chatEvents: events, endProcess: true };
|
|
16080
|
+
}
|
|
16081
|
+
return { ...base, display: [], chatEvents: events };
|
|
16082
|
+
}
|
|
16083
|
+
};
|
|
16084
|
+
}
|
|
16085
|
+
|
|
16086
|
+
// src/providers/chat/index.ts
|
|
16087
|
+
var CHAT_PROVIDERS = ["codex", "claude"];
|
|
16088
|
+
function resolveChatProvider(type, request, attachments) {
|
|
16089
|
+
switch (type) {
|
|
16090
|
+
case "codex":
|
|
16091
|
+
return codexChatProvider(request, attachments);
|
|
16092
|
+
case "claude":
|
|
16093
|
+
return claudeChatProvider(attachments);
|
|
16094
|
+
default:
|
|
16095
|
+
throw new Error(`\u6B64 CLI \u7684\u5B9E\u65F6\u804A\u5929\u9A71\u52A8\u5C1A\u672A\u542F\u7528\uFF1A${type}`);
|
|
16096
|
+
}
|
|
16097
|
+
}
|
|
16098
|
+
|
|
15836
16099
|
// src/providers/model-catalog-collector.ts
|
|
15837
16100
|
import { createHash } from "node:crypto";
|
|
15838
16101
|
import { stat } from "node:fs/promises";
|
|
@@ -16404,8 +16667,8 @@ var OrphanRegistry = class {
|
|
|
16404
16667
|
file;
|
|
16405
16668
|
records = /* @__PURE__ */ new Map();
|
|
16406
16669
|
writing = Promise.resolve();
|
|
16407
|
-
add(
|
|
16408
|
-
this.records.set(
|
|
16670
|
+
add(record3) {
|
|
16671
|
+
this.records.set(record3.runId, record3);
|
|
16409
16672
|
this.persist();
|
|
16410
16673
|
}
|
|
16411
16674
|
remove(runId) {
|
|
@@ -16429,12 +16692,12 @@ var OrphanRegistry = class {
|
|
|
16429
16692
|
return [];
|
|
16430
16693
|
}
|
|
16431
16694
|
const reaped = [];
|
|
16432
|
-
for (const
|
|
16433
|
-
if (!
|
|
16434
|
-
const command = await commandOf(
|
|
16435
|
-
if (command && command.includes(basename(
|
|
16436
|
-
await killProcessTree(
|
|
16437
|
-
reaped.push(
|
|
16695
|
+
for (const record3 of previous) {
|
|
16696
|
+
if (!record3 || typeof record3.pid !== "number" || record3.pid <= 1) continue;
|
|
16697
|
+
const command = await commandOf(record3.pid);
|
|
16698
|
+
if (command && command.includes(basename(record3.executable))) {
|
|
16699
|
+
await killProcessTree(record3.pid);
|
|
16700
|
+
reaped.push(record3);
|
|
16438
16701
|
}
|
|
16439
16702
|
}
|
|
16440
16703
|
this.records.clear();
|
|
@@ -16454,8 +16717,8 @@ function commandOf(pid) {
|
|
|
16454
16717
|
|
|
16455
16718
|
// src/runs/run-supervisor.ts
|
|
16456
16719
|
import { spawn as spawn7 } from "node:child_process";
|
|
16457
|
-
import { realpath } from "node:fs/promises";
|
|
16458
|
-
import { isAbsolute as isAbsolute2, relative, resolve as resolve3 } from "node:path";
|
|
16720
|
+
import { mkdir as mkdir4, realpath, stat as stat2, writeFile as writeFile4 } from "node:fs/promises";
|
|
16721
|
+
import { basename as basename2, isAbsolute as isAbsolute2, join as join6, relative, resolve as resolve3 } from "node:path";
|
|
16459
16722
|
import { createInterface as createInterface2 } from "node:readline";
|
|
16460
16723
|
|
|
16461
16724
|
// src/session/terminal-line-sanitizer.ts
|
|
@@ -16594,6 +16857,7 @@ var RunSupervisor = class {
|
|
|
16594
16857
|
recent = /* @__PURE__ */ new Map();
|
|
16595
16858
|
/** Run ids whose finish report has not been acknowledged yet. */
|
|
16596
16859
|
unreported = /* @__PURE__ */ new Set();
|
|
16860
|
+
chatRunIds = /* @__PURE__ */ new Set();
|
|
16597
16861
|
get activeCount() {
|
|
16598
16862
|
return this.active.size;
|
|
16599
16863
|
}
|
|
@@ -16601,8 +16865,15 @@ var RunSupervisor = class {
|
|
|
16601
16865
|
activeRunIds() {
|
|
16602
16866
|
return [.../* @__PURE__ */ new Set([...this.active.keys(), ...this.unreported])];
|
|
16603
16867
|
}
|
|
16868
|
+
activeChatRunIds() {
|
|
16869
|
+
return this.activeRunIds().filter((id) => this.chatRunIds.has(id));
|
|
16870
|
+
}
|
|
16871
|
+
activeTaskRunIds() {
|
|
16872
|
+
return this.activeRunIds().filter((id) => !this.chatRunIds.has(id));
|
|
16873
|
+
}
|
|
16604
16874
|
markReported(runId) {
|
|
16605
16875
|
this.unreported.delete(runId);
|
|
16876
|
+
this.chatRunIds.delete(runId);
|
|
16606
16877
|
}
|
|
16607
16878
|
recentRuns() {
|
|
16608
16879
|
const running = [...this.active.values()].map((run) => ({ runId: run.runId, workstationId: run.workstationId, status: "running", at: run.startedAt }));
|
|
@@ -16618,9 +16889,10 @@ var RunSupervisor = class {
|
|
|
16618
16889
|
this.logger.info("run-duplicate-ignored", { runId: payload.run_id });
|
|
16619
16890
|
return;
|
|
16620
16891
|
}
|
|
16892
|
+
if (payload.chat_session_id) this.chatRunIds.add(payload.run_id);
|
|
16621
16893
|
const refuse = async (error51) => {
|
|
16622
16894
|
this.logger.warn("run-refused", { runId: payload.run_id, error: error51 });
|
|
16623
|
-
this.remember({ runId: payload.run_id, workstationId: payload.workstation_id, tail: new TerminalTailBuffer(), finishedAt: Date.now(), status: "failed" });
|
|
16895
|
+
this.remember({ runId: payload.run_id, workstationId: payload.workstation_id, tail: new TerminalTailBuffer(), finishedAt: Date.now(), status: "failed", chat: !!payload.chat_session_id });
|
|
16624
16896
|
await this.report(payload.run_id, { status: "failed", error: error51, duration_ms: 0 });
|
|
16625
16897
|
};
|
|
16626
16898
|
if ([...this.active.values()].some((run2) => run2.workstationId === payload.workstation_id)) {
|
|
@@ -16633,10 +16905,29 @@ var RunSupervisor = class {
|
|
|
16633
16905
|
let workdir;
|
|
16634
16906
|
try {
|
|
16635
16907
|
provider = resolveHeadlessProvider(payload.agent_type);
|
|
16636
|
-
workdir = await this.validateWorkdir(payload.workdir || this.config.allowedRoots[0]);
|
|
16908
|
+
workdir = payload.chat_session_id && !payload.workdir ? await this.defaultChatWorkdir() : await this.validateWorkdir(payload.workdir || this.config.allowedRoots[0]);
|
|
16637
16909
|
} catch (error51) {
|
|
16638
16910
|
return refuse(errorMessage(error51));
|
|
16639
16911
|
}
|
|
16912
|
+
const request = {
|
|
16913
|
+
prompt: payload.prompt,
|
|
16914
|
+
sessionId: payload.session_id,
|
|
16915
|
+
model: payload.model,
|
|
16916
|
+
reasoningEffort: payload.reasoning_effort,
|
|
16917
|
+
workdir
|
|
16918
|
+
};
|
|
16919
|
+
if (payload.chat_session_id) {
|
|
16920
|
+
try {
|
|
16921
|
+
if (!this.reporter.chatEvents) throw new Error("\u5B9E\u65F6\u804A\u5929\u8F6C\u53D1\u672A\u914D\u7F6E");
|
|
16922
|
+
const attachments = await this.prepareChatAttachments(payload);
|
|
16923
|
+
provider = resolveChatProvider(payload.agent_type, request, attachments);
|
|
16924
|
+
} catch (error51) {
|
|
16925
|
+
return refuse(errorMessage(error51));
|
|
16926
|
+
}
|
|
16927
|
+
}
|
|
16928
|
+
if (!payload.prompt && !payload.attachments?.length) return refuse("\u6D88\u606F\u5185\u5BB9\u4E3A\u7A7A");
|
|
16929
|
+
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 >= this.config.maxConcurrentRuns) return refuse("\u672C\u673A\u540C\u65F6\u8FD0\u884C\u7684\u4EFB\u52A1\u5DF2\u8FBE\u4E0A\u9650");
|
|
16640
16931
|
const launch = provider.buildLaunch({
|
|
16641
16932
|
prompt: payload.prompt,
|
|
16642
16933
|
sessionId: payload.session_id,
|
|
@@ -16652,12 +16943,20 @@ var RunSupervisor = class {
|
|
|
16652
16943
|
detached: process.platform !== "win32",
|
|
16653
16944
|
shell: false,
|
|
16654
16945
|
windowsHide: true,
|
|
16655
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
16946
|
+
stdio: [provider.startInput ? "pipe" : "ignore", "pipe", "pipe"]
|
|
16656
16947
|
});
|
|
16657
16948
|
} catch (error51) {
|
|
16658
16949
|
return refuse(`\u65E0\u6CD5\u542F\u52A8 ${provider.id}\uFF1A${errorMessage(error51)}`);
|
|
16659
16950
|
}
|
|
16660
16951
|
const run = {
|
|
16952
|
+
chat: Boolean(payload.chat_session_id),
|
|
16953
|
+
chatReady: false,
|
|
16954
|
+
eventSequence: 0,
|
|
16955
|
+
pendingEvents: [],
|
|
16956
|
+
eventTail: Promise.resolve(),
|
|
16957
|
+
pendingBytes: 0,
|
|
16958
|
+
queuedBytes: 0,
|
|
16959
|
+
forwardingFailed: false,
|
|
16661
16960
|
runId: payload.run_id,
|
|
16662
16961
|
workstationId: payload.workstation_id,
|
|
16663
16962
|
agentType: provider.id,
|
|
@@ -16683,6 +16982,7 @@ var RunSupervisor = class {
|
|
|
16683
16982
|
`);
|
|
16684
16983
|
this.logger.info("run-started", { runId: run.runId, agentType: provider.id, pid: run.pid, resume: Boolean(payload.session_id) });
|
|
16685
16984
|
let spawnError = "";
|
|
16985
|
+
child.stdin?.on("error", () => void 0);
|
|
16686
16986
|
child.once("error", (error51) => {
|
|
16687
16987
|
spawnError = error51.message;
|
|
16688
16988
|
});
|
|
@@ -16696,6 +16996,24 @@ var RunSupervisor = class {
|
|
|
16696
16996
|
`);
|
|
16697
16997
|
});
|
|
16698
16998
|
child.once("close", (code, signal2) => void this.onExit(run, code, signal2, spawnError));
|
|
16999
|
+
if (provider.startInput) {
|
|
17000
|
+
child.once("spawn", () => {
|
|
17001
|
+
try {
|
|
17002
|
+
provider.startInput?.((message) => {
|
|
17003
|
+
child.stdin?.write(message);
|
|
17004
|
+
});
|
|
17005
|
+
} catch (error51) {
|
|
17006
|
+
run.turnError = errorMessage(error51);
|
|
17007
|
+
void killProcessTree(run.pid);
|
|
17008
|
+
}
|
|
17009
|
+
});
|
|
17010
|
+
run.timers.push(setTimeout(() => {
|
|
17011
|
+
if (!run.chatReady && this.active.has(run.runId)) {
|
|
17012
|
+
run.turnError = "CLI \u5B9E\u65F6\u4F1A\u8BDD\u521D\u59CB\u5316\u8D85\u65F6";
|
|
17013
|
+
void killProcessTree(run.pid);
|
|
17014
|
+
}
|
|
17015
|
+
}, 6e4));
|
|
17016
|
+
}
|
|
16699
17017
|
const maxMs = this.config.maxRunSeconds * 1e3;
|
|
16700
17018
|
run.timers.push(setTimeout(() => {
|
|
16701
17019
|
run.timedOut = true;
|
|
@@ -16710,6 +17028,10 @@ var RunSupervisor = class {
|
|
|
16710
17028
|
onStdout(run, rawLine) {
|
|
16711
17029
|
const line = sanitizeTerminalLine(rawLine);
|
|
16712
17030
|
const parsed = run.provider.parseLine(line);
|
|
17031
|
+
if (run.chat && !run.forwardingFailed) {
|
|
17032
|
+
for (const event of parsed.chatEvents ?? []) this.queueChatEvent(run, event);
|
|
17033
|
+
if (parsed.sessionId) run.chatReady = true;
|
|
17034
|
+
}
|
|
16713
17035
|
for (const row of parsed.display) run.tail.append(`${row}
|
|
16714
17036
|
`);
|
|
16715
17037
|
if (parsed.message !== void 0) run.finalMessage = parsed.message;
|
|
@@ -16718,6 +17040,14 @@ var RunSupervisor = class {
|
|
|
16718
17040
|
run.sessionId = parsed.sessionId;
|
|
16719
17041
|
void this.reporter.started(run.runId, { session_id: parsed.sessionId }).catch(() => void 0);
|
|
16720
17042
|
}
|
|
17043
|
+
if (parsed.endProcess) {
|
|
17044
|
+
run.child.stdin?.end();
|
|
17045
|
+
run.timers.push(setTimeout(() => {
|
|
17046
|
+
if (!this.active.has(run.runId)) return;
|
|
17047
|
+
run.killedAfterCompletion = Boolean(parsed.turnCompleted);
|
|
17048
|
+
void killProcessTree(run.pid);
|
|
17049
|
+
}, 1e3));
|
|
17050
|
+
}
|
|
16721
17051
|
if ((parsed.turnCompleted || parsed.turnError) && !run.turnCompleted) {
|
|
16722
17052
|
run.turnCompleted = true;
|
|
16723
17053
|
run.timers.push(setTimeout(() => {
|
|
@@ -16732,6 +17062,9 @@ var RunSupervisor = class {
|
|
|
16732
17062
|
for (const timer of run.timers) clearTimeout(timer);
|
|
16733
17063
|
this.active.delete(run.runId);
|
|
16734
17064
|
this.orphans?.remove(run.runId);
|
|
17065
|
+
if (run.eventTimer) clearTimeout(run.eventTimer);
|
|
17066
|
+
this.flushChatEvents(run);
|
|
17067
|
+
await run.eventTail;
|
|
16735
17068
|
const durationMs = Date.now() - run.startedAt;
|
|
16736
17069
|
let report;
|
|
16737
17070
|
if (run.cancelRequested) {
|
|
@@ -16753,10 +17086,66 @@ var RunSupervisor = class {
|
|
|
16753
17086
|
if (run.finalMessage) report.final_message = run.finalMessage;
|
|
16754
17087
|
run.tail.append(`\u25CF \u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF1A${report.status}${code !== null ? `\uFF08exit ${code}\uFF09` : ""}
|
|
16755
17088
|
`);
|
|
16756
|
-
this.remember({ runId: run.runId, workstationId: run.workstationId, tail: run.tail, finishedAt: Date.now(), status: report.status });
|
|
17089
|
+
this.remember({ runId: run.runId, workstationId: run.workstationId, tail: run.tail, finishedAt: Date.now(), status: report.status, chat: !!run.chat });
|
|
16757
17090
|
this.logger.info("run-finished", { runId: run.runId, status: report.status, code, durationMs });
|
|
16758
17091
|
await this.report(run.runId, report);
|
|
16759
17092
|
}
|
|
17093
|
+
queueChatEvent(run, event) {
|
|
17094
|
+
const bytes = Buffer.byteLength(JSON.stringify(event));
|
|
17095
|
+
if (run.queuedBytes + bytes > 8 * 1024 * 1024) {
|
|
17096
|
+
run.forwardingFailed = true;
|
|
17097
|
+
run.turnError = "\u5B9E\u65F6\u8F93\u51FA\u8F6C\u53D1\u79EF\u538B\uFF0C\u8BF7\u68C0\u67E5\u8BBE\u5907\u7F51\u7EDC";
|
|
17098
|
+
void killProcessTree(run.pid);
|
|
17099
|
+
return;
|
|
17100
|
+
}
|
|
17101
|
+
run.pendingEvents.push({ ...event, sequence: ++run.eventSequence });
|
|
17102
|
+
run.pendingBytes += bytes;
|
|
17103
|
+
run.queuedBytes += bytes;
|
|
17104
|
+
if (run.pendingBytes > 512 * 1024 || run.pendingEvents.length >= 64) this.flushChatEvents(run);
|
|
17105
|
+
else if (!run.eventTimer) run.eventTimer = setTimeout(() => {
|
|
17106
|
+
delete run.eventTimer;
|
|
17107
|
+
this.flushChatEvents(run);
|
|
17108
|
+
}, 60);
|
|
17109
|
+
}
|
|
17110
|
+
flushChatEvents(run) {
|
|
17111
|
+
if (!run.pendingEvents.length) return;
|
|
17112
|
+
if (run.eventTimer) {
|
|
17113
|
+
clearTimeout(run.eventTimer);
|
|
17114
|
+
delete run.eventTimer;
|
|
17115
|
+
}
|
|
17116
|
+
const events = run.pendingEvents.splice(0);
|
|
17117
|
+
const bytes = run.pendingBytes;
|
|
17118
|
+
run.pendingBytes = 0;
|
|
17119
|
+
run.eventTail = run.eventTail.then(async () => {
|
|
17120
|
+
if (run.forwardingFailed) return;
|
|
17121
|
+
await this.reporter.chatEvents?.(run.runId, events);
|
|
17122
|
+
}).catch((error51) => {
|
|
17123
|
+
run.forwardingFailed = true;
|
|
17124
|
+
run.turnError = `\u5B9E\u65F6\u8F93\u51FA\u8F6C\u53D1\u5931\u8D25\uFF1A${errorMessage(error51)}`;
|
|
17125
|
+
this.logger.warn("chat-events-failed", { runId: run.runId, error: errorMessage(error51) });
|
|
17126
|
+
if (this.active.has(run.runId)) void killProcessTree(run.pid);
|
|
17127
|
+
}).finally(() => {
|
|
17128
|
+
run.queuedBytes -= bytes;
|
|
17129
|
+
});
|
|
17130
|
+
}
|
|
17131
|
+
async prepareChatAttachments(payload) {
|
|
17132
|
+
if (!payload.attachments?.length) return [];
|
|
17133
|
+
if (!this.reporter.chatMedia) throw new Error("\u9644\u4EF6\u8F6C\u53D1\u672A\u914D\u7F6E");
|
|
17134
|
+
const directory = join6(this.config.dataDir, "chat-media", encodeURIComponent(payload.chat_session_id ?? payload.run_id));
|
|
17135
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
17136
|
+
const files = [];
|
|
17137
|
+
for (const attachment of payload.attachments) {
|
|
17138
|
+
const name = basename2(attachment.name).replace(/[^\p{L}\p{N}._-]/gu, "_").slice(-120) || "attachment";
|
|
17139
|
+
const path = join6(directory, `${encodeURIComponent(attachment.media_id)}-${name}`);
|
|
17140
|
+
const existing = await stat2(path).catch(() => void 0);
|
|
17141
|
+
if (!existing?.isFile()) {
|
|
17142
|
+
const data = await this.reporter.chatMedia(payload.run_id, attachment.media_id);
|
|
17143
|
+
await writeFile4(path, data, { mode: 384 });
|
|
17144
|
+
}
|
|
17145
|
+
files.push({ path, name: attachment.name, kind: attachment.kind });
|
|
17146
|
+
}
|
|
17147
|
+
return files;
|
|
17148
|
+
}
|
|
16760
17149
|
async report(runId, report) {
|
|
16761
17150
|
this.unreported.add(runId);
|
|
16762
17151
|
await this.reporter.finished(runId, report);
|
|
@@ -16799,6 +17188,15 @@ var RunSupervisor = class {
|
|
|
16799
17188
|
if (item.finishedAt < cutoff || this.recent.size > MAX_RECENT_RUNS) this.recent.delete(id);
|
|
16800
17189
|
}
|
|
16801
17190
|
}
|
|
17191
|
+
async defaultChatWorkdir() {
|
|
17192
|
+
for (const root of this.config.allowedRoots) {
|
|
17193
|
+
try {
|
|
17194
|
+
return await this.validateWorkdir(root);
|
|
17195
|
+
} catch {
|
|
17196
|
+
}
|
|
17197
|
+
}
|
|
17198
|
+
throw new Error("\u6CA1\u6709\u53EF\u7528\u7684\u804A\u5929\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u8BF7\u914D\u7F6E\u4ECD\u5B58\u5728\u7684\u5141\u8BB8\u76EE\u5F55");
|
|
17199
|
+
}
|
|
16802
17200
|
async validateWorkdir(input) {
|
|
16803
17201
|
if (!input) throw new Error("\u6CA1\u6709\u914D\u7F6E\u5141\u8BB8\u7684\u5DE5\u4F5C\u76EE\u5F55");
|
|
16804
17202
|
let candidate;
|
|
@@ -16873,9 +17271,31 @@ var GatewayClient = class {
|
|
|
16873
17271
|
const data = await this.request("GET", `/commands?wait=${waitSeconds}`, void 0, (waitSeconds + 15) * 1e3, signal2);
|
|
16874
17272
|
return data.items ?? [];
|
|
16875
17273
|
}
|
|
17274
|
+
chatHeartbeat(runnerId, providers, activeRuns) {
|
|
17275
|
+
return this.request("POST", "/chat-heartbeat", { runner_id: runnerId, providers, active_runs: activeRuns }, 15e3);
|
|
17276
|
+
}
|
|
17277
|
+
async pollChat(waitSeconds, runnerId, signal2) {
|
|
17278
|
+
const data = await this.request("GET", `/chat-commands?wait=${waitSeconds}&runner_id=${encodeURIComponent(runnerId)}`, void 0, (waitSeconds + 15) * 1e3, signal2);
|
|
17279
|
+
return data.items ?? [];
|
|
17280
|
+
}
|
|
16876
17281
|
commandResult(commandId, body) {
|
|
16877
17282
|
return this.request("POST", `/commands/${encodeURIComponent(commandId)}/result`, body, 15e3);
|
|
16878
17283
|
}
|
|
17284
|
+
chatEvents(runId, events) {
|
|
17285
|
+
return this.request("POST", `/runs/${encodeURIComponent(runId)}/chat-events`, { events }, 15e3);
|
|
17286
|
+
}
|
|
17287
|
+
async chatMedia(runId, mediaId) {
|
|
17288
|
+
const response = await fetch(`${this.config.serverUrl}/api/agent/runs/${encodeURIComponent(runId)}/chat-media/${encodeURIComponent(mediaId)}`, {
|
|
17289
|
+
headers: { authorization: `Bearer ${this.config.token}` },
|
|
17290
|
+
signal: AbortSignal.timeout(3e4)
|
|
17291
|
+
});
|
|
17292
|
+
if (!response.ok) throw new GatewayError("\u65E0\u6CD5\u8BFB\u53D6\u804A\u5929\u9644\u4EF6", response.status);
|
|
17293
|
+
const declared = Number(response.headers.get("content-length") ?? 0);
|
|
17294
|
+
if (declared > 10 * 1024 * 1024) throw new GatewayError("\u804A\u5929\u9644\u4EF6\u8D85\u8FC7 10 MB", 413);
|
|
17295
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
17296
|
+
if (bytes.length > 10 * 1024 * 1024) throw new GatewayError("\u804A\u5929\u9644\u4EF6\u8D85\u8FC7 10 MB", 413);
|
|
17297
|
+
return bytes;
|
|
17298
|
+
}
|
|
16879
17299
|
runStarted(runId, body) {
|
|
16880
17300
|
return this.request("POST", `/runs/${encodeURIComponent(runId)}/started`, body, 15e3);
|
|
16881
17301
|
}
|
|
@@ -16885,7 +17305,7 @@ var GatewayClient = class {
|
|
|
16885
17305
|
};
|
|
16886
17306
|
|
|
16887
17307
|
// src/workspace/directory-browser.ts
|
|
16888
|
-
import { readdir, realpath as realpath2, stat as
|
|
17308
|
+
import { readdir, realpath as realpath2, stat as stat3 } from "node:fs/promises";
|
|
16889
17309
|
import { homedir as homedir5 } from "node:os";
|
|
16890
17310
|
import { dirname as dirname6, parse as parse3, resolve as resolve4 } from "node:path";
|
|
16891
17311
|
var MAX_ENTRIES = 1e3;
|
|
@@ -16894,7 +17314,7 @@ async function listDirectory(requested, allowedRoots) {
|
|
|
16894
17314
|
const directory = await realpath2(resolve4(raw)).catch(() => {
|
|
16895
17315
|
throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${requested}`);
|
|
16896
17316
|
});
|
|
16897
|
-
if (!(await
|
|
17317
|
+
if (!(await stat3(directory)).isDirectory()) throw new Error("\u8FD9\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55");
|
|
16898
17318
|
const entries = await readdir(directory, { withFileTypes: true }).catch((error51) => {
|
|
16899
17319
|
throw new Error(error51.code === "EACCES" || error51.code === "EPERM" ? "\u6CA1\u6709\u6743\u9650\u8BFB\u53D6\u8FD9\u4E2A\u76EE\u5F55" : error51.message);
|
|
16900
17320
|
});
|
|
@@ -16923,9 +17343,9 @@ async function listDirectory(requested, allowedRoots) {
|
|
|
16923
17343
|
|
|
16924
17344
|
// src/workspace/workspace-manager.ts
|
|
16925
17345
|
import { spawn as spawn8 } from "node:child_process";
|
|
16926
|
-
import { chmod as chmod3, mkdir as
|
|
17346
|
+
import { chmod as chmod3, mkdir as mkdir5, mkdtemp, opendir, realpath as realpath3, rm as rm2, stat as stat4, writeFile as writeFile5 } from "node:fs/promises";
|
|
16927
17347
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
16928
|
-
import { isAbsolute as isAbsolute3, join as
|
|
17348
|
+
import { isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve5 } from "node:path";
|
|
16929
17349
|
var MAX_NODES = 2e3;
|
|
16930
17350
|
var MAX_DIRECTORY_ENTRIES = 12e3;
|
|
16931
17351
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -16979,7 +17399,7 @@ var WorkspaceManager = class {
|
|
|
16979
17399
|
return root;
|
|
16980
17400
|
}
|
|
16981
17401
|
async syncTree(rootPath, request) {
|
|
16982
|
-
await
|
|
17402
|
+
await mkdir5(rootPath, { recursive: true });
|
|
16983
17403
|
await addAllowedRoot(this.config, rootPath);
|
|
16984
17404
|
const results = [];
|
|
16985
17405
|
const requestedPaths = /* @__PURE__ */ new Set();
|
|
@@ -16989,7 +17409,7 @@ var WorkspaceManager = class {
|
|
|
16989
17409
|
requestedPaths.add(relativePath);
|
|
16990
17410
|
const target = this.resolveWithinRoot(rootPath, relativePath);
|
|
16991
17411
|
try {
|
|
16992
|
-
const metadata = await
|
|
17412
|
+
const metadata = await stat4(target).catch(() => void 0);
|
|
16993
17413
|
if (metadata) {
|
|
16994
17414
|
if (!metadata.isDirectory()) {
|
|
16995
17415
|
throw new Error("The existing workspace path is not a directory");
|
|
@@ -17003,7 +17423,7 @@ var WorkspaceManager = class {
|
|
|
17003
17423
|
});
|
|
17004
17424
|
continue;
|
|
17005
17425
|
}
|
|
17006
|
-
await
|
|
17426
|
+
await mkdir5(target, { recursive: true });
|
|
17007
17427
|
results.push({
|
|
17008
17428
|
relative_path: relativePath,
|
|
17009
17429
|
status: "created",
|
|
@@ -17047,12 +17467,12 @@ var WorkspaceManager = class {
|
|
|
17047
17467
|
let created = false;
|
|
17048
17468
|
let metadata;
|
|
17049
17469
|
try {
|
|
17050
|
-
metadata = await
|
|
17470
|
+
metadata = await stat4(target);
|
|
17051
17471
|
} catch {
|
|
17052
17472
|
}
|
|
17053
17473
|
if (!metadata) {
|
|
17054
17474
|
if (!gitUrl) throw new Error("A missing repository needs a Git remote URL");
|
|
17055
|
-
await
|
|
17475
|
+
await mkdir5(resolve5(target, ".."), { recursive: true });
|
|
17056
17476
|
const cloneArgs = branch ? ["clone", "--branch", branch, "--single-branch", gitUrl, target] : ["clone", gitUrl, target];
|
|
17057
17477
|
await this.runGit(cloneArgs, rootPath, timeoutSeconds, false, gitEnvironment);
|
|
17058
17478
|
created = true;
|
|
@@ -17147,7 +17567,7 @@ var WorkspaceManager = class {
|
|
|
17147
17567
|
}
|
|
17148
17568
|
async scanTree(rootPath, maxDepth, scanRoots) {
|
|
17149
17569
|
const resolvedRoot = await realpath3(rootPath);
|
|
17150
|
-
const rootMetadata = await
|
|
17570
|
+
const rootMetadata = await stat4(resolvedRoot);
|
|
17151
17571
|
if (!rootMetadata.isDirectory()) throw new Error("The workspace root is not a directory");
|
|
17152
17572
|
const nodes = [];
|
|
17153
17573
|
let entriesRead = 0;
|
|
@@ -17202,7 +17622,7 @@ var WorkspaceManager = class {
|
|
|
17202
17622
|
const target = this.resolveWithinRoot(resolvedRoot, relativePath);
|
|
17203
17623
|
let metadata;
|
|
17204
17624
|
try {
|
|
17205
|
-
metadata = await
|
|
17625
|
+
metadata = await stat4(target);
|
|
17206
17626
|
} catch (error51) {
|
|
17207
17627
|
this.logger.warn("workspace-scan-root-missing", {
|
|
17208
17628
|
rootPath: target,
|
|
@@ -17299,13 +17719,13 @@ var WorkspaceManager = class {
|
|
|
17299
17719
|
}
|
|
17300
17720
|
async createGitAuthEnvironment(auth) {
|
|
17301
17721
|
if (!auth) return void 0;
|
|
17302
|
-
const directory = await mkdtemp(
|
|
17722
|
+
const directory = await mkdtemp(join7(tmpdir(), "toc-ocra-git-auth-"));
|
|
17303
17723
|
const isWindows = process.platform === "win32";
|
|
17304
|
-
const scriptPath =
|
|
17724
|
+
const scriptPath = join7(directory, isWindows ? "askpass.cmd" : "askpass.sh");
|
|
17305
17725
|
let script;
|
|
17306
17726
|
if (isWindows) {
|
|
17307
|
-
const helperPath =
|
|
17308
|
-
await
|
|
17727
|
+
const helperPath = join7(directory, "askpass.cjs");
|
|
17728
|
+
await writeFile5(
|
|
17309
17729
|
helperPath,
|
|
17310
17730
|
'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',
|
|
17311
17731
|
{ mode: 448 }
|
|
@@ -17314,7 +17734,7 @@ var WorkspaceManager = class {
|
|
|
17314
17734
|
} else {
|
|
17315
17735
|
script = '#!/bin/sh\ncase "$1" in *Username*|*username*) printf "%s\\n" "$ECHO_GIT_USERNAME" ;; *) printf "%s\\n" "$ECHO_GIT_PASSWORD" ;; esac\n';
|
|
17316
17736
|
}
|
|
17317
|
-
await
|
|
17737
|
+
await writeFile5(scriptPath, script, { mode: 448 });
|
|
17318
17738
|
if (!isWindows) await chmod3(scriptPath, 448);
|
|
17319
17739
|
return {
|
|
17320
17740
|
env: {
|
|
@@ -17347,7 +17767,9 @@ var RunStartPayloadSchema = external_exports.object({
|
|
|
17347
17767
|
agent_type: external_exports.string().min(1).max(40),
|
|
17348
17768
|
task_chain_id: external_exports.string().optional().default(""),
|
|
17349
17769
|
message_id: external_exports.string().optional().default(""),
|
|
17350
|
-
prompt: external_exports.string().
|
|
17770
|
+
prompt: external_exports.string().max(512 * 1024),
|
|
17771
|
+
chat_session_id: external_exports.string().max(64).optional(),
|
|
17772
|
+
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(),
|
|
17351
17773
|
session_id: external_exports.string().max(200).optional().default(""),
|
|
17352
17774
|
model: external_exports.string().max(100).optional().default(""),
|
|
17353
17775
|
reasoning_effort: external_exports.string().max(40).optional().default(""),
|
|
@@ -17402,9 +17824,10 @@ var sleep2 = (ms, signal2) => new Promise((resolve7) => {
|
|
|
17402
17824
|
}, { once: true });
|
|
17403
17825
|
});
|
|
17404
17826
|
var AgentRuntime = class {
|
|
17405
|
-
constructor(config2, version2, logger) {
|
|
17827
|
+
constructor(config2, version2, logger, chatOnly = false) {
|
|
17406
17828
|
this.config = config2;
|
|
17407
17829
|
this.version = version2;
|
|
17830
|
+
this.chatOnly = chatOnly;
|
|
17408
17831
|
this.ownsLogger = !logger;
|
|
17409
17832
|
this.logger = logger ?? createDiagnosticLogger(config2.logFile, { level: config2.logLevel, maxAgeMs: 5 * 60 * 1e3 });
|
|
17410
17833
|
this.client = new GatewayClient(config2);
|
|
@@ -17413,17 +17836,22 @@ var AgentRuntime = class {
|
|
|
17413
17836
|
started: async (runId, report) => {
|
|
17414
17837
|
await this.retry(() => this.client.runStarted(runId, report), 3);
|
|
17415
17838
|
},
|
|
17839
|
+
chatEvents: async (runId, events) => {
|
|
17840
|
+
await this.retry(() => this.client.chatEvents(runId, events), 5);
|
|
17841
|
+
},
|
|
17842
|
+
chatMedia: (runId, mediaId) => this.client.chatMedia(runId, mediaId),
|
|
17416
17843
|
finished: async (runId, report) => {
|
|
17417
17844
|
this.pendingReports.set(runId, report);
|
|
17418
17845
|
await this.flushReport(runId);
|
|
17419
17846
|
}
|
|
17420
17847
|
};
|
|
17421
17848
|
this.workspaces = new WorkspaceManager(config2, this.logger);
|
|
17422
|
-
this.orphans = new OrphanRegistry(
|
|
17849
|
+
this.orphans = new OrphanRegistry(join8(config2.dataDir, chatOnly ? "chat-active-runs.json" : "active-runs.json"));
|
|
17423
17850
|
this.runs = new RunSupervisor(config2, this.logger, reporter, this.orphans);
|
|
17424
17851
|
}
|
|
17425
17852
|
config;
|
|
17426
17853
|
version;
|
|
17854
|
+
chatOnly;
|
|
17427
17855
|
logger;
|
|
17428
17856
|
client;
|
|
17429
17857
|
runs;
|
|
@@ -17431,6 +17859,10 @@ var AgentRuntime = class {
|
|
|
17431
17859
|
workspaces;
|
|
17432
17860
|
stopController = new AbortController();
|
|
17433
17861
|
startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17862
|
+
chatRunnerId = randomUUID2();
|
|
17863
|
+
chatReady = false;
|
|
17864
|
+
chatLeaseInitialized = false;
|
|
17865
|
+
chatError;
|
|
17434
17866
|
detection;
|
|
17435
17867
|
policy = DEFAULT_POLICY;
|
|
17436
17868
|
heartbeatInterval = 30;
|
|
@@ -17450,13 +17882,14 @@ var AgentRuntime = class {
|
|
|
17450
17882
|
/** Hash of the catalogs the server has stored (from its heartbeat reply). */
|
|
17451
17883
|
serverCatalogHash;
|
|
17452
17884
|
async start() {
|
|
17453
|
-
const reaped = await this.orphans.reapPrevious();
|
|
17885
|
+
const reaped = this.chatOnly ? [] : await this.orphans.reapPrevious();
|
|
17454
17886
|
if (reaped.length > 0) this.logger.warn("orphan-runs-stopped", { runIds: reaped.map((r) => r.runId) });
|
|
17455
17887
|
this.detection = await cliDetection.detect();
|
|
17456
17888
|
await this.refreshCatalogs();
|
|
17457
17889
|
await this.heartbeat();
|
|
17458
17890
|
void this.heartbeatLoop();
|
|
17459
|
-
void this.pollLoop();
|
|
17891
|
+
if (!this.chatOnly) void this.pollLoop();
|
|
17892
|
+
void this.pollLoop(true);
|
|
17460
17893
|
this.logger.info("agent-started", { serverUrl: this.config.serverUrl, deviceId: this.config.deviceId });
|
|
17461
17894
|
process.stdout.write(`toc-ocra ${this.version} \u5DF2\u542F\u52A8\uFF1A${this.config.serverUrl}\uFF08\u8BBE\u5907 ${this.deviceName}\uFF09
|
|
17462
17895
|
`);
|
|
@@ -17503,12 +17936,12 @@ var AgentRuntime = class {
|
|
|
17503
17936
|
for (const runId of [...this.pendingReports.keys()]) await this.flushReport(runId, 1);
|
|
17504
17937
|
const report = this.catalogReport;
|
|
17505
17938
|
const sendCatalogs = report !== void 0 && report.hash !== this.serverCatalogHash;
|
|
17506
|
-
try {
|
|
17939
|
+
if (!this.chatOnly) try {
|
|
17507
17940
|
const response = await this.client.heartbeat({
|
|
17508
17941
|
agent_version: this.version,
|
|
17509
17942
|
hostname: this.config.hostname,
|
|
17510
17943
|
capabilities: this.capabilitySummary(),
|
|
17511
|
-
active_runs: this.runs.
|
|
17944
|
+
active_runs: this.runs.activeTaskRunIds(),
|
|
17512
17945
|
...sendCatalogs ? { model_catalogs: report } : {}
|
|
17513
17946
|
});
|
|
17514
17947
|
this.serverCatalogHash = response.model_catalog_hash ?? (sendCatalogs ? report.hash : this.serverCatalogHash);
|
|
@@ -17522,6 +17955,27 @@ var AgentRuntime = class {
|
|
|
17522
17955
|
} catch (error51) {
|
|
17523
17956
|
this.noteFailure("heartbeat", error51);
|
|
17524
17957
|
}
|
|
17958
|
+
try {
|
|
17959
|
+
await this.client.chatHeartbeat(this.chatRunnerId, CHAT_PROVIDERS.filter((id) => this.detection?.detectedIds.includes(id)), this.runs.activeChatRunIds());
|
|
17960
|
+
if (!this.chatLeaseInitialized && this.chatOnly) {
|
|
17961
|
+
const reaped = await this.orphans.reapPrevious();
|
|
17962
|
+
if (reaped.length) this.logger.warn("chat-orphans-stopped", { runIds: reaped.map((run) => run.runId) });
|
|
17963
|
+
}
|
|
17964
|
+
this.chatLeaseInitialized = true;
|
|
17965
|
+
if (!this.chatReady) this.logger.info("chat-channel-ready");
|
|
17966
|
+
this.chatReady = true;
|
|
17967
|
+
this.chatError = void 0;
|
|
17968
|
+
if (this.chatOnly) {
|
|
17969
|
+
this.lastHeartbeatOk = true;
|
|
17970
|
+
this.heartbeatInterval = 20;
|
|
17971
|
+
}
|
|
17972
|
+
} catch (error51) {
|
|
17973
|
+
this.chatReady = false;
|
|
17974
|
+
const reason = error51 instanceof Error ? error51.message : String(error51);
|
|
17975
|
+
if (this.chatError !== reason) this.logger.warn("chat-channel-unavailable", { error: reason });
|
|
17976
|
+
this.chatError = reason;
|
|
17977
|
+
if (this.chatOnly) this.noteFailure("chat-heartbeat", error51);
|
|
17978
|
+
}
|
|
17525
17979
|
void this.refreshCatalogs();
|
|
17526
17980
|
}
|
|
17527
17981
|
/** Re-reads catalogs whose sources changed; concurrent calls share one run. */
|
|
@@ -17545,6 +17999,7 @@ var AgentRuntime = class {
|
|
|
17545
17999
|
if (!snapshot) return { providers: [] };
|
|
17546
18000
|
return {
|
|
17547
18001
|
providers: snapshot.providers.map((p) => ({ id: p.id, installed: p.installed, runnable: p.available, version: p.version ?? "", path: p.resolvedPath ?? "" })),
|
|
18002
|
+
cli_chat: { version: 1, providers: CHAT_PROVIDERS.filter((id) => snapshot.detectedIds.includes(id)) },
|
|
17548
18003
|
detected_ids: snapshot.detectedIds,
|
|
17549
18004
|
refreshed_at: snapshot.refreshedAt
|
|
17550
18005
|
};
|
|
@@ -17561,16 +18016,23 @@ var AgentRuntime = class {
|
|
|
17561
18016
|
this.lastError = { reason, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
17562
18017
|
}
|
|
17563
18018
|
// ---- Commands ----
|
|
17564
|
-
async pollLoop() {
|
|
18019
|
+
async pollLoop(chat = false) {
|
|
17565
18020
|
let backoff = 1e3;
|
|
17566
18021
|
while (!this.stopController.signal.aborted) {
|
|
17567
18022
|
try {
|
|
17568
|
-
|
|
18023
|
+
if (chat && !this.chatReady) {
|
|
18024
|
+
await sleep2(1e3, this.stopController.signal);
|
|
18025
|
+
continue;
|
|
18026
|
+
}
|
|
18027
|
+
const commands = chat ? await this.client.pollChat(this.pollWait, this.chatRunnerId, this.stopController.signal) : await this.client.poll(this.pollWait, this.stopController.signal);
|
|
17569
18028
|
backoff = 1e3;
|
|
17570
18029
|
for (const command of commands) void this.handle(command);
|
|
17571
18030
|
} catch (error51) {
|
|
17572
18031
|
if (this.stopController.signal.aborted) return;
|
|
17573
|
-
|
|
18032
|
+
if (chat) {
|
|
18033
|
+
this.chatReady = false;
|
|
18034
|
+
this.logger.warn("chat-poll-failed", { error: error51 instanceof Error ? error51.message : String(error51) });
|
|
18035
|
+
} else this.noteFailure("poll", error51);
|
|
17574
18036
|
const unauthorized = error51 instanceof GatewayError && error51.unauthorized;
|
|
17575
18037
|
await sleep2(unauthorized ? 6e4 : backoff, this.stopController.signal);
|
|
17576
18038
|
backoff = Math.min(backoff * 2, 3e4);
|
|
@@ -17591,11 +18053,13 @@ var AgentRuntime = class {
|
|
|
17591
18053
|
}
|
|
17592
18054
|
async execute(command) {
|
|
17593
18055
|
switch (command.type) {
|
|
18056
|
+
case "chat.run.start":
|
|
17594
18057
|
case "run.start": {
|
|
17595
18058
|
const payload = RunStartPayloadSchema.parse(command.payload);
|
|
17596
18059
|
await this.runs.start(payload);
|
|
17597
18060
|
return { success: true, result: { accepted: true } };
|
|
17598
18061
|
}
|
|
18062
|
+
case "chat.run.cancel":
|
|
17599
18063
|
case "run.cancel": {
|
|
17600
18064
|
const payload = RunCancelPayloadSchema.parse(command.payload);
|
|
17601
18065
|
const cancelled = await this.runs.cancel(payload.run_id);
|
|
@@ -17923,7 +18387,7 @@ function preferSystemNode() {
|
|
|
17923
18387
|
const names = process.platform === "win32" ? ["node.exe"] : ["node"];
|
|
17924
18388
|
for (const directory of (process.env.PATH ?? "").split(delimiter5)) {
|
|
17925
18389
|
for (const name of names) {
|
|
17926
|
-
const candidate = directory &&
|
|
18390
|
+
const candidate = directory && join9(directory, name);
|
|
17927
18391
|
if (candidate && existsSync6(candidate)) {
|
|
17928
18392
|
process.env.TOC_OCRA_NODE_PATH = candidate;
|
|
17929
18393
|
return;
|