@modelzen/feishu-codex-bridge 0.4.2 → 0.5.0
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/cli.js +1298 -181
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -127,6 +127,13 @@ function secretKeyForApp(appId) {
|
|
|
127
127
|
function getShowToolCalls(cfg) {
|
|
128
128
|
return cfg.preferences?.showToolCalls !== false;
|
|
129
129
|
}
|
|
130
|
+
function getModelDisplay(cfg) {
|
|
131
|
+
const v = cfg.preferences?.showModel;
|
|
132
|
+
if (v === "running" || v === "always" || v === "off") return v;
|
|
133
|
+
if (v === true) return "always";
|
|
134
|
+
if (v === false) return "off";
|
|
135
|
+
return "running";
|
|
136
|
+
}
|
|
130
137
|
function getMaxConcurrentRuns(cfg) {
|
|
131
138
|
const raw = cfg.preferences?.maxConcurrentRuns;
|
|
132
139
|
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 1) return 10;
|
|
@@ -798,6 +805,25 @@ var BACKEND_CATALOG = [
|
|
|
798
805
|
},
|
|
799
806
|
// supportedModes undefined ⇒ 全档(qa/write/full)。
|
|
800
807
|
blurb: "\u80FD\u529B\u6700\u5168\uFF08goal/steer/compact/resume + \u771F\u6C99\u7BB1\u53EA\u8BFB\u6863\uFF09"
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
id: "claude-agent",
|
|
811
|
+
agentFamily: "claude",
|
|
812
|
+
displayName: "Claude",
|
|
813
|
+
access: "sdk",
|
|
814
|
+
dep: {
|
|
815
|
+
// SDK 是桥的硬依赖(随桥内置),isBackendDepInstalled 路径①经 bridge 自身
|
|
816
|
+
// node_modules 的 require.resolve 恒命中 → 在 picker 里始终判为「已装」可选。
|
|
817
|
+
kind: "npm-ondemand",
|
|
818
|
+
pkg: "@anthropic-ai/claude-agent-sdk",
|
|
819
|
+
// 实测 SDK + 依赖装到用户目录约 265MB(含自带 Claude Code 运行时);首次下载较久。
|
|
820
|
+
approxSizeMB: 265,
|
|
821
|
+
detectHint: "\u6309\u9700\u4E0B\u8F7D @anthropic-ai/claude-agent-sdk\uFF08\u7EA6 265MB\uFF0C\u590D\u7528\u672C\u673A Claude \u767B\u5F55\u6001\uFF09",
|
|
822
|
+
installCmd: "\u968F\u6865\u5DF2\u5185\u7F6E\uFF08\u5982\u7F3A\u5931\uFF1Anpm i @anthropic-ai/claude-agent-sdk\uFF09"
|
|
823
|
+
},
|
|
824
|
+
// 必须与 ClaudeAgentBackend.supportedModes 完全一致(单测强制)。
|
|
825
|
+
supportedModes: ["qa", "write", "full"],
|
|
826
|
+
blurb: "Claude Code\uFF08SDK \u5185\u7F6E\uFF0C\u590D\u7528\u672C\u673A\u767B\u5F55\uFF1Bqa/write \u8D70 OS \u6C99\u7BB1\uFF0C\u80FD\u529B\u8F83 Codex \u7CBE\u7B80\uFF09"
|
|
801
827
|
}
|
|
802
828
|
];
|
|
803
829
|
function visibleCatalog() {
|
|
@@ -2043,7 +2069,960 @@ var STATIC_MODELS = [
|
|
|
2043
2069
|
hidden: false,
|
|
2044
2070
|
isDefault: true,
|
|
2045
2071
|
supportedEfforts: ["low", "medium", "high"],
|
|
2046
|
-
defaultEffort: "medium"
|
|
2072
|
+
defaultEffort: "medium"
|
|
2073
|
+
}
|
|
2074
|
+
];
|
|
2075
|
+
|
|
2076
|
+
// src/agent/claude-agent/backend.ts
|
|
2077
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2078
|
+
|
|
2079
|
+
// src/agent/backend-loader.ts
|
|
2080
|
+
import { createRequire } from "module";
|
|
2081
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
2082
|
+
import { join as join6 } from "path";
|
|
2083
|
+
import { pathToFileURL } from "url";
|
|
2084
|
+
var BackendNotInstalledError = class extends Error {
|
|
2085
|
+
constructor(pkg) {
|
|
2086
|
+
super(`\u540E\u7AEF\u4F9D\u8D56\u300C${pkg}\u300D\u672A\u5B89\u88C5\uFF08bridge \u81EA\u8EAB\u4E0E\u7528\u6237\u79C1\u88C5\u76EE\u5F55\u5747\u672A\u627E\u5230\uFF09`);
|
|
2087
|
+
this.pkg = pkg;
|
|
2088
|
+
this.name = "BackendNotInstalledError";
|
|
2089
|
+
}
|
|
2090
|
+
pkg;
|
|
2091
|
+
};
|
|
2092
|
+
function userAnchor() {
|
|
2093
|
+
return join6(paths.backendsDir, "__backend_anchor__.cjs");
|
|
2094
|
+
}
|
|
2095
|
+
var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["ERR_MODULE_NOT_FOUND", "MODULE_NOT_FOUND", "ERR_PACKAGE_PATH_NOT_EXPORTED"]);
|
|
2096
|
+
function isNotFound(err) {
|
|
2097
|
+
const code = err?.code;
|
|
2098
|
+
if (code !== void 0 && NOT_FOUND_CODES.has(code)) return true;
|
|
2099
|
+
const msg = err?.message ?? "";
|
|
2100
|
+
return /Failed to (load|resolve) (url|import)/i.test(msg);
|
|
2101
|
+
}
|
|
2102
|
+
async function loadBackendDep(pkg) {
|
|
2103
|
+
try {
|
|
2104
|
+
return await import(pkg);
|
|
2105
|
+
} catch (err) {
|
|
2106
|
+
if (!isNotFound(err)) throw err;
|
|
2107
|
+
}
|
|
2108
|
+
let resolved;
|
|
2109
|
+
try {
|
|
2110
|
+
resolved = createRequire(userAnchor()).resolve(pkg);
|
|
2111
|
+
} catch {
|
|
2112
|
+
throw new BackendNotInstalledError(pkg);
|
|
2113
|
+
}
|
|
2114
|
+
return await import(pathToFileURL(resolved).href);
|
|
2115
|
+
}
|
|
2116
|
+
function isBackendDepInstalled(pkg) {
|
|
2117
|
+
try {
|
|
2118
|
+
createRequire(import.meta.url).resolve(pkg);
|
|
2119
|
+
return true;
|
|
2120
|
+
} catch {
|
|
2121
|
+
}
|
|
2122
|
+
try {
|
|
2123
|
+
createRequire(userAnchor()).resolve(pkg);
|
|
2124
|
+
return true;
|
|
2125
|
+
} catch {
|
|
2126
|
+
return false;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
function backendsBinPath(binName) {
|
|
2130
|
+
const dir = join6(paths.backendsDir, "node_modules", ".bin");
|
|
2131
|
+
const candidates = process.platform === "win32" ? [join6(dir, `${binName}.cmd`), join6(dir, binName)] : [join6(dir, binName)];
|
|
2132
|
+
return candidates.find((p) => existsSync3(p)) ?? null;
|
|
2133
|
+
}
|
|
2134
|
+
function isBackendBinInstalled(binName) {
|
|
2135
|
+
return backendsBinPath(binName) !== null;
|
|
2136
|
+
}
|
|
2137
|
+
function isBackendEntryInstalled(entry) {
|
|
2138
|
+
const { kind, binName, pkg } = entry.dep;
|
|
2139
|
+
if (kind === "external-cli") return false;
|
|
2140
|
+
if (binName) return isBackendBinInstalled(binName);
|
|
2141
|
+
return pkg ? isBackendDepInstalled(pkg) : false;
|
|
2142
|
+
}
|
|
2143
|
+
function isBackendInstalledInUserDir(entry) {
|
|
2144
|
+
const { binName, pkg } = entry.dep;
|
|
2145
|
+
if (binName) return isBackendBinInstalled(binName);
|
|
2146
|
+
if (!pkg) return false;
|
|
2147
|
+
try {
|
|
2148
|
+
createRequire(userAnchor()).resolve(pkg);
|
|
2149
|
+
return true;
|
|
2150
|
+
} catch {
|
|
2151
|
+
return false;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
function installedBackendVersion(pkg) {
|
|
2155
|
+
const readVer = (file) => {
|
|
2156
|
+
try {
|
|
2157
|
+
const j = JSON.parse(readFileSync2(file, "utf8"));
|
|
2158
|
+
return typeof j.version === "string" ? j.version : null;
|
|
2159
|
+
} catch {
|
|
2160
|
+
return null;
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
const inUser = join6(paths.backendsDir, "node_modules", ...pkg.split("/"), "package.json");
|
|
2164
|
+
const v1 = readVer(inUser);
|
|
2165
|
+
if (v1) return v1;
|
|
2166
|
+
try {
|
|
2167
|
+
const resolved = createRequire(import.meta.url).resolve(`${pkg}/package.json`);
|
|
2168
|
+
return readVer(resolved);
|
|
2169
|
+
} catch {
|
|
2170
|
+
return null;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
// src/agent/claude-agent/permission.ts
|
|
2175
|
+
var WRITE_TOOLS = ["Write", "Edit", "NotebookEdit"];
|
|
2176
|
+
var NETWORK_TOOLS = ["WebFetch", "WebSearch"];
|
|
2177
|
+
function permissionOptions(mode, network, cwd) {
|
|
2178
|
+
const tier = mode ?? "full";
|
|
2179
|
+
if (tier === "full") {
|
|
2180
|
+
return { permissionMode: "bypassPermissions", allowDangerouslySkipPermissions: true };
|
|
2181
|
+
}
|
|
2182
|
+
const sandbox = {
|
|
2183
|
+
enabled: true,
|
|
2184
|
+
failIfUnavailable: true,
|
|
2185
|
+
// fail-closed: error out instead of running unsandboxed.
|
|
2186
|
+
autoAllowBashIfSandboxed: true,
|
|
2187
|
+
// sandboxed Bash needn't prompt — the sandbox is the gate.
|
|
2188
|
+
allowUnsandboxedCommands: false,
|
|
2189
|
+
// CRITICAL: block the dangerouslyDisableSandbox escape.
|
|
2190
|
+
...tier === "qa" ? { filesystem: { denyWrite: [cwd] } } : {}
|
|
2191
|
+
};
|
|
2192
|
+
const disallowedTools = [];
|
|
2193
|
+
if (tier === "qa") disallowedTools.push(...WRITE_TOOLS);
|
|
2194
|
+
if (!network) disallowedTools.push(...NETWORK_TOOLS);
|
|
2195
|
+
return {
|
|
2196
|
+
// bypassPermissions = never prompt; the sandbox (above) is the real boundary.
|
|
2197
|
+
permissionMode: "bypassPermissions",
|
|
2198
|
+
allowDangerouslySkipPermissions: true,
|
|
2199
|
+
sandbox,
|
|
2200
|
+
...disallowedTools.length ? { disallowedTools } : {}
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
// src/agent/claude-agent/event-map.ts
|
|
2205
|
+
function createTurnMapper(ctx = {}) {
|
|
2206
|
+
let blockSeq = 0;
|
|
2207
|
+
const open3 = /* @__PURE__ */ new Map();
|
|
2208
|
+
let systemEmitted = false;
|
|
2209
|
+
let initModel;
|
|
2210
|
+
function map(msg) {
|
|
2211
|
+
const m = msg;
|
|
2212
|
+
switch (m.type) {
|
|
2213
|
+
case "system":
|
|
2214
|
+
if (m.subtype === "init") {
|
|
2215
|
+
if (typeof m.model === "string") initModel = m.model;
|
|
2216
|
+
if (!systemEmitted) {
|
|
2217
|
+
systemEmitted = true;
|
|
2218
|
+
return [{ type: "system", threadId: String(m.session_id ?? "") }];
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
if (m.subtype === "compact_boundary") return [{ type: "context_compacted" }];
|
|
2222
|
+
return [];
|
|
2223
|
+
case "stream_event":
|
|
2224
|
+
return mapStreamEvent(m.event);
|
|
2225
|
+
case "assistant": {
|
|
2226
|
+
const content = m.message?.content ?? [];
|
|
2227
|
+
const out = [];
|
|
2228
|
+
for (const b of content) {
|
|
2229
|
+
if (b.type === "tool_use" || b.type === "server_tool_use") {
|
|
2230
|
+
out.push({
|
|
2231
|
+
type: "tool_use",
|
|
2232
|
+
itemId: String(b.id ?? `tool${++blockSeq}`),
|
|
2233
|
+
title: toolTitle(b.name ?? "\u5DE5\u5177", b.input ?? {}, ctx.cwd),
|
|
2234
|
+
detail: toolDetail(b.name ?? "", b.input ?? {})
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
return out;
|
|
2239
|
+
}
|
|
2240
|
+
case "user": {
|
|
2241
|
+
const content = m.message?.content;
|
|
2242
|
+
if (!Array.isArray(content)) return [];
|
|
2243
|
+
const out = [];
|
|
2244
|
+
for (const b of content) {
|
|
2245
|
+
if (b.type === "tool_result") {
|
|
2246
|
+
out.push({
|
|
2247
|
+
type: "tool_result",
|
|
2248
|
+
itemId: String(b.tool_use_id ?? ""),
|
|
2249
|
+
output: toolResultText(b.content),
|
|
2250
|
+
exitCode: b.is_error ? 1 : 0
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
return out;
|
|
2255
|
+
}
|
|
2256
|
+
case "result": {
|
|
2257
|
+
const out = [];
|
|
2258
|
+
const usage = m.usage;
|
|
2259
|
+
if (usage) {
|
|
2260
|
+
out.push({
|
|
2261
|
+
type: "usage",
|
|
2262
|
+
inputTokens: usage.input_tokens,
|
|
2263
|
+
outputTokens: usage.output_tokens
|
|
2264
|
+
});
|
|
2265
|
+
const used = (usage.input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0);
|
|
2266
|
+
if (used > 0) {
|
|
2267
|
+
out.push({ type: "context_usage", usedTokens: used, contextWindow: contextWindowFor(initModel) });
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
return out;
|
|
2271
|
+
}
|
|
2272
|
+
// Transient API hiccup the SDK auto-retries — surface a retrying footer
|
|
2273
|
+
// (run-state keeps the ⏹ button and lets the next deltas overwrite it).
|
|
2274
|
+
case "system_api_retry":
|
|
2275
|
+
case "api_retry":
|
|
2276
|
+
return [{ type: "error", message: "\u7F51\u7EDC\u6CE2\u52A8\uFF0C\u6B63\u5728\u91CD\u8BD5\u2026", willRetry: true }];
|
|
2277
|
+
default:
|
|
2278
|
+
return [];
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
function mapStreamEvent(ev) {
|
|
2282
|
+
if (!ev || typeof ev.type !== "string") return [];
|
|
2283
|
+
switch (ev.type) {
|
|
2284
|
+
case "content_block_start": {
|
|
2285
|
+
const idx = ev.index;
|
|
2286
|
+
const cb = ev.content_block ?? {};
|
|
2287
|
+
const kind = cb.type === "thinking" || cb.type === "redacted_thinking" ? "thinking" : cb.type === "text" ? "text" : cb.type === "tool_use" || cb.type === "server_tool_use" ? "tool_use" : "other";
|
|
2288
|
+
open3.set(idx, { itemId: `b${++blockSeq}`, kind, acc: "" });
|
|
2289
|
+
return [];
|
|
2290
|
+
}
|
|
2291
|
+
case "content_block_delta": {
|
|
2292
|
+
const b = open3.get(ev.index);
|
|
2293
|
+
if (!b) return [];
|
|
2294
|
+
const d = ev.delta ?? {};
|
|
2295
|
+
if (d.type === "text_delta" && typeof d.text === "string") {
|
|
2296
|
+
b.acc += d.text;
|
|
2297
|
+
return [{ type: "text_delta", itemId: b.itemId, delta: d.text }];
|
|
2298
|
+
}
|
|
2299
|
+
if (d.type === "thinking_delta" && typeof d.thinking === "string") {
|
|
2300
|
+
b.acc += d.thinking;
|
|
2301
|
+
return [{ type: "thinking_delta", itemId: b.itemId, delta: d.thinking }];
|
|
2302
|
+
}
|
|
2303
|
+
return [];
|
|
2304
|
+
}
|
|
2305
|
+
case "content_block_stop": {
|
|
2306
|
+
const idx = ev.index;
|
|
2307
|
+
const b = open3.get(idx);
|
|
2308
|
+
open3.delete(idx);
|
|
2309
|
+
if (!b || !b.acc) return [];
|
|
2310
|
+
if (b.kind === "text") return [{ type: "text", itemId: b.itemId, text: b.acc }];
|
|
2311
|
+
if (b.kind === "thinking") return [{ type: "thinking", itemId: b.itemId, text: b.acc }];
|
|
2312
|
+
return [];
|
|
2313
|
+
}
|
|
2314
|
+
default:
|
|
2315
|
+
return [];
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
return { map };
|
|
2319
|
+
}
|
|
2320
|
+
function toolTitle(name, input2, cwd) {
|
|
2321
|
+
const s = (k2) => typeof input2[k2] === "string" ? input2[k2] : "";
|
|
2322
|
+
switch (name) {
|
|
2323
|
+
case "Bash":
|
|
2324
|
+
case "BashOutput":
|
|
2325
|
+
return s("command") || "Shell \u547D\u4EE4";
|
|
2326
|
+
case "Read":
|
|
2327
|
+
return `\u8BFB\u53D6 ${displayPath2(s("file_path"), cwd)}`;
|
|
2328
|
+
case "Write":
|
|
2329
|
+
return `\u5199\u5165 ${displayPath2(s("file_path"), cwd)}`;
|
|
2330
|
+
case "Edit":
|
|
2331
|
+
case "MultiEdit":
|
|
2332
|
+
return `\u7F16\u8F91 ${displayPath2(s("file_path"), cwd)}`;
|
|
2333
|
+
case "NotebookEdit":
|
|
2334
|
+
return `\u7F16\u8F91\u7B14\u8BB0\u672C ${displayPath2(s("notebook_path"), cwd)}`;
|
|
2335
|
+
case "Glob":
|
|
2336
|
+
return `\u67E5\u627E ${s("pattern")}`.trim();
|
|
2337
|
+
case "Grep":
|
|
2338
|
+
return `\u641C\u7D22 ${s("pattern")}`.trim();
|
|
2339
|
+
case "WebFetch":
|
|
2340
|
+
return `\u6293\u53D6\u7F51\u9875 ${s("url")}`.trim();
|
|
2341
|
+
case "WebSearch":
|
|
2342
|
+
return `\u8054\u7F51\u641C\u7D22 ${s("query")}`.trim();
|
|
2343
|
+
case "Task":
|
|
2344
|
+
return `\u5B50\u4EFB\u52A1\uFF1A${s("description") || s("subagent_type") || ""}`.trim();
|
|
2345
|
+
case "TodoWrite":
|
|
2346
|
+
return "\u66F4\u65B0\u5F85\u529E\u6E05\u5355";
|
|
2347
|
+
case "ExitPlanMode":
|
|
2348
|
+
return "\u63D0\u4EA4\u65B9\u6848";
|
|
2349
|
+
default:
|
|
2350
|
+
return name || "\u5DE5\u5177\u8C03\u7528";
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
function toolDetail(name, input2) {
|
|
2354
|
+
if (name === "Bash" && typeof input2.description === "string" && input2.description) {
|
|
2355
|
+
return input2.description;
|
|
2356
|
+
}
|
|
2357
|
+
return void 0;
|
|
2358
|
+
}
|
|
2359
|
+
function toolResultText(content) {
|
|
2360
|
+
if (content == null) return void 0;
|
|
2361
|
+
if (typeof content === "string") return content || void 0;
|
|
2362
|
+
if (Array.isArray(content)) {
|
|
2363
|
+
const text = content.map((b) => {
|
|
2364
|
+
const x = b;
|
|
2365
|
+
if (x?.type === "text" && typeof x.text === "string") return x.text;
|
|
2366
|
+
if (x?.type === "image") return "[\u56FE\u7247]";
|
|
2367
|
+
return "";
|
|
2368
|
+
}).join("").trim();
|
|
2369
|
+
return text || void 0;
|
|
2370
|
+
}
|
|
2371
|
+
return void 0;
|
|
2372
|
+
}
|
|
2373
|
+
var PATH_TAIL_MAX2 = 40;
|
|
2374
|
+
function displayPath2(p, cwd) {
|
|
2375
|
+
if (!p) return "\u6587\u4EF6";
|
|
2376
|
+
if (cwd) {
|
|
2377
|
+
const sep2 = cwd.includes("\\") ? "\\" : "/";
|
|
2378
|
+
const root = cwd.endsWith(sep2) ? cwd : cwd + sep2;
|
|
2379
|
+
if (p.startsWith(root) && p.length > root.length) return p.slice(root.length);
|
|
2380
|
+
}
|
|
2381
|
+
if (p.length <= PATH_TAIL_MAX2 || !p.includes("/")) return p;
|
|
2382
|
+
const segs = p.split("/");
|
|
2383
|
+
let out = segs[segs.length - 1] ?? p;
|
|
2384
|
+
for (let i = segs.length - 2; i >= 0; i--) {
|
|
2385
|
+
const cand = `${segs[i]}/${out}`;
|
|
2386
|
+
if (cand.length > PATH_TAIL_MAX2) break;
|
|
2387
|
+
out = cand;
|
|
2388
|
+
}
|
|
2389
|
+
return `\u2026/${out}`;
|
|
2390
|
+
}
|
|
2391
|
+
function contextWindowFor(model) {
|
|
2392
|
+
const id = typeof model === "string" ? model.toLowerCase() : "";
|
|
2393
|
+
if (!id) return null;
|
|
2394
|
+
if (id.includes("1m") || id.includes("[1m]")) return 1e6;
|
|
2395
|
+
return 2e5;
|
|
2396
|
+
}
|
|
2397
|
+
function resultErrorText(m) {
|
|
2398
|
+
const result = m.result;
|
|
2399
|
+
if (typeof result === "string" && result.trim()) return result.trim();
|
|
2400
|
+
const subtype = m.subtype;
|
|
2401
|
+
if (typeof subtype === "string") {
|
|
2402
|
+
if (subtype === "error_max_turns") return "\u5DF2\u8FBE\u5230\u6700\u5927\u8F6E\u6B21\u9650\u5236";
|
|
2403
|
+
if (subtype === "error_max_budget_usd") return "\u5DF2\u8FBE\u5230\u9884\u7B97\u4E0A\u9650";
|
|
2404
|
+
return `\u8FD0\u884C\u51FA\u9519\uFF08${subtype}\uFF09`;
|
|
2405
|
+
}
|
|
2406
|
+
return "\u8FD0\u884C\u51FA\u9519";
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// src/agent/claude-agent/history.ts
|
|
2410
|
+
function mapSessionSummary(s) {
|
|
2411
|
+
const preview = (s.customTitle || s.summary || s.firstPrompt || "").trim();
|
|
2412
|
+
return {
|
|
2413
|
+
sessionId: s.sessionId,
|
|
2414
|
+
preview,
|
|
2415
|
+
createdAt: msToSec(s.createdAt ?? s.lastModified),
|
|
2416
|
+
updatedAt: msToSec(s.lastModified),
|
|
2417
|
+
name: s.customTitle || void 0
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
function msToSec(ms) {
|
|
2421
|
+
return ms && ms > 0 ? Math.floor(ms / 1e3) : 0;
|
|
2422
|
+
}
|
|
2423
|
+
function blocksOf(message) {
|
|
2424
|
+
const content = message?.content;
|
|
2425
|
+
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
2426
|
+
if (Array.isArray(content)) return content;
|
|
2427
|
+
return [];
|
|
2428
|
+
}
|
|
2429
|
+
function isBoilerplateUserText2(text) {
|
|
2430
|
+
const t = text.trimStart();
|
|
2431
|
+
return t.startsWith("<environment_context>") || t.startsWith("# AGENTS.md") || t.startsWith("<system-reminder>") || t.startsWith("Caveat:");
|
|
2432
|
+
}
|
|
2433
|
+
function toolResultText2(content) {
|
|
2434
|
+
if (content == null) return void 0;
|
|
2435
|
+
if (typeof content === "string") return content || void 0;
|
|
2436
|
+
if (Array.isArray(content)) {
|
|
2437
|
+
const text = content.map((b) => {
|
|
2438
|
+
const x = b;
|
|
2439
|
+
return x?.type === "text" && typeof x.text === "string" ? x.text : "";
|
|
2440
|
+
}).join("").trim();
|
|
2441
|
+
return text || void 0;
|
|
2442
|
+
}
|
|
2443
|
+
return void 0;
|
|
2444
|
+
}
|
|
2445
|
+
function foldSessionMessages(messages, maxTurns, cwd) {
|
|
2446
|
+
const turns = [];
|
|
2447
|
+
const toolById = /* @__PURE__ */ new Map();
|
|
2448
|
+
let cur = null;
|
|
2449
|
+
const flush = () => {
|
|
2450
|
+
if (cur && cur._hasContent) {
|
|
2451
|
+
const { _hasContent, ...turn } = cur;
|
|
2452
|
+
void _hasContent;
|
|
2453
|
+
turns.push(turn);
|
|
2454
|
+
}
|
|
2455
|
+
cur = null;
|
|
2456
|
+
};
|
|
2457
|
+
const ensure = () => {
|
|
2458
|
+
if (!cur) cur = { userText: "", assistantText: "", reasoning: "", tools: [], _hasContent: false };
|
|
2459
|
+
return cur;
|
|
2460
|
+
};
|
|
2461
|
+
for (const msg of messages) {
|
|
2462
|
+
const blocks = blocksOf(msg.message);
|
|
2463
|
+
if (msg.type === "user") {
|
|
2464
|
+
const texts = [];
|
|
2465
|
+
let attachedResult = false;
|
|
2466
|
+
for (const b of blocks) {
|
|
2467
|
+
if (b.type === "text" && b.text && !isBoilerplateUserText2(b.text)) texts.push(b.text);
|
|
2468
|
+
else if (b.type === "tool_result" && b.tool_use_id) {
|
|
2469
|
+
const tool = toolById.get(b.tool_use_id);
|
|
2470
|
+
if (tool) {
|
|
2471
|
+
tool.output = toolResultText2(b.content);
|
|
2472
|
+
if (b.is_error) tool.failed = true;
|
|
2473
|
+
attachedResult = true;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
const userText = texts.join("\n").trim();
|
|
2478
|
+
if (userText) {
|
|
2479
|
+
flush();
|
|
2480
|
+
const t = ensure();
|
|
2481
|
+
t.userText = userText;
|
|
2482
|
+
t._hasContent = true;
|
|
2483
|
+
} else if (attachedResult) {
|
|
2484
|
+
ensure()._hasContent = true;
|
|
2485
|
+
}
|
|
2486
|
+
} else if (msg.type === "assistant") {
|
|
2487
|
+
const t = ensure();
|
|
2488
|
+
for (const b of blocks) {
|
|
2489
|
+
if (b.type === "text" && b.text) {
|
|
2490
|
+
t.assistantText = t.assistantText ? `${t.assistantText}
|
|
2491
|
+
|
|
2492
|
+
${b.text}` : b.text;
|
|
2493
|
+
t._hasContent = true;
|
|
2494
|
+
} else if (b.type === "thinking" && b.thinking) {
|
|
2495
|
+
t.reasoning = t.reasoning ? `${t.reasoning}
|
|
2496
|
+
|
|
2497
|
+
${b.thinking}` : b.thinking;
|
|
2498
|
+
t._hasContent = true;
|
|
2499
|
+
} else if ((b.type === "tool_use" || b.type === "server_tool_use") && b.id) {
|
|
2500
|
+
const tool = { title: toolTitle(b.name ?? "\u5DE5\u5177", b.input ?? {}, cwd) };
|
|
2501
|
+
t.tools.push(tool);
|
|
2502
|
+
toolById.set(b.id, tool);
|
|
2503
|
+
t._hasContent = true;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
flush();
|
|
2509
|
+
const totalTurns = turns.length;
|
|
2510
|
+
const kept = totalTurns > maxTurns ? turns.slice(totalTurns - maxTurns) : turns;
|
|
2511
|
+
return { turns: kept, totalTurns };
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/agent/claude-agent/thread.ts
|
|
2515
|
+
var COMPACT_TIMEOUT_MS2 = 12e4;
|
|
2516
|
+
var ABORT_ESCALATE_MS = 4e3;
|
|
2517
|
+
var Inbox = class {
|
|
2518
|
+
buf = [];
|
|
2519
|
+
waiters = [];
|
|
2520
|
+
push(v) {
|
|
2521
|
+
const w = this.waiters.shift();
|
|
2522
|
+
if (w) w(v);
|
|
2523
|
+
else this.buf.push(v);
|
|
2524
|
+
}
|
|
2525
|
+
next() {
|
|
2526
|
+
const v = this.buf.shift();
|
|
2527
|
+
if (v !== void 0) return Promise.resolve(v);
|
|
2528
|
+
return new Promise((resolve8) => this.waiters.push(resolve8));
|
|
2529
|
+
}
|
|
2530
|
+
};
|
|
2531
|
+
var PushablePrompt = class {
|
|
2532
|
+
buf = [];
|
|
2533
|
+
waiters = [];
|
|
2534
|
+
closed = false;
|
|
2535
|
+
push(msg) {
|
|
2536
|
+
if (this.closed) return;
|
|
2537
|
+
const w = this.waiters.shift();
|
|
2538
|
+
if (w) w(msg);
|
|
2539
|
+
else this.buf.push(msg);
|
|
2540
|
+
}
|
|
2541
|
+
close() {
|
|
2542
|
+
this.closed = true;
|
|
2543
|
+
while (this.waiters.length) this.waiters.shift()(null);
|
|
2544
|
+
}
|
|
2545
|
+
async *[Symbol.asyncIterator]() {
|
|
2546
|
+
while (true) {
|
|
2547
|
+
const v = this.buf.shift();
|
|
2548
|
+
if (v !== void 0) {
|
|
2549
|
+
yield v;
|
|
2550
|
+
continue;
|
|
2551
|
+
}
|
|
2552
|
+
if (this.closed) return;
|
|
2553
|
+
const next = await new Promise((resolve8) => this.waiters.push(resolve8));
|
|
2554
|
+
if (next == null) return;
|
|
2555
|
+
yield next;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
};
|
|
2559
|
+
function toSdkEffort(e) {
|
|
2560
|
+
if (!e) return void 0;
|
|
2561
|
+
if (e === "none" || e === "minimal") return "low";
|
|
2562
|
+
return e;
|
|
2563
|
+
}
|
|
2564
|
+
function toUserMessage(input2) {
|
|
2565
|
+
const text = input2.text ?? "";
|
|
2566
|
+
return {
|
|
2567
|
+
type: "user",
|
|
2568
|
+
message: { role: "user", content: text },
|
|
2569
|
+
parent_tool_use_id: null
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function goalPrompt(objective) {
|
|
2573
|
+
return [
|
|
2574
|
+
"\u3010\u81EA\u4E3B\u76EE\u6807\u3011\u8BF7\u8FDE\u7EED\u3001\u81EA\u4E3B\u5730\u5B8C\u6210\u4E0B\u9762\u7684\u76EE\u6807\uFF1A\u6309\u9700\u4F7F\u7528\u5DE5\u5177\uFF0C\u4E00\u6B65\u6B65\u505A\u5230\u5B8C\u6210\u4E3A\u6B62\uFF0C",
|
|
2575
|
+
"\u4E2D\u9014\u4E0D\u8981\u505C\u4E0B\u6765\u7B49\u6211\u786E\u8BA4\uFF1B\u5B8C\u6210\u540E\u7528\u4E00\u6BB5\u8BDD\u603B\u7ED3\u505A\u4E86\u4EC0\u4E48\u3002",
|
|
2576
|
+
"",
|
|
2577
|
+
`\u76EE\u6807\uFF1A${objective}`
|
|
2578
|
+
].join("\n");
|
|
2579
|
+
}
|
|
2580
|
+
function goalStatusFromResult(subtype) {
|
|
2581
|
+
if (subtype === "error_max_budget_usd") return "budgetLimited";
|
|
2582
|
+
return "blocked";
|
|
2583
|
+
}
|
|
2584
|
+
var ClaudeAgentThread = class {
|
|
2585
|
+
sessionId;
|
|
2586
|
+
cwd;
|
|
2587
|
+
model;
|
|
2588
|
+
effort;
|
|
2589
|
+
input = new PushablePrompt();
|
|
2590
|
+
query;
|
|
2591
|
+
abortController = new AbortController();
|
|
2592
|
+
dead = false;
|
|
2593
|
+
interruptRequested = false;
|
|
2594
|
+
/** true while a runGoal() turn is in flight — clearGoal() only hard-stops then. */
|
|
2595
|
+
goalRunning = false;
|
|
2596
|
+
/** true while ANY turn (runStreamed/runGoal) is in flight — gates abort escalation. */
|
|
2597
|
+
turnInFlight = false;
|
|
2598
|
+
escalateTimer;
|
|
2599
|
+
turnSeq = 0;
|
|
2600
|
+
currentTurnId;
|
|
2601
|
+
lastActivityAt = Date.now();
|
|
2602
|
+
sink;
|
|
2603
|
+
constructor(cfg) {
|
|
2604
|
+
this.sessionId = cfg.sessionId;
|
|
2605
|
+
this.cwd = cfg.cwd;
|
|
2606
|
+
this.model = cfg.model;
|
|
2607
|
+
this.effort = cfg.effort;
|
|
2608
|
+
const options = {
|
|
2609
|
+
cwd: cfg.cwd,
|
|
2610
|
+
abortController: this.abortController,
|
|
2611
|
+
includePartialMessages: true,
|
|
2612
|
+
...cfg.model ? { model: cfg.model } : {},
|
|
2613
|
+
...toSdkEffort(cfg.effort) ? { effort: toSdkEffort(cfg.effort) } : {},
|
|
2614
|
+
...cfg.resume ? { resume: cfg.sessionId } : { sessionId: cfg.sessionId },
|
|
2615
|
+
...cfg.systemPromptAppend ? { systemPrompt: { type: "preset", preset: "claude_code", append: cfg.systemPromptAppend } } : {},
|
|
2616
|
+
// permission tier (permissionMode / sandbox / canUseTool / disallowedTools)
|
|
2617
|
+
...cfg.permission,
|
|
2618
|
+
stderr: (d) => {
|
|
2619
|
+
const s = String(d).trim();
|
|
2620
|
+
if (s) log.info("agent", "sdk-stderr", { backend: "claude-agent", line: s.slice(0, 300) });
|
|
2621
|
+
}
|
|
2622
|
+
};
|
|
2623
|
+
this.query = cfg.query({ prompt: this.input, options });
|
|
2624
|
+
void this.pump();
|
|
2625
|
+
}
|
|
2626
|
+
/** Single background consumer: route every query message to the current turn. */
|
|
2627
|
+
async pump() {
|
|
2628
|
+
try {
|
|
2629
|
+
for await (const msg of this.query) {
|
|
2630
|
+
this.lastActivityAt = Date.now();
|
|
2631
|
+
this.sink?.({ kind: "msg", msg });
|
|
2632
|
+
}
|
|
2633
|
+
this.dead = true;
|
|
2634
|
+
this.sink?.({ kind: "end" });
|
|
2635
|
+
} catch (err) {
|
|
2636
|
+
this.dead = true;
|
|
2637
|
+
log.fail("agent", err, { backend: "claude-agent", phase: "pump" });
|
|
2638
|
+
this.sink?.({ kind: "error", err });
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
runStreamed(input2, turn) {
|
|
2642
|
+
const turnId = `t${++this.turnSeq}`;
|
|
2643
|
+
this.currentTurnId = turnId;
|
|
2644
|
+
this.interruptRequested = false;
|
|
2645
|
+
this.turnInFlight = true;
|
|
2646
|
+
if (turn?.model && turn.model !== this.model) {
|
|
2647
|
+
this.model = turn.model;
|
|
2648
|
+
this.query.setModel(turn.model).catch((err) => log.fail("agent", err, { phase: "setModel" }));
|
|
2649
|
+
}
|
|
2650
|
+
if (turn?.effort) this.effort = turn.effort;
|
|
2651
|
+
const mapper = createTurnMapper({ cwd: this.cwd });
|
|
2652
|
+
const inbox = new Inbox();
|
|
2653
|
+
const mySink = (item) => inbox.push(item);
|
|
2654
|
+
this.sink = mySink;
|
|
2655
|
+
this.input.push(toUserMessage(input2));
|
|
2656
|
+
const self = this;
|
|
2657
|
+
async function* gen() {
|
|
2658
|
+
yield { type: "turn_started", turnId };
|
|
2659
|
+
try {
|
|
2660
|
+
while (true) {
|
|
2661
|
+
const item = await inbox.next();
|
|
2662
|
+
if (item.kind === "end" || item.kind === "error") {
|
|
2663
|
+
if (self.interruptRequested) {
|
|
2664
|
+
yield { type: "done", turnId };
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
const msg2 = item.kind === "error" && item.err instanceof Error ? item.err.message : "Claude \u4F1A\u8BDD\u8FDB\u7A0B\u5DF2\u9000\u51FA";
|
|
2668
|
+
yield { type: "error", message: msg2 || "Claude \u4F1A\u8BDD\u51FA\u9519", willRetry: false };
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
const msg = item.msg;
|
|
2672
|
+
self.lastActivityAt = Date.now();
|
|
2673
|
+
for (const ev of mapper.map(item.msg)) yield ev;
|
|
2674
|
+
if (msg.type === "result") {
|
|
2675
|
+
const ok = msg.subtype === "success" || self.interruptRequested;
|
|
2676
|
+
if (ok) {
|
|
2677
|
+
try {
|
|
2678
|
+
const cu = await self.query.getContextUsage();
|
|
2679
|
+
if (cu && typeof cu.totalTokens === "number" && typeof cu.maxTokens === "number") {
|
|
2680
|
+
yield { type: "context_usage", usedTokens: cu.totalTokens, contextWindow: cu.maxTokens };
|
|
2681
|
+
}
|
|
2682
|
+
} catch (err) {
|
|
2683
|
+
log.fail("agent", err, { backend: "claude-agent", phase: "getContextUsage" });
|
|
2684
|
+
}
|
|
2685
|
+
yield { type: "done", turnId };
|
|
2686
|
+
} else {
|
|
2687
|
+
yield { type: "error", message: resultErrorText(msg), willRetry: false };
|
|
2688
|
+
}
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
} finally {
|
|
2693
|
+
self.endTurn();
|
|
2694
|
+
if (self.sink === mySink) self.sink = void 0;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
return {
|
|
2698
|
+
events: gen(),
|
|
2699
|
+
turnId: () => self.currentTurnId,
|
|
2700
|
+
lastActivity: () => self.lastActivityAt
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
/** End-of-turn bookkeeping: clear the in-flight flag + any pending ⏹ escalation. */
|
|
2704
|
+
endTurn() {
|
|
2705
|
+
this.turnInFlight = false;
|
|
2706
|
+
if (this.escalateTimer) {
|
|
2707
|
+
clearTimeout(this.escalateTimer);
|
|
2708
|
+
this.escalateTimer = void 0;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* /goal —— Claude 没有 codex 那种「目标引擎 + 多轮自动续跑」,但 Claude Code 的
|
|
2713
|
+
* agent loop 本身就能在 ONE query turn 内自主多步跑完一个目标。所以这里把目标当作
|
|
2714
|
+
* 「一个自主轮」:发带自主提示的目标 → 流式跑 → 合成 goal_update 状态(active 起、
|
|
2715
|
+
* complete/blocked/budgetLimited 收)。
|
|
2716
|
+
*
|
|
2717
|
+
* ── 与 codex 的差异(如实)──────────────────────────────────────────────
|
|
2718
|
+
* - codex:N 个自动续跑的 turn(N 张卡片)+ 原生状态机(active/paused/complete/
|
|
2719
|
+
* budgetLimited/usageLimited/blocked)+ token 预算。
|
|
2720
|
+
* - claude:1 个自主 turn(1 张卡片,内部多步)+ 合成状态(仅 active→complete/
|
|
2721
|
+
* blocked/budgetLimited)。无 paused/usageLimited、无预算。
|
|
2722
|
+
* - ⏹ 终止 / 🎯 结束:经 clearGoal() 用 abortController 硬停(spike 实测 ~2s 停,
|
|
2723
|
+
* 且 abort 后会话可 resume 续聊)——比工具执行中途的 interrupt() 可靠。
|
|
2724
|
+
*/
|
|
2725
|
+
runGoal(objective) {
|
|
2726
|
+
const turnId = `g${++this.turnSeq}`;
|
|
2727
|
+
this.currentTurnId = turnId;
|
|
2728
|
+
this.interruptRequested = false;
|
|
2729
|
+
this.goalRunning = true;
|
|
2730
|
+
this.turnInFlight = true;
|
|
2731
|
+
const startedAt = Date.now();
|
|
2732
|
+
const mapper = createTurnMapper({ cwd: this.cwd });
|
|
2733
|
+
const inbox = new Inbox();
|
|
2734
|
+
const mySink = (item) => inbox.push(item);
|
|
2735
|
+
this.sink = mySink;
|
|
2736
|
+
this.input.push(toUserMessage({ text: goalPrompt(objective) }));
|
|
2737
|
+
const self = this;
|
|
2738
|
+
async function* gen() {
|
|
2739
|
+
yield { type: "goal_update", status: "active", objective, tokensUsed: 0, timeUsedSeconds: 0, tokenBudget: null };
|
|
2740
|
+
yield { type: "turn_started", turnId };
|
|
2741
|
+
let tokensUsed = 0;
|
|
2742
|
+
const finishGoal = (status) => ({
|
|
2743
|
+
type: "goal_update",
|
|
2744
|
+
status,
|
|
2745
|
+
objective,
|
|
2746
|
+
tokensUsed,
|
|
2747
|
+
timeUsedSeconds: Math.round((Date.now() - startedAt) / 1e3),
|
|
2748
|
+
tokenBudget: null
|
|
2749
|
+
});
|
|
2750
|
+
try {
|
|
2751
|
+
while (true) {
|
|
2752
|
+
const item = await inbox.next();
|
|
2753
|
+
if (item.kind === "end" || item.kind === "error") {
|
|
2754
|
+
if (self.interruptRequested) {
|
|
2755
|
+
yield { type: "done", turnId };
|
|
2756
|
+
} else {
|
|
2757
|
+
const m = item.kind === "error" && item.err instanceof Error ? item.err.message : "Claude \u4F1A\u8BDD\u8FDB\u7A0B\u5DF2\u9000\u51FA";
|
|
2758
|
+
yield finishGoal("blocked");
|
|
2759
|
+
yield { type: "error", message: m, willRetry: false };
|
|
2760
|
+
}
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
const msg = item.msg;
|
|
2764
|
+
self.lastActivityAt = Date.now();
|
|
2765
|
+
for (const ev of mapper.map(item.msg)) {
|
|
2766
|
+
if (ev.type === "usage") tokensUsed = (ev.inputTokens ?? 0) + (ev.outputTokens ?? 0);
|
|
2767
|
+
yield ev;
|
|
2768
|
+
}
|
|
2769
|
+
if (msg.type === "result") {
|
|
2770
|
+
self.goalRunning = false;
|
|
2771
|
+
const status = msg.subtype === "success" || self.interruptRequested ? "complete" : goalStatusFromResult(msg.subtype);
|
|
2772
|
+
try {
|
|
2773
|
+
const cu = await self.query.getContextUsage();
|
|
2774
|
+
if (cu && typeof cu.totalTokens === "number" && typeof cu.maxTokens === "number") {
|
|
2775
|
+
yield { type: "context_usage", usedTokens: cu.totalTokens, contextWindow: cu.maxTokens };
|
|
2776
|
+
}
|
|
2777
|
+
} catch {
|
|
2778
|
+
}
|
|
2779
|
+
yield finishGoal(status);
|
|
2780
|
+
yield { type: "done", turnId };
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
} finally {
|
|
2785
|
+
self.goalRunning = false;
|
|
2786
|
+
self.endTurn();
|
|
2787
|
+
if (self.sink === mySink) self.sink = void 0;
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
return { events: gen(), turnId: () => self.currentTurnId, lastActivity: () => self.lastActivityAt };
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Clear/terminate the goal. Claude has no goal engine, so "clear" = hard-stop the
|
|
2794
|
+
* in-flight goal turn via the AbortController (interrupt() can hang mid-tool;
|
|
2795
|
+
* abort reliably stops in ~2s and the session stays resumable, so chat continues
|
|
2796
|
+
* via resolveThread's resume on the next message). No-op when no goal is running
|
|
2797
|
+
* (this is also the orchestrator's idempotent end-of-run cleanup — keep the warm
|
|
2798
|
+
* query alive on natural completion).
|
|
2799
|
+
*/
|
|
2800
|
+
async clearGoal() {
|
|
2801
|
+
if (!this.goalRunning) return;
|
|
2802
|
+
this.goalRunning = false;
|
|
2803
|
+
this.interruptRequested = true;
|
|
2804
|
+
this.dead = true;
|
|
2805
|
+
try {
|
|
2806
|
+
this.abortController.abort();
|
|
2807
|
+
} catch {
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
async steer() {
|
|
2811
|
+
throw new Error("claude-agent \u540E\u7AEF\u6682\u4E0D\u652F\u6301\u98DE\u884C\u4E2D\u5F15\u5BFC\uFF08steer\uFF09\uFF0C\u5C06\u81EA\u52A8\u6539\u4E3A\u4E0B\u4E00\u8F6E\u53D1\u9001");
|
|
2812
|
+
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Manual /compact. Claude Code's `/compact` IS a real slash command (verified:
|
|
2815
|
+
* `supportedCommands()` lists it, and sending "/compact" as input is intercepted
|
|
2816
|
+
* and executed, not echoed). We send it through the persistent query and drain to
|
|
2817
|
+
* the result; a `compact_boundary`(trigger:manual) means it actually compacted —
|
|
2818
|
+
* "Not enough messages to compact." just ends with no boundary (compacted=false).
|
|
2819
|
+
*/
|
|
2820
|
+
async compact() {
|
|
2821
|
+
if (this.dead) throw new Error("Claude \u4F1A\u8BDD\u5DF2\u7ED3\u675F\uFF0C\u65E0\u6CD5\u538B\u7F29");
|
|
2822
|
+
const inbox = new Inbox();
|
|
2823
|
+
const mySink = (item) => inbox.push(item);
|
|
2824
|
+
this.sink = mySink;
|
|
2825
|
+
this.interruptRequested = false;
|
|
2826
|
+
let timer;
|
|
2827
|
+
const timeout = new Promise((resolve8) => {
|
|
2828
|
+
timer = setTimeout(() => resolve8("timeout"), COMPACT_TIMEOUT_MS2);
|
|
2829
|
+
});
|
|
2830
|
+
let compacted = false;
|
|
2831
|
+
try {
|
|
2832
|
+
this.input.push(toUserMessage({ text: "/compact" }));
|
|
2833
|
+
while (true) {
|
|
2834
|
+
const step = await Promise.race([inbox.next(), timeout]);
|
|
2835
|
+
if (step === "timeout") throw new Error(`\u538B\u7F29\u8D85\u65F6\uFF08Claude \u672A\u5728 ${COMPACT_TIMEOUT_MS2 / 1e3}s \u5185\u5B8C\u6210\uFF09`);
|
|
2836
|
+
if (step.kind === "end") throw new Error("Claude \u4F1A\u8BDD\u8FDB\u7A0B\u5DF2\u9000\u51FA");
|
|
2837
|
+
if (step.kind === "error") throw step.err instanceof Error ? step.err : new Error(String(step.err));
|
|
2838
|
+
const msg = step.msg;
|
|
2839
|
+
this.lastActivityAt = Date.now();
|
|
2840
|
+
if (msg.type === "system" && msg.subtype === "compact_boundary") compacted = true;
|
|
2841
|
+
if (msg.type === "result") break;
|
|
2842
|
+
}
|
|
2843
|
+
} finally {
|
|
2844
|
+
if (timer) clearTimeout(timer);
|
|
2845
|
+
if (this.sink === mySink) this.sink = void 0;
|
|
2846
|
+
}
|
|
2847
|
+
let usage = null;
|
|
2848
|
+
try {
|
|
2849
|
+
const cu = await this.query.getContextUsage();
|
|
2850
|
+
if (cu && typeof cu.totalTokens === "number" && typeof cu.maxTokens === "number") {
|
|
2851
|
+
usage = { usedTokens: cu.totalTokens, contextWindow: cu.maxTokens };
|
|
2852
|
+
}
|
|
2853
|
+
} catch {
|
|
2854
|
+
}
|
|
2855
|
+
return { compacted, usage };
|
|
2856
|
+
}
|
|
2857
|
+
async abort(_turnId) {
|
|
2858
|
+
this.interruptRequested = true;
|
|
2859
|
+
try {
|
|
2860
|
+
await this.query.interrupt();
|
|
2861
|
+
} catch (err) {
|
|
2862
|
+
log.fail("agent", err, { backend: "claude-agent", phase: "interrupt" });
|
|
2863
|
+
}
|
|
2864
|
+
if (this.escalateTimer || !this.turnInFlight) return;
|
|
2865
|
+
this.escalateTimer = setTimeout(() => {
|
|
2866
|
+
this.escalateTimer = void 0;
|
|
2867
|
+
if (this.dead || !this.turnInFlight) return;
|
|
2868
|
+
log.info("agent", "interrupt-escalate", { backend: "claude-agent" });
|
|
2869
|
+
this.dead = true;
|
|
2870
|
+
try {
|
|
2871
|
+
this.abortController.abort();
|
|
2872
|
+
} catch {
|
|
2873
|
+
}
|
|
2874
|
+
}, ABORT_ESCALATE_MS);
|
|
2875
|
+
}
|
|
2876
|
+
isAlive() {
|
|
2877
|
+
return !this.dead;
|
|
2878
|
+
}
|
|
2879
|
+
async close() {
|
|
2880
|
+
this.dead = true;
|
|
2881
|
+
this.endTurn();
|
|
2882
|
+
try {
|
|
2883
|
+
this.input.close();
|
|
2884
|
+
} catch {
|
|
2885
|
+
}
|
|
2886
|
+
try {
|
|
2887
|
+
this.abortController.abort();
|
|
2888
|
+
} catch {
|
|
2889
|
+
}
|
|
2890
|
+
try {
|
|
2891
|
+
this.query.close?.();
|
|
2892
|
+
} catch {
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
};
|
|
2896
|
+
|
|
2897
|
+
// src/agent/claude-agent/backend.ts
|
|
2898
|
+
var SDK_PKG = "@anthropic-ai/claude-agent-sdk";
|
|
2899
|
+
var sdkPromise;
|
|
2900
|
+
function loadSdk() {
|
|
2901
|
+
sdkPromise ??= loadBackendDep(SDK_PKG);
|
|
2902
|
+
return sdkPromise;
|
|
2903
|
+
}
|
|
2904
|
+
var ClaudeAgentBackend = class {
|
|
2905
|
+
id = "claude-agent";
|
|
2906
|
+
displayName = "Claude";
|
|
2907
|
+
capabilities = {
|
|
2908
|
+
// /goal:goal-like —— 一个自主轮跑完目标 + 合成状态 + abort 硬停可终止续聊
|
|
2909
|
+
// (非 codex 的多轮目标引擎,差异见 thread.runGoal)。
|
|
2910
|
+
goal: true,
|
|
2911
|
+
steer: false,
|
|
2912
|
+
// /compact:Claude Code 原生斜杠命令(发 "/compact" 即触发,见 thread.compact)。
|
|
2913
|
+
compact: true,
|
|
2914
|
+
// resume 历史卡:读 ~/.claude/projects 会话存储(与 `claude -r` 同源,双向可见)。
|
|
2915
|
+
resume: true,
|
|
2916
|
+
approvals: false
|
|
2917
|
+
};
|
|
2918
|
+
// Claude's sandbox supports macOS (Seatbelt) and Linux (bubblewrap), so all
|
|
2919
|
+
// three tiers are offered; qa/write fail-closed at runtime if the sandbox can't
|
|
2920
|
+
// start (permission.ts sets sandbox.failIfUnavailable). See the security delta
|
|
2921
|
+
// documented in permission.ts / CLAUDE_AGENT_PROGRESS.md.
|
|
2922
|
+
supportedModes = ["qa", "write", "full"];
|
|
2923
|
+
async isAvailable() {
|
|
2924
|
+
return (await this.doctor()).ok;
|
|
2925
|
+
}
|
|
2926
|
+
async doctor() {
|
|
2927
|
+
if (!isBackendDepInstalled(SDK_PKG)) {
|
|
2928
|
+
return {
|
|
2929
|
+
ok: false,
|
|
2930
|
+
version: null,
|
|
2931
|
+
installable: true,
|
|
2932
|
+
depState: "not-installed",
|
|
2933
|
+
hint: "\u70B9\u300C\u4E0B\u8F7D\u300D\u5B89\u88C5 Claude Agent SDK\uFF08\u96F6 sudo\uFF0C\u88C5\u5230\u7528\u6237\u76EE\u5F55\uFF09"
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
return {
|
|
2937
|
+
ok: true,
|
|
2938
|
+
version: installedBackendVersion(SDK_PKG),
|
|
2939
|
+
location: SDK_PKG,
|
|
2940
|
+
depState: "installed",
|
|
2941
|
+
hint: "\u590D\u7528\u672C\u673A Claude \u767B\u5F55\u6001\uFF08\u672A\u767B\u5F55\u8BF7\u5148 `claude` \u767B\u5F55\uFF0C\u6216\u8BBE\u7F6E ANTHROPIC_API_KEY\uFF09"
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
async listModels() {
|
|
2945
|
+
return STATIC_MODELS2;
|
|
2946
|
+
}
|
|
2947
|
+
/** 最近会话(newest first),读 ~/.claude/projects/<cwd-hash> 的 JSONL 存储——
|
|
2948
|
+
* 与 `claude -r` 同源,故能列出本机用 `claude` 手开的会话。绝不抛错(契约)。 */
|
|
2949
|
+
async listThreads(cwd, limit = 15) {
|
|
2950
|
+
try {
|
|
2951
|
+
const sdk = await loadSdk();
|
|
2952
|
+
const sessions = await sdk.listSessions({ dir: cwd, limit });
|
|
2953
|
+
return sessions.map(mapSessionSummary).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
2954
|
+
} catch (err) {
|
|
2955
|
+
log.fail("agent", err, { backend: "claude-agent", phase: "listSessions" });
|
|
2956
|
+
return [];
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
/** 某会话的转写摘要(resume 历史卡)——读 getSessionMessages 折叠成 turns,不起会话、
|
|
2960
|
+
* 无 token 成本。绝不抛错(返回空)。 */
|
|
2961
|
+
async readHistory(cwd, sessionId, maxTurns = 10) {
|
|
2962
|
+
try {
|
|
2963
|
+
const sdk = await loadSdk();
|
|
2964
|
+
const messages = await sdk.getSessionMessages(sessionId, { dir: cwd });
|
|
2965
|
+
return foldSessionMessages(messages, maxTurns, cwd);
|
|
2966
|
+
} catch (err) {
|
|
2967
|
+
log.fail("agent", err, { backend: "claude-agent", phase: "getSessionMessages", sessionId });
|
|
2968
|
+
return { turns: [], totalTurns: 0 };
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
async startThread(opts) {
|
|
2972
|
+
const sdk = await loadSdk();
|
|
2973
|
+
return new ClaudeAgentThread({
|
|
2974
|
+
sessionId: randomUUID2(),
|
|
2975
|
+
resume: false,
|
|
2976
|
+
cwd: opts.cwd,
|
|
2977
|
+
model: opts.model,
|
|
2978
|
+
effort: opts.effort,
|
|
2979
|
+
permission: permissionOptions(opts.mode, opts.network, opts.cwd),
|
|
2980
|
+
systemPromptAppend: BRIDGE_DEVELOPER_INSTRUCTIONS,
|
|
2981
|
+
query: sdk.query
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
2984
|
+
async resumeThread(opts) {
|
|
2985
|
+
const sdk = await loadSdk();
|
|
2986
|
+
return new ClaudeAgentThread({
|
|
2987
|
+
sessionId: opts.sessionId,
|
|
2988
|
+
resume: true,
|
|
2989
|
+
cwd: opts.cwd,
|
|
2990
|
+
model: opts.model,
|
|
2991
|
+
effort: opts.effort,
|
|
2992
|
+
permission: permissionOptions(opts.mode, opts.network, opts.cwd),
|
|
2993
|
+
systemPromptAppend: BRIDGE_DEVELOPER_INSTRUCTIONS,
|
|
2994
|
+
query: sdk.query
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
var EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
2999
|
+
var STATIC_MODELS2 = [
|
|
3000
|
+
{
|
|
3001
|
+
id: "claude-opus-4-8",
|
|
3002
|
+
displayName: "Claude Opus 4.8",
|
|
3003
|
+
description: "\u6700\u5F3A\uFF0C\u590D\u6742\u63A8\u7406 / \u957F\u7A0B agentic",
|
|
3004
|
+
supportedEfforts: EFFORTS,
|
|
3005
|
+
defaultEffort: "high",
|
|
3006
|
+
isDefault: true,
|
|
3007
|
+
hidden: false
|
|
3008
|
+
},
|
|
3009
|
+
{
|
|
3010
|
+
id: "claude-sonnet-4-6",
|
|
3011
|
+
displayName: "Claude Sonnet 4.6",
|
|
3012
|
+
description: "\u5747\u8861\uFF0C\u65E5\u5E38\u7F16\u7801",
|
|
3013
|
+
supportedEfforts: EFFORTS,
|
|
3014
|
+
defaultEffort: "medium",
|
|
3015
|
+
isDefault: false,
|
|
3016
|
+
hidden: false
|
|
3017
|
+
},
|
|
3018
|
+
{
|
|
3019
|
+
id: "claude-haiku-4-5",
|
|
3020
|
+
displayName: "Claude Haiku 4.5",
|
|
3021
|
+
description: "\u6700\u5FEB\uFF0C\u8F7B\u91CF\u4EFB\u52A1",
|
|
3022
|
+
supportedEfforts: ["low", "medium", "high"],
|
|
3023
|
+
defaultEffort: "low",
|
|
3024
|
+
isDefault: false,
|
|
3025
|
+
hidden: false
|
|
2047
3026
|
}
|
|
2048
3027
|
];
|
|
2049
3028
|
|
|
@@ -2091,72 +3070,6 @@ async function effectiveDefaultBackend(opts) {
|
|
|
2091
3070
|
return defaultCache;
|
|
2092
3071
|
}
|
|
2093
3072
|
|
|
2094
|
-
// src/agent/backend-loader.ts
|
|
2095
|
-
import { createRequire } from "module";
|
|
2096
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
2097
|
-
import { join as join6 } from "path";
|
|
2098
|
-
import { pathToFileURL } from "url";
|
|
2099
|
-
function userAnchor() {
|
|
2100
|
-
return join6(paths.backendsDir, "__backend_anchor__.cjs");
|
|
2101
|
-
}
|
|
2102
|
-
function isBackendDepInstalled(pkg) {
|
|
2103
|
-
try {
|
|
2104
|
-
createRequire(import.meta.url).resolve(pkg);
|
|
2105
|
-
return true;
|
|
2106
|
-
} catch {
|
|
2107
|
-
}
|
|
2108
|
-
try {
|
|
2109
|
-
createRequire(userAnchor()).resolve(pkg);
|
|
2110
|
-
return true;
|
|
2111
|
-
} catch {
|
|
2112
|
-
return false;
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
function backendsBinPath(binName) {
|
|
2116
|
-
const dir = join6(paths.backendsDir, "node_modules", ".bin");
|
|
2117
|
-
const candidates = process.platform === "win32" ? [join6(dir, `${binName}.cmd`), join6(dir, binName)] : [join6(dir, binName)];
|
|
2118
|
-
return candidates.find((p) => existsSync3(p)) ?? null;
|
|
2119
|
-
}
|
|
2120
|
-
function isBackendBinInstalled(binName) {
|
|
2121
|
-
return backendsBinPath(binName) !== null;
|
|
2122
|
-
}
|
|
2123
|
-
function isBackendEntryInstalled(entry) {
|
|
2124
|
-
const { kind, binName, pkg } = entry.dep;
|
|
2125
|
-
if (kind === "external-cli") return false;
|
|
2126
|
-
if (binName) return isBackendBinInstalled(binName);
|
|
2127
|
-
return pkg ? isBackendDepInstalled(pkg) : false;
|
|
2128
|
-
}
|
|
2129
|
-
function isBackendInstalledInUserDir(entry) {
|
|
2130
|
-
const { binName, pkg } = entry.dep;
|
|
2131
|
-
if (binName) return isBackendBinInstalled(binName);
|
|
2132
|
-
if (!pkg) return false;
|
|
2133
|
-
try {
|
|
2134
|
-
createRequire(userAnchor()).resolve(pkg);
|
|
2135
|
-
return true;
|
|
2136
|
-
} catch {
|
|
2137
|
-
return false;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
function installedBackendVersion(pkg) {
|
|
2141
|
-
const readVer = (file) => {
|
|
2142
|
-
try {
|
|
2143
|
-
const j = JSON.parse(readFileSync2(file, "utf8"));
|
|
2144
|
-
return typeof j.version === "string" ? j.version : null;
|
|
2145
|
-
} catch {
|
|
2146
|
-
return null;
|
|
2147
|
-
}
|
|
2148
|
-
};
|
|
2149
|
-
const inUser = join6(paths.backendsDir, "node_modules", ...pkg.split("/"), "package.json");
|
|
2150
|
-
const v1 = readVer(inUser);
|
|
2151
|
-
if (v1) return v1;
|
|
2152
|
-
try {
|
|
2153
|
-
const resolved = createRequire(import.meta.url).resolve(`${pkg}/package.json`);
|
|
2154
|
-
return readVer(resolved);
|
|
2155
|
-
} catch {
|
|
2156
|
-
return null;
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
3073
|
// src/agent/installer.ts
|
|
2161
3074
|
import { mkdir as mkdir4, rm as rm2, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
2162
3075
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -2338,6 +3251,7 @@ async function rollback(bareName) {
|
|
|
2338
3251
|
const target = join8(paths.backendsDir, "node_modules", ...bareName.split("/"));
|
|
2339
3252
|
await rm2(target, { recursive: true, force: true }).catch(() => void 0);
|
|
2340
3253
|
await removeBackendsDep(bareName);
|
|
3254
|
+
await rm2(join8(paths.backendsDir, "package-lock.json"), { force: true }).catch(() => void 0);
|
|
2341
3255
|
}
|
|
2342
3256
|
async function removeBackendsDep(bareName) {
|
|
2343
3257
|
const pkgFile = join8(paths.backendsDir, "package.json");
|
|
@@ -2359,7 +3273,8 @@ function stripVersion(pkg) {
|
|
|
2359
3273
|
|
|
2360
3274
|
// src/agent/index.ts
|
|
2361
3275
|
var REGISTRY = /* @__PURE__ */ new Map([
|
|
2362
|
-
["codex-appserver", () => new CodexAppServerBackend()]
|
|
3276
|
+
["codex-appserver", () => new CodexAppServerBackend()],
|
|
3277
|
+
["claude-agent", () => new ClaudeAgentBackend()]
|
|
2363
3278
|
]);
|
|
2364
3279
|
function backendIds() {
|
|
2365
3280
|
return catalogBackendIds();
|
|
@@ -2830,7 +3745,7 @@ import { createLarkChannel, Domain } from "@larksuiteoapi/node-sdk";
|
|
|
2830
3745
|
|
|
2831
3746
|
// src/project/registry.ts
|
|
2832
3747
|
import { mkdir as mkdir5, readFile as readFile6, rename as rename4, writeFile as writeFile5 } from "fs/promises";
|
|
2833
|
-
import { randomUUID as
|
|
3748
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2834
3749
|
import { dirname as dirname5 } from "path";
|
|
2835
3750
|
function defaultNoMention(p) {
|
|
2836
3751
|
return !((p.origin ?? "created") === "joined" && (p.kind ?? "multi") === "single");
|
|
@@ -2875,7 +3790,7 @@ function withLock(fn) {
|
|
|
2875
3790
|
}
|
|
2876
3791
|
async function write(projects) {
|
|
2877
3792
|
await mkdir5(dirname5(paths.projectsFile), { recursive: true });
|
|
2878
|
-
const tmp = `${paths.projectsFile}.tmp-${process.pid}-${
|
|
3793
|
+
const tmp = `${paths.projectsFile}.tmp-${process.pid}-${randomUUID3()}`;
|
|
2879
3794
|
const body = { version: FILE_VERSION2, projects };
|
|
2880
3795
|
await writeFile5(tmp, `${JSON.stringify(body, null, 2)}
|
|
2881
3796
|
`, "utf8");
|
|
@@ -3274,14 +4189,14 @@ function buildHelpCard(scope, noMention = true, isAdmin2 = false, caps) {
|
|
|
3274
4189
|
}
|
|
3275
4190
|
return card(elements, { header: { title: "\u{1F916} \u53EF\u7528\u547D\u4EE4", template: "blue" }, summary: "\u53EF\u7528\u547D\u4EE4" });
|
|
3276
4191
|
}
|
|
3277
|
-
function buildWelcomeCard(kind, docUrl, noMention = true, caps) {
|
|
4192
|
+
function buildWelcomeCard(kind, docUrl, noMention = true, caps, agentName = "Codex") {
|
|
3278
4193
|
const showGoal = caps?.goal ?? true;
|
|
3279
4194
|
const showCompact = caps?.compact ?? true;
|
|
3280
4195
|
const showResume = caps?.resume ?? true;
|
|
3281
4196
|
const goalLine = "\xB7 `/goal <\u76EE\u6807>` \u2192 \u81EA\u4E3B\u591A\u8F6E\u8DD1\u5230\u5B8C\u6210\uFF08\u5361\u4E0A \u23F9 \u7EC8\u6B62 / \u{1F3AF} \u7ED3\u675F\u76EE\u6807\uFF09";
|
|
3282
4197
|
const ctxLine = showCompact ? "\xB7 `/context` \xB7 `/compact` \u2192 \u770B / \u538B\u7F29\u4E0A\u4E0B\u6587" : "\xB7 `/context` \u2192 \u770B\u4E0A\u4E0B\u6587\u5360\u6BD4";
|
|
3283
4198
|
const elements = [
|
|
3284
|
-
md(
|
|
4199
|
+
md(`\u{1F44B} **\u6B22\u8FCE\u4F7F\u7528 ${agentName} Bridge** \u2014 \u672C\u7FA4\u5DF2\u7ED1\u5B9A\u4E00\u4E2A\u9879\u76EE\u76EE\u5F55\uFF0C\u5728\u7FA4\u91CC\u5C31\u80FD\u9A71\u52A8\u672C\u673A ${agentName} \u5E72\u6D3B\u3002`),
|
|
3285
4200
|
hr()
|
|
3286
4201
|
];
|
|
3287
4202
|
if (kind === "single") {
|
|
@@ -3347,6 +4262,7 @@ var DM = {
|
|
|
3347
4262
|
rmDo: "dm.rmDo",
|
|
3348
4263
|
rmCancel: "dm.rmCancel",
|
|
3349
4264
|
setTools: "dm.set.tools",
|
|
4265
|
+
setShowModel: "dm.set.showModel",
|
|
3350
4266
|
setWatchdog: "dm.set.watchdog",
|
|
3351
4267
|
// 假死超时「自定义…」:watchdogCustom 打开输入卡,watchdogCustomSubmit 保存任意秒数
|
|
3352
4268
|
watchdogCustom: "dm.set.watchdog.custom",
|
|
@@ -3503,6 +4419,17 @@ function buildUpdateCard(state) {
|
|
|
3503
4419
|
);
|
|
3504
4420
|
}
|
|
3505
4421
|
}
|
|
4422
|
+
function buildReconnectCard(conn) {
|
|
4423
|
+
const template = conn === "connected" ? "green" : "orange";
|
|
4424
|
+
return card(
|
|
4425
|
+
[
|
|
4426
|
+
md(`\u957F\u8FDE\u63A5\u72B6\u6001\uFF1A**${conn}**`),
|
|
4427
|
+
note("SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u91CD\u8DD1 `feishu-codex-bridge run`\uFF08\u524D\u53F0\uFF09\u6216 `feishu-codex-bridge restart`\uFF08\u540E\u53F0\u5B88\u62A4\uFF09\u3002"),
|
|
4428
|
+
backToMenu()
|
|
4429
|
+
],
|
|
4430
|
+
{ header: { title: "\u{1F504} \u957F\u8FDE\u63A5", template } }
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
3506
4433
|
function connLabel(state) {
|
|
3507
4434
|
switch (state) {
|
|
3508
4435
|
case "connected":
|
|
@@ -3672,7 +4599,7 @@ function buildNewProjectFormCard(opts = {}) {
|
|
|
3672
4599
|
];
|
|
3673
4600
|
if (backends.length > 1) {
|
|
3674
4601
|
formItems.push(
|
|
3675
|
-
note("\u{1F9E0} \u540E\u7AEF Agent\uFF08\u521B\u5EFA\u540E**\u56FA\u5B9A\u4E0D\u53EF\u5207\u6362**\uFF1B\u672A\u4E0B\u8F7D\u7684\
|
|
4602
|
+
note("\u{1F9E0} \u540E\u7AEF Agent\uFF08\u521B\u5EFA\u540E**\u56FA\u5B9A\u4E0D\u53EF\u5207\u6362**\uFF1B\u6807\u6CE8\u300C\u672A\u4E0B\u8F7D\u300D\u7684\u9700\u5148\u53BB Web\u300C\u540E\u7AEF Agent\u300D\u9875\u4E0B\u8F7D\uFF0C\u9009\u5B83\u4F1A\u63D0\u793A\uFF09"),
|
|
3676
4603
|
selectMenu({ name: "backend", placeholder: "\u9009\u62E9\u540E\u7AEF Agent", options: backends, initial: backends[0]?.value })
|
|
3677
4604
|
);
|
|
3678
4605
|
} else if (backends.length === 1) {
|
|
@@ -3809,38 +4736,78 @@ function optionRow(label, actionId, current, opts) {
|
|
|
3809
4736
|
actions(opts.map((o) => button(o.label, { a: actionId, v: o.value }, o.value === current ? "primary" : "default")))
|
|
3810
4737
|
];
|
|
3811
4738
|
}
|
|
4739
|
+
function settingSection(title) {
|
|
4740
|
+
return { tag: "markdown", content: `**${title}**`, text_size: "notation", text_color: "grey" };
|
|
4741
|
+
}
|
|
4742
|
+
function settingItem(name, desc, actionId, current, opts) {
|
|
4743
|
+
return [
|
|
4744
|
+
md(`**${name}**`),
|
|
4745
|
+
note(desc),
|
|
4746
|
+
actions(opts.map((o) => button(o.label, { a: actionId, v: o.value }, o.value === current ? "primary" : "default")))
|
|
4747
|
+
];
|
|
4748
|
+
}
|
|
3812
4749
|
function buildSettingsCard(cfg) {
|
|
3813
4750
|
const watchdogSec = cfg.preferences?.runIdleTimeoutSeconds ?? 120;
|
|
3814
4751
|
return card(
|
|
3815
4752
|
[
|
|
3816
|
-
|
|
3817
|
-
...
|
|
3818
|
-
{
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
4753
|
+
settingSection("\u{1F4E4} \u8F93\u51FA\u5C55\u793A"),
|
|
4754
|
+
...settingItem(
|
|
4755
|
+
"\u{1F527} \u5DE5\u5177\u8C03\u7528",
|
|
4756
|
+
"\u8F93\u51FA\u65F6\u663E\u793A\u6267\u884C\u7684\u547D\u4EE4 / \u5DE5\u5177\u8C03\u7528\uFF1B\u5173\u6389\u53EA\u770B\u6700\u7EC8\u56DE\u7B54\u3002",
|
|
4757
|
+
DM.setTools,
|
|
4758
|
+
getShowToolCalls(cfg) ? "on" : "off",
|
|
4759
|
+
[
|
|
4760
|
+
{ label: "\u663E\u793A", value: "on" },
|
|
4761
|
+
{ label: "\u9690\u85CF", value: "off" }
|
|
4762
|
+
]
|
|
4763
|
+
),
|
|
4764
|
+
...settingItem(
|
|
4765
|
+
"\u{1F9E0} \u6A21\u578B\u663E\u793A",
|
|
4766
|
+
"\u6BCF\u6761\u56DE\u590D\u53F3\u4E0B\u89D2\u663E\u793A\u300C\u6A21\u578B \xB7 \u63A8\u7406\u5F3A\u5EA6\u300D\u3002\u4EC5\u8F93\u51FA\u65F6\uFF1D\u53EA\u5728\u751F\u6210\u4E2D\u663E\u793A\uFF1B\u59CB\u7EC8\uFF1D\u751F\u6210\u5B8C\u540E\u5361\u7247\u4E5F\u4FDD\u7559\u3002",
|
|
4767
|
+
DM.setShowModel,
|
|
4768
|
+
getModelDisplay(cfg),
|
|
4769
|
+
[
|
|
4770
|
+
{ label: "\u5173\u95ED", value: "off" },
|
|
4771
|
+
{ label: "\u4EC5\u8F93\u51FA\u65F6", value: "running" },
|
|
4772
|
+
{ label: "\u59CB\u7EC8", value: "always" }
|
|
4773
|
+
]
|
|
4774
|
+
),
|
|
4775
|
+
hr(),
|
|
4776
|
+
settingSection("\u23F1 \u8FD0\u884C\u63A7\u5236"),
|
|
4777
|
+
md(`**\u23F1 \u5047\u6B7B\u8D85\u65F6** \xB7 \u5F53\u524D **${watchdogSec === 0 ? "\u5173\u95ED" : `${watchdogSec} \u79D2`}**`),
|
|
4778
|
+
note("\u591A\u4E45\u6CA1\u6709\u4EFB\u4F55\u8F93\u51FA\u5C31\u81EA\u52A8\u7EC8\u6B62\u672C\u8F6E\uFF08\u9632\u5361\u6B7B\uFF09\u3002"),
|
|
3822
4779
|
actions([
|
|
3823
4780
|
...[0, 120, 300].map(
|
|
3824
4781
|
(v) => button(v === 0 ? "\u5173\u95ED" : `${v}\u79D2`, { a: DM.setWatchdog, v: String(v) }, v === watchdogSec ? "primary" : "default")
|
|
3825
4782
|
),
|
|
3826
4783
|
button("\u81EA\u5B9A\u4E49\u2026", { a: DM.watchdogCustom })
|
|
3827
4784
|
]),
|
|
3828
|
-
...
|
|
3829
|
-
{
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
4785
|
+
...settingItem(
|
|
4786
|
+
"\u{1F4E5} \u8FD0\u884C\u4E2D\u6765\u65B0\u6D88\u606F",
|
|
4787
|
+
"\u6B63\u5728\u8DD1\u65F6\u4F60\u53C8\u53D1\u6D88\u606F\uFF1A\u5F15\u5BFC\uFF1D\u63D2\u8FDB\u5F53\u524D\u8F6E\u7EA0\u504F\uFF1B\u6392\u961F\uFF1D\u7B49\u8FD9\u8F6E\u8DD1\u5B8C\u518D\u5904\u7406\u3002",
|
|
4788
|
+
DM.setPending,
|
|
4789
|
+
getPendingPolicy(cfg),
|
|
4790
|
+
[
|
|
4791
|
+
{ label: "\u5F15\u5BFC", value: "steer" },
|
|
4792
|
+
{ label: "\u6392\u961F", value: "queue" }
|
|
4793
|
+
]
|
|
4794
|
+
),
|
|
4795
|
+
...settingItem(
|
|
4796
|
+
"\u26A1 \u5E76\u53D1\u4E0A\u9650",
|
|
4797
|
+
"\u6240\u6709\u7FA4 / \u8BDD\u9898\u5168\u5C40\u540C\u65F6\u6700\u591A\u8DD1\u51E0\u4E2A\uFF0C\u6EE1\u4E86\u6392\u961F\uFF08\u6392\u961F\u5361\u53EF \u23F9 \u53D6\u6D88\uFF09\u3002\u6539\u540E\u9700\u91CD\u542F\u751F\u6548\u3002",
|
|
4798
|
+
DM.setConcurrency,
|
|
4799
|
+
String(getMaxConcurrentRuns(cfg)),
|
|
4800
|
+
[
|
|
4801
|
+
{ label: "1", value: "1" },
|
|
4802
|
+
{ label: "5", value: "5" },
|
|
4803
|
+
{ label: "10", value: "10" },
|
|
4804
|
+
{ label: "20", value: "20" }
|
|
4805
|
+
]
|
|
4806
|
+
),
|
|
3840
4807
|
hr(),
|
|
3841
4808
|
actions([button("\u{1F46E} \u7BA1\u7406\u5458", { a: DM.admins }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
|
|
3842
4809
|
],
|
|
3843
|
-
{ header: { title: "\u2699\uFE0F \u8BBE\u7F6E", template: "blue" } }
|
|
4810
|
+
{ header: { title: "\u2699\uFE0F \u5168\u5C40\u8BBE\u7F6E", template: "blue" } }
|
|
3844
4811
|
);
|
|
3845
4812
|
}
|
|
3846
4813
|
function buildWatchdogCustomCard(cfg) {
|
|
@@ -4904,24 +5871,6 @@ function buildRunCard(rc) {
|
|
|
4904
5871
|
}
|
|
4905
5872
|
function renderRunning(state, rc) {
|
|
4906
5873
|
const elements = [];
|
|
4907
|
-
if (rc.cardKey && rc.goalControls) {
|
|
4908
|
-
if (rc.goalEnding) {
|
|
4909
|
-
elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
|
|
4910
|
-
elements.push(noteMd("_\u{1F3AF} \u76EE\u6807\u5DF2\u89E3\u9664\uFF0C\u672C\u8F6E\u8F93\u51FA\u5B8C\u6210\u540E\u505C\u6B62_"));
|
|
4911
|
-
} else {
|
|
4912
|
-
elements.push(
|
|
4913
|
-
actions(
|
|
4914
|
-
[
|
|
4915
|
-
button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger"),
|
|
4916
|
-
button("\u{1F3AF} \u7ED3\u675F\u76EE\u6807", { a: RC.endGoal, m: rc.cardKey }, "default")
|
|
4917
|
-
],
|
|
4918
|
-
CONTROLS_EID
|
|
4919
|
-
)
|
|
4920
|
-
);
|
|
4921
|
-
}
|
|
4922
|
-
} else if (rc.cardKey && !rc.hideStop) {
|
|
4923
|
-
elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
|
|
4924
|
-
}
|
|
4925
5874
|
const reasoning = reasoningContent(state);
|
|
4926
5875
|
if (reasoning) elements.push(reasoningPanel(reasoning, state.reasoningActive));
|
|
4927
5876
|
const showTools = rc.showTools !== false;
|
|
@@ -4937,9 +5886,30 @@ function renderRunning(state, rc) {
|
|
|
4937
5886
|
if (tools.length > 0) elements.push(...renderToolGroup(tools, false));
|
|
4938
5887
|
const answer = textParts.join("\n\n");
|
|
4939
5888
|
if (answer) elements.push(mdStream(answer, ANSWER_EID));
|
|
4940
|
-
|
|
5889
|
+
const mEl = modelEl(rc);
|
|
5890
|
+
if (state.footer && mEl) elements.push(splitRow(footerStatus(state.footer), mEl));
|
|
5891
|
+
else if (state.footer) elements.push(footerStatus(state.footer));
|
|
5892
|
+
else if (mEl) elements.push(mEl);
|
|
4941
5893
|
const gauge = gaugeEl(state);
|
|
4942
5894
|
if (gauge) elements.push(gauge);
|
|
5895
|
+
if (rc.cardKey && rc.goalControls) {
|
|
5896
|
+
if (rc.goalEnding) {
|
|
5897
|
+
elements.push(noteMd("_\u{1F3AF} \u76EE\u6807\u5DF2\u89E3\u9664\uFF0C\u672C\u8F6E\u8F93\u51FA\u5B8C\u6210\u540E\u505C\u6B62_"));
|
|
5898
|
+
elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
|
|
5899
|
+
} else {
|
|
5900
|
+
elements.push(
|
|
5901
|
+
actions(
|
|
5902
|
+
[
|
|
5903
|
+
button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger"),
|
|
5904
|
+
button("\u{1F3AF} \u7ED3\u675F\u76EE\u6807", { a: RC.endGoal, m: rc.cardKey }, "default")
|
|
5905
|
+
],
|
|
5906
|
+
CONTROLS_EID
|
|
5907
|
+
)
|
|
5908
|
+
);
|
|
5909
|
+
}
|
|
5910
|
+
} else if (rc.cardKey && !rc.hideStop) {
|
|
5911
|
+
elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
|
|
5912
|
+
}
|
|
4943
5913
|
return elements;
|
|
4944
5914
|
}
|
|
4945
5915
|
function renderTerminal(state, rc) {
|
|
@@ -4977,6 +5947,8 @@ function renderTerminal(state, rc) {
|
|
|
4977
5947
|
}
|
|
4978
5948
|
const gauge = gaugeEl(state);
|
|
4979
5949
|
if (gauge) elements.push(gauge);
|
|
5950
|
+
const mEl = rc.modelOnTerminal ? modelEl(rc) : null;
|
|
5951
|
+
if (mEl) elements.push(mEl);
|
|
4980
5952
|
return elements;
|
|
4981
5953
|
}
|
|
4982
5954
|
function errorAdvice(msg) {
|
|
@@ -5098,9 +6070,35 @@ function collapsedToolSummary(tools, finalized) {
|
|
|
5098
6070
|
body: tools.map((t) => `- ${toolHeaderText(t)}`).join("\n")
|
|
5099
6071
|
});
|
|
5100
6072
|
}
|
|
6073
|
+
function footerStatusText(status) {
|
|
6074
|
+
return status === "thinking" ? "\u{1F9E0} \u6B63\u5728\u601D\u8003" : status === "tool_running" ? "\u{1F9F0} \u6B63\u5728\u8C03\u7528\u5DE5\u5177" : status === "retrying" ? "\u26A0\uFE0F \u77AC\u65AD\uFF0C\u81EA\u52A8\u91CD\u8BD5\u4E2D\u2026" : "\u270D\uFE0F \u6B63\u5728\u8F93\u51FA";
|
|
6075
|
+
}
|
|
5101
6076
|
function footerStatus(status) {
|
|
5102
|
-
|
|
5103
|
-
|
|
6077
|
+
return noteMd(footerStatusText(status));
|
|
6078
|
+
}
|
|
6079
|
+
var EFFORT_TIER = {
|
|
6080
|
+
none: { label: "\u65E0", color: "grey" },
|
|
6081
|
+
minimal: { label: "\u6781\u7B80", color: "grey" },
|
|
6082
|
+
low: { label: "\u4F4E", color: "yellow" },
|
|
6083
|
+
medium: { label: "\u4E2D", color: "green" },
|
|
6084
|
+
high: { label: "\u9AD8", color: "violet" },
|
|
6085
|
+
xhigh: { label: "\u6781\u9AD8", color: "purple" }
|
|
6086
|
+
};
|
|
6087
|
+
function modelEffortMd(model, effort) {
|
|
6088
|
+
if (!effort) return model;
|
|
6089
|
+
const t = EFFORT_TIER[effort];
|
|
6090
|
+
if (!t) return `${model} \xB7 ${effort}`;
|
|
6091
|
+
return `${model} \xB7 <font color='${t.color}'>${t.label}</font>`;
|
|
6092
|
+
}
|
|
6093
|
+
function modelEl(rc) {
|
|
6094
|
+
if (!rc.model) return null;
|
|
6095
|
+
return {
|
|
6096
|
+
tag: "markdown",
|
|
6097
|
+
content: modelEffortMd(rc.model, rc.effort),
|
|
6098
|
+
text_size: "notation",
|
|
6099
|
+
text_color: "grey",
|
|
6100
|
+
text_align: "right"
|
|
6101
|
+
};
|
|
5104
6102
|
}
|
|
5105
6103
|
function summaryText(state) {
|
|
5106
6104
|
if (state.terminal === "interrupted") return "\u5DF2\u4E2D\u65AD";
|
|
@@ -6806,7 +7804,7 @@ function buildUsageShareCard(data, opts = {}) {
|
|
|
6806
7804
|
}
|
|
6807
7805
|
|
|
6808
7806
|
// src/web/discovery.ts
|
|
6809
|
-
import { randomUUID as
|
|
7807
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
6810
7808
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
6811
7809
|
import { dirname as dirname10 } from "path";
|
|
6812
7810
|
function publishWebConsole(rec, file = paths.webConsoleFile) {
|
|
@@ -6848,7 +7846,7 @@ function stableWebConsoleToken(file = paths.webTokenFile) {
|
|
|
6848
7846
|
if (t) return t;
|
|
6849
7847
|
} catch {
|
|
6850
7848
|
}
|
|
6851
|
-
const token =
|
|
7849
|
+
const token = randomUUID4();
|
|
6852
7850
|
try {
|
|
6853
7851
|
mkdirSync2(dirname10(file), { recursive: true });
|
|
6854
7852
|
try {
|
|
@@ -6980,6 +7978,9 @@ function welcomeCaps(backend) {
|
|
|
6980
7978
|
return void 0;
|
|
6981
7979
|
}
|
|
6982
7980
|
}
|
|
7981
|
+
function welcomeAgentName(backend) {
|
|
7982
|
+
return backend && catalogById(backend)?.displayName || "Codex";
|
|
7983
|
+
}
|
|
6983
7984
|
var HELP_DOC_URL = "https://my.feishu.cn/wiki/PZ23wGr7JiKK5RkIG4rcZXzGn5g";
|
|
6984
7985
|
function sidebarPcUrl(url) {
|
|
6985
7986
|
return `https://applink.feishu.cn/client/web_url/open?mode=sidebar-semi&url=${encodeURIComponent(url)}`;
|
|
@@ -6987,10 +7988,13 @@ function sidebarPcUrl(url) {
|
|
|
6987
7988
|
async function onboardGroup(channel, project) {
|
|
6988
7989
|
const kind = project.kind ?? "multi";
|
|
6989
7990
|
const chatId = project.chatId;
|
|
7991
|
+
const agentName = welcomeAgentName(project.backend);
|
|
6990
7992
|
const decorate = (project.origin ?? "created") !== "joined";
|
|
6991
7993
|
try {
|
|
6992
7994
|
const noMention = project.noMention ?? defaultNoMention(project);
|
|
6993
|
-
const content = JSON.stringify(
|
|
7995
|
+
const content = JSON.stringify(
|
|
7996
|
+
buildWelcomeCard(kind, HELP_DOC_URL || void 0, noMention, welcomeCaps(project.backend), agentName)
|
|
7997
|
+
);
|
|
6994
7998
|
const sent = await channel.rawClient.im.v1.message.create({
|
|
6995
7999
|
params: { receive_id_type: "chat_id" },
|
|
6996
8000
|
data: { receive_id: chatId, msg_type: "interactive", content }
|
|
@@ -7026,7 +8030,7 @@ async function onboardGroup(channel, project) {
|
|
|
7026
8030
|
{
|
|
7027
8031
|
chat_menu_item: {
|
|
7028
8032
|
action_type: "REDIRECT_LINK",
|
|
7029
|
-
name:
|
|
8033
|
+
name: `\u{1F916} ${agentName}`,
|
|
7030
8034
|
redirect_link: { common_url: HELP_DOC_URL, pc_url: sidebarPcUrl(HELP_DOC_URL) }
|
|
7031
8035
|
}
|
|
7032
8036
|
}
|
|
@@ -7045,7 +8049,12 @@ async function onboardGroup(channel, project) {
|
|
|
7045
8049
|
function assertBackendUsable(backend, mode) {
|
|
7046
8050
|
if (!backend) return;
|
|
7047
8051
|
const ok = projectCreatableBackends(mode, isBackendEntryInstalled).some((e) => e.id === backend);
|
|
7048
|
-
if (
|
|
8052
|
+
if (ok) return;
|
|
8053
|
+
const entry = catalogById(backend);
|
|
8054
|
+
if (entry && isInstallable(entry)) {
|
|
8055
|
+
throw new Error(`\u300C${entry.displayName}\u300D\u5C1A\u672A\u4E0B\u8F7D\u2014\u2014\u8BF7\u5230 Web \u63A7\u5236\u53F0\u300C\u540E\u7AEF Agent\u300D\u9875\u70B9\u300C\u4E0B\u8F7D\u300D\u88C5\u597D\u540E\uFF0C\u518D\u56DE\u5361\u7247\u9009\u7528\u3002`);
|
|
8056
|
+
}
|
|
8057
|
+
throw new Error(`\u6240\u9009\u540E\u7AEF\u300C${backend}\u300D\u5F53\u524D\u4E0D\u53EF\u7528\uFF08\u672A\u4E0B\u8F7D\u6216\u4E0D\u652F\u6301\u8BE5\u6743\u9650\u6863\uFF09\uFF0C\u8BF7\u56DE\u5361\u7247\u91CD\u65B0\u9009\u62E9`);
|
|
7049
8058
|
}
|
|
7050
8059
|
async function resolveCwd(name, existingPath) {
|
|
7051
8060
|
if (existingPath) {
|
|
@@ -7138,7 +8147,7 @@ async function leaveChat(channel, chatId) {
|
|
|
7138
8147
|
|
|
7139
8148
|
// src/bot/session-store.ts
|
|
7140
8149
|
import { mkdir as mkdir11, readFile as readFile10, rename as rename5, writeFile as writeFile9 } from "fs/promises";
|
|
7141
|
-
import { randomUUID as
|
|
8150
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
7142
8151
|
import { dirname as dirname11 } from "path";
|
|
7143
8152
|
var FILE_VERSION3 = 2;
|
|
7144
8153
|
var LEGACY_V1_SESSION_FIELD = "codexThreadId";
|
|
@@ -7176,7 +8185,7 @@ function withLock2(fn) {
|
|
|
7176
8185
|
}
|
|
7177
8186
|
async function write2(sessions) {
|
|
7178
8187
|
await mkdir11(dirname11(paths.sessionsFile), { recursive: true });
|
|
7179
|
-
const tmp = `${paths.sessionsFile}.tmp-${process.pid}-${
|
|
8188
|
+
const tmp = `${paths.sessionsFile}.tmp-${process.pid}-${randomUUID5()}`;
|
|
7180
8189
|
const body = { version: FILE_VERSION3, sessions };
|
|
7181
8190
|
await writeFile9(tmp, `${JSON.stringify(body, null, 2)}
|
|
7182
8191
|
`, "utf8");
|
|
@@ -8044,7 +9053,10 @@ function asTier(v) {
|
|
|
8044
9053
|
return v === "qa" || v === "write" || v === "full" ? v : void 0;
|
|
8045
9054
|
}
|
|
8046
9055
|
function backendOptionsFor(mode) {
|
|
8047
|
-
const opts =
|
|
9056
|
+
const opts = visibleCatalog().filter((e) => !e.supportedModes || e.supportedModes.includes(mode)).map((e) => {
|
|
9057
|
+
const installed = e.id === DEFAULT_BACKEND_ID || isBackendEntryInstalled(e);
|
|
9058
|
+
return { label: installed ? e.displayName : `${e.displayName}\uFF08\u672A\u4E0B\u8F7D\xB7\u53BB\u63A7\u5236\u53F0\u4E0B\u8F7D\uFF09`, value: e.id };
|
|
9059
|
+
});
|
|
8048
9060
|
return opts.length > 1 ? opts : [];
|
|
8049
9061
|
}
|
|
8050
9062
|
function bindModeFor(backend) {
|
|
@@ -9034,6 +10046,35 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
9034
10046
|
}
|
|
9035
10047
|
return buildProjectListCard(projects, byChat);
|
|
9036
10048
|
};
|
|
10049
|
+
const buildDoctorInfo = async () => {
|
|
10050
|
+
const codexProbe = await backend.doctor({ force: true });
|
|
10051
|
+
const app = cfg.accounts.app;
|
|
10052
|
+
const secret = await getSecret(secretKeyForApp(app.id)).catch(() => void 0);
|
|
10053
|
+
const scopeCheck = secret ? await validateAppCredentials(app.id, secret, app.tenant).catch(() => void 0) : void 0;
|
|
10054
|
+
const missingScopes = scopeCheck?.missingScopes;
|
|
10055
|
+
const missingJoinScopes = scopeCheck?.missingJoinScopes;
|
|
10056
|
+
return {
|
|
10057
|
+
codexOk: codexProbe.ok,
|
|
10058
|
+
codexVer: codexProbe.version,
|
|
10059
|
+
conn: channel.getConnectionStatus?.()?.state ?? "unknown",
|
|
10060
|
+
bridgeVer: bridgeVersion(),
|
|
10061
|
+
node: process.version,
|
|
10062
|
+
platform: `${process.platform}-${process.arch}`,
|
|
10063
|
+
logStdout: serviceStdoutPath(),
|
|
10064
|
+
logStderr: serviceStderrPath(),
|
|
10065
|
+
configFile: paths.configFile,
|
|
10066
|
+
missingScopes,
|
|
10067
|
+
// 缺失时预选缺失项(精准开通);查不到/全开通时预选全部必需 scope 供核对。
|
|
10068
|
+
scopeGrantUrl: buildScopeGrantUrl(
|
|
10069
|
+
app.id,
|
|
10070
|
+
app.tenant,
|
|
10071
|
+
missingScopes && missingScopes.length ? missingScopes : void 0
|
|
10072
|
+
),
|
|
10073
|
+
missingJoinScopes,
|
|
10074
|
+
// 「加入存量群」按钮恒预选这两项 opt-in scope(它们不在必需清单里)。
|
|
10075
|
+
joinScopeGrantUrl: buildScopeGrantUrl(app.id, app.tenant, JOIN_GROUP_SCOPES)
|
|
10076
|
+
};
|
|
10077
|
+
};
|
|
9037
10078
|
dispatcher.on(DM.menu, ({ evt }) => {
|
|
9038
10079
|
if (dmAdmin(evt.operator?.openId)) freshMenu(evt);
|
|
9039
10080
|
}).on(DM.newProject, ({ evt }) => {
|
|
@@ -9098,41 +10139,15 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
9098
10139
|
if (dmAdmin(evt.operator?.openId)) await patch(evt, buildSettingsCard(cfg));
|
|
9099
10140
|
}).on(DM.doctor, async ({ evt }) => {
|
|
9100
10141
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
9101
|
-
|
|
9102
|
-
const app = cfg.accounts.app;
|
|
9103
|
-
const secret = await getSecret(secretKeyForApp(app.id)).catch(() => void 0);
|
|
9104
|
-
const scopeCheck = secret ? await validateAppCredentials(app.id, secret, app.tenant).catch(() => void 0) : void 0;
|
|
9105
|
-
const missingScopes = scopeCheck?.missingScopes;
|
|
9106
|
-
const missingJoinScopes = scopeCheck?.missingJoinScopes;
|
|
9107
|
-
const info = {
|
|
9108
|
-
codexOk: codexProbe.ok,
|
|
9109
|
-
codexVer: codexProbe.version,
|
|
9110
|
-
conn: channel.getConnectionStatus?.()?.state ?? "unknown",
|
|
9111
|
-
bridgeVer: bridgeVersion(),
|
|
9112
|
-
node: process.version,
|
|
9113
|
-
platform: `${process.platform}-${process.arch}`,
|
|
9114
|
-
logStdout: serviceStdoutPath(),
|
|
9115
|
-
logStderr: serviceStderrPath(),
|
|
9116
|
-
configFile: paths.configFile,
|
|
9117
|
-
missingScopes,
|
|
9118
|
-
// 缺失时预选缺失项(精准开通);查不到/全开通时预选全部必需 scope 供核对。
|
|
9119
|
-
scopeGrantUrl: buildScopeGrantUrl(
|
|
9120
|
-
app.id,
|
|
9121
|
-
app.tenant,
|
|
9122
|
-
missingScopes && missingScopes.length ? missingScopes : void 0
|
|
9123
|
-
),
|
|
9124
|
-
missingJoinScopes,
|
|
9125
|
-
// 「加入存量群」按钮恒预选这两项 opt-in scope(它们不在必需清单里)。
|
|
9126
|
-
joinScopeGrantUrl: buildScopeGrantUrl(app.id, app.tenant, JOIN_GROUP_SCOPES)
|
|
9127
|
-
};
|
|
9128
|
-
await sendManagedCard(channel, evt.chatId, buildDoctorCard(info), evt.messageId).catch(
|
|
10142
|
+
await sendManagedCard(channel, evt.chatId, buildDoctorCard(await buildDoctorInfo()), evt.messageId).catch(
|
|
9129
10143
|
(err) => log.fail("console", err, { cmd: "doctor" })
|
|
9130
10144
|
);
|
|
9131
10145
|
}).on(DM.reconnect, async ({ evt }) => {
|
|
9132
10146
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
9133
10147
|
const conn = channel.getConnectionStatus?.()?.state ?? "unknown";
|
|
9134
|
-
await channel
|
|
9135
|
-
|
|
10148
|
+
await sendManagedCard(channel, evt.chatId, buildReconnectCard(conn), evt.messageId).catch(
|
|
10149
|
+
(err) => log.fail("console", err, { cmd: "reconnect" })
|
|
10150
|
+
);
|
|
9136
10151
|
}).on(DM.update, ({ evt }) => {
|
|
9137
10152
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
9138
10153
|
void (async () => {
|
|
@@ -9239,6 +10254,10 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
9239
10254
|
});
|
|
9240
10255
|
}).on(DM.setTools, ({ evt, value }) => {
|
|
9241
10256
|
applyPref(evt, (p) => p.showToolCalls = value.v === "on");
|
|
10257
|
+
}).on(DM.setShowModel, ({ evt, value }) => {
|
|
10258
|
+
applyPref(evt, (p) => {
|
|
10259
|
+
p.showModel = value.v === "running" ? "running" : value.v === "always" ? "always" : "off";
|
|
10260
|
+
});
|
|
9242
10261
|
}).on(DM.setWatchdog, ({ evt, value }) => {
|
|
9243
10262
|
const n = Number(value.v);
|
|
9244
10263
|
if (Number.isFinite(n)) applyPref(evt, (p) => p.runIdleTimeoutSeconds = n);
|
|
@@ -9553,6 +10572,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
9553
10572
|
firstRec = void 0;
|
|
9554
10573
|
const turnModel = rec?.model ?? opts.model;
|
|
9555
10574
|
const turnEffort = rec?.effort ?? opts.effort;
|
|
10575
|
+
const modelDisp = getModelDisplay(cfg);
|
|
9556
10576
|
const run = opts.thread.runStreamed(turnInput, { model: turnModel, effort: turnEffort });
|
|
9557
10577
|
const turnStartAt = Date.now();
|
|
9558
10578
|
state.run = run;
|
|
@@ -9562,7 +10582,9 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
9562
10582
|
const rc = {
|
|
9563
10583
|
rs: render.snapshot(),
|
|
9564
10584
|
requesterOpenId: opts.requesterOpenId,
|
|
9565
|
-
showTools: render.showTools
|
|
10585
|
+
showTools: render.showTools,
|
|
10586
|
+
// 模型显示档位:footnote 本轮 model·推理强度;always 档终态卡也保留。
|
|
10587
|
+
...modelDisp !== "off" && turnModel ? { model: turnModel, effort: turnEffort, modelOnTerminal: modelDisp === "always" } : {}
|
|
9566
10588
|
};
|
|
9567
10589
|
const adoptThreadId = async (messageId) => {
|
|
9568
10590
|
if (activeKey.startsWith("pending:")) {
|
|
@@ -9841,7 +10863,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
9841
10863
|
const startTurn = () => {
|
|
9842
10864
|
const render = new RunRender();
|
|
9843
10865
|
render.showTools = getShowToolCalls(cfg);
|
|
9844
|
-
const
|
|
10866
|
+
const goalModelDisp = getModelDisplay(cfg);
|
|
10867
|
+
const rc = {
|
|
10868
|
+
rs: render.snapshot(),
|
|
10869
|
+
requesterOpenId: opts.requesterOpenId,
|
|
10870
|
+
showTools: render.showTools,
|
|
10871
|
+
goalControls: true,
|
|
10872
|
+
...goalModelDisp !== "off" && opts.model ? { model: opts.model, effort: opts.effort, modelOnTerminal: goalModelDisp === "always" } : {}
|
|
10873
|
+
};
|
|
9845
10874
|
return { render, rc, stream: null, cardMsgId: null };
|
|
9846
10875
|
};
|
|
9847
10876
|
const ensureCard = async (ctx) => {
|
|
@@ -10208,9 +11237,60 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
10208
11237
|
}).catch(() => void 0);
|
|
10209
11238
|
return;
|
|
10210
11239
|
}
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
11240
|
+
const sendDm = (c) => sendManagedCard(channel, op, c, void 0, false, "open_id");
|
|
11241
|
+
try {
|
|
11242
|
+
switch (evt.eventKey) {
|
|
11243
|
+
case DM.newProject:
|
|
11244
|
+
await sendDm(buildNewProjectFormCard({ backends: backendOptionsFor("full") }));
|
|
11245
|
+
break;
|
|
11246
|
+
case DM.projects:
|
|
11247
|
+
await sendDm(await renderProjectList());
|
|
11248
|
+
break;
|
|
11249
|
+
case DM.settings:
|
|
11250
|
+
await sendDm(buildSettingsCard(cfg));
|
|
11251
|
+
break;
|
|
11252
|
+
case DM.doctor:
|
|
11253
|
+
await sendDm(buildDoctorCard(await buildDoctorInfo()));
|
|
11254
|
+
break;
|
|
11255
|
+
case DM.reconnect:
|
|
11256
|
+
await sendDm(buildReconnectCard(channel.getConnectionStatus?.()?.state ?? "unknown"));
|
|
11257
|
+
break;
|
|
11258
|
+
case DM.usage: {
|
|
11259
|
+
const { messageId } = await sendDm(buildUsageCard({ phase: "loading" }));
|
|
11260
|
+
let state;
|
|
11261
|
+
try {
|
|
11262
|
+
state = { phase: "ready", data: await fetchUsageBundle(false) };
|
|
11263
|
+
} catch (e) {
|
|
11264
|
+
log.fail("console", e, { phase: "usage", via: "menu" });
|
|
11265
|
+
state = {
|
|
11266
|
+
phase: "error",
|
|
11267
|
+
kind: e instanceof UsageError ? e.kind : "transient",
|
|
11268
|
+
message: e instanceof Error ? e.message : String(e)
|
|
11269
|
+
};
|
|
11270
|
+
}
|
|
11271
|
+
const ok = await updateManagedCard(channel, messageId, buildUsageCard(state)).catch(() => false);
|
|
11272
|
+
if (!ok) await sendDm(buildUsageCard(state));
|
|
11273
|
+
break;
|
|
11274
|
+
}
|
|
11275
|
+
case DM.update: {
|
|
11276
|
+
const { messageId } = await sendDm(buildUpdateCard({ phase: "checking" }));
|
|
11277
|
+
const current = currentVersion();
|
|
11278
|
+
const latest = await latestVersion().catch(() => null);
|
|
11279
|
+
const hasUpdate = !!latest && isNewer(latest, current);
|
|
11280
|
+
log.info("console", "update-check", { current, latest, hasUpdate, via: "menu" });
|
|
11281
|
+
await updateManagedCard(
|
|
11282
|
+
channel,
|
|
11283
|
+
messageId,
|
|
11284
|
+
buildUpdateCard({ phase: "checked", current, latest, hasUpdate, dev: isDevSource() })
|
|
11285
|
+
).catch((e) => log.fail("console", e, { phase: "update-check", via: "menu" }));
|
|
11286
|
+
break;
|
|
11287
|
+
}
|
|
11288
|
+
default:
|
|
11289
|
+
await sendDm(buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }));
|
|
11290
|
+
}
|
|
11291
|
+
} catch (err) {
|
|
11292
|
+
log.fail("console", err, { cmd: "menu-card", key: evt.eventKey });
|
|
11293
|
+
}
|
|
10214
11294
|
};
|
|
10215
11295
|
const reaper = setInterval(() => {
|
|
10216
11296
|
const now = Date.now();
|
|
@@ -11094,7 +12174,7 @@ async function doRestart(phase) {
|
|
|
11094
12174
|
|
|
11095
12175
|
// src/web/server.ts
|
|
11096
12176
|
import { createServer } from "http";
|
|
11097
|
-
import { randomUUID as
|
|
12177
|
+
import { randomUUID as randomUUID6, timingSafeEqual } from "crypto";
|
|
11098
12178
|
import { mkdirSync as mkdirSync3, watch } from "fs";
|
|
11099
12179
|
import { open as open2, stat as stat5 } from "fs/promises";
|
|
11100
12180
|
import { join as join19 } from "path";
|
|
@@ -11635,6 +12715,9 @@ var UI_HTML = `<!doctype html>
|
|
|
11635
12715
|
.iconbtn svg { width: 15px; height: 15px; }
|
|
11636
12716
|
.menu { position: absolute; top: 28px; right: 0; background: #1c1c21; border: 1px solid var(--border-2); border-radius: 10px; padding: 5px; min-width: 152px; box-shadow: var(--shadow-md); z-index: 25; display: none; }
|
|
11637
12717
|
.menu.open { display: block; }
|
|
12718
|
+
/* \u672B\u884C\u7684\u4E0B\u62C9\u5411\u4E0A\u5C55\u5F00\uFF1A\u8868\u683C .bka-tbl \u7528 overflow:hidden \u505A\u5706\u89D2\uFF0C\u4F1A\u88C1\u6389\u6700\u540E\u4E00\u884C
|
|
12719
|
+
\u5411\u4E0B\u5C55\u5F00\u7684\u83DC\u5355\uFF08\u5982 Claude \u884C\u7684\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u88AB\u5207\u6389\uFF09\u2014\u2014\u672B\u884C\u6539\u4E3A\u5411\u4E0A\u5F39\u51FA\u5373\u53EF\u907F\u5F00\u3002 */
|
|
12720
|
+
.bka-row:last-child .menu { top: auto; bottom: 30px; }
|
|
11638
12721
|
.mi { display: flex; align-items: center; gap: 9px; padding: 7px 9px; border-radius: 7px; font-size: 12.5px; color: var(--text); cursor: pointer; }
|
|
11639
12722
|
.mi svg { width: 14px; height: 14px; color: var(--text-2); }
|
|
11640
12723
|
.mi:hover { background: var(--hover); }
|
|
@@ -13767,10 +14850,11 @@ var DEFAULT_WEB_PORT = 51847;
|
|
|
13767
14850
|
var COOKIE_NAME = "fcb_console_token";
|
|
13768
14851
|
var SSE_INITIAL_TAIL_BYTES = 16 * 1024;
|
|
13769
14852
|
function createWebServer(opts) {
|
|
13770
|
-
const token = opts.token ??
|
|
14853
|
+
const token = opts.token ?? randomUUID6();
|
|
13771
14854
|
const html = opts.html ?? UI_HTML;
|
|
13772
14855
|
const logDir = opts.logDir ?? join19(paths.appDir, "logs");
|
|
13773
14856
|
const sseCleanups = /* @__PURE__ */ new Set();
|
|
14857
|
+
const installJobs = /* @__PURE__ */ new Map();
|
|
13774
14858
|
let qrSession = null;
|
|
13775
14859
|
const server = createServer((req, res) => {
|
|
13776
14860
|
void handle(req, res).catch((err) => {
|
|
@@ -14119,7 +15203,7 @@ function createWebServer(opts) {
|
|
|
14119
15203
|
function handleRegisterQrStream(req, res) {
|
|
14120
15204
|
qrSession?.abort.abort();
|
|
14121
15205
|
const abort = new AbortController();
|
|
14122
|
-
const session = { id:
|
|
15206
|
+
const session = { id: randomUUID6(), abort };
|
|
14123
15207
|
qrSession = session;
|
|
14124
15208
|
res.writeHead(200, {
|
|
14125
15209
|
"Content-Type": "text/event-stream",
|
|
@@ -14181,35 +15265,68 @@ data: ${JSON.stringify(data)}
|
|
|
14181
15265
|
"X-Accel-Buffering": "no"
|
|
14182
15266
|
});
|
|
14183
15267
|
res.write(": connected\n\n");
|
|
14184
|
-
const heartbeat = setInterval(() =>
|
|
14185
|
-
|
|
15268
|
+
const heartbeat = setInterval(() => {
|
|
15269
|
+
try {
|
|
15270
|
+
res.write(": ka\n\n");
|
|
15271
|
+
} catch {
|
|
15272
|
+
}
|
|
15273
|
+
}, 15e3);
|
|
14186
15274
|
let ended = false;
|
|
14187
|
-
const
|
|
14188
|
-
|
|
14189
|
-
|
|
15275
|
+
const conn = {
|
|
15276
|
+
send: (data) => {
|
|
15277
|
+
if (ended) return;
|
|
15278
|
+
try {
|
|
15279
|
+
res.write(`data: ${JSON.stringify(data)}
|
|
14190
15280
|
|
|
14191
15281
|
`);
|
|
15282
|
+
} catch {
|
|
15283
|
+
}
|
|
15284
|
+
},
|
|
15285
|
+
done: () => {
|
|
15286
|
+
if (ended) return;
|
|
15287
|
+
ended = true;
|
|
15288
|
+
clearInterval(heartbeat);
|
|
15289
|
+
sseCleanups.delete(conn.done);
|
|
15290
|
+
try {
|
|
15291
|
+
res.end();
|
|
15292
|
+
} catch {
|
|
15293
|
+
}
|
|
15294
|
+
}
|
|
14192
15295
|
};
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
14197
|
-
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
14202
|
-
|
|
15296
|
+
sseCleanups.add(conn.done);
|
|
15297
|
+
const key = `${id}|${update ? 1 : 0}`;
|
|
15298
|
+
const running = installJobs.get(key);
|
|
15299
|
+
if (running && !running.settled) {
|
|
15300
|
+
running.buffer.forEach(conn.send);
|
|
15301
|
+
running.conns.add(conn);
|
|
15302
|
+
req.on("close", () => {
|
|
15303
|
+
running.conns.delete(conn);
|
|
15304
|
+
conn.done();
|
|
15305
|
+
});
|
|
15306
|
+
return;
|
|
15307
|
+
}
|
|
15308
|
+
const job = { buffer: [], conns: /* @__PURE__ */ new Set([conn]), abort: new AbortController(), settled: false };
|
|
15309
|
+
installJobs.set(key, job);
|
|
15310
|
+
req.on("close", () => {
|
|
15311
|
+
job.conns.delete(conn);
|
|
15312
|
+
conn.done();
|
|
15313
|
+
});
|
|
15314
|
+
const emit2 = (data) => {
|
|
15315
|
+
if (data.type === "log") job.buffer.push(data);
|
|
15316
|
+
for (const c of [...job.conns]) c.send(data);
|
|
14203
15317
|
};
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
else sendData({ type: "error", code: result.aborted ? "aborted" : "install_failed", message: result.tail });
|
|
15318
|
+
void opts.service.installBackend(id, (chunk) => emit2({ type: "log", chunk }), job.abort.signal, { update }).then((result) => {
|
|
15319
|
+
emit2(
|
|
15320
|
+
result.ok ? { type: "done" } : { type: "error", code: result.aborted ? "aborted" : "install_failed", message: result.tail }
|
|
15321
|
+
);
|
|
14209
15322
|
}).catch((err) => {
|
|
14210
15323
|
const code = err instanceof NotWiredYetError ? "not_wired_yet" : "unknown";
|
|
14211
|
-
|
|
14212
|
-
}).finally(
|
|
15324
|
+
emit2({ type: "error", code, message: err instanceof Error ? err.message : String(err) });
|
|
15325
|
+
}).finally(() => {
|
|
15326
|
+
job.settled = true;
|
|
15327
|
+
installJobs.delete(key);
|
|
15328
|
+
for (const c of [...job.conns]) c.done();
|
|
15329
|
+
});
|
|
14213
15330
|
}
|
|
14214
15331
|
async function handleDiagnosis(res, botParam) {
|
|
14215
15332
|
const botId = botParam ?? await defaultBotId();
|