jinzd-ai-cli 0.4.303 → 0.4.305
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/{batch-PEH5MTMC.js → batch-OX27K4GG.js} +2 -2
- package/dist/{chunk-DHQCJC2B.js → chunk-3ZABGT3S.js} +1 -1
- package/dist/{chunk-RWM2GFRC.js → chunk-45UVJXFQ.js} +6 -2
- package/dist/{chunk-55AANMEM.js → chunk-4DH5LZR5.js} +1 -1
- package/dist/{chunk-23SJVOQD.js → chunk-72AR23R2.js} +26 -5
- package/dist/{chunk-6ZRLM3NG.js → chunk-AXKERUXH.js} +3 -3
- package/dist/{chunk-VOIOUJXI.js → chunk-C2Z77RTU.js} +13 -5
- package/dist/{chunk-ZMCZMR4R.js → chunk-GOIDKTBE.js} +12 -11
- package/dist/{chunk-ORDEOJXX.js → chunk-I6RATYRF.js} +1 -1
- package/dist/{chunk-WCJR5HCF.js → chunk-IPUTC5EX.js} +30 -12
- package/dist/{chunk-7XU4BATV.js → chunk-KBDKKHXB.js} +15 -6
- package/dist/{chunk-4DEMGCK4.js → chunk-LF67HWWR.js} +37 -13
- package/dist/{chunk-7WD2TBWY.js → chunk-NBYSFFCO.js} +1 -1
- package/dist/{chunk-XEKMGE2G.js → chunk-NFLE34QG.js} +1 -1
- package/dist/{chunk-BXP6YZ2P.js → chunk-NRSYBKWA.js} +5 -4
- package/dist/{chunk-IEQAE3QG.js → chunk-OAXLMF3U.js} +5 -4
- package/dist/{chunk-3GOHR45I.js → chunk-PA4C452B.js} +1 -1
- package/dist/{chunk-Q24BN6WF.js → chunk-Q4DAKI7I.js} +1 -1
- package/dist/{chunk-EMLRCVY2.js → chunk-SZ4226XY.js} +1 -1
- package/dist/{chunk-YK42KYKN.js → chunk-UFK5CTBU.js} +3 -3
- package/dist/{chunk-PPIISCFN.js → chunk-UFW4S3RU.js} +37 -13
- package/dist/{chunk-CKH4KQ4E.js → chunk-WQ3OMEC4.js} +6 -2
- package/dist/{chunk-UUSRWSSX.js → chunk-XJOK5353.js} +15 -6
- package/dist/{chunk-PEDN3AWT.js → chunk-YPOWDFOV.js} +1 -1
- package/dist/{ci-UXULLQMN.js → ci-IIMLOXFH.js} +4 -4
- package/dist/{ci-format-23FWBZN6.js → ci-format-FMBJC3EY.js} +2 -2
- package/dist/{constants-A6VKRPMA.js → constants-55QQ3RYE.js} +1 -1
- package/dist/{constants-HL7DYRDJ.js → constants-7NVAPQVR.js} +1 -1
- package/dist/{doctor-cli-FXBLI3EA.js → doctor-cli-ZHZ2XOF3.js} +4 -4
- package/dist/electron-server.js +250 -108
- package/dist/{hub-NLULKDKU.js → hub-MF53NIM2.js} +4 -4
- package/dist/{hub-server-RF6LURXY.js → hub-server-CBKCP5VD.js} +1 -1
- package/dist/index.js +24 -23
- package/dist/{indexer-RBEKMFT4.js → indexer-FPKY7LCT.js} +2 -2
- package/dist/{indexer-TNDWGFRI.js → indexer-P3FPRTPJ.js} +2 -2
- package/dist/{persistent-memory-NLXNNWOW.js → persistent-memory-LCOKUDTM.js} +2 -2
- package/dist/{persistent-memory-R4KLRI5Q.js → persistent-memory-NACQW2VB.js} +2 -2
- package/dist/{pr-K6Z73QYN.js → pr-NHWE5FW7.js} +4 -4
- package/dist/{run-tests-RYUB6PVF.js → run-tests-EUPJLDXS.js} +2 -2
- package/dist/{run-tests-PQ3SUDDN.js → run-tests-ZG5X25BJ.js} +2 -2
- package/dist/{semantic-HLAE2O4F.js → semantic-7GM5M4MU.js} +3 -3
- package/dist/{semantic-VPNKXKKK.js → semantic-QOBAXEQ4.js} +3 -3
- package/dist/{server-QCCH3277.js → server-PG7QCH53.js} +193 -82
- package/dist/{server-UJDYIBVR.js → server-PNYRE3AL.js} +9 -9
- package/dist/{store-MWNHVGJT.js → store-CMSSWZF7.js} +3 -1
- package/dist/{store-VO37H6LS.js → store-SIKHCUYJ.js} +3 -1
- package/dist/{task-orchestrator-QAQFB7DG.js → task-orchestrator-WAJTCE6D.js} +9 -9
- package/dist/{usage-PMHXXU2P.js → usage-DOES3DOS.js} +2 -2
- package/dist/{vector-store-JBAE6PS4.js → vector-store-JAXWDZYW.js} +1 -1
- package/dist/{vector-store-BBDXB5IQ.js → vector-store-PWIOQFTP.js} +1 -1
- package/dist/web/client/app.js +135 -60
- package/dist/web/client/app.js.br +0 -0
- package/dist/web/client/app.js.gz +0 -0
- package/dist/web/client/chat-input.js +38 -0
- package/dist/web/client/chat-input.js.br +0 -0
- package/dist/web/client/chat-input.js.gz +0 -0
- package/dist/web/client/index.html +1 -1
- package/dist/web/client/index.html.br +0 -0
- package/dist/web/client/index.html.gz +0 -0
- package/dist/web/client/stream-render.js +33 -0
- package/dist/web/client/style.css +2 -1
- package/dist/web/client/style.css.br +0 -0
- package/dist/web/client/style.css.gz +0 -0
- package/package.json +1 -1
package/dist/electron-server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasSemanticIndex,
|
|
3
3
|
semanticSearch
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-KBDKKHXB.js";
|
|
5
|
+
import "./chunk-OAXLMF3U.js";
|
|
6
6
|
import {
|
|
7
7
|
buildChatIndex,
|
|
8
8
|
clearChatIndex,
|
|
@@ -22,10 +22,10 @@ import {
|
|
|
22
22
|
} from "./chunk-SKET65WZ.js";
|
|
23
23
|
import {
|
|
24
24
|
indexProject
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-UFW4S3RU.js";
|
|
26
26
|
import {
|
|
27
27
|
loadIndex
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-45UVJXFQ.js";
|
|
29
29
|
import {
|
|
30
30
|
addMemoryEntry,
|
|
31
31
|
backupMemoryStore,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
touchMemoryReferences,
|
|
51
51
|
updateMemoryApproval,
|
|
52
52
|
updateMemoryEntry
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-YPOWDFOV.js";
|
|
54
54
|
import {
|
|
55
55
|
DEFAULT_PATTERNS,
|
|
56
56
|
redactJson,
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
} from "./chunk-CGLYJRFA.js";
|
|
67
67
|
import {
|
|
68
68
|
runTestsTool
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-SZ4226XY.js";
|
|
70
70
|
import {
|
|
71
71
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
72
72
|
APP_NAME,
|
|
@@ -100,12 +100,30 @@ import {
|
|
|
100
100
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
101
101
|
VERSION,
|
|
102
102
|
buildUserIdentityPrompt
|
|
103
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-PA4C452B.js";
|
|
104
104
|
|
|
105
105
|
// src/web/server.ts
|
|
106
106
|
import express from "express";
|
|
107
107
|
import { createServer } from "http";
|
|
108
108
|
import { WebSocketServer } from "ws";
|
|
109
|
+
|
|
110
|
+
// src/web/chat-limits.ts
|
|
111
|
+
var MAX_CHAT_BYTES = 16 * 1024 * 1024;
|
|
112
|
+
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
113
|
+
var MAX_IMAGE_BASE64_CHARS = Math.ceil(MAX_IMAGE_BYTES / 3) * 4;
|
|
114
|
+
var IMAGE_MIMES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"]);
|
|
115
|
+
function imageValidationError(images) {
|
|
116
|
+
if (images === void 0) return;
|
|
117
|
+
if (!Array.isArray(images)) return "Images must be an array.";
|
|
118
|
+
for (const image of images) {
|
|
119
|
+
if (!image || typeof image.data !== "string" || !IMAGE_MIMES.has(image.mime)) {
|
|
120
|
+
return "Unsupported image format. Use PNG, JPEG, GIF, WebP or SVG.";
|
|
121
|
+
}
|
|
122
|
+
if (image.data.length > MAX_IMAGE_BASE64_CHARS) return "Image too large (maximum 10 MiB before base64 encoding).";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/web/server.ts
|
|
109
127
|
import { join as join28, dirname as dirname11, resolve as resolve12, relative as relative6, sep as sep5, extname as extname3, resolve as resolvePath } from "path";
|
|
110
128
|
import { existsSync as existsSync32, readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync16, realpathSync as realpathSync3, createReadStream } from "fs";
|
|
111
129
|
import { networkInterfaces } from "os";
|
|
@@ -6448,6 +6466,11 @@ function buildErrorHint(command, stderr) {
|
|
|
6448
6466
|
`Hint: On Windows, "-ErrorAction SilentlyContinue" silences the error MESSAGE but does NOT change the exit code \u2014 the process still exits 1 when the path doesn't exist (because powershell.exe -NonInteractive flips the exit code on any non-terminating error, silenced or not). If you're just probing for existence, use Test-Path instead \u2014 it returns $true/$false without raising: \`if (Test-Path ./mydir) { Get-ChildItem ./mydir }\` or unconditionally use \`New-Item -ItemType Directory -Force -Path ./mydir\` which is idempotent. Don't retry the same -ErrorAction SilentlyContinue probe expecting a different result.`
|
|
6449
6467
|
);
|
|
6450
6468
|
}
|
|
6469
|
+
if (IS_WINDOWS3 && /Select-Object\s+(?:[^|]*\s)?-(?:First|Last)\s+\d+/i.test(command) && /\|/.test(command)) {
|
|
6470
|
+
hints.push(
|
|
6471
|
+
`Hint: "Select-Object -First N" stops the upstream pipeline as soon as it has N items (PipelineStoppedException), and PowerShell reports that as a FAILURE even when the upstream command did its job \u2014 check the output above: if it shows the work completed (e.g. "EXPORTED 25 slide(s)"), this is a false failure and you should NOT retry. To avoid it, capture first and slice after: \`$out = <command>; $out | Select-Object -First N\`, or drop the -First and let the full output through.`
|
|
6472
|
+
);
|
|
6473
|
+
}
|
|
6451
6474
|
if (IS_WINDOWS3 && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
|
|
6452
6475
|
hints.push(
|
|
6453
6476
|
`Hint: 'Write-Content' / 'Write-File' are NOT PowerShell cmdlets (common LLM hallucination). To write a file, prefer the built-in 'write_file' tool \u2014 it handles encoding/escaping cleanly and supports diff preview. If you must stay in bash: use 'Set-Content -Path <file> -Value <string> -Encoding utf8' (overwrites) or 'Add-Content' (appends) or 'Out-File -FilePath <file> -Encoding utf8'.`
|
|
@@ -7811,26 +7834,11 @@ function checkNetworkPolicy(toolName, args, policy) {
|
|
|
7811
7834
|
// src/tools/truncate.ts
|
|
7812
7835
|
var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
|
|
7813
7836
|
var activeCap = DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP;
|
|
7814
|
-
function
|
|
7815
|
-
if (!cap || cap <= 0) {
|
|
7816
|
-
activeCap = DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP;
|
|
7817
|
-
} else {
|
|
7818
|
-
activeCap = Math.max(DEFAULT_MAX_TOOL_OUTPUT_CHARS, cap);
|
|
7819
|
-
}
|
|
7820
|
-
if (lastContextWindow > 0) {
|
|
7821
|
-
activeMaxChars = getMaxOutputChars(lastContextWindow);
|
|
7822
|
-
}
|
|
7823
|
-
}
|
|
7824
|
-
function getMaxOutputChars(contextWindow) {
|
|
7837
|
+
function getMaxOutputChars(contextWindow, cap = activeCap) {
|
|
7825
7838
|
if (!contextWindow || contextWindow <= 0) return DEFAULT_MAX_TOOL_OUTPUT_CHARS;
|
|
7826
|
-
return Math.max(DEFAULT_MAX_TOOL_OUTPUT_CHARS, Math.min(Math.floor(contextWindow / 4),
|
|
7839
|
+
return Math.max(DEFAULT_MAX_TOOL_OUTPUT_CHARS, Math.min(Math.floor(contextWindow / 4), cap > 0 ? cap : DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP));
|
|
7827
7840
|
}
|
|
7828
7841
|
var activeMaxChars = DEFAULT_MAX_TOOL_OUTPUT_CHARS;
|
|
7829
|
-
var lastContextWindow = 0;
|
|
7830
|
-
function setContextWindow(contextWindow) {
|
|
7831
|
-
lastContextWindow = contextWindow;
|
|
7832
|
-
activeMaxChars = getMaxOutputChars(contextWindow);
|
|
7833
|
-
}
|
|
7834
7842
|
function getActiveMaxChars() {
|
|
7835
7843
|
return activeMaxChars;
|
|
7836
7844
|
}
|
|
@@ -8228,7 +8236,12 @@ function buildErrorPreview(content) {
|
|
|
8228
8236
|
undecorated.push(raw[i]);
|
|
8229
8237
|
}
|
|
8230
8238
|
const meaningful = undecorated.filter(
|
|
8231
|
-
(l) => !/^(?:\[[^\]]+\]\s*)?Exit code \d+:$/.test(l.trim()) &&
|
|
8239
|
+
(l) => !/^(?:\[[^\]]+\]\s*)?Exit code \d+:$/.test(l.trim()) && // `formatFailureHeader()` 在拿不到退出码时打的那句 —— 与 "Exit code N:" 同类噪声。
|
|
8240
|
+
// 不过滤的话它会占掉第一个展示位:2026-09-05 实战里 `export_slides.ps1 | Select-Object
|
|
8241
|
+
// -First 3` 提前掐断管道被 PowerShell 报成失败,预览成了
|
|
8242
|
+
// 「Command failed before exit code could be determined: · …\slide-02.png」——
|
|
8243
|
+
// 两段都没用,而真正有信息的 `EXPORTED 25 slide(s)` 就在第二行、从没上屏。
|
|
8244
|
+
!/^(?:\[[^\]]+\]\s*)?Command failed before exit code could be determined:$/.test(l.trim()) && !/^Traceback \(most recent call last\):$/.test(l.trim()) && !l.startsWith("[Command failed") && !l.startsWith("\u{1F4A1} ") && l.trim() !== ""
|
|
8232
8245
|
);
|
|
8233
8246
|
let primary = -1;
|
|
8234
8247
|
for (let i = meaningful.length - 1; i >= 0; i--) {
|
|
@@ -8729,7 +8742,7 @@ var ToolExecutor = class {
|
|
|
8729
8742
|
... (${lines.length - maxLines} more lines)`) : "";
|
|
8730
8743
|
const truncatedNote = wasTruncated ? theme.warning(`
|
|
8731
8744
|
\u26A1 Output truncated to ${getActiveMaxChars()} chars before sending to AI`) : "";
|
|
8732
|
-
console.log(theme.toolResult(
|
|
8745
|
+
console.log(theme.toolResult(`\u2713 ${name} result: `) + theme.dim(preview) + moreLines + truncatedNote);
|
|
8733
8746
|
}
|
|
8734
8747
|
console.log();
|
|
8735
8748
|
}
|
|
@@ -8955,7 +8968,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
8955
8968
|
const mode = appendMode ? "appended" : "written";
|
|
8956
8969
|
void (async () => {
|
|
8957
8970
|
try {
|
|
8958
|
-
const { updateFile } = await import("./indexer-
|
|
8971
|
+
const { updateFile } = await import("./indexer-FPKY7LCT.js");
|
|
8959
8972
|
await updateFile(process.cwd(), filePath);
|
|
8960
8973
|
} catch {
|
|
8961
8974
|
}
|
|
@@ -9382,6 +9395,9 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
9382
9395
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
9383
9396
|
atomicWriteFileSync(filePath, res.content);
|
|
9384
9397
|
}
|
|
9398
|
+
if (!res.ok) {
|
|
9399
|
+
throw new ToolError("edit_file", lines.join("\n"));
|
|
9400
|
+
}
|
|
9385
9401
|
return lines.join("\n");
|
|
9386
9402
|
}
|
|
9387
9403
|
if (args["edits"] !== void 0) {
|
|
@@ -9434,6 +9450,9 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
9434
9450
|
lines.push(` \u2717 #${r.index + 1}: ${r.error}`);
|
|
9435
9451
|
}
|
|
9436
9452
|
}
|
|
9453
|
+
if (anyFailed && stopOnError) {
|
|
9454
|
+
throw new ToolError("edit_file", lines.join("\n"));
|
|
9455
|
+
}
|
|
9437
9456
|
return lines.join("\n");
|
|
9438
9457
|
}
|
|
9439
9458
|
const replaceArgs = normalizeReplaceArgs(args);
|
|
@@ -14080,6 +14099,27 @@ function suggestSkillName(names, input) {
|
|
|
14080
14099
|
// src/web/tool-executor-web.ts
|
|
14081
14100
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
14082
14101
|
import { tmpdir as tmpdir2 } from "os";
|
|
14102
|
+
|
|
14103
|
+
// src/web/socket-send.ts
|
|
14104
|
+
var MAX_PENDING_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
14105
|
+
var stalled = /* @__PURE__ */ new WeakSet();
|
|
14106
|
+
function sendSocketMessage(ws, message) {
|
|
14107
|
+
if (ws.readyState !== ws.OPEN || stalled.has(ws)) return false;
|
|
14108
|
+
const data = JSON.stringify(message);
|
|
14109
|
+
if ((ws.bufferedAmount || 0) + Buffer.byteLength(data) > MAX_PENDING_OUTPUT_BYTES) {
|
|
14110
|
+
stalled.add(ws);
|
|
14111
|
+
ws.close(1013, "Output backlog exceeded; reconnect to reload session");
|
|
14112
|
+
const timer = setTimeout(() => {
|
|
14113
|
+
if (ws.readyState !== ws.CLOSED) ws.terminate();
|
|
14114
|
+
}, 1e3);
|
|
14115
|
+
timer.unref();
|
|
14116
|
+
return false;
|
|
14117
|
+
}
|
|
14118
|
+
ws.send(data);
|
|
14119
|
+
return true;
|
|
14120
|
+
}
|
|
14121
|
+
|
|
14122
|
+
// src/web/tool-executor-web.ts
|
|
14083
14123
|
import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
|
|
14084
14124
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
14085
14125
|
constructor(registry, ws) {
|
|
@@ -14109,6 +14149,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14109
14149
|
// 5 minutes
|
|
14110
14150
|
/** Publicly readable by SessionHandler to check if confirm is active */
|
|
14111
14151
|
confirming = false;
|
|
14152
|
+
maxOutputChars = DEFAULT_MAX_TOOL_OUTPUT_CHARS;
|
|
14112
14153
|
/** Session-level auto-approve toggle (/yolo command) */
|
|
14113
14154
|
sessionAutoApprove = false;
|
|
14114
14155
|
/** Session-level Auto Mode: rule-based low-risk auto approval. */
|
|
@@ -14191,9 +14232,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14191
14232
|
this.confirming = false;
|
|
14192
14233
|
}
|
|
14193
14234
|
send(msg) {
|
|
14194
|
-
|
|
14195
|
-
this.ws.send(JSON.stringify(msg));
|
|
14196
|
-
}
|
|
14235
|
+
sendSocketMessage(this.ws, msg);
|
|
14197
14236
|
}
|
|
14198
14237
|
sendToolCallStart(call) {
|
|
14199
14238
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
@@ -14386,7 +14425,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14386
14425
|
if (!toolCallStarted) this.sendToolCallStart(call);
|
|
14387
14426
|
try {
|
|
14388
14427
|
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
14389
|
-
const content = truncateOutput(rawContent, call.name);
|
|
14428
|
+
const content = truncateOutput(rawContent, call.name, this.maxOutputChars);
|
|
14390
14429
|
this.sendToolCallResult(call, rawContent, false);
|
|
14391
14430
|
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
14392
14431
|
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
@@ -14451,7 +14490,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14451
14490
|
}
|
|
14452
14491
|
try {
|
|
14453
14492
|
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
14454
|
-
const content = truncateOutput(rawContent, call.name);
|
|
14493
|
+
const content = truncateOutput(rawContent, call.name, this.maxOutputChars);
|
|
14455
14494
|
this.sendToolCallResult(call, rawContent, false);
|
|
14456
14495
|
results[i] = { callId: call.id, content, isError: false };
|
|
14457
14496
|
} catch (err) {
|
|
@@ -15400,6 +15439,7 @@ var HubAgent = class {
|
|
|
15400
15439
|
model: this.modelId,
|
|
15401
15440
|
systemPrompt,
|
|
15402
15441
|
stream: false,
|
|
15442
|
+
signal: opts?.signal,
|
|
15403
15443
|
temperature: 0.7,
|
|
15404
15444
|
maxTokens: 4096,
|
|
15405
15445
|
...this.thinking ?? {}
|
|
@@ -15434,15 +15474,21 @@ var HubAgent = class {
|
|
|
15434
15474
|
model: this.modelId,
|
|
15435
15475
|
systemPrompt,
|
|
15436
15476
|
stream: true,
|
|
15477
|
+
signal: opts?.signal,
|
|
15437
15478
|
temperature: 0.7,
|
|
15438
15479
|
maxTokens: 4096,
|
|
15439
15480
|
...this.thinking ?? {}
|
|
15440
15481
|
});
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15482
|
+
try {
|
|
15483
|
+
for await (const chunk of stream) {
|
|
15484
|
+
if (chunk.delta) {
|
|
15485
|
+
raw += chunk.delta;
|
|
15486
|
+
onToken?.(chunk.delta);
|
|
15487
|
+
}
|
|
15488
|
+
if (opts?.signal?.aborted) break;
|
|
15445
15489
|
}
|
|
15490
|
+
} catch (error) {
|
|
15491
|
+
if (!opts?.signal?.aborted) throw error;
|
|
15446
15492
|
}
|
|
15447
15493
|
const { content, passed, converged } = parseTurn(raw);
|
|
15448
15494
|
return {
|
|
@@ -15457,7 +15503,7 @@ var HubAgent = class {
|
|
|
15457
15503
|
/**
|
|
15458
15504
|
* Generate a summary of the entire discussion from this agent's perspective.
|
|
15459
15505
|
*/
|
|
15460
|
-
async summarize(topic, history) {
|
|
15506
|
+
async summarize(topic, history, signal) {
|
|
15461
15507
|
const provider = this.providers.get(this.providerId);
|
|
15462
15508
|
if (!provider) {
|
|
15463
15509
|
throw new Error(`Provider "${this.providerId}" not available`);
|
|
@@ -15474,6 +15520,7 @@ var HubAgent = class {
|
|
|
15474
15520
|
model: this.modelId,
|
|
15475
15521
|
stream: false,
|
|
15476
15522
|
temperature: 0.3,
|
|
15523
|
+
signal,
|
|
15477
15524
|
maxTokens: 4096,
|
|
15478
15525
|
...this.thinking ?? {}
|
|
15479
15526
|
});
|
|
@@ -15567,6 +15614,8 @@ var DiscussionOrchestrator = class {
|
|
|
15567
15614
|
agents = [];
|
|
15568
15615
|
state;
|
|
15569
15616
|
aborted = false;
|
|
15617
|
+
controller = new AbortController();
|
|
15618
|
+
cancelReview;
|
|
15570
15619
|
humanSteer;
|
|
15571
15620
|
voteConverge;
|
|
15572
15621
|
/** Callback for rendering events */
|
|
@@ -15606,6 +15655,8 @@ var DiscussionOrchestrator = class {
|
|
|
15606
15655
|
/** Signal the orchestrator to stop after current turn */
|
|
15607
15656
|
abort() {
|
|
15608
15657
|
this.aborted = true;
|
|
15658
|
+
this.controller.abort();
|
|
15659
|
+
this.cancelReview?.();
|
|
15609
15660
|
}
|
|
15610
15661
|
/**
|
|
15611
15662
|
* Run the full discussion on a given topic.
|
|
@@ -15616,6 +15667,7 @@ var DiscussionOrchestrator = class {
|
|
|
15616
15667
|
this.state.round = 0;
|
|
15617
15668
|
this.state.finished = false;
|
|
15618
15669
|
this.aborted = false;
|
|
15670
|
+
this.controller = new AbortController();
|
|
15619
15671
|
try {
|
|
15620
15672
|
for (let round = 1; round <= this.state.maxRounds; round++) {
|
|
15621
15673
|
if (this.aborted) {
|
|
@@ -15636,7 +15688,7 @@ var DiscussionOrchestrator = class {
|
|
|
15636
15688
|
round,
|
|
15637
15689
|
this.state.maxRounds,
|
|
15638
15690
|
(token) => this.emit({ type: "agent_token", roleId: agent.role.id, token }),
|
|
15639
|
-
{ voteConverge: this.voteConverge }
|
|
15691
|
+
{ voteConverge: this.voteConverge, signal: this.controller.signal }
|
|
15640
15692
|
);
|
|
15641
15693
|
this.state.messages.push(message);
|
|
15642
15694
|
if (message.converged) convergedCount++;
|
|
@@ -15648,6 +15700,7 @@ var DiscussionOrchestrator = class {
|
|
|
15648
15700
|
this.emit({ type: "agent_spoke", roleId: agent.role.id, message });
|
|
15649
15701
|
}
|
|
15650
15702
|
} catch (err) {
|
|
15703
|
+
if (this.aborted) break;
|
|
15651
15704
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
15652
15705
|
this.emit({ type: "error", roleId: agent.role.id, message: errMsg });
|
|
15653
15706
|
this.state.messages.push({
|
|
@@ -15659,6 +15712,10 @@ var DiscussionOrchestrator = class {
|
|
|
15659
15712
|
}
|
|
15660
15713
|
}
|
|
15661
15714
|
this.emit({ type: "round_end", round });
|
|
15715
|
+
if (this.aborted) {
|
|
15716
|
+
this.emit({ type: "discussion_end", reason: "user_interrupt" });
|
|
15717
|
+
break;
|
|
15718
|
+
}
|
|
15662
15719
|
if (allPassed && round > 1) {
|
|
15663
15720
|
this.emit({ type: "discussion_end", reason: "consensus" });
|
|
15664
15721
|
break;
|
|
@@ -15675,7 +15732,18 @@ var DiscussionOrchestrator = class {
|
|
|
15675
15732
|
break;
|
|
15676
15733
|
}
|
|
15677
15734
|
if (this.humanSteer && this.onRoundReview && !this.aborted) {
|
|
15678
|
-
const
|
|
15735
|
+
const stopped = new Promise((resolve13) => {
|
|
15736
|
+
this.cancelReview = () => resolve13({ action: "stop" });
|
|
15737
|
+
});
|
|
15738
|
+
let steer;
|
|
15739
|
+
try {
|
|
15740
|
+
steer = await Promise.race([
|
|
15741
|
+
this.onRoundReview({ round, maxRounds: this.state.maxRounds, state: this.state }),
|
|
15742
|
+
stopped
|
|
15743
|
+
]);
|
|
15744
|
+
} finally {
|
|
15745
|
+
this.cancelReview = void 0;
|
|
15746
|
+
}
|
|
15679
15747
|
if (steer.action === "stop") {
|
|
15680
15748
|
this.emit({ type: "discussion_end", reason: "human_stop" });
|
|
15681
15749
|
break;
|
|
@@ -15696,7 +15764,7 @@ var DiscussionOrchestrator = class {
|
|
|
15696
15764
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
15697
15765
|
this.emit({ type: "error", message: errMsg });
|
|
15698
15766
|
}
|
|
15699
|
-
await this.generateSummary();
|
|
15767
|
+
if (!this.aborted) await this.generateSummary();
|
|
15700
15768
|
this.state.finished = true;
|
|
15701
15769
|
return this.state;
|
|
15702
15770
|
}
|
|
@@ -15714,7 +15782,7 @@ var DiscussionOrchestrator = class {
|
|
|
15714
15782
|
return;
|
|
15715
15783
|
}
|
|
15716
15784
|
try {
|
|
15717
|
-
this.state.summary = await summarizer.summarize(this.state.topic, this.state.messages);
|
|
15785
|
+
this.state.summary = await summarizer.summarize(this.state.topic, this.state.messages, this.controller.signal);
|
|
15718
15786
|
this.emit({ type: "summary", content: this.state.summary });
|
|
15719
15787
|
} catch (err) {
|
|
15720
15788
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -16190,11 +16258,11 @@ function routeDecisionRows(message, currentModel, decision) {
|
|
|
16190
16258
|
}
|
|
16191
16259
|
|
|
16192
16260
|
// src/web/commands/provider-commands.ts
|
|
16193
|
-
function syncSessionProvider(ctx) {
|
|
16261
|
+
async function syncSessionProvider(ctx) {
|
|
16194
16262
|
const session = ctx.sessions.current;
|
|
16195
16263
|
if (!session) return;
|
|
16196
16264
|
session.updateProvider(ctx.currentProvider, ctx.currentModel);
|
|
16197
|
-
ctx.saveIfNeeded();
|
|
16265
|
+
await ctx.saveIfNeeded();
|
|
16198
16266
|
}
|
|
16199
16267
|
async function handleProvider(args, ctx) {
|
|
16200
16268
|
const id = args[0];
|
|
@@ -16237,7 +16305,7 @@ async function handleProvider(args, ctx) {
|
|
|
16237
16305
|
ctx.currentProvider = outcome.providerId;
|
|
16238
16306
|
ctx.currentModel = outcome.model;
|
|
16239
16307
|
ctx.updateContextWindow();
|
|
16240
|
-
syncSessionProvider(ctx);
|
|
16308
|
+
await syncSessionProvider(ctx);
|
|
16241
16309
|
ctx.send({ type: "info", message: outcome.message });
|
|
16242
16310
|
ctx.sendStatus();
|
|
16243
16311
|
}
|
|
@@ -16291,7 +16359,7 @@ async function handleModel(args, ctx) {
|
|
|
16291
16359
|
}
|
|
16292
16360
|
ctx.currentModel = outcome.model;
|
|
16293
16361
|
ctx.updateContextWindow();
|
|
16294
|
-
syncSessionProvider(ctx);
|
|
16362
|
+
await syncSessionProvider(ctx);
|
|
16295
16363
|
ctx.send({
|
|
16296
16364
|
type: "info",
|
|
16297
16365
|
message: outcome.warning ? `${outcome.message} \u2014 ${outcome.warning}` : outcome.message
|
|
@@ -17176,7 +17244,7 @@ async function handleClear(_args, ctx) {
|
|
|
17176
17244
|
const prior = ctx.sessions.current;
|
|
17177
17245
|
const priorId = prior?.id ?? null;
|
|
17178
17246
|
const priorCount = prior?.messages.length ?? 0;
|
|
17179
|
-
ctx.saveIfNeeded();
|
|
17247
|
+
await ctx.saveIfNeeded();
|
|
17180
17248
|
const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
|
|
17181
17249
|
ctx.unsavedSessions.set(created.id, created);
|
|
17182
17250
|
ctx.resetWebSessionUsage();
|
|
@@ -17198,7 +17266,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
|
|
|
17198
17266
|
Cost: ${formatCost(cost)}` : "";
|
|
17199
17267
|
let memoryLine = "";
|
|
17200
17268
|
try {
|
|
17201
|
-
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-
|
|
17269
|
+
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-LCOKUDTM.js");
|
|
17202
17270
|
const pending = countPendingMemories2(ctx.config.getConfigDir());
|
|
17203
17271
|
if (pending > 0) memoryLine = `
|
|
17204
17272
|
Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
|
|
@@ -17214,8 +17282,13 @@ Tokens: in=${ctx.sessionTokenUsage.inputTokens} out=${ctx.sessionTokenUsage.outp
|
|
|
17214
17282
|
}
|
|
17215
17283
|
async function handleSession(args, ctx) {
|
|
17216
17284
|
const sub = args[0];
|
|
17285
|
+
if (sub === "save") {
|
|
17286
|
+
await ctx.saveIfNeeded();
|
|
17287
|
+
if (!ctx.sessions.current?.dirty) ctx.send({ type: "info", message: "Session saved." });
|
|
17288
|
+
return;
|
|
17289
|
+
}
|
|
17217
17290
|
if (sub === "new") {
|
|
17218
|
-
ctx.saveIfNeeded();
|
|
17291
|
+
await ctx.saveIfNeeded();
|
|
17219
17292
|
const created = ctx.sessions.createSession(ctx.currentProvider, ctx.currentModel);
|
|
17220
17293
|
ctx.unsavedSessions.set(created.id, created);
|
|
17221
17294
|
ctx.resetWebSessionUsage();
|
|
@@ -17226,7 +17299,7 @@ async function handleSession(args, ctx) {
|
|
|
17226
17299
|
}
|
|
17227
17300
|
if (sub === "load" && args[1]) {
|
|
17228
17301
|
const targetId = args[1];
|
|
17229
|
-
ctx.saveIfNeeded();
|
|
17302
|
+
await ctx.saveIfNeeded();
|
|
17230
17303
|
const cachedExact = ctx.unsavedSessions.get(targetId);
|
|
17231
17304
|
const cached = cachedExact ?? [...ctx.unsavedSessions.values()].find((s) => s.id.startsWith(targetId));
|
|
17232
17305
|
if (cached) {
|
|
@@ -18181,7 +18254,7 @@ async function handleTest(args, ctx) {
|
|
|
18181
18254
|
const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
|
|
18182
18255
|
testArgs = isCommand ? { command: argStr } : { filter: argStr };
|
|
18183
18256
|
}
|
|
18184
|
-
const runTests = ctx.runTests ?? (await import("./run-tests-
|
|
18257
|
+
const runTests = ctx.runTests ?? (await import("./run-tests-EUPJLDXS.js")).executeTests;
|
|
18185
18258
|
const report = await runTests(testArgs);
|
|
18186
18259
|
ctx.send({ type: "info", message: report });
|
|
18187
18260
|
} catch (err) {
|
|
@@ -18473,9 +18546,9 @@ async function handleContext(_args, ctx) {
|
|
|
18473
18546
|
async function handleIndex(args, ctx) {
|
|
18474
18547
|
const sub = (args[0] ?? "status").toLowerCase();
|
|
18475
18548
|
const root = process.cwd();
|
|
18476
|
-
const { loadIndex: loadIndex2, clearIndex } = await import("./store-
|
|
18477
|
-
const { indexProject: indexProject2 } = await import("./indexer-
|
|
18478
|
-
const { loadVectorStore, clearVectorStore } = await import("./vector-store-
|
|
18549
|
+
const { loadIndex: loadIndex2, clearIndex } = await import("./store-SIKHCUYJ.js");
|
|
18550
|
+
const { indexProject: indexProject2 } = await import("./indexer-FPKY7LCT.js");
|
|
18551
|
+
const { loadVectorStore, clearVectorStore } = await import("./vector-store-JAXWDZYW.js");
|
|
18479
18552
|
if (sub === "status") {
|
|
18480
18553
|
const idx = loadIndex2(root);
|
|
18481
18554
|
const vec = loadVectorStore(root);
|
|
@@ -18498,6 +18571,7 @@ async function handleIndex(args, ctx) {
|
|
|
18498
18571
|
} else if (sub === "rebuild") {
|
|
18499
18572
|
ctx.send({ type: "info", message: `Indexing ${root}\u2026` });
|
|
18500
18573
|
const { stats } = await indexProject2(root, { force: true });
|
|
18574
|
+
if (stats.incomplete) ctx.send({ type: "info", message: "Partial index: scan limit or unreadable files reached. Unscanned entries were retained." });
|
|
18501
18575
|
ctx.send({
|
|
18502
18576
|
type: "info",
|
|
18503
18577
|
message: `\u2713 Indexed ${stats.filesParsed} files (${stats.symbols} symbols) in ${stats.durationMs}ms`
|
|
@@ -18524,7 +18598,7 @@ async function handleIndex(args, ctx) {
|
|
|
18524
18598
|
message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
|
|
18525
18599
|
});
|
|
18526
18600
|
try {
|
|
18527
|
-
const { rebuildSemanticIndex } = await import("./semantic-
|
|
18601
|
+
const { rebuildSemanticIndex } = await import("./semantic-QOBAXEQ4.js");
|
|
18528
18602
|
const stats = await rebuildSemanticIndex(root);
|
|
18529
18603
|
const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
|
|
18530
18604
|
ctx.send({
|
|
@@ -18571,7 +18645,7 @@ async function handleMemory(args, ctx) {
|
|
|
18571
18645
|
}
|
|
18572
18646
|
if (intent.kind === "status") {
|
|
18573
18647
|
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-DGUOUUQW.js");
|
|
18574
|
-
const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-
|
|
18648
|
+
const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-LCOKUDTM.js");
|
|
18575
18649
|
const memories = listMemoryEntries2(configDir, { includeExpired: true, includeRejected: true });
|
|
18576
18650
|
const injected = formatMemoryForPrompt2(configDir, process.cwd(), ctx.config.get("memory")?.maxChars);
|
|
18577
18651
|
const view = buildMemoryStatusView({
|
|
@@ -18594,7 +18668,7 @@ async function handleMemory(args, ctx) {
|
|
|
18594
18668
|
return;
|
|
18595
18669
|
}
|
|
18596
18670
|
if (intent.kind === "recall") {
|
|
18597
|
-
const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-
|
|
18671
|
+
const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-LCOKUDTM.js");
|
|
18598
18672
|
const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-DGUOUUQW.js");
|
|
18599
18673
|
const persistentHits = searchPersistentMemories2(configDir, intent.query, process.cwd(), 3);
|
|
18600
18674
|
if (persistentHits.length > 0) {
|
|
@@ -18715,7 +18789,7 @@ async function handleMemory(args, ctx) {
|
|
|
18715
18789
|
ctx.handleMemoryManage(intent.action, args[1], intent.action === "expire" ? args[2] : void 0);
|
|
18716
18790
|
} else if (intent.kind === "export") {
|
|
18717
18791
|
if (args[1] === "md") {
|
|
18718
|
-
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-
|
|
18792
|
+
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-LCOKUDTM.js");
|
|
18719
18793
|
ctx.send({
|
|
18720
18794
|
type: "export_data",
|
|
18721
18795
|
format: "md",
|
|
@@ -19720,7 +19794,7 @@ No tools match "${filter}".
|
|
|
19720
19794
|
const { join: join29 } = await import("path");
|
|
19721
19795
|
const { existsSync: existsSync33 } = await import("fs");
|
|
19722
19796
|
const { getGitRoot: getGitRoot2 } = await import("./git-context-VNCA2GU7.js");
|
|
19723
|
-
const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-
|
|
19797
|
+
const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-55QQ3RYE.js");
|
|
19724
19798
|
const { approveProject, hashMcpFile } = await import("./project-trust-MUG325AW.js");
|
|
19725
19799
|
const cwd = process.cwd();
|
|
19726
19800
|
const projectRoot = getGitRoot2(cwd) ?? cwd;
|
|
@@ -20639,7 +20713,7 @@ function createReviewCommands() {
|
|
|
20639
20713
|
usage: "/test [command|filter]",
|
|
20640
20714
|
async execute(args, ctx) {
|
|
20641
20715
|
try {
|
|
20642
|
-
const { executeTests } = await import("./run-tests-
|
|
20716
|
+
const { executeTests } = await import("./run-tests-EUPJLDXS.js");
|
|
20643
20717
|
const argStr = args.join(" ").trim();
|
|
20644
20718
|
let testArgs = {};
|
|
20645
20719
|
if (argStr) {
|
|
@@ -20890,9 +20964,9 @@ function createMemoryCommands() {
|
|
|
20890
20964
|
async execute(args, ctx) {
|
|
20891
20965
|
const sub = (args[0] ?? "status").toLowerCase();
|
|
20892
20966
|
const root = process.cwd();
|
|
20893
|
-
const { loadIndex: loadIndex2, clearIndex } = await import("./store-
|
|
20894
|
-
const { indexProject: indexProject2 } = await import("./indexer-
|
|
20895
|
-
const { loadVectorStore, clearVectorStore } = await import("./vector-store-
|
|
20967
|
+
const { loadIndex: loadIndex2, clearIndex } = await import("./store-SIKHCUYJ.js");
|
|
20968
|
+
const { indexProject: indexProject2 } = await import("./indexer-FPKY7LCT.js");
|
|
20969
|
+
const { loadVectorStore, clearVectorStore } = await import("./vector-store-JAXWDZYW.js");
|
|
20896
20970
|
if (sub === "status") {
|
|
20897
20971
|
const idx = loadIndex2(root);
|
|
20898
20972
|
const vec = loadVectorStore(root);
|
|
@@ -20923,6 +20997,7 @@ function createMemoryCommands() {
|
|
|
20923
20997
|
}
|
|
20924
20998
|
});
|
|
20925
20999
|
process.stdout.write("\r\x1B[K");
|
|
21000
|
+
if (stats.incomplete) console.log(theme.dim(" Partial index: scan limit or unreadable files reached; unscanned entries retained."));
|
|
20926
21001
|
console.log(theme.success(
|
|
20927
21002
|
` \u2713 Indexed ${stats.filesParsed} files (${stats.symbols} symbols) in ${stats.durationMs}ms`
|
|
20928
21003
|
));
|
|
@@ -20951,7 +21026,7 @@ function createMemoryCommands() {
|
|
|
20951
21026
|
}
|
|
20952
21027
|
console.log(theme.dim(` Building semantic index for ${idx.symbolCount} symbols\u2026`));
|
|
20953
21028
|
console.log(theme.dim(" (First run downloads ~117 MB embedding model to ~/.aicli/models/)"));
|
|
20954
|
-
const { rebuildSemanticIndex } = await import("./semantic-
|
|
21029
|
+
const { rebuildSemanticIndex } = await import("./semantic-QOBAXEQ4.js");
|
|
20955
21030
|
try {
|
|
20956
21031
|
const stats = await rebuildSemanticIndex(root, {
|
|
20957
21032
|
onProgress: (done, total) => {
|
|
@@ -22550,7 +22625,7 @@ var webCommandHandlers = {
|
|
|
22550
22625
|
search: handleSearch,
|
|
22551
22626
|
undo: handleUndo
|
|
22552
22627
|
};
|
|
22553
|
-
var SessionHandler = class {
|
|
22628
|
+
var SessionHandler = class _SessionHandler {
|
|
22554
22629
|
ws;
|
|
22555
22630
|
config;
|
|
22556
22631
|
costTracker;
|
|
@@ -22601,6 +22676,7 @@ var SessionHandler = class {
|
|
|
22601
22676
|
abortController = null;
|
|
22602
22677
|
userInterjection = null;
|
|
22603
22678
|
processing = false;
|
|
22679
|
+
disconnected = false;
|
|
22604
22680
|
/** P4: active hub discussion orchestrator (web room), if any. */
|
|
22605
22681
|
hubOrchestrator = null;
|
|
22606
22682
|
/** P4b: pending between-rounds human-steer prompts (requestId → resolver). */
|
|
@@ -22627,9 +22703,13 @@ var SessionHandler = class {
|
|
|
22627
22703
|
* 会话一旦保存到磁盘(有消息后),会从此 Map 中移除。
|
|
22628
22704
|
*/
|
|
22629
22705
|
unsavedSessions = /* @__PURE__ */ new Map();
|
|
22706
|
+
// Failed saves survive a socket reconnect, scoped to the owning config directory.
|
|
22707
|
+
static recovery = /* @__PURE__ */ new Map();
|
|
22708
|
+
static acceptedChats = /* @__PURE__ */ new Map();
|
|
22630
22709
|
constructor(ws, shared) {
|
|
22631
22710
|
this.ws = ws;
|
|
22632
22711
|
this.config = shared.config;
|
|
22712
|
+
this.unsavedSessions = new Map(_SessionHandler.recovery.get(this.config.getConfigDir()));
|
|
22633
22713
|
this.costTracker = shared.costTracker;
|
|
22634
22714
|
this.providers = shared.providers;
|
|
22635
22715
|
this.sessions = shared.sessions;
|
|
@@ -22697,9 +22777,7 @@ var SessionHandler = class {
|
|
|
22697
22777
|
* 接口之间的任何漂移(少一个成员、签名改了)都会当场编译失败,而不是等到运行时。
|
|
22698
22778
|
*/
|
|
22699
22779
|
send(msg) {
|
|
22700
|
-
|
|
22701
|
-
this.ws.send(JSON.stringify(msg));
|
|
22702
|
-
}
|
|
22780
|
+
sendSocketMessage(this.ws, msg);
|
|
22703
22781
|
}
|
|
22704
22782
|
sendStatus() {
|
|
22705
22783
|
const providerList = this.providers.listAvailable().map((p) => ({
|
|
@@ -22753,6 +22831,7 @@ var SessionHandler = class {
|
|
|
22753
22831
|
});
|
|
22754
22832
|
}
|
|
22755
22833
|
async handleMessage(raw) {
|
|
22834
|
+
if (this.disconnected) return;
|
|
22756
22835
|
let msg;
|
|
22757
22836
|
try {
|
|
22758
22837
|
msg = JSON.parse(raw);
|
|
@@ -22761,8 +22840,33 @@ var SessionHandler = class {
|
|
|
22761
22840
|
return;
|
|
22762
22841
|
}
|
|
22763
22842
|
switch (msg.type) {
|
|
22764
|
-
case "chat":
|
|
22843
|
+
case "chat": {
|
|
22844
|
+
if (msg.messageId) {
|
|
22845
|
+
if (typeof msg.messageId !== "string" || msg.messageId.length > 200) throw new Error("Invalid message ID");
|
|
22846
|
+
const scope = this.config.getConfigDir();
|
|
22847
|
+
const accepted = _SessionHandler.acceptedChats.get(scope) ?? /* @__PURE__ */ new Map();
|
|
22848
|
+
const prior = accepted.get(msg.messageId);
|
|
22849
|
+
if (prior) {
|
|
22850
|
+
this.send({ type: "chat_ack", messageId: msg.messageId, accepted: true, duplicate: true, sessionId: prior });
|
|
22851
|
+
return;
|
|
22852
|
+
}
|
|
22853
|
+
if (this.processing || typeof msg.content !== "string") {
|
|
22854
|
+
this.send({ type: "chat_ack", messageId: msg.messageId, accepted: false, error: "A request is already running, or the message is invalid. Your draft was kept." });
|
|
22855
|
+
return;
|
|
22856
|
+
}
|
|
22857
|
+
const imageError = imageValidationError(msg.images);
|
|
22858
|
+
if (imageError) {
|
|
22859
|
+
this.send({ type: "chat_ack", messageId: msg.messageId, accepted: false, error: imageError });
|
|
22860
|
+
return;
|
|
22861
|
+
}
|
|
22862
|
+
this.ensureSession();
|
|
22863
|
+
accepted.set(msg.messageId, this.sessions.current.id);
|
|
22864
|
+
while (accepted.size > 1e3) accepted.delete(accepted.keys().next().value);
|
|
22865
|
+
_SessionHandler.acceptedChats.set(scope, accepted);
|
|
22866
|
+
this.send({ type: "chat_ack", messageId: msg.messageId, accepted: true, sessionId: this.sessions.current.id });
|
|
22867
|
+
}
|
|
22765
22868
|
return this.handleChat(msg.content, msg.images);
|
|
22869
|
+
}
|
|
22766
22870
|
case "command":
|
|
22767
22871
|
return this.handleCommand(msg.name, msg.args);
|
|
22768
22872
|
case "confirm_response":
|
|
@@ -22804,7 +22908,7 @@ var SessionHandler = class {
|
|
|
22804
22908
|
case "hub_start":
|
|
22805
22909
|
return this.handleHubStart(msg);
|
|
22806
22910
|
case "hub_abort":
|
|
22807
|
-
this.
|
|
22911
|
+
this.cancelHub();
|
|
22808
22912
|
return;
|
|
22809
22913
|
case "hub_steer": {
|
|
22810
22914
|
const resolve13 = this.pendingHubReview.get(msg.requestId);
|
|
@@ -22819,7 +22923,10 @@ var SessionHandler = class {
|
|
|
22819
22923
|
return;
|
|
22820
22924
|
}
|
|
22821
22925
|
}
|
|
22822
|
-
onDisconnect() {
|
|
22926
|
+
onDisconnect(closeSocket = true) {
|
|
22927
|
+
if (this.disconnected) return;
|
|
22928
|
+
this.disconnected = true;
|
|
22929
|
+
if (closeSocket && this.ws.readyState === this.ws.OPEN) this.ws.close(1001, "Connection closed; reconnect to restore session");
|
|
22823
22930
|
runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
22824
22931
|
this.toolExecutor.cancelAll();
|
|
22825
22932
|
if (this.abortController) this.abortController.abort();
|
|
@@ -22827,18 +22934,24 @@ var SessionHandler = class {
|
|
|
22827
22934
|
this.pendingAskUser.clear();
|
|
22828
22935
|
for (const resolve13 of this.pendingAutoPause.values()) resolve13({ action: "stop" });
|
|
22829
22936
|
this.pendingAutoPause.clear();
|
|
22830
|
-
this.
|
|
22937
|
+
this.cancelHub();
|
|
22938
|
+
void this.saveIfNeeded();
|
|
22831
22939
|
if (this._bashSessionKey) clearBashSession(this._bashSessionKey);
|
|
22832
22940
|
}
|
|
22941
|
+
cancelHub() {
|
|
22942
|
+
this.hubOrchestrator?.abort();
|
|
22943
|
+
for (const resolve13 of this.pendingHubReview.values()) resolve13({ action: "stop" });
|
|
22944
|
+
this.pendingHubReview.clear();
|
|
22945
|
+
}
|
|
22833
22946
|
/** 根据当前模型 context window 更新工具输出截断上限 */
|
|
22834
22947
|
updateContextWindow() {
|
|
22835
22948
|
try {
|
|
22836
22949
|
const cfgOutputCap = this.config.get("maxToolOutputChars");
|
|
22837
|
-
if (typeof cfgOutputCap === "number" && cfgOutputCap > 0) setMaxOutputCap(cfgOutputCap);
|
|
22838
22950
|
const provider = this.providers.get(this.currentProvider);
|
|
22839
22951
|
const modelInfo = provider?.info.models.find((m) => m.id === this.currentModel);
|
|
22840
|
-
|
|
22952
|
+
this.toolExecutor.maxOutputChars = getMaxOutputChars(modelInfo?.contextWindow, cfgOutputCap ?? 0);
|
|
22841
22953
|
} catch {
|
|
22954
|
+
this.toolExecutor.maxOutputChars = DEFAULT_MAX_TOOL_OUTPUT_CHARS;
|
|
22842
22955
|
}
|
|
22843
22956
|
}
|
|
22844
22957
|
/** 获取当前模型的 context window 大小(0 表示未知)*/
|
|
@@ -22890,12 +23003,24 @@ var SessionHandler = class {
|
|
|
22890
23003
|
return total;
|
|
22891
23004
|
}
|
|
22892
23005
|
/** Save session only if it exists and has messages (never persist empty "Untitled" sessions). */
|
|
22893
|
-
saveIfNeeded() {
|
|
22894
|
-
|
|
22895
|
-
|
|
22896
|
-
|
|
22897
|
-
|
|
22898
|
-
|
|
23006
|
+
async saveIfNeeded() {
|
|
23007
|
+
const session = this.sessions.current;
|
|
23008
|
+
if (!session?.messages.length) return;
|
|
23009
|
+
const scope = this.config.getConfigDir();
|
|
23010
|
+
try {
|
|
23011
|
+
if (session.dirty) autoTrimSessionIfNeeded(session);
|
|
23012
|
+
await this.sessions.save();
|
|
23013
|
+
if (session.dirty) return;
|
|
23014
|
+
this.unsavedSessions.delete(session.id);
|
|
23015
|
+
_SessionHandler.recovery.get(scope)?.delete(session.id);
|
|
23016
|
+
} catch (err) {
|
|
23017
|
+
this.unsavedSessions.set(session.id, session);
|
|
23018
|
+
const recovered = _SessionHandler.recovery.get(scope) ?? /* @__PURE__ */ new Map();
|
|
23019
|
+
recovered.set(session.id, session);
|
|
23020
|
+
_SessionHandler.recovery.set(scope, recovered);
|
|
23021
|
+
const message = `Session ${session.id} was NOT saved: ${err instanceof Error ? err.message : String(err)}. Content is retained in memory; use /session save to retry. Do not stop the server before saving.`;
|
|
23022
|
+
this.send({ type: "info", message });
|
|
23023
|
+
process.stderr.write(message + "\n");
|
|
22899
23024
|
}
|
|
22900
23025
|
}
|
|
22901
23026
|
/** Lazily create a session if none exists yet (deferred from constructor). */
|
|
@@ -23020,20 +23145,15 @@ var SessionHandler = class {
|
|
|
23020
23145
|
const kinds = [...new Set(secretHits.map((h) => h.kind))].join(", ");
|
|
23021
23146
|
this.send({ type: "info", message: "Possible secret in prompt (" + kinds + ")." });
|
|
23022
23147
|
}
|
|
23023
|
-
const
|
|
23024
|
-
|
|
23148
|
+
const imageError = imageValidationError(images);
|
|
23149
|
+
if (imageError) {
|
|
23150
|
+
this.send({ type: "error", message: imageError });
|
|
23151
|
+
return;
|
|
23152
|
+
}
|
|
23025
23153
|
let msgContent;
|
|
23026
23154
|
if (images && images.length > 0) {
|
|
23027
23155
|
const parts = [];
|
|
23028
23156
|
for (const img of images) {
|
|
23029
|
-
if (!ALLOWED_IMAGE_MIMES.has(img.mime)) {
|
|
23030
|
-
this.send({ type: "error", message: `Rejected image: unsupported MIME type "${img.mime}"` });
|
|
23031
|
-
return;
|
|
23032
|
-
}
|
|
23033
|
-
if (img.data && img.data.length > MAX_IMAGE_SIZE) {
|
|
23034
|
-
this.send({ type: "error", message: `Rejected image: too large (${(img.data.length / 1024 / 1024).toFixed(1)} MB, max 10 MB)` });
|
|
23035
|
-
return;
|
|
23036
|
-
}
|
|
23037
23157
|
parts.push({
|
|
23038
23158
|
type: "image_url",
|
|
23039
23159
|
image_url: { url: `data:${img.mime};base64,${img.data}` }
|
|
@@ -23087,7 +23207,7 @@ var SessionHandler = class {
|
|
|
23087
23207
|
this.send({ type: "error", message });
|
|
23088
23208
|
} finally {
|
|
23089
23209
|
this.processing = false;
|
|
23090
|
-
this.saveIfNeeded();
|
|
23210
|
+
await this.saveIfNeeded();
|
|
23091
23211
|
this.sendStatus();
|
|
23092
23212
|
this.sendSessionList();
|
|
23093
23213
|
}
|
|
@@ -23604,6 +23724,10 @@ ${summaryContent}`,
|
|
|
23604
23724
|
}
|
|
23605
23725
|
// ── Commands ─────────────────────────────────────────────────────
|
|
23606
23726
|
async handleCommand(name, args) {
|
|
23727
|
+
if (this.processing && ["session", "clear", "provider", "model", "rewind", "branch", "compact"].includes(name)) {
|
|
23728
|
+
this.send({ type: "info", message: "Stop the current request before changing sessions or conversation settings." });
|
|
23729
|
+
return;
|
|
23730
|
+
}
|
|
23607
23731
|
const cliOnlyReason = CLI_ONLY_WEB_COMMANDS.get(name);
|
|
23608
23732
|
if (cliOnlyReason) {
|
|
23609
23733
|
this.send({
|
|
@@ -24550,7 +24674,7 @@ async function startWebServer(options = {}) {
|
|
|
24550
24674
|
next();
|
|
24551
24675
|
});
|
|
24552
24676
|
const server = createServer(app);
|
|
24553
|
-
const WS_MAX_PAYLOAD =
|
|
24677
|
+
const WS_MAX_PAYLOAD = MAX_CHAT_BYTES;
|
|
24554
24678
|
const WS_MSG_RATE_PER_SEC = 30;
|
|
24555
24679
|
const WS_MSG_BURST = 60;
|
|
24556
24680
|
const WS_PREAUTH_TIMEOUT_MS = 3e4;
|
|
@@ -24623,6 +24747,7 @@ async function startWebServer(options = {}) {
|
|
|
24623
24747
|
app.get("/api/status", (_req, res) => {
|
|
24624
24748
|
res.json({
|
|
24625
24749
|
version: VERSION,
|
|
24750
|
+
chatLimits: { maxChatBytes: MAX_CHAT_BYTES, maxImageBytes: MAX_IMAGE_BYTES },
|
|
24626
24751
|
providers: availableProviders.map((p) => ({
|
|
24627
24752
|
id: p.info.id,
|
|
24628
24753
|
displayName: p.info.displayName,
|
|
@@ -24831,11 +24956,6 @@ async function startWebServer(options = {}) {
|
|
|
24831
24956
|
const tabId = params.get("tabId") || `tab-${Date.now()}`;
|
|
24832
24957
|
const cookies = parseCookie(req.headers.cookie ?? "");
|
|
24833
24958
|
const token = cookies["aicli_token"] || "";
|
|
24834
|
-
const existing = handlers.get(tabId);
|
|
24835
|
-
if (existing) {
|
|
24836
|
-
existing.onDisconnect();
|
|
24837
|
-
handlers.delete(tabId);
|
|
24838
|
-
}
|
|
24839
24959
|
let tokens = WS_MSG_BURST;
|
|
24840
24960
|
let lastRefill = Date.now();
|
|
24841
24961
|
const takeToken = () => {
|
|
@@ -24851,6 +24971,32 @@ async function startWebServer(options = {}) {
|
|
|
24851
24971
|
};
|
|
24852
24972
|
let authenticatedUser = null;
|
|
24853
24973
|
let handler = null;
|
|
24974
|
+
let handlerKey = "";
|
|
24975
|
+
const attachHandler = (resources) => {
|
|
24976
|
+
if (handler) {
|
|
24977
|
+
handler.onDisconnect(false);
|
|
24978
|
+
if (handlers.get(handlerKey) === handler) handlers.delete(handlerKey);
|
|
24979
|
+
}
|
|
24980
|
+
handlerKey = JSON.stringify([authenticatedUser, tabId]);
|
|
24981
|
+
const existing = handlers.get(handlerKey);
|
|
24982
|
+
existing?.onDisconnect();
|
|
24983
|
+
const attached = new SessionHandler(ws, { ...resources, sessions: new SessionManager(resources.config) });
|
|
24984
|
+
handlers.set(handlerKey, attached);
|
|
24985
|
+
return attached;
|
|
24986
|
+
};
|
|
24987
|
+
let alive = true;
|
|
24988
|
+
ws.on("pong", () => {
|
|
24989
|
+
alive = true;
|
|
24990
|
+
});
|
|
24991
|
+
const heartbeat = setInterval(() => {
|
|
24992
|
+
if (!alive) {
|
|
24993
|
+
ws.terminate();
|
|
24994
|
+
return;
|
|
24995
|
+
}
|
|
24996
|
+
alive = false;
|
|
24997
|
+
if (ws.readyState === ws.OPEN) ws.ping();
|
|
24998
|
+
}, 3e4);
|
|
24999
|
+
heartbeat.unref();
|
|
24854
25000
|
let preAuthTimer = setTimeout(() => {
|
|
24855
25001
|
if (!handler && ws.readyState === ws.OPEN) {
|
|
24856
25002
|
try {
|
|
@@ -24871,14 +25017,12 @@ async function startWebServer(options = {}) {
|
|
|
24871
25017
|
const authRequired = authManager.isEnabled();
|
|
24872
25018
|
if (!authRequired) {
|
|
24873
25019
|
console.log(` \u2713 Tab connected: ${tabId.slice(0, 12)} (no auth) (total: ${handlers.size + 1})`);
|
|
24874
|
-
handler =
|
|
24875
|
-
handlers.set(tabId, handler);
|
|
25020
|
+
handler = attachHandler(shared);
|
|
24876
25021
|
clearPreAuthTimer();
|
|
24877
25022
|
} else if (authenticatedUser) {
|
|
24878
25023
|
console.log(` \u2713 Tab connected: ${tabId.slice(0, 12)} (user: ${authenticatedUser}) (total: ${handlers.size + 1})`);
|
|
24879
25024
|
const userShared = getUserShared(authenticatedUser);
|
|
24880
|
-
handler =
|
|
24881
|
-
handlers.set(tabId, handler);
|
|
25025
|
+
handler = attachHandler(userShared);
|
|
24882
25026
|
clearPreAuthTimer();
|
|
24883
25027
|
} else {
|
|
24884
25028
|
console.log(` \u23F3 Tab waiting auth: ${tabId.slice(0, 12)}`);
|
|
@@ -24923,8 +25067,7 @@ async function startWebServer(options = {}) {
|
|
|
24923
25067
|
authManager.login(username, password);
|
|
24924
25068
|
authenticatedUser = username;
|
|
24925
25069
|
const userShared = getUserShared(username);
|
|
24926
|
-
handler =
|
|
24927
|
-
handlers.set(tabId, handler);
|
|
25070
|
+
handler = attachHandler(userShared);
|
|
24928
25071
|
clearPreAuthTimer();
|
|
24929
25072
|
console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
|
|
24930
25073
|
ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
|
|
@@ -24939,8 +25082,7 @@ async function startWebServer(options = {}) {
|
|
|
24939
25082
|
}
|
|
24940
25083
|
authenticatedUser = verifiedUser;
|
|
24941
25084
|
const userShared = getUserShared(verifiedUser);
|
|
24942
|
-
handler =
|
|
24943
|
-
handlers.set(tabId, handler);
|
|
25085
|
+
handler = attachHandler(userShared);
|
|
24944
25086
|
clearPreAuthTimer();
|
|
24945
25087
|
console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
|
|
24946
25088
|
ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
|
|
@@ -24954,8 +25096,7 @@ async function startWebServer(options = {}) {
|
|
|
24954
25096
|
}
|
|
24955
25097
|
authenticatedUser = username;
|
|
24956
25098
|
const userShared = getUserShared(username);
|
|
24957
|
-
handler =
|
|
24958
|
-
handlers.set(tabId, handler);
|
|
25099
|
+
handler = attachHandler(userShared);
|
|
24959
25100
|
clearPreAuthTimer();
|
|
24960
25101
|
console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
|
|
24961
25102
|
ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
|
|
@@ -24983,10 +25124,11 @@ async function startWebServer(options = {}) {
|
|
|
24983
25124
|
}
|
|
24984
25125
|
});
|
|
24985
25126
|
ws.on("close", () => {
|
|
25127
|
+
clearInterval(heartbeat);
|
|
24986
25128
|
clearPreAuthTimer();
|
|
24987
|
-
const wasTracked = handlers.
|
|
25129
|
+
const wasTracked = handlers.get(handlerKey) === handler;
|
|
24988
25130
|
if (handler) handler.onDisconnect();
|
|
24989
|
-
if (wasTracked) handlers.delete(
|
|
25131
|
+
if (wasTracked) handlers.delete(handlerKey);
|
|
24990
25132
|
console.log(` \u2717 Tab disconnected: ${tabId.slice(0, 12)} (total: ${handlers.size})`);
|
|
24991
25133
|
});
|
|
24992
25134
|
ws.on("error", (err) => {
|