toc-ocra 0.2.4 → 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 +428 -45
- 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/codex-quota.d.ts +10 -1
- package/dist/src/providers/codex-quota.js +41 -11
- package/dist/src/providers/codex-quota.js.map +1 -1
- package/dist/src/providers/provider.d.ts +10 -0
- package/dist/src/providers/provider.js.map +1 -1
- package/dist/src/providers/quota-reader.d.ts +8 -0
- package/dist/src/providers/quota-reader.js +19 -0
- package/dist/src/providers/quota-reader.js.map +1 -0
- 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 +82 -29
- 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";
|
|
@@ -16217,7 +16218,7 @@ var ModelCatalogCollector = class {
|
|
|
16217
16218
|
// src/providers/codex-quota.ts
|
|
16218
16219
|
import { spawn as spawn5 } from "node:child_process";
|
|
16219
16220
|
import { createInterface } from "node:readline";
|
|
16220
|
-
async function
|
|
16221
|
+
async function readCodexQuotaWindows() {
|
|
16221
16222
|
const launch = resolveExecutable("codex", ["app-server", "--stdio"], providerEnvironment(), process.env.TOC_OCRA_CODEX_PATH);
|
|
16222
16223
|
const child = spawn5(launch.executable, launch.args, { env: launch.env, stdio: ["pipe", "pipe", "ignore"] });
|
|
16223
16224
|
let nextId = 0;
|
|
@@ -16281,11 +16282,28 @@ async function readCodexQuotaResetAt() {
|
|
|
16281
16282
|
});
|
|
16282
16283
|
child.stdin.write('{"jsonrpc":"2.0","method":"initialized","params":{}}\n');
|
|
16283
16284
|
const result = await request("account/rateLimits/read", {});
|
|
16284
|
-
const
|
|
16285
|
-
|
|
16286
|
-
const
|
|
16287
|
-
|
|
16288
|
-
|
|
16285
|
+
const buckets = Object.entries(result?.rateLimitsByLimitId ?? {});
|
|
16286
|
+
if (!buckets.length && result?.rateLimits) buckets.push([String(result.rateLimits.limitId || "codex"), result.rateLimits]);
|
|
16287
|
+
const windows = buckets.flatMap(([limitId, snapshot]) => ["primary", "secondary"].flatMap((key) => {
|
|
16288
|
+
const window = snapshot[key];
|
|
16289
|
+
if (!window) return [];
|
|
16290
|
+
const resetRaw = Number(window.resetsAt);
|
|
16291
|
+
const resetsAt = Number.isFinite(resetRaw) && resetRaw > 0 ? Math.floor(resetRaw > 1e10 ? resetRaw / 1e3 : resetRaw) : 0;
|
|
16292
|
+
const durationMins = Number(window.windowDurationMins);
|
|
16293
|
+
const usedRaw = Number(window.usedPercent);
|
|
16294
|
+
const windowLabel = durationMins === 300 ? "5 \u5C0F\u65F6\u989D\u5EA6" : durationMins >= 1e4 ? "\u6BCF\u5468\u989D\u5EA6" : durationMins > 0 ? `${durationMins} \u5206\u949F\u989D\u5EA6` : key === "primary" ? "\u4E3B\u8981\u989D\u5EA6" : "\u6B21\u8981\u989D\u5EA6";
|
|
16295
|
+
const bucketName = typeof snapshot.limitName === "string" && snapshot.limitName.trim() ? snapshot.limitName.trim() : limitId === "codex" ? "" : limitId;
|
|
16296
|
+
const item = {
|
|
16297
|
+
key: `${limitId}:${key}`,
|
|
16298
|
+
label: bucketName ? `${bucketName} \xB7 ${windowLabel}` : windowLabel,
|
|
16299
|
+
resets_at: resetsAt,
|
|
16300
|
+
...Number.isFinite(usedRaw) && usedRaw >= 0 && usedRaw <= 100 ? { used_percent: usedRaw } : {},
|
|
16301
|
+
...Number.isFinite(durationMins) && durationMins > 0 ? { window_seconds: Math.floor(durationMins * 60) } : {}
|
|
16302
|
+
};
|
|
16303
|
+
return [item];
|
|
16304
|
+
}));
|
|
16305
|
+
if (!windows.length || !windows.some((window) => window.resets_at > 0)) throw new Error("Codex \u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u989D\u5EA6\u7A97\u53E3");
|
|
16306
|
+
return windows;
|
|
16289
16307
|
} finally {
|
|
16290
16308
|
for (const waiter of pending.values()) waiter.reject(new Error("Codex app-server \u5DF2\u5173\u95ED"));
|
|
16291
16309
|
pending.clear();
|
|
@@ -16294,6 +16312,21 @@ async function readCodexQuotaResetAt() {
|
|
|
16294
16312
|
}
|
|
16295
16313
|
}
|
|
16296
16314
|
|
|
16315
|
+
// src/providers/quota-reader.ts
|
|
16316
|
+
var quotaReaders = {
|
|
16317
|
+
codex: readCodexQuotaWindows
|
|
16318
|
+
};
|
|
16319
|
+
async function readCLIQuotaReport(agentType) {
|
|
16320
|
+
const provider = agentType.trim().toLowerCase();
|
|
16321
|
+
const read = quotaReaders[provider];
|
|
16322
|
+
if (!read) throw new Error(`${provider || "\u8BE5 CLI"} \u6682\u4E0D\u652F\u6301\u81EA\u52A8\u8BFB\u53D6\u989D\u5EA6\uFF0C\u8BF7\u4F7F\u7528\u624B\u52A8\u7EF4\u62A4`);
|
|
16323
|
+
const quotaWindows = await read();
|
|
16324
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
16325
|
+
const reset = quotaWindows.map((window) => window.resets_at).filter((value) => value > now).sort((a, b) => a - b)[0];
|
|
16326
|
+
if (!quotaWindows.length || !reset) throw new Error(`${provider} \u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u672A\u6765\u989D\u5EA6\u91CD\u7F6E\u65F6\u95F4`);
|
|
16327
|
+
return { agent_type: provider, quota_windows: quotaWindows, next_reset_at: reset, fetched_at: now };
|
|
16328
|
+
}
|
|
16329
|
+
|
|
16297
16330
|
// src/runs/orphan-registry.ts
|
|
16298
16331
|
import { execFile as execFile2 } from "node:child_process";
|
|
16299
16332
|
import { mkdir as mkdir3, readFile as readFile4, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
|
|
@@ -16422,10 +16455,158 @@ function commandOf(pid) {
|
|
|
16422
16455
|
|
|
16423
16456
|
// src/runs/run-supervisor.ts
|
|
16424
16457
|
import { spawn as spawn7 } from "node:child_process";
|
|
16425
|
-
import { realpath } from "node:fs/promises";
|
|
16426
|
-
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";
|
|
16427
16460
|
import { createInterface as createInterface2 } from "node:readline";
|
|
16428
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
|
+
|
|
16429
16610
|
// src/session/terminal-line-sanitizer.ts
|
|
16430
16611
|
var TERMINAL_ESCAPE = /\u001b(?:\][^\u0007]*?(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-Z\\-_])/g;
|
|
16431
16612
|
var CONTROL_CHARACTER = /[\u0000-\u0008\u000b-\u001f\u007f]/g;
|
|
@@ -16562,6 +16743,7 @@ var RunSupervisor = class {
|
|
|
16562
16743
|
recent = /* @__PURE__ */ new Map();
|
|
16563
16744
|
/** Run ids whose finish report has not been acknowledged yet. */
|
|
16564
16745
|
unreported = /* @__PURE__ */ new Set();
|
|
16746
|
+
chatRunIds = /* @__PURE__ */ new Set();
|
|
16565
16747
|
get activeCount() {
|
|
16566
16748
|
return this.active.size;
|
|
16567
16749
|
}
|
|
@@ -16569,8 +16751,15 @@ var RunSupervisor = class {
|
|
|
16569
16751
|
activeRunIds() {
|
|
16570
16752
|
return [.../* @__PURE__ */ new Set([...this.active.keys(), ...this.unreported])];
|
|
16571
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
|
+
}
|
|
16572
16760
|
markReported(runId) {
|
|
16573
16761
|
this.unreported.delete(runId);
|
|
16762
|
+
this.chatRunIds.delete(runId);
|
|
16574
16763
|
}
|
|
16575
16764
|
recentRuns() {
|
|
16576
16765
|
const running = [...this.active.values()].map((run) => ({ runId: run.runId, workstationId: run.workstationId, status: "running", at: run.startedAt }));
|
|
@@ -16586,9 +16775,10 @@ var RunSupervisor = class {
|
|
|
16586
16775
|
this.logger.info("run-duplicate-ignored", { runId: payload.run_id });
|
|
16587
16776
|
return;
|
|
16588
16777
|
}
|
|
16778
|
+
if (payload.chat_session_id) this.chatRunIds.add(payload.run_id);
|
|
16589
16779
|
const refuse = async (error51) => {
|
|
16590
16780
|
this.logger.warn("run-refused", { runId: payload.run_id, error: error51 });
|
|
16591
|
-
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 });
|
|
16592
16782
|
await this.report(payload.run_id, { status: "failed", error: error51, duration_ms: 0 });
|
|
16593
16783
|
};
|
|
16594
16784
|
if ([...this.active.values()].some((run2) => run2.workstationId === payload.workstation_id)) {
|
|
@@ -16601,10 +16791,30 @@ var RunSupervisor = class {
|
|
|
16601
16791
|
let workdir;
|
|
16602
16792
|
try {
|
|
16603
16793
|
provider = resolveHeadlessProvider(payload.agent_type);
|
|
16604
|
-
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]);
|
|
16605
16795
|
} catch (error51) {
|
|
16606
16796
|
return refuse(errorMessage(error51));
|
|
16607
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");
|
|
16608
16818
|
const launch = provider.buildLaunch({
|
|
16609
16819
|
prompt: payload.prompt,
|
|
16610
16820
|
sessionId: payload.session_id,
|
|
@@ -16620,12 +16830,20 @@ var RunSupervisor = class {
|
|
|
16620
16830
|
detached: process.platform !== "win32",
|
|
16621
16831
|
shell: false,
|
|
16622
16832
|
windowsHide: true,
|
|
16623
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
16833
|
+
stdio: [provider.startInput ? "pipe" : "ignore", "pipe", "pipe"]
|
|
16624
16834
|
});
|
|
16625
16835
|
} catch (error51) {
|
|
16626
16836
|
return refuse(`\u65E0\u6CD5\u542F\u52A8 ${provider.id}\uFF1A${errorMessage(error51)}`);
|
|
16627
16837
|
}
|
|
16628
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,
|
|
16629
16847
|
runId: payload.run_id,
|
|
16630
16848
|
workstationId: payload.workstation_id,
|
|
16631
16849
|
agentType: provider.id,
|
|
@@ -16651,6 +16869,7 @@ var RunSupervisor = class {
|
|
|
16651
16869
|
`);
|
|
16652
16870
|
this.logger.info("run-started", { runId: run.runId, agentType: provider.id, pid: run.pid, resume: Boolean(payload.session_id) });
|
|
16653
16871
|
let spawnError = "";
|
|
16872
|
+
child.stdin?.on("error", () => void 0);
|
|
16654
16873
|
child.once("error", (error51) => {
|
|
16655
16874
|
spawnError = error51.message;
|
|
16656
16875
|
});
|
|
@@ -16664,6 +16883,24 @@ var RunSupervisor = class {
|
|
|
16664
16883
|
`);
|
|
16665
16884
|
});
|
|
16666
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
|
+
}
|
|
16667
16904
|
const maxMs = this.config.maxRunSeconds * 1e3;
|
|
16668
16905
|
run.timers.push(setTimeout(() => {
|
|
16669
16906
|
run.timedOut = true;
|
|
@@ -16678,6 +16915,10 @@ var RunSupervisor = class {
|
|
|
16678
16915
|
onStdout(run, rawLine) {
|
|
16679
16916
|
const line = sanitizeTerminalLine(rawLine);
|
|
16680
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
|
+
}
|
|
16681
16922
|
for (const row of parsed.display) run.tail.append(`${row}
|
|
16682
16923
|
`);
|
|
16683
16924
|
if (parsed.message !== void 0) run.finalMessage = parsed.message;
|
|
@@ -16686,6 +16927,14 @@ var RunSupervisor = class {
|
|
|
16686
16927
|
run.sessionId = parsed.sessionId;
|
|
16687
16928
|
void this.reporter.started(run.runId, { session_id: parsed.sessionId }).catch(() => void 0);
|
|
16688
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
|
+
}
|
|
16689
16938
|
if ((parsed.turnCompleted || parsed.turnError) && !run.turnCompleted) {
|
|
16690
16939
|
run.turnCompleted = true;
|
|
16691
16940
|
run.timers.push(setTimeout(() => {
|
|
@@ -16700,6 +16949,9 @@ var RunSupervisor = class {
|
|
|
16700
16949
|
for (const timer of run.timers) clearTimeout(timer);
|
|
16701
16950
|
this.active.delete(run.runId);
|
|
16702
16951
|
this.orphans?.remove(run.runId);
|
|
16952
|
+
if (run.eventTimer) clearTimeout(run.eventTimer);
|
|
16953
|
+
this.flushChatEvents(run);
|
|
16954
|
+
await run.eventTail;
|
|
16703
16955
|
const durationMs = Date.now() - run.startedAt;
|
|
16704
16956
|
let report;
|
|
16705
16957
|
if (run.cancelRequested) {
|
|
@@ -16721,10 +16973,66 @@ var RunSupervisor = class {
|
|
|
16721
16973
|
if (run.finalMessage) report.final_message = run.finalMessage;
|
|
16722
16974
|
run.tail.append(`\u25CF \u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF1A${report.status}${code !== null ? `\uFF08exit ${code}\uFF09` : ""}
|
|
16723
16975
|
`);
|
|
16724
|
-
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 });
|
|
16725
16977
|
this.logger.info("run-finished", { runId: run.runId, status: report.status, code, durationMs });
|
|
16726
16978
|
await this.report(run.runId, report);
|
|
16727
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
|
+
}
|
|
16728
17036
|
async report(runId, report) {
|
|
16729
17037
|
this.unreported.add(runId);
|
|
16730
17038
|
await this.reporter.finished(runId, report);
|
|
@@ -16767,6 +17075,15 @@ var RunSupervisor = class {
|
|
|
16767
17075
|
if (item.finishedAt < cutoff || this.recent.size > MAX_RECENT_RUNS) this.recent.delete(id);
|
|
16768
17076
|
}
|
|
16769
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
|
+
}
|
|
16770
17087
|
async validateWorkdir(input) {
|
|
16771
17088
|
if (!input) throw new Error("\u6CA1\u6709\u914D\u7F6E\u5141\u8BB8\u7684\u5DE5\u4F5C\u76EE\u5F55");
|
|
16772
17089
|
let candidate;
|
|
@@ -16841,9 +17158,31 @@ var GatewayClient = class {
|
|
|
16841
17158
|
const data = await this.request("GET", `/commands?wait=${waitSeconds}`, void 0, (waitSeconds + 15) * 1e3, signal2);
|
|
16842
17159
|
return data.items ?? [];
|
|
16843
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
|
+
}
|
|
16844
17168
|
commandResult(commandId, body) {
|
|
16845
17169
|
return this.request("POST", `/commands/${encodeURIComponent(commandId)}/result`, body, 15e3);
|
|
16846
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
|
+
}
|
|
16847
17186
|
runStarted(runId, body) {
|
|
16848
17187
|
return this.request("POST", `/runs/${encodeURIComponent(runId)}/started`, body, 15e3);
|
|
16849
17188
|
}
|
|
@@ -16853,7 +17192,7 @@ var GatewayClient = class {
|
|
|
16853
17192
|
};
|
|
16854
17193
|
|
|
16855
17194
|
// src/workspace/directory-browser.ts
|
|
16856
|
-
import { readdir, realpath as realpath2, stat as
|
|
17195
|
+
import { readdir, realpath as realpath2, stat as stat3 } from "node:fs/promises";
|
|
16857
17196
|
import { homedir as homedir5 } from "node:os";
|
|
16858
17197
|
import { dirname as dirname6, parse as parse3, resolve as resolve4 } from "node:path";
|
|
16859
17198
|
var MAX_ENTRIES = 1e3;
|
|
@@ -16862,7 +17201,7 @@ async function listDirectory(requested, allowedRoots) {
|
|
|
16862
17201
|
const directory = await realpath2(resolve4(raw)).catch(() => {
|
|
16863
17202
|
throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${requested}`);
|
|
16864
17203
|
});
|
|
16865
|
-
if (!(await
|
|
17204
|
+
if (!(await stat3(directory)).isDirectory()) throw new Error("\u8FD9\u4E0D\u662F\u4E00\u4E2A\u76EE\u5F55");
|
|
16866
17205
|
const entries = await readdir(directory, { withFileTypes: true }).catch((error51) => {
|
|
16867
17206
|
throw new Error(error51.code === "EACCES" || error51.code === "EPERM" ? "\u6CA1\u6709\u6743\u9650\u8BFB\u53D6\u8FD9\u4E2A\u76EE\u5F55" : error51.message);
|
|
16868
17207
|
});
|
|
@@ -16891,9 +17230,9 @@ async function listDirectory(requested, allowedRoots) {
|
|
|
16891
17230
|
|
|
16892
17231
|
// src/workspace/workspace-manager.ts
|
|
16893
17232
|
import { spawn as spawn8 } from "node:child_process";
|
|
16894
|
-
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";
|
|
16895
17234
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
16896
|
-
import { isAbsolute as isAbsolute3, join as
|
|
17235
|
+
import { isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve5 } from "node:path";
|
|
16897
17236
|
var MAX_NODES = 2e3;
|
|
16898
17237
|
var MAX_DIRECTORY_ENTRIES = 12e3;
|
|
16899
17238
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -16947,7 +17286,7 @@ var WorkspaceManager = class {
|
|
|
16947
17286
|
return root;
|
|
16948
17287
|
}
|
|
16949
17288
|
async syncTree(rootPath, request) {
|
|
16950
|
-
await
|
|
17289
|
+
await mkdir5(rootPath, { recursive: true });
|
|
16951
17290
|
await addAllowedRoot(this.config, rootPath);
|
|
16952
17291
|
const results = [];
|
|
16953
17292
|
const requestedPaths = /* @__PURE__ */ new Set();
|
|
@@ -16957,7 +17296,7 @@ var WorkspaceManager = class {
|
|
|
16957
17296
|
requestedPaths.add(relativePath);
|
|
16958
17297
|
const target = this.resolveWithinRoot(rootPath, relativePath);
|
|
16959
17298
|
try {
|
|
16960
|
-
const metadata = await
|
|
17299
|
+
const metadata = await stat4(target).catch(() => void 0);
|
|
16961
17300
|
if (metadata) {
|
|
16962
17301
|
if (!metadata.isDirectory()) {
|
|
16963
17302
|
throw new Error("The existing workspace path is not a directory");
|
|
@@ -16971,7 +17310,7 @@ var WorkspaceManager = class {
|
|
|
16971
17310
|
});
|
|
16972
17311
|
continue;
|
|
16973
17312
|
}
|
|
16974
|
-
await
|
|
17313
|
+
await mkdir5(target, { recursive: true });
|
|
16975
17314
|
results.push({
|
|
16976
17315
|
relative_path: relativePath,
|
|
16977
17316
|
status: "created",
|
|
@@ -17015,12 +17354,12 @@ var WorkspaceManager = class {
|
|
|
17015
17354
|
let created = false;
|
|
17016
17355
|
let metadata;
|
|
17017
17356
|
try {
|
|
17018
|
-
metadata = await
|
|
17357
|
+
metadata = await stat4(target);
|
|
17019
17358
|
} catch {
|
|
17020
17359
|
}
|
|
17021
17360
|
if (!metadata) {
|
|
17022
17361
|
if (!gitUrl) throw new Error("A missing repository needs a Git remote URL");
|
|
17023
|
-
await
|
|
17362
|
+
await mkdir5(resolve5(target, ".."), { recursive: true });
|
|
17024
17363
|
const cloneArgs = branch ? ["clone", "--branch", branch, "--single-branch", gitUrl, target] : ["clone", gitUrl, target];
|
|
17025
17364
|
await this.runGit(cloneArgs, rootPath, timeoutSeconds, false, gitEnvironment);
|
|
17026
17365
|
created = true;
|
|
@@ -17115,7 +17454,7 @@ var WorkspaceManager = class {
|
|
|
17115
17454
|
}
|
|
17116
17455
|
async scanTree(rootPath, maxDepth, scanRoots) {
|
|
17117
17456
|
const resolvedRoot = await realpath3(rootPath);
|
|
17118
|
-
const rootMetadata = await
|
|
17457
|
+
const rootMetadata = await stat4(resolvedRoot);
|
|
17119
17458
|
if (!rootMetadata.isDirectory()) throw new Error("The workspace root is not a directory");
|
|
17120
17459
|
const nodes = [];
|
|
17121
17460
|
let entriesRead = 0;
|
|
@@ -17170,7 +17509,7 @@ var WorkspaceManager = class {
|
|
|
17170
17509
|
const target = this.resolveWithinRoot(resolvedRoot, relativePath);
|
|
17171
17510
|
let metadata;
|
|
17172
17511
|
try {
|
|
17173
|
-
metadata = await
|
|
17512
|
+
metadata = await stat4(target);
|
|
17174
17513
|
} catch (error51) {
|
|
17175
17514
|
this.logger.warn("workspace-scan-root-missing", {
|
|
17176
17515
|
rootPath: target,
|
|
@@ -17267,13 +17606,13 @@ var WorkspaceManager = class {
|
|
|
17267
17606
|
}
|
|
17268
17607
|
async createGitAuthEnvironment(auth) {
|
|
17269
17608
|
if (!auth) return void 0;
|
|
17270
|
-
const directory = await mkdtemp(
|
|
17609
|
+
const directory = await mkdtemp(join7(tmpdir(), "toc-ocra-git-auth-"));
|
|
17271
17610
|
const isWindows = process.platform === "win32";
|
|
17272
|
-
const scriptPath =
|
|
17611
|
+
const scriptPath = join7(directory, isWindows ? "askpass.cmd" : "askpass.sh");
|
|
17273
17612
|
let script;
|
|
17274
17613
|
if (isWindows) {
|
|
17275
|
-
const helperPath =
|
|
17276
|
-
await
|
|
17614
|
+
const helperPath = join7(directory, "askpass.cjs");
|
|
17615
|
+
await writeFile5(
|
|
17277
17616
|
helperPath,
|
|
17278
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',
|
|
17279
17618
|
{ mode: 448 }
|
|
@@ -17282,7 +17621,7 @@ var WorkspaceManager = class {
|
|
|
17282
17621
|
} else {
|
|
17283
17622
|
script = '#!/bin/sh\ncase "$1" in *Username*|*username*) printf "%s\\n" "$ECHO_GIT_USERNAME" ;; *) printf "%s\\n" "$ECHO_GIT_PASSWORD" ;; esac\n';
|
|
17284
17623
|
}
|
|
17285
|
-
await
|
|
17624
|
+
await writeFile5(scriptPath, script, { mode: 448 });
|
|
17286
17625
|
if (!isWindows) await chmod3(scriptPath, 448);
|
|
17287
17626
|
return {
|
|
17288
17627
|
env: {
|
|
@@ -17315,7 +17654,9 @@ var RunStartPayloadSchema = external_exports.object({
|
|
|
17315
17654
|
agent_type: external_exports.string().min(1).max(40),
|
|
17316
17655
|
task_chain_id: external_exports.string().optional().default(""),
|
|
17317
17656
|
message_id: external_exports.string().optional().default(""),
|
|
17318
|
-
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(),
|
|
17319
17660
|
session_id: external_exports.string().max(200).optional().default(""),
|
|
17320
17661
|
model: external_exports.string().max(100).optional().default(""),
|
|
17321
17662
|
reasoning_effort: external_exports.string().max(40).optional().default(""),
|
|
@@ -17370,9 +17711,10 @@ var sleep2 = (ms, signal2) => new Promise((resolve7) => {
|
|
|
17370
17711
|
}, { once: true });
|
|
17371
17712
|
});
|
|
17372
17713
|
var AgentRuntime = class {
|
|
17373
|
-
constructor(config2, version2, logger) {
|
|
17714
|
+
constructor(config2, version2, logger, chatOnly = false) {
|
|
17374
17715
|
this.config = config2;
|
|
17375
17716
|
this.version = version2;
|
|
17717
|
+
this.chatOnly = chatOnly;
|
|
17376
17718
|
this.ownsLogger = !logger;
|
|
17377
17719
|
this.logger = logger ?? createDiagnosticLogger(config2.logFile, { level: config2.logLevel, maxAgeMs: 5 * 60 * 1e3 });
|
|
17378
17720
|
this.client = new GatewayClient(config2);
|
|
@@ -17381,17 +17723,22 @@ var AgentRuntime = class {
|
|
|
17381
17723
|
started: async (runId, report) => {
|
|
17382
17724
|
await this.retry(() => this.client.runStarted(runId, report), 3);
|
|
17383
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),
|
|
17384
17730
|
finished: async (runId, report) => {
|
|
17385
17731
|
this.pendingReports.set(runId, report);
|
|
17386
17732
|
await this.flushReport(runId);
|
|
17387
17733
|
}
|
|
17388
17734
|
};
|
|
17389
17735
|
this.workspaces = new WorkspaceManager(config2, this.logger);
|
|
17390
|
-
this.orphans = new OrphanRegistry(
|
|
17736
|
+
this.orphans = new OrphanRegistry(join8(config2.dataDir, chatOnly ? "chat-active-runs.json" : "active-runs.json"));
|
|
17391
17737
|
this.runs = new RunSupervisor(config2, this.logger, reporter, this.orphans);
|
|
17392
17738
|
}
|
|
17393
17739
|
config;
|
|
17394
17740
|
version;
|
|
17741
|
+
chatOnly;
|
|
17395
17742
|
logger;
|
|
17396
17743
|
client;
|
|
17397
17744
|
runs;
|
|
@@ -17399,6 +17746,10 @@ var AgentRuntime = class {
|
|
|
17399
17746
|
workspaces;
|
|
17400
17747
|
stopController = new AbortController();
|
|
17401
17748
|
startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17749
|
+
chatRunnerId = randomUUID2();
|
|
17750
|
+
chatReady = false;
|
|
17751
|
+
chatLeaseInitialized = false;
|
|
17752
|
+
chatError;
|
|
17402
17753
|
detection;
|
|
17403
17754
|
policy = DEFAULT_POLICY;
|
|
17404
17755
|
heartbeatInterval = 30;
|
|
@@ -17418,13 +17769,14 @@ var AgentRuntime = class {
|
|
|
17418
17769
|
/** Hash of the catalogs the server has stored (from its heartbeat reply). */
|
|
17419
17770
|
serverCatalogHash;
|
|
17420
17771
|
async start() {
|
|
17421
|
-
const reaped = await this.orphans.reapPrevious();
|
|
17772
|
+
const reaped = this.chatOnly ? [] : await this.orphans.reapPrevious();
|
|
17422
17773
|
if (reaped.length > 0) this.logger.warn("orphan-runs-stopped", { runIds: reaped.map((r) => r.runId) });
|
|
17423
17774
|
this.detection = await cliDetection.detect();
|
|
17424
17775
|
await this.refreshCatalogs();
|
|
17425
17776
|
await this.heartbeat();
|
|
17426
17777
|
void this.heartbeatLoop();
|
|
17427
|
-
void this.pollLoop();
|
|
17778
|
+
if (!this.chatOnly) void this.pollLoop();
|
|
17779
|
+
void this.pollLoop(true);
|
|
17428
17780
|
this.logger.info("agent-started", { serverUrl: this.config.serverUrl, deviceId: this.config.deviceId });
|
|
17429
17781
|
process.stdout.write(`toc-ocra ${this.version} \u5DF2\u542F\u52A8\uFF1A${this.config.serverUrl}\uFF08\u8BBE\u5907 ${this.deviceName}\uFF09
|
|
17430
17782
|
`);
|
|
@@ -17471,12 +17823,12 @@ var AgentRuntime = class {
|
|
|
17471
17823
|
for (const runId of [...this.pendingReports.keys()]) await this.flushReport(runId, 1);
|
|
17472
17824
|
const report = this.catalogReport;
|
|
17473
17825
|
const sendCatalogs = report !== void 0 && report.hash !== this.serverCatalogHash;
|
|
17474
|
-
try {
|
|
17826
|
+
if (!this.chatOnly) try {
|
|
17475
17827
|
const response = await this.client.heartbeat({
|
|
17476
17828
|
agent_version: this.version,
|
|
17477
17829
|
hostname: this.config.hostname,
|
|
17478
17830
|
capabilities: this.capabilitySummary(),
|
|
17479
|
-
active_runs: this.runs.
|
|
17831
|
+
active_runs: this.runs.activeTaskRunIds(),
|
|
17480
17832
|
...sendCatalogs ? { model_catalogs: report } : {}
|
|
17481
17833
|
});
|
|
17482
17834
|
this.serverCatalogHash = response.model_catalog_hash ?? (sendCatalogs ? report.hash : this.serverCatalogHash);
|
|
@@ -17490,6 +17842,27 @@ var AgentRuntime = class {
|
|
|
17490
17842
|
} catch (error51) {
|
|
17491
17843
|
this.noteFailure("heartbeat", error51);
|
|
17492
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
|
+
}
|
|
17493
17866
|
void this.refreshCatalogs();
|
|
17494
17867
|
}
|
|
17495
17868
|
/** Re-reads catalogs whose sources changed; concurrent calls share one run. */
|
|
@@ -17513,6 +17886,7 @@ var AgentRuntime = class {
|
|
|
17513
17886
|
if (!snapshot) return { providers: [] };
|
|
17514
17887
|
return {
|
|
17515
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"] },
|
|
17516
17890
|
detected_ids: snapshot.detectedIds,
|
|
17517
17891
|
refreshed_at: snapshot.refreshedAt
|
|
17518
17892
|
};
|
|
@@ -17529,16 +17903,23 @@ var AgentRuntime = class {
|
|
|
17529
17903
|
this.lastError = { reason, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
17530
17904
|
}
|
|
17531
17905
|
// ---- Commands ----
|
|
17532
|
-
async pollLoop() {
|
|
17906
|
+
async pollLoop(chat = false) {
|
|
17533
17907
|
let backoff = 1e3;
|
|
17534
17908
|
while (!this.stopController.signal.aborted) {
|
|
17535
17909
|
try {
|
|
17536
|
-
|
|
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);
|
|
17537
17915
|
backoff = 1e3;
|
|
17538
17916
|
for (const command of commands) void this.handle(command);
|
|
17539
17917
|
} catch (error51) {
|
|
17540
17918
|
if (this.stopController.signal.aborted) return;
|
|
17541
|
-
|
|
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);
|
|
17542
17923
|
const unauthorized = error51 instanceof GatewayError && error51.unauthorized;
|
|
17543
17924
|
await sleep2(unauthorized ? 6e4 : backoff, this.stopController.signal);
|
|
17544
17925
|
backoff = Math.min(backoff * 2, 3e4);
|
|
@@ -17559,11 +17940,13 @@ var AgentRuntime = class {
|
|
|
17559
17940
|
}
|
|
17560
17941
|
async execute(command) {
|
|
17561
17942
|
switch (command.type) {
|
|
17943
|
+
case "chat.run.start":
|
|
17562
17944
|
case "run.start": {
|
|
17563
17945
|
const payload = RunStartPayloadSchema.parse(command.payload);
|
|
17564
17946
|
await this.runs.start(payload);
|
|
17565
17947
|
return { success: true, result: { accepted: true } };
|
|
17566
17948
|
}
|
|
17949
|
+
case "chat.run.cancel":
|
|
17567
17950
|
case "run.cancel": {
|
|
17568
17951
|
const payload = RunCancelPayloadSchema.parse(command.payload);
|
|
17569
17952
|
const cancelled = await this.runs.cancel(payload.run_id);
|
|
@@ -17590,11 +17973,11 @@ var AgentRuntime = class {
|
|
|
17590
17973
|
}
|
|
17591
17974
|
case "cli.quota": {
|
|
17592
17975
|
const payload = CliQuotaPayloadSchema.parse(command.payload ?? {});
|
|
17593
|
-
|
|
17594
|
-
return { success:
|
|
17976
|
+
try {
|
|
17977
|
+
return { success: true, result: await readCLIQuotaReport(payload.agent_type) };
|
|
17978
|
+
} catch (error51) {
|
|
17979
|
+
return { success: false, error: error51 instanceof Error ? error51.message : String(error51) };
|
|
17595
17980
|
}
|
|
17596
|
-
const resetAt = await readCodexQuotaResetAt();
|
|
17597
|
-
return { success: true, result: { agent_type: "codex", next_reset_at: resetAt, fetched_at: Math.floor(Date.now() / 1e3) } };
|
|
17598
17981
|
}
|
|
17599
17982
|
case "script.run": {
|
|
17600
17983
|
const payload = ScriptRunPayloadSchema.parse(command.payload);
|
|
@@ -17891,7 +18274,7 @@ function preferSystemNode() {
|
|
|
17891
18274
|
const names = process.platform === "win32" ? ["node.exe"] : ["node"];
|
|
17892
18275
|
for (const directory of (process.env.PATH ?? "").split(delimiter5)) {
|
|
17893
18276
|
for (const name of names) {
|
|
17894
|
-
const candidate = directory &&
|
|
18277
|
+
const candidate = directory && join9(directory, name);
|
|
17895
18278
|
if (candidate && existsSync6(candidate)) {
|
|
17896
18279
|
process.env.TOC_OCRA_NODE_PATH = candidate;
|
|
17897
18280
|
return;
|