toc-ocra 0.2.5 → 0.2.6
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 +386 -35
- 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/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/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 +154 -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 +76 -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";
|
|
@@ -16454,10 +16455,158 @@ function commandOf(pid) {
|
|
|
16454
16455
|
|
|
16455
16456
|
// src/runs/run-supervisor.ts
|
|
16456
16457
|
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";
|
|
16458
|
+
import { mkdir as mkdir4, realpath, stat as stat2, writeFile as writeFile4 } from "node:fs/promises";
|
|
16459
|
+
import { basename as basename2, isAbsolute as isAbsolute2, join as join6, relative, resolve as resolve3 } from "node:path";
|
|
16459
16460
|
import { createInterface as createInterface2 } from "node:readline";
|
|
16460
16461
|
|
|
16462
|
+
// src/providers/chat/codex-app-server.ts
|
|
16463
|
+
function codexChatProvider(request, attachments) {
|
|
16464
|
+
let write;
|
|
16465
|
+
let threadId = request.sessionId;
|
|
16466
|
+
let hasStartedTurn = false;
|
|
16467
|
+
const send = (message) => write?.(`${JSON.stringify(message)}
|
|
16468
|
+
`);
|
|
16469
|
+
const options = {
|
|
16470
|
+
cwd: request.workdir,
|
|
16471
|
+
approvalPolicy: "never",
|
|
16472
|
+
sandbox: "danger-full-access",
|
|
16473
|
+
...request.model ? { model: request.model } : {}
|
|
16474
|
+
};
|
|
16475
|
+
const threadRequest = () => send({
|
|
16476
|
+
id: 2,
|
|
16477
|
+
method: threadId ? "thread/resume" : "thread/start",
|
|
16478
|
+
params: { ...options, ...threadId ? { threadId } : { ephemeral: false } }
|
|
16479
|
+
});
|
|
16480
|
+
const startTurn = () => {
|
|
16481
|
+
if (hasStartedTurn) return;
|
|
16482
|
+
hasStartedTurn = true;
|
|
16483
|
+
const input = [];
|
|
16484
|
+
if (request.prompt) input.push({ type: "text", text: request.prompt });
|
|
16485
|
+
for (const attachment of attachments) {
|
|
16486
|
+
if (attachment.kind === "image") input.push({ type: "localImage", path: attachment.path });
|
|
16487
|
+
else input.push({ type: "text", text: `\u9644\u4EF6\u6587\u4EF6\uFF1A${attachment.name}
|
|
16488
|
+
\u672C\u673A\u8DEF\u5F84\uFF1A${attachment.path}` });
|
|
16489
|
+
}
|
|
16490
|
+
send({ id: 3, method: "turn/start", params: {
|
|
16491
|
+
threadId,
|
|
16492
|
+
input,
|
|
16493
|
+
cwd: request.workdir,
|
|
16494
|
+
approvalPolicy: "never",
|
|
16495
|
+
sandboxPolicy: { type: "dangerFullAccess" },
|
|
16496
|
+
...request.model ? { model: request.model } : {},
|
|
16497
|
+
...request.reasoningEffort ? { effort: request.reasoningEffort } : {}
|
|
16498
|
+
} });
|
|
16499
|
+
};
|
|
16500
|
+
const empty = () => ({ display: [] });
|
|
16501
|
+
return {
|
|
16502
|
+
id: "codex",
|
|
16503
|
+
buildLaunch() {
|
|
16504
|
+
return resolveExecutable("codex", ["app-server", "--listen", "stdio://"], providerEnvironment(), process.env.TOC_OCRA_CODEX_PATH);
|
|
16505
|
+
},
|
|
16506
|
+
startInput(writer) {
|
|
16507
|
+
write = writer;
|
|
16508
|
+
send({ id: 1, method: "initialize", params: {
|
|
16509
|
+
clientInfo: { name: "echo_cli_chat", title: "Echo CLI Chat", version: "1.0" },
|
|
16510
|
+
capabilities: { experimentalApi: true }
|
|
16511
|
+
} });
|
|
16512
|
+
},
|
|
16513
|
+
parseLine(line) {
|
|
16514
|
+
const message = tryJson(line);
|
|
16515
|
+
if (!message) return empty();
|
|
16516
|
+
if (message.id !== void 0 && !message.method) {
|
|
16517
|
+
const error51 = message.error;
|
|
16518
|
+
if (error51) return { display: [], turnError: str(error51.message) ?? "Codex \u4F1A\u8BDD\u8BF7\u6C42\u5931\u8D25", endProcess: true };
|
|
16519
|
+
if (message.id === 1) {
|
|
16520
|
+
send({ method: "initialized" });
|
|
16521
|
+
threadRequest();
|
|
16522
|
+
return empty();
|
|
16523
|
+
}
|
|
16524
|
+
if (message.id === 2) {
|
|
16525
|
+
const result = message.result;
|
|
16526
|
+
const thread = result?.thread;
|
|
16527
|
+
const id = str(thread?.id);
|
|
16528
|
+
if (!id) return { display: [], turnError: "Codex \u672A\u8FD4\u56DE\u539F\u751F\u4F1A\u8BDD ID", endProcess: true };
|
|
16529
|
+
threadId = id;
|
|
16530
|
+
startTurn();
|
|
16531
|
+
return { display: [`\u25CF \u4F1A\u8BDD ${id}`], sessionId: id };
|
|
16532
|
+
}
|
|
16533
|
+
return empty();
|
|
16534
|
+
}
|
|
16535
|
+
const params = message.params ?? {};
|
|
16536
|
+
if (params.threadId && params.threadId !== threadId) return empty();
|
|
16537
|
+
if (message.id !== void 0) {
|
|
16538
|
+
send({ id: message.id, error: { code: -32601, message: "\u5F53\u524D\u804A\u5929\u5C1A\u672A\u63A5\u5165\u4EA4\u4E92\u5F0F\u6743\u9650\u6216\u95EE\u7B54" } });
|
|
16539
|
+
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 };
|
|
16540
|
+
}
|
|
16541
|
+
const itemId = str(params.itemId) ?? "";
|
|
16542
|
+
switch (message.method) {
|
|
16543
|
+
case "item/agentMessage/delta":
|
|
16544
|
+
case "item/reasoning/textDelta":
|
|
16545
|
+
case "item/reasoning/summaryTextDelta": {
|
|
16546
|
+
const text = typeof params.delta === "string" ? params.delta : "";
|
|
16547
|
+
return { display: [], chatEvents: itemId && text ? [{ event: message.method === "item/agentMessage/delta" ? "text_delta" : "thought_delta", item_id: itemId, text }] : [] };
|
|
16548
|
+
}
|
|
16549
|
+
case "turn/plan/updated": {
|
|
16550
|
+
const plan = Array.isArray(params.plan) ? params.plan : [];
|
|
16551
|
+
return { display: [], chatEvents: [{ event: "item", item: {
|
|
16552
|
+
itemId: "plan",
|
|
16553
|
+
type: "plan",
|
|
16554
|
+
entries: plan.map((entry) => ({ content: String(entry.step ?? ""), status: String(entry.status ?? "pending") }))
|
|
16555
|
+
} }] };
|
|
16556
|
+
}
|
|
16557
|
+
case "item/started":
|
|
16558
|
+
case "item/completed": {
|
|
16559
|
+
const item = params.item ?? {};
|
|
16560
|
+
const id = str(item.id);
|
|
16561
|
+
if (!id || item.type === "userMessage") return empty();
|
|
16562
|
+
const completed = message.method === "item/completed";
|
|
16563
|
+
const status = completed ? item.status === "failed" ? "failed" : "completed" : "in_progress";
|
|
16564
|
+
let normalized;
|
|
16565
|
+
switch (item.type) {
|
|
16566
|
+
case "agentMessage":
|
|
16567
|
+
normalized = { itemId: id, type: "text", text: String(item.text ?? ""), status };
|
|
16568
|
+
break;
|
|
16569
|
+
case "reasoning": {
|
|
16570
|
+
const summary = Array.isArray(item.summary) ? item.summary.map(String).join("\n\n") : "";
|
|
16571
|
+
normalized = { itemId: id, type: "thought", text: summary, status };
|
|
16572
|
+
break;
|
|
16573
|
+
}
|
|
16574
|
+
case "plan":
|
|
16575
|
+
normalized = { itemId: id, type: "text", text: String(item.text ?? ""), status };
|
|
16576
|
+
break;
|
|
16577
|
+
case "commandExecution":
|
|
16578
|
+
normalized = { itemId: id, type: "tool_call", name: "shell", kind: "execute", title: String(item.command ?? "\u6267\u884C\u547D\u4EE4"), text: clip(String(item.aggregatedOutput ?? ""), 64e3), status };
|
|
16579
|
+
break;
|
|
16580
|
+
case "fileChange":
|
|
16581
|
+
normalized = { itemId: id, type: "tool_call", name: "file_change", kind: "edit", title: "\u4FEE\u6539\u6587\u4EF6", text: clip(JSON.stringify(item.changes ?? []), 64e3), status };
|
|
16582
|
+
break;
|
|
16583
|
+
case "mcpToolCall":
|
|
16584
|
+
normalized = { itemId: id, type: "tool_call", name: String(item.tool ?? "MCP"), kind: "call", title: String(item.tool ?? "\u5DE5\u5177\u8C03\u7528"), text: clip(JSON.stringify(item.result ?? item.arguments ?? {}), 64e3), status };
|
|
16585
|
+
break;
|
|
16586
|
+
default:
|
|
16587
|
+
normalized = { itemId: id, type: "tool_call", name: String(item.type ?? "tool"), title: String(item.type ?? "\u6267\u884C\u8FC7\u7A0B"), status };
|
|
16588
|
+
break;
|
|
16589
|
+
}
|
|
16590
|
+
if (completed && normalized.type === "thought" && !normalized.text) return empty();
|
|
16591
|
+
return {
|
|
16592
|
+
display: [],
|
|
16593
|
+
chatEvents: [{ event: "item", item: normalized }],
|
|
16594
|
+
...completed && item.type === "agentMessage" ? { message: String(item.text ?? "") } : {}
|
|
16595
|
+
};
|
|
16596
|
+
}
|
|
16597
|
+
case "turn/completed": {
|
|
16598
|
+
const turn = params.turn ?? {};
|
|
16599
|
+
const error51 = turn.error ?? {};
|
|
16600
|
+
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 };
|
|
16601
|
+
return { display: ["\u25CF \u672C\u8F6E\u5B8C\u6210"], turnCompleted: true, endProcess: true };
|
|
16602
|
+
}
|
|
16603
|
+
default:
|
|
16604
|
+
return empty();
|
|
16605
|
+
}
|
|
16606
|
+
}
|
|
16607
|
+
};
|
|
16608
|
+
}
|
|
16609
|
+
|
|
16461
16610
|
// src/session/terminal-line-sanitizer.ts
|
|
16462
16611
|
var TERMINAL_ESCAPE = /\u001b(?:\][^\u0007]*?(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-Z\\-_])/g;
|
|
16463
16612
|
var CONTROL_CHARACTER = /[\u0000-\u0008\u000b-\u001f\u007f]/g;
|
|
@@ -16594,6 +16743,7 @@ var RunSupervisor = class {
|
|
|
16594
16743
|
recent = /* @__PURE__ */ new Map();
|
|
16595
16744
|
/** Run ids whose finish report has not been acknowledged yet. */
|
|
16596
16745
|
unreported = /* @__PURE__ */ new Set();
|
|
16746
|
+
chatRunIds = /* @__PURE__ */ new Set();
|
|
16597
16747
|
get activeCount() {
|
|
16598
16748
|
return this.active.size;
|
|
16599
16749
|
}
|
|
@@ -16601,8 +16751,15 @@ var RunSupervisor = class {
|
|
|
16601
16751
|
activeRunIds() {
|
|
16602
16752
|
return [.../* @__PURE__ */ new Set([...this.active.keys(), ...this.unreported])];
|
|
16603
16753
|
}
|
|
16754
|
+
activeChatRunIds() {
|
|
16755
|
+
return this.activeRunIds().filter((id) => this.chatRunIds.has(id));
|
|
16756
|
+
}
|
|
16757
|
+
activeTaskRunIds() {
|
|
16758
|
+
return this.activeRunIds().filter((id) => !this.chatRunIds.has(id));
|
|
16759
|
+
}
|
|
16604
16760
|
markReported(runId) {
|
|
16605
16761
|
this.unreported.delete(runId);
|
|
16762
|
+
this.chatRunIds.delete(runId);
|
|
16606
16763
|
}
|
|
16607
16764
|
recentRuns() {
|
|
16608
16765
|
const running = [...this.active.values()].map((run) => ({ runId: run.runId, workstationId: run.workstationId, status: "running", at: run.startedAt }));
|
|
@@ -16618,9 +16775,10 @@ var RunSupervisor = class {
|
|
|
16618
16775
|
this.logger.info("run-duplicate-ignored", { runId: payload.run_id });
|
|
16619
16776
|
return;
|
|
16620
16777
|
}
|
|
16778
|
+
if (payload.chat_session_id) this.chatRunIds.add(payload.run_id);
|
|
16621
16779
|
const refuse = async (error51) => {
|
|
16622
16780
|
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" });
|
|
16781
|
+
this.remember({ runId: payload.run_id, workstationId: payload.workstation_id, tail: new TerminalTailBuffer(), finishedAt: Date.now(), status: "failed", chat: !!payload.chat_session_id });
|
|
16624
16782
|
await this.report(payload.run_id, { status: "failed", error: error51, duration_ms: 0 });
|
|
16625
16783
|
};
|
|
16626
16784
|
if ([...this.active.values()].some((run2) => run2.workstationId === payload.workstation_id)) {
|
|
@@ -16633,10 +16791,30 @@ var RunSupervisor = class {
|
|
|
16633
16791
|
let workdir;
|
|
16634
16792
|
try {
|
|
16635
16793
|
provider = resolveHeadlessProvider(payload.agent_type);
|
|
16636
|
-
workdir = await this.validateWorkdir(payload.workdir || this.config.allowedRoots[0]);
|
|
16794
|
+
workdir = payload.chat_session_id && !payload.workdir ? await this.defaultChatWorkdir() : await this.validateWorkdir(payload.workdir || this.config.allowedRoots[0]);
|
|
16637
16795
|
} catch (error51) {
|
|
16638
16796
|
return refuse(errorMessage(error51));
|
|
16639
16797
|
}
|
|
16798
|
+
const request = {
|
|
16799
|
+
prompt: payload.prompt,
|
|
16800
|
+
sessionId: payload.session_id,
|
|
16801
|
+
model: payload.model,
|
|
16802
|
+
reasoningEffort: payload.reasoning_effort,
|
|
16803
|
+
workdir
|
|
16804
|
+
};
|
|
16805
|
+
if (payload.chat_session_id) {
|
|
16806
|
+
try {
|
|
16807
|
+
if (payload.agent_type !== "codex") throw new Error("\u6B64 CLI \u7684\u5B9E\u65F6\u804A\u5929\u9A71\u52A8\u5C1A\u672A\u542F\u7528");
|
|
16808
|
+
if (!this.reporter.chatEvents) throw new Error("\u5B9E\u65F6\u804A\u5929\u8F6C\u53D1\u672A\u914D\u7F6E");
|
|
16809
|
+
const attachments = await this.prepareChatAttachments(payload);
|
|
16810
|
+
provider = codexChatProvider(request, attachments);
|
|
16811
|
+
} catch (error51) {
|
|
16812
|
+
return refuse(errorMessage(error51));
|
|
16813
|
+
}
|
|
16814
|
+
}
|
|
16815
|
+
if (!payload.prompt && !payload.attachments?.length) return refuse("\u6D88\u606F\u5185\u5BB9\u4E3A\u7A7A");
|
|
16816
|
+
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");
|
|
16817
|
+
if (this.active.size >= this.config.maxConcurrentRuns) return refuse("\u672C\u673A\u540C\u65F6\u8FD0\u884C\u7684\u4EFB\u52A1\u5DF2\u8FBE\u4E0A\u9650");
|
|
16640
16818
|
const launch = provider.buildLaunch({
|
|
16641
16819
|
prompt: payload.prompt,
|
|
16642
16820
|
sessionId: payload.session_id,
|
|
@@ -16652,12 +16830,20 @@ var RunSupervisor = class {
|
|
|
16652
16830
|
detached: process.platform !== "win32",
|
|
16653
16831
|
shell: false,
|
|
16654
16832
|
windowsHide: true,
|
|
16655
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
16833
|
+
stdio: [provider.startInput ? "pipe" : "ignore", "pipe", "pipe"]
|
|
16656
16834
|
});
|
|
16657
16835
|
} catch (error51) {
|
|
16658
16836
|
return refuse(`\u65E0\u6CD5\u542F\u52A8 ${provider.id}\uFF1A${errorMessage(error51)}`);
|
|
16659
16837
|
}
|
|
16660
16838
|
const run = {
|
|
16839
|
+
chat: Boolean(payload.chat_session_id),
|
|
16840
|
+
chatReady: false,
|
|
16841
|
+
eventSequence: 0,
|
|
16842
|
+
pendingEvents: [],
|
|
16843
|
+
eventTail: Promise.resolve(),
|
|
16844
|
+
pendingBytes: 0,
|
|
16845
|
+
queuedBytes: 0,
|
|
16846
|
+
forwardingFailed: false,
|
|
16661
16847
|
runId: payload.run_id,
|
|
16662
16848
|
workstationId: payload.workstation_id,
|
|
16663
16849
|
agentType: provider.id,
|
|
@@ -16683,6 +16869,7 @@ var RunSupervisor = class {
|
|
|
16683
16869
|
`);
|
|
16684
16870
|
this.logger.info("run-started", { runId: run.runId, agentType: provider.id, pid: run.pid, resume: Boolean(payload.session_id) });
|
|
16685
16871
|
let spawnError = "";
|
|
16872
|
+
child.stdin?.on("error", () => void 0);
|
|
16686
16873
|
child.once("error", (error51) => {
|
|
16687
16874
|
spawnError = error51.message;
|
|
16688
16875
|
});
|
|
@@ -16696,6 +16883,24 @@ var RunSupervisor = class {
|
|
|
16696
16883
|
`);
|
|
16697
16884
|
});
|
|
16698
16885
|
child.once("close", (code, signal2) => void this.onExit(run, code, signal2, spawnError));
|
|
16886
|
+
if (provider.startInput) {
|
|
16887
|
+
child.once("spawn", () => {
|
|
16888
|
+
try {
|
|
16889
|
+
provider.startInput?.((message) => {
|
|
16890
|
+
child.stdin?.write(message);
|
|
16891
|
+
});
|
|
16892
|
+
} catch (error51) {
|
|
16893
|
+
run.turnError = errorMessage(error51);
|
|
16894
|
+
void killProcessTree(run.pid);
|
|
16895
|
+
}
|
|
16896
|
+
});
|
|
16897
|
+
run.timers.push(setTimeout(() => {
|
|
16898
|
+
if (!run.chatReady && this.active.has(run.runId)) {
|
|
16899
|
+
run.turnError = "CLI \u5B9E\u65F6\u4F1A\u8BDD\u521D\u59CB\u5316\u8D85\u65F6";
|
|
16900
|
+
void killProcessTree(run.pid);
|
|
16901
|
+
}
|
|
16902
|
+
}, 6e4));
|
|
16903
|
+
}
|
|
16699
16904
|
const maxMs = this.config.maxRunSeconds * 1e3;
|
|
16700
16905
|
run.timers.push(setTimeout(() => {
|
|
16701
16906
|
run.timedOut = true;
|
|
@@ -16710,6 +16915,10 @@ var RunSupervisor = class {
|
|
|
16710
16915
|
onStdout(run, rawLine) {
|
|
16711
16916
|
const line = sanitizeTerminalLine(rawLine);
|
|
16712
16917
|
const parsed = run.provider.parseLine(line);
|
|
16918
|
+
if (run.chat && !run.forwardingFailed) {
|
|
16919
|
+
for (const event of parsed.chatEvents ?? []) this.queueChatEvent(run, event);
|
|
16920
|
+
if (parsed.sessionId) run.chatReady = true;
|
|
16921
|
+
}
|
|
16713
16922
|
for (const row of parsed.display) run.tail.append(`${row}
|
|
16714
16923
|
`);
|
|
16715
16924
|
if (parsed.message !== void 0) run.finalMessage = parsed.message;
|
|
@@ -16718,6 +16927,14 @@ var RunSupervisor = class {
|
|
|
16718
16927
|
run.sessionId = parsed.sessionId;
|
|
16719
16928
|
void this.reporter.started(run.runId, { session_id: parsed.sessionId }).catch(() => void 0);
|
|
16720
16929
|
}
|
|
16930
|
+
if (parsed.endProcess) {
|
|
16931
|
+
run.child.stdin?.end();
|
|
16932
|
+
run.timers.push(setTimeout(() => {
|
|
16933
|
+
if (!this.active.has(run.runId)) return;
|
|
16934
|
+
run.killedAfterCompletion = Boolean(parsed.turnCompleted);
|
|
16935
|
+
void killProcessTree(run.pid);
|
|
16936
|
+
}, 1e3));
|
|
16937
|
+
}
|
|
16721
16938
|
if ((parsed.turnCompleted || parsed.turnError) && !run.turnCompleted) {
|
|
16722
16939
|
run.turnCompleted = true;
|
|
16723
16940
|
run.timers.push(setTimeout(() => {
|
|
@@ -16732,6 +16949,9 @@ var RunSupervisor = class {
|
|
|
16732
16949
|
for (const timer of run.timers) clearTimeout(timer);
|
|
16733
16950
|
this.active.delete(run.runId);
|
|
16734
16951
|
this.orphans?.remove(run.runId);
|
|
16952
|
+
if (run.eventTimer) clearTimeout(run.eventTimer);
|
|
16953
|
+
this.flushChatEvents(run);
|
|
16954
|
+
await run.eventTail;
|
|
16735
16955
|
const durationMs = Date.now() - run.startedAt;
|
|
16736
16956
|
let report;
|
|
16737
16957
|
if (run.cancelRequested) {
|
|
@@ -16753,10 +16973,66 @@ var RunSupervisor = class {
|
|
|
16753
16973
|
if (run.finalMessage) report.final_message = run.finalMessage;
|
|
16754
16974
|
run.tail.append(`\u25CF \u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF1A${report.status}${code !== null ? `\uFF08exit ${code}\uFF09` : ""}
|
|
16755
16975
|
`);
|
|
16756
|
-
this.remember({ runId: run.runId, workstationId: run.workstationId, tail: run.tail, finishedAt: Date.now(), status: report.status });
|
|
16976
|
+
this.remember({ runId: run.runId, workstationId: run.workstationId, tail: run.tail, finishedAt: Date.now(), status: report.status, chat: !!run.chat });
|
|
16757
16977
|
this.logger.info("run-finished", { runId: run.runId, status: report.status, code, durationMs });
|
|
16758
16978
|
await this.report(run.runId, report);
|
|
16759
16979
|
}
|
|
16980
|
+
queueChatEvent(run, event) {
|
|
16981
|
+
const bytes = Buffer.byteLength(JSON.stringify(event));
|
|
16982
|
+
if (run.queuedBytes + bytes > 8 * 1024 * 1024) {
|
|
16983
|
+
run.forwardingFailed = true;
|
|
16984
|
+
run.turnError = "\u5B9E\u65F6\u8F93\u51FA\u8F6C\u53D1\u79EF\u538B\uFF0C\u8BF7\u68C0\u67E5\u8BBE\u5907\u7F51\u7EDC";
|
|
16985
|
+
void killProcessTree(run.pid);
|
|
16986
|
+
return;
|
|
16987
|
+
}
|
|
16988
|
+
run.pendingEvents.push({ ...event, sequence: ++run.eventSequence });
|
|
16989
|
+
run.pendingBytes += bytes;
|
|
16990
|
+
run.queuedBytes += bytes;
|
|
16991
|
+
if (run.pendingBytes > 512 * 1024 || run.pendingEvents.length >= 64) this.flushChatEvents(run);
|
|
16992
|
+
else if (!run.eventTimer) run.eventTimer = setTimeout(() => {
|
|
16993
|
+
delete run.eventTimer;
|
|
16994
|
+
this.flushChatEvents(run);
|
|
16995
|
+
}, 60);
|
|
16996
|
+
}
|
|
16997
|
+
flushChatEvents(run) {
|
|
16998
|
+
if (!run.pendingEvents.length) return;
|
|
16999
|
+
if (run.eventTimer) {
|
|
17000
|
+
clearTimeout(run.eventTimer);
|
|
17001
|
+
delete run.eventTimer;
|
|
17002
|
+
}
|
|
17003
|
+
const events = run.pendingEvents.splice(0);
|
|
17004
|
+
const bytes = run.pendingBytes;
|
|
17005
|
+
run.pendingBytes = 0;
|
|
17006
|
+
run.eventTail = run.eventTail.then(async () => {
|
|
17007
|
+
if (run.forwardingFailed) return;
|
|
17008
|
+
await this.reporter.chatEvents?.(run.runId, events);
|
|
17009
|
+
}).catch((error51) => {
|
|
17010
|
+
run.forwardingFailed = true;
|
|
17011
|
+
run.turnError = `\u5B9E\u65F6\u8F93\u51FA\u8F6C\u53D1\u5931\u8D25\uFF1A${errorMessage(error51)}`;
|
|
17012
|
+
this.logger.warn("chat-events-failed", { runId: run.runId, error: errorMessage(error51) });
|
|
17013
|
+
if (this.active.has(run.runId)) void killProcessTree(run.pid);
|
|
17014
|
+
}).finally(() => {
|
|
17015
|
+
run.queuedBytes -= bytes;
|
|
17016
|
+
});
|
|
17017
|
+
}
|
|
17018
|
+
async prepareChatAttachments(payload) {
|
|
17019
|
+
if (!payload.attachments?.length) return [];
|
|
17020
|
+
if (!this.reporter.chatMedia) throw new Error("\u9644\u4EF6\u8F6C\u53D1\u672A\u914D\u7F6E");
|
|
17021
|
+
const directory = join6(this.config.dataDir, "chat-media", encodeURIComponent(payload.chat_session_id ?? payload.run_id));
|
|
17022
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
17023
|
+
const files = [];
|
|
17024
|
+
for (const attachment of payload.attachments) {
|
|
17025
|
+
const name = basename2(attachment.name).replace(/[^\p{L}\p{N}._-]/gu, "_").slice(-120) || "attachment";
|
|
17026
|
+
const path = join6(directory, `${encodeURIComponent(attachment.media_id)}-${name}`);
|
|
17027
|
+
const existing = await stat2(path).catch(() => void 0);
|
|
17028
|
+
if (!existing?.isFile()) {
|
|
17029
|
+
const data = await this.reporter.chatMedia(payload.run_id, attachment.media_id);
|
|
17030
|
+
await writeFile4(path, data, { mode: 384 });
|
|
17031
|
+
}
|
|
17032
|
+
files.push({ path, name: attachment.name, kind: attachment.kind });
|
|
17033
|
+
}
|
|
17034
|
+
return files;
|
|
17035
|
+
}
|
|
16760
17036
|
async report(runId, report) {
|
|
16761
17037
|
this.unreported.add(runId);
|
|
16762
17038
|
await this.reporter.finished(runId, report);
|
|
@@ -16799,6 +17075,15 @@ var RunSupervisor = class {
|
|
|
16799
17075
|
if (item.finishedAt < cutoff || this.recent.size > MAX_RECENT_RUNS) this.recent.delete(id);
|
|
16800
17076
|
}
|
|
16801
17077
|
}
|
|
17078
|
+
async defaultChatWorkdir() {
|
|
17079
|
+
for (const root of this.config.allowedRoots) {
|
|
17080
|
+
try {
|
|
17081
|
+
return await this.validateWorkdir(root);
|
|
17082
|
+
} catch {
|
|
17083
|
+
}
|
|
17084
|
+
}
|
|
17085
|
+
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");
|
|
17086
|
+
}
|
|
16802
17087
|
async validateWorkdir(input) {
|
|
16803
17088
|
if (!input) throw new Error("\u6CA1\u6709\u914D\u7F6E\u5141\u8BB8\u7684\u5DE5\u4F5C\u76EE\u5F55");
|
|
16804
17089
|
let candidate;
|
|
@@ -16873,9 +17158,31 @@ var GatewayClient = class {
|
|
|
16873
17158
|
const data = await this.request("GET", `/commands?wait=${waitSeconds}`, void 0, (waitSeconds + 15) * 1e3, signal2);
|
|
16874
17159
|
return data.items ?? [];
|
|
16875
17160
|
}
|
|
17161
|
+
chatHeartbeat(runnerId, providers, activeRuns) {
|
|
17162
|
+
return this.request("POST", "/chat-heartbeat", { runner_id: runnerId, providers, active_runs: activeRuns }, 15e3);
|
|
17163
|
+
}
|
|
17164
|
+
async pollChat(waitSeconds, runnerId, signal2) {
|
|
17165
|
+
const data = await this.request("GET", `/chat-commands?wait=${waitSeconds}&runner_id=${encodeURIComponent(runnerId)}`, void 0, (waitSeconds + 15) * 1e3, signal2);
|
|
17166
|
+
return data.items ?? [];
|
|
17167
|
+
}
|
|
16876
17168
|
commandResult(commandId, body) {
|
|
16877
17169
|
return this.request("POST", `/commands/${encodeURIComponent(commandId)}/result`, body, 15e3);
|
|
16878
17170
|
}
|
|
17171
|
+
chatEvents(runId, events) {
|
|
17172
|
+
return this.request("POST", `/runs/${encodeURIComponent(runId)}/chat-events`, { events }, 15e3);
|
|
17173
|
+
}
|
|
17174
|
+
async chatMedia(runId, mediaId) {
|
|
17175
|
+
const response = await fetch(`${this.config.serverUrl}/api/agent/runs/${encodeURIComponent(runId)}/chat-media/${encodeURIComponent(mediaId)}`, {
|
|
17176
|
+
headers: { authorization: `Bearer ${this.config.token}` },
|
|
17177
|
+
signal: AbortSignal.timeout(3e4)
|
|
17178
|
+
});
|
|
17179
|
+
if (!response.ok) throw new GatewayError("\u65E0\u6CD5\u8BFB\u53D6\u804A\u5929\u9644\u4EF6", response.status);
|
|
17180
|
+
const declared = Number(response.headers.get("content-length") ?? 0);
|
|
17181
|
+
if (declared > 10 * 1024 * 1024) throw new GatewayError("\u804A\u5929\u9644\u4EF6\u8D85\u8FC7 10 MB", 413);
|
|
17182
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
17183
|
+
if (bytes.length > 10 * 1024 * 1024) throw new GatewayError("\u804A\u5929\u9644\u4EF6\u8D85\u8FC7 10 MB", 413);
|
|
17184
|
+
return bytes;
|
|
17185
|
+
}
|
|
16879
17186
|
runStarted(runId, body) {
|
|
16880
17187
|
return this.request("POST", `/runs/${encodeURIComponent(runId)}/started`, body, 15e3);
|
|
16881
17188
|
}
|
|
@@ -16885,7 +17192,7 @@ var GatewayClient = class {
|
|
|
16885
17192
|
};
|
|
16886
17193
|
|
|
16887
17194
|
// src/workspace/directory-browser.ts
|
|
16888
|
-
import { readdir, realpath as realpath2, stat as
|
|
17195
|
+
import { readdir, realpath as realpath2, stat as stat3 } from "node:fs/promises";
|
|
16889
17196
|
import { homedir as homedir5 } from "node:os";
|
|
16890
17197
|
import { dirname as dirname6, parse as parse3, resolve as resolve4 } from "node:path";
|
|
16891
17198
|
var MAX_ENTRIES = 1e3;
|
|
@@ -16894,7 +17201,7 @@ async function listDirectory(requested, allowedRoots) {
|
|
|
16894
17201
|
const directory = await realpath2(resolve4(raw)).catch(() => {
|
|
16895
17202
|
throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${requested}`);
|
|
16896
17203
|
});
|
|
16897
|
-
if (!(await
|
|
17204
|
+
if (!(await stat3(directory)).isDirectory()) throw new Error("\u8FD9\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55");
|
|
16898
17205
|
const entries = await readdir(directory, { withFileTypes: true }).catch((error51) => {
|
|
16899
17206
|
throw new Error(error51.code === "EACCES" || error51.code === "EPERM" ? "\u6CA1\u6709\u6743\u9650\u8BFB\u53D6\u8FD9\u4E2A\u76EE\u5F55" : error51.message);
|
|
16900
17207
|
});
|
|
@@ -16923,9 +17230,9 @@ async function listDirectory(requested, allowedRoots) {
|
|
|
16923
17230
|
|
|
16924
17231
|
// src/workspace/workspace-manager.ts
|
|
16925
17232
|
import { spawn as spawn8 } from "node:child_process";
|
|
16926
|
-
import { chmod as chmod3, mkdir as
|
|
17233
|
+
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
17234
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
16928
|
-
import { isAbsolute as isAbsolute3, join as
|
|
17235
|
+
import { isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve5 } from "node:path";
|
|
16929
17236
|
var MAX_NODES = 2e3;
|
|
16930
17237
|
var MAX_DIRECTORY_ENTRIES = 12e3;
|
|
16931
17238
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -16979,7 +17286,7 @@ var WorkspaceManager = class {
|
|
|
16979
17286
|
return root;
|
|
16980
17287
|
}
|
|
16981
17288
|
async syncTree(rootPath, request) {
|
|
16982
|
-
await
|
|
17289
|
+
await mkdir5(rootPath, { recursive: true });
|
|
16983
17290
|
await addAllowedRoot(this.config, rootPath);
|
|
16984
17291
|
const results = [];
|
|
16985
17292
|
const requestedPaths = /* @__PURE__ */ new Set();
|
|
@@ -16989,7 +17296,7 @@ var WorkspaceManager = class {
|
|
|
16989
17296
|
requestedPaths.add(relativePath);
|
|
16990
17297
|
const target = this.resolveWithinRoot(rootPath, relativePath);
|
|
16991
17298
|
try {
|
|
16992
|
-
const metadata = await
|
|
17299
|
+
const metadata = await stat4(target).catch(() => void 0);
|
|
16993
17300
|
if (metadata) {
|
|
16994
17301
|
if (!metadata.isDirectory()) {
|
|
16995
17302
|
throw new Error("The existing workspace path is not a directory");
|
|
@@ -17003,7 +17310,7 @@ var WorkspaceManager = class {
|
|
|
17003
17310
|
});
|
|
17004
17311
|
continue;
|
|
17005
17312
|
}
|
|
17006
|
-
await
|
|
17313
|
+
await mkdir5(target, { recursive: true });
|
|
17007
17314
|
results.push({
|
|
17008
17315
|
relative_path: relativePath,
|
|
17009
17316
|
status: "created",
|
|
@@ -17047,12 +17354,12 @@ var WorkspaceManager = class {
|
|
|
17047
17354
|
let created = false;
|
|
17048
17355
|
let metadata;
|
|
17049
17356
|
try {
|
|
17050
|
-
metadata = await
|
|
17357
|
+
metadata = await stat4(target);
|
|
17051
17358
|
} catch {
|
|
17052
17359
|
}
|
|
17053
17360
|
if (!metadata) {
|
|
17054
17361
|
if (!gitUrl) throw new Error("A missing repository needs a Git remote URL");
|
|
17055
|
-
await
|
|
17362
|
+
await mkdir5(resolve5(target, ".."), { recursive: true });
|
|
17056
17363
|
const cloneArgs = branch ? ["clone", "--branch", branch, "--single-branch", gitUrl, target] : ["clone", gitUrl, target];
|
|
17057
17364
|
await this.runGit(cloneArgs, rootPath, timeoutSeconds, false, gitEnvironment);
|
|
17058
17365
|
created = true;
|
|
@@ -17147,7 +17454,7 @@ var WorkspaceManager = class {
|
|
|
17147
17454
|
}
|
|
17148
17455
|
async scanTree(rootPath, maxDepth, scanRoots) {
|
|
17149
17456
|
const resolvedRoot = await realpath3(rootPath);
|
|
17150
|
-
const rootMetadata = await
|
|
17457
|
+
const rootMetadata = await stat4(resolvedRoot);
|
|
17151
17458
|
if (!rootMetadata.isDirectory()) throw new Error("The workspace root is not a directory");
|
|
17152
17459
|
const nodes = [];
|
|
17153
17460
|
let entriesRead = 0;
|
|
@@ -17202,7 +17509,7 @@ var WorkspaceManager = class {
|
|
|
17202
17509
|
const target = this.resolveWithinRoot(resolvedRoot, relativePath);
|
|
17203
17510
|
let metadata;
|
|
17204
17511
|
try {
|
|
17205
|
-
metadata = await
|
|
17512
|
+
metadata = await stat4(target);
|
|
17206
17513
|
} catch (error51) {
|
|
17207
17514
|
this.logger.warn("workspace-scan-root-missing", {
|
|
17208
17515
|
rootPath: target,
|
|
@@ -17299,13 +17606,13 @@ var WorkspaceManager = class {
|
|
|
17299
17606
|
}
|
|
17300
17607
|
async createGitAuthEnvironment(auth) {
|
|
17301
17608
|
if (!auth) return void 0;
|
|
17302
|
-
const directory = await mkdtemp(
|
|
17609
|
+
const directory = await mkdtemp(join7(tmpdir(), "toc-ocra-git-auth-"));
|
|
17303
17610
|
const isWindows = process.platform === "win32";
|
|
17304
|
-
const scriptPath =
|
|
17611
|
+
const scriptPath = join7(directory, isWindows ? "askpass.cmd" : "askpass.sh");
|
|
17305
17612
|
let script;
|
|
17306
17613
|
if (isWindows) {
|
|
17307
|
-
const helperPath =
|
|
17308
|
-
await
|
|
17614
|
+
const helperPath = join7(directory, "askpass.cjs");
|
|
17615
|
+
await writeFile5(
|
|
17309
17616
|
helperPath,
|
|
17310
17617
|
'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
17618
|
{ mode: 448 }
|
|
@@ -17314,7 +17621,7 @@ var WorkspaceManager = class {
|
|
|
17314
17621
|
} else {
|
|
17315
17622
|
script = '#!/bin/sh\ncase "$1" in *Username*|*username*) printf "%s\\n" "$ECHO_GIT_USERNAME" ;; *) printf "%s\\n" "$ECHO_GIT_PASSWORD" ;; esac\n';
|
|
17316
17623
|
}
|
|
17317
|
-
await
|
|
17624
|
+
await writeFile5(scriptPath, script, { mode: 448 });
|
|
17318
17625
|
if (!isWindows) await chmod3(scriptPath, 448);
|
|
17319
17626
|
return {
|
|
17320
17627
|
env: {
|
|
@@ -17347,7 +17654,9 @@ var RunStartPayloadSchema = external_exports.object({
|
|
|
17347
17654
|
agent_type: external_exports.string().min(1).max(40),
|
|
17348
17655
|
task_chain_id: external_exports.string().optional().default(""),
|
|
17349
17656
|
message_id: external_exports.string().optional().default(""),
|
|
17350
|
-
prompt: external_exports.string().
|
|
17657
|
+
prompt: external_exports.string().max(512 * 1024),
|
|
17658
|
+
chat_session_id: external_exports.string().max(64).optional(),
|
|
17659
|
+
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
17660
|
session_id: external_exports.string().max(200).optional().default(""),
|
|
17352
17661
|
model: external_exports.string().max(100).optional().default(""),
|
|
17353
17662
|
reasoning_effort: external_exports.string().max(40).optional().default(""),
|
|
@@ -17402,9 +17711,10 @@ var sleep2 = (ms, signal2) => new Promise((resolve7) => {
|
|
|
17402
17711
|
}, { once: true });
|
|
17403
17712
|
});
|
|
17404
17713
|
var AgentRuntime = class {
|
|
17405
|
-
constructor(config2, version2, logger) {
|
|
17714
|
+
constructor(config2, version2, logger, chatOnly = false) {
|
|
17406
17715
|
this.config = config2;
|
|
17407
17716
|
this.version = version2;
|
|
17717
|
+
this.chatOnly = chatOnly;
|
|
17408
17718
|
this.ownsLogger = !logger;
|
|
17409
17719
|
this.logger = logger ?? createDiagnosticLogger(config2.logFile, { level: config2.logLevel, maxAgeMs: 5 * 60 * 1e3 });
|
|
17410
17720
|
this.client = new GatewayClient(config2);
|
|
@@ -17413,17 +17723,22 @@ var AgentRuntime = class {
|
|
|
17413
17723
|
started: async (runId, report) => {
|
|
17414
17724
|
await this.retry(() => this.client.runStarted(runId, report), 3);
|
|
17415
17725
|
},
|
|
17726
|
+
chatEvents: async (runId, events) => {
|
|
17727
|
+
await this.retry(() => this.client.chatEvents(runId, events), 5);
|
|
17728
|
+
},
|
|
17729
|
+
chatMedia: (runId, mediaId) => this.client.chatMedia(runId, mediaId),
|
|
17416
17730
|
finished: async (runId, report) => {
|
|
17417
17731
|
this.pendingReports.set(runId, report);
|
|
17418
17732
|
await this.flushReport(runId);
|
|
17419
17733
|
}
|
|
17420
17734
|
};
|
|
17421
17735
|
this.workspaces = new WorkspaceManager(config2, this.logger);
|
|
17422
|
-
this.orphans = new OrphanRegistry(
|
|
17736
|
+
this.orphans = new OrphanRegistry(join8(config2.dataDir, chatOnly ? "chat-active-runs.json" : "active-runs.json"));
|
|
17423
17737
|
this.runs = new RunSupervisor(config2, this.logger, reporter, this.orphans);
|
|
17424
17738
|
}
|
|
17425
17739
|
config;
|
|
17426
17740
|
version;
|
|
17741
|
+
chatOnly;
|
|
17427
17742
|
logger;
|
|
17428
17743
|
client;
|
|
17429
17744
|
runs;
|
|
@@ -17431,6 +17746,10 @@ var AgentRuntime = class {
|
|
|
17431
17746
|
workspaces;
|
|
17432
17747
|
stopController = new AbortController();
|
|
17433
17748
|
startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17749
|
+
chatRunnerId = randomUUID2();
|
|
17750
|
+
chatReady = false;
|
|
17751
|
+
chatLeaseInitialized = false;
|
|
17752
|
+
chatError;
|
|
17434
17753
|
detection;
|
|
17435
17754
|
policy = DEFAULT_POLICY;
|
|
17436
17755
|
heartbeatInterval = 30;
|
|
@@ -17450,13 +17769,14 @@ var AgentRuntime = class {
|
|
|
17450
17769
|
/** Hash of the catalogs the server has stored (from its heartbeat reply). */
|
|
17451
17770
|
serverCatalogHash;
|
|
17452
17771
|
async start() {
|
|
17453
|
-
const reaped = await this.orphans.reapPrevious();
|
|
17772
|
+
const reaped = this.chatOnly ? [] : await this.orphans.reapPrevious();
|
|
17454
17773
|
if (reaped.length > 0) this.logger.warn("orphan-runs-stopped", { runIds: reaped.map((r) => r.runId) });
|
|
17455
17774
|
this.detection = await cliDetection.detect();
|
|
17456
17775
|
await this.refreshCatalogs();
|
|
17457
17776
|
await this.heartbeat();
|
|
17458
17777
|
void this.heartbeatLoop();
|
|
17459
|
-
void this.pollLoop();
|
|
17778
|
+
if (!this.chatOnly) void this.pollLoop();
|
|
17779
|
+
void this.pollLoop(true);
|
|
17460
17780
|
this.logger.info("agent-started", { serverUrl: this.config.serverUrl, deviceId: this.config.deviceId });
|
|
17461
17781
|
process.stdout.write(`toc-ocra ${this.version} \u5DF2\u542F\u52A8\uFF1A${this.config.serverUrl}\uFF08\u8BBE\u5907 ${this.deviceName}\uFF09
|
|
17462
17782
|
`);
|
|
@@ -17503,12 +17823,12 @@ var AgentRuntime = class {
|
|
|
17503
17823
|
for (const runId of [...this.pendingReports.keys()]) await this.flushReport(runId, 1);
|
|
17504
17824
|
const report = this.catalogReport;
|
|
17505
17825
|
const sendCatalogs = report !== void 0 && report.hash !== this.serverCatalogHash;
|
|
17506
|
-
try {
|
|
17826
|
+
if (!this.chatOnly) try {
|
|
17507
17827
|
const response = await this.client.heartbeat({
|
|
17508
17828
|
agent_version: this.version,
|
|
17509
17829
|
hostname: this.config.hostname,
|
|
17510
17830
|
capabilities: this.capabilitySummary(),
|
|
17511
|
-
active_runs: this.runs.
|
|
17831
|
+
active_runs: this.runs.activeTaskRunIds(),
|
|
17512
17832
|
...sendCatalogs ? { model_catalogs: report } : {}
|
|
17513
17833
|
});
|
|
17514
17834
|
this.serverCatalogHash = response.model_catalog_hash ?? (sendCatalogs ? report.hash : this.serverCatalogHash);
|
|
@@ -17522,6 +17842,27 @@ var AgentRuntime = class {
|
|
|
17522
17842
|
} catch (error51) {
|
|
17523
17843
|
this.noteFailure("heartbeat", error51);
|
|
17524
17844
|
}
|
|
17845
|
+
try {
|
|
17846
|
+
await this.client.chatHeartbeat(this.chatRunnerId, this.detection?.detectedIds.includes("codex") ? ["codex"] : [], this.runs.activeChatRunIds());
|
|
17847
|
+
if (!this.chatLeaseInitialized && this.chatOnly) {
|
|
17848
|
+
const reaped = await this.orphans.reapPrevious();
|
|
17849
|
+
if (reaped.length) this.logger.warn("chat-orphans-stopped", { runIds: reaped.map((run) => run.runId) });
|
|
17850
|
+
}
|
|
17851
|
+
this.chatLeaseInitialized = true;
|
|
17852
|
+
if (!this.chatReady) this.logger.info("chat-channel-ready");
|
|
17853
|
+
this.chatReady = true;
|
|
17854
|
+
this.chatError = void 0;
|
|
17855
|
+
if (this.chatOnly) {
|
|
17856
|
+
this.lastHeartbeatOk = true;
|
|
17857
|
+
this.heartbeatInterval = 20;
|
|
17858
|
+
}
|
|
17859
|
+
} catch (error51) {
|
|
17860
|
+
this.chatReady = false;
|
|
17861
|
+
const reason = error51 instanceof Error ? error51.message : String(error51);
|
|
17862
|
+
if (this.chatError !== reason) this.logger.warn("chat-channel-unavailable", { error: reason });
|
|
17863
|
+
this.chatError = reason;
|
|
17864
|
+
if (this.chatOnly) this.noteFailure("chat-heartbeat", error51);
|
|
17865
|
+
}
|
|
17525
17866
|
void this.refreshCatalogs();
|
|
17526
17867
|
}
|
|
17527
17868
|
/** Re-reads catalogs whose sources changed; concurrent calls share one run. */
|
|
@@ -17545,6 +17886,7 @@ var AgentRuntime = class {
|
|
|
17545
17886
|
if (!snapshot) return { providers: [] };
|
|
17546
17887
|
return {
|
|
17547
17888
|
providers: snapshot.providers.map((p) => ({ id: p.id, installed: p.installed, runnable: p.available, version: p.version ?? "", path: p.resolvedPath ?? "" })),
|
|
17889
|
+
cli_chat: { version: 1, providers: ["codex"] },
|
|
17548
17890
|
detected_ids: snapshot.detectedIds,
|
|
17549
17891
|
refreshed_at: snapshot.refreshedAt
|
|
17550
17892
|
};
|
|
@@ -17561,16 +17903,23 @@ var AgentRuntime = class {
|
|
|
17561
17903
|
this.lastError = { reason, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
17562
17904
|
}
|
|
17563
17905
|
// ---- Commands ----
|
|
17564
|
-
async pollLoop() {
|
|
17906
|
+
async pollLoop(chat = false) {
|
|
17565
17907
|
let backoff = 1e3;
|
|
17566
17908
|
while (!this.stopController.signal.aborted) {
|
|
17567
17909
|
try {
|
|
17568
|
-
|
|
17910
|
+
if (chat && !this.chatReady) {
|
|
17911
|
+
await sleep2(1e3, this.stopController.signal);
|
|
17912
|
+
continue;
|
|
17913
|
+
}
|
|
17914
|
+
const commands = chat ? await this.client.pollChat(this.pollWait, this.chatRunnerId, this.stopController.signal) : await this.client.poll(this.pollWait, this.stopController.signal);
|
|
17569
17915
|
backoff = 1e3;
|
|
17570
17916
|
for (const command of commands) void this.handle(command);
|
|
17571
17917
|
} catch (error51) {
|
|
17572
17918
|
if (this.stopController.signal.aborted) return;
|
|
17573
|
-
|
|
17919
|
+
if (chat) {
|
|
17920
|
+
this.chatReady = false;
|
|
17921
|
+
this.logger.warn("chat-poll-failed", { error: error51 instanceof Error ? error51.message : String(error51) });
|
|
17922
|
+
} else this.noteFailure("poll", error51);
|
|
17574
17923
|
const unauthorized = error51 instanceof GatewayError && error51.unauthorized;
|
|
17575
17924
|
await sleep2(unauthorized ? 6e4 : backoff, this.stopController.signal);
|
|
17576
17925
|
backoff = Math.min(backoff * 2, 3e4);
|
|
@@ -17591,11 +17940,13 @@ var AgentRuntime = class {
|
|
|
17591
17940
|
}
|
|
17592
17941
|
async execute(command) {
|
|
17593
17942
|
switch (command.type) {
|
|
17943
|
+
case "chat.run.start":
|
|
17594
17944
|
case "run.start": {
|
|
17595
17945
|
const payload = RunStartPayloadSchema.parse(command.payload);
|
|
17596
17946
|
await this.runs.start(payload);
|
|
17597
17947
|
return { success: true, result: { accepted: true } };
|
|
17598
17948
|
}
|
|
17949
|
+
case "chat.run.cancel":
|
|
17599
17950
|
case "run.cancel": {
|
|
17600
17951
|
const payload = RunCancelPayloadSchema.parse(command.payload);
|
|
17601
17952
|
const cancelled = await this.runs.cancel(payload.run_id);
|
|
@@ -17923,7 +18274,7 @@ function preferSystemNode() {
|
|
|
17923
18274
|
const names = process.platform === "win32" ? ["node.exe"] : ["node"];
|
|
17924
18275
|
for (const directory of (process.env.PATH ?? "").split(delimiter5)) {
|
|
17925
18276
|
for (const name of names) {
|
|
17926
|
-
const candidate = directory &&
|
|
18277
|
+
const candidate = directory && join9(directory, name);
|
|
17927
18278
|
if (candidate && existsSync6(candidate)) {
|
|
17928
18279
|
process.env.TOC_OCRA_NODE_PATH = candidate;
|
|
17929
18280
|
return;
|