chatccc 0.2.235 → 0.2.237
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/README.md +1 -1
- package/package.json +1 -2
- package/src/__tests__/builtin-session-search.test.ts +34 -0
- package/src/__tests__/claude-raw-stream-log.test.ts +96 -96
- package/src/__tests__/claude-sdk-installer.test.ts +285 -285
- package/src/__tests__/restart.test.ts +55 -0
- package/src/__tests__/web-ui.test.ts +436 -411
- package/src/adapters/claude-adapter.ts +673 -673
- package/src/builtin/session-search.ts +7 -0
- package/src/claude-sdk-installer.ts +324 -324
- package/src/orchestrator.ts +41 -15
- package/src/web-ui.ts +51 -5
package/src/orchestrator.ts
CHANGED
|
@@ -818,38 +818,64 @@ export interface RestartSpawnDeps {
|
|
|
818
818
|
trace?: typeof appendStartupTrace;
|
|
819
819
|
/** restart 子进程 stderr 的落盘目录;默认 ~/.chatccc/logs */
|
|
820
820
|
restartLogDir?: string;
|
|
821
|
+
/** 终端检测(默认按 process.stdout/stderr.isTTY);测试注入 */
|
|
822
|
+
isTty?: () => boolean;
|
|
821
823
|
}
|
|
822
824
|
|
|
823
825
|
/**
|
|
824
826
|
* spawn 自重启子进程。
|
|
825
827
|
*
|
|
826
|
-
* stderr
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
828
|
+
* stdout/stderr 按启动方式分流:
|
|
829
|
+
* - **终端(TTY)场景**(用户从 cmd/PowerShell/node.exe 窗口启动):stdio 用
|
|
830
|
+
* ["ignore", "inherit", "inherit"] 直接继承终端句柄,restart 后窗口日志不中断。
|
|
831
|
+
* 终端句柄的生命周期不随父进程退出而关闭,因此不存在 EPIPE 风险。
|
|
832
|
+
* - **非终端场景**(守护进程/黑匣子等管道或文件启动):stderr 重定向到磁盘日志
|
|
833
|
+
* 文件(restart-*.log),子进程继承文件句柄,父进程退出不影响写入。
|
|
834
|
+
* 不能用 pipe 收集:pipe 读端随父进程退出关闭后,子进程(尤其经 tsx 包装)
|
|
835
|
+
* 再写 stderr(如飞书 SDK 内部 console.warn)会 EPIPE → uncaughtException
|
|
836
|
+
* → 整个服务崩溃。若日志文件打开失败,退回 pipe 收集(旧行为),并记录 trace。
|
|
831
837
|
*/
|
|
832
838
|
export function spawnRestartChild(deps: RestartSpawnDeps = {}): ChildProcess {
|
|
833
839
|
const projectRoot = deps.projectRoot ?? PROJECT_ROOT;
|
|
834
840
|
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
835
841
|
const trace = deps.trace ?? appendStartupTrace;
|
|
836
842
|
const restartLogDir = deps.restartLogDir ?? LOG_DIR;
|
|
843
|
+
const isTty = deps.isTty ?? (() => process.stdout.isTTY === true || process.stderr.isTTY === true);
|
|
837
844
|
const { command, args } = buildRestartSpawnSpec(projectRoot);
|
|
838
845
|
|
|
839
846
|
let stderrFd: number | undefined;
|
|
840
847
|
const stdio: StdioOptions = ["ignore", "ignore", "pipe"];
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
848
|
+
const tty = isTty();
|
|
849
|
+
if (tty) {
|
|
850
|
+
// 终端场景:全部 inherit(含 stdin)。注意 stdin 不能是 "ignore":
|
|
851
|
+
// Windows 上 detached + stdio[0]=ignore 的组合(DETACHED_PROCESS)会让
|
|
852
|
+
// 子进程丢失控制台关联,或在部分 Node/libuv 组合下触发 CREATE_NEW_CONSOLE
|
|
853
|
+
// 弹出新窗口——日志全跑到新窗口,用户当前窗口反而看不到。
|
|
854
|
+
// 全 inherit 让子进程直接复用当前终端句柄,父进程退出后窗口日志不中断,
|
|
855
|
+
// 且终端句柄不随父进程退出关闭,天然无 EPIPE 风险。
|
|
856
|
+
stdio[0] = "inherit";
|
|
857
|
+
stdio[1] = "inherit";
|
|
858
|
+
stdio[2] = "inherit";
|
|
859
|
+
} else {
|
|
860
|
+
try {
|
|
861
|
+
mkdirSync(restartLogDir, { recursive: true });
|
|
862
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
863
|
+
const restartLogPath = join(restartLogDir, `restart-${timestamp}.log`);
|
|
864
|
+
stderrFd = openSync(restartLogPath, "a");
|
|
865
|
+
stdio[2] = stderrFd;
|
|
866
|
+
} catch (err) {
|
|
867
|
+
trace("restart: stderr log open failed, falling back to pipe", {
|
|
868
|
+
error: err instanceof Error ? err.message : String(err),
|
|
869
|
+
});
|
|
870
|
+
}
|
|
851
871
|
}
|
|
852
872
|
|
|
873
|
+
// 运行时自检:记录本次 restart 子进程的启动方式,便于确认日志走向。
|
|
874
|
+
trace("restart: spawn child", {
|
|
875
|
+
isTty: tty,
|
|
876
|
+
stdio: JSON.stringify(stdio),
|
|
877
|
+
});
|
|
878
|
+
|
|
853
879
|
const child = spawnImpl(command, args, {
|
|
854
880
|
cwd: projectRoot,
|
|
855
881
|
detached: true,
|
package/src/web-ui.ts
CHANGED
|
@@ -637,11 +637,16 @@ async function handleForgetIlink(_req: IncomingMessage, res: ServerResponse): Pr
|
|
|
637
637
|
}
|
|
638
638
|
|
|
639
639
|
async function handleClaudeSdkStatus(_req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
640
|
+
const progress = getLastInstallProgress();
|
|
640
641
|
jsonReply(res, 200, {
|
|
641
642
|
installed: isClaudeSdkInstalled(),
|
|
642
643
|
version: getClaudeSdkInstalledVersion(),
|
|
643
644
|
running: isInstallRunning(),
|
|
644
|
-
|
|
645
|
+
// 展开到顶层:前端轮询 JS 读顶层 phase/message/percent/error(历史契约),
|
|
646
|
+
// 若只放在 progress 嵌套里,前端 s.phase 恒为 undefined → 进度条永不渲染。
|
|
647
|
+
...progress,
|
|
648
|
+
// 保留嵌套结构,兼容其它调用方。
|
|
649
|
+
progress,
|
|
645
650
|
});
|
|
646
651
|
}
|
|
647
652
|
|
|
@@ -935,7 +940,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
935
940
|
<!-- Claude 卡片 -->
|
|
936
941
|
<div class="agent-card" id="agent-card-claude">
|
|
937
942
|
<div class="agent-card-header">
|
|
938
|
-
<input type="checkbox" class="agent-toggle" id="agent-enable-claude" onchange="
|
|
943
|
+
<input type="checkbox" class="agent-toggle" id="agent-enable-claude" onchange="onClaudeToggle(this)">
|
|
939
944
|
<div class="meta">
|
|
940
945
|
<div class="name">Claude Code</div>
|
|
941
946
|
<div class="desc">Anthropic Claude Code CLI<br>模型、effort 均为选填</div>
|
|
@@ -975,7 +980,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
|
|
|
975
980
|
</div>
|
|
976
981
|
<div id="claude-engine-progress-text" style="font-size:12px;color:#64748b;margin-top:4px"></div>
|
|
977
982
|
</div>
|
|
978
|
-
<button class="btn btn-outline" id="claude-engine-install-btn" onclick="installClaudeEngine()"
|
|
983
|
+
<button class="btn btn-outline" id="claude-engine-install-btn" onclick="installClaudeEngine()">安装 Claude Code SDK</button>
|
|
979
984
|
<div class="hint" style="margin-top:6px">ChatCCC 通过 Claude Agent SDK 调用 Claude Code;SDK 引擎按需下载到本机(仅启用 Claude Code 时需要),安装期间请保持网络畅通。</div>
|
|
980
985
|
</div>
|
|
981
986
|
</fieldset>
|
|
@@ -1596,6 +1601,10 @@ function renderStep2() {
|
|
|
1596
1601
|
var cursorOn = isAgentEnabled(c.cursor, CURSOR_FALLBACK_KEYS);
|
|
1597
1602
|
var codexOn = isAgentEnabled(c.codex, CODEX_FALLBACK_KEYS);
|
|
1598
1603
|
var cccOn = isAgentEnabled(c.ccc, CCC_FALLBACK_KEYS);
|
|
1604
|
+
// 全新用户:四个 Agent 均无启用/配置痕迹时,只默认勾选 DeepCCC(ccc),其余不勾
|
|
1605
|
+
if (!claudeOn && !cursorOn && !codexOn && !cccOn) {
|
|
1606
|
+
cccOn = true;
|
|
1607
|
+
}
|
|
1599
1608
|
state.defaultAgent = resolveDefaultAgentFromConfig(c, claudeOn, cursorOn, codexOn, cccOn);
|
|
1600
1609
|
document.getElementById('agent-enable-claude').checked = claudeOn;
|
|
1601
1610
|
document.getElementById('agent-enable-cursor').checked = cursorOn;
|
|
@@ -2228,6 +2237,38 @@ function validateCli(tool) {
|
|
|
2228
2237
|
|
|
2229
2238
|
// ---- Claude Code 引擎(Agent SDK)按需安装 ----
|
|
2230
2239
|
var claudeEnginePollTimer = null;
|
|
2240
|
+
var claudeEngineState = null;
|
|
2241
|
+
|
|
2242
|
+
// Claude Code 开关:从关闭 → 打开时弹窗确认(SDK 必装才能使用)。
|
|
2243
|
+
// 页面初始化(按已有 config 设置开关)直接调 onAgentToggle,不经由此函数,不会误弹窗。
|
|
2244
|
+
function onClaudeToggle(el) {
|
|
2245
|
+
var enabled = el.checked;
|
|
2246
|
+
if (!enabled) {
|
|
2247
|
+
onAgentToggle('claude', false);
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
// 实时查询后端安装状态(不依赖可能过期的 claudeEngineState 缓存),
|
|
2251
|
+
// 已安装/正在安装时直接打开开关,不重复安装。
|
|
2252
|
+
api('/api/claude-sdk/status', 'GET').then(function(s){
|
|
2253
|
+
if (!s || !s.phase) { el.checked = false; return; }
|
|
2254
|
+
claudeEngineState = s;
|
|
2255
|
+
var sdkReady = s.installed === true || s.phase === 'done' ||
|
|
2256
|
+
s.phase === 'downloading' || s.phase === 'installing' || s.phase === 'detecting';
|
|
2257
|
+
if (sdkReady) {
|
|
2258
|
+
// 已安装 / 正在安装 / 正在检测:直接打开开关,不再触发安装
|
|
2259
|
+
onAgentToggle('claude', true);
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
if (!confirm('Claude Code 必须先安装 Claude Code SDK 才能使用(必装)。是否立即安装?')) {
|
|
2263
|
+
el.checked = false; // 用户取消 → 回滚开关为关闭
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
onAgentToggle('claude', true);
|
|
2267
|
+
installClaudeEngine();
|
|
2268
|
+
}).catch(function(){
|
|
2269
|
+
el.checked = false; // 状态查询失败 → 回滚开关,避免误判
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2231
2272
|
|
|
2232
2273
|
function claudeEngineEl(id) { return document.getElementById(id); }
|
|
2233
2274
|
|
|
@@ -2240,6 +2281,10 @@ function claudeEngineRenderStatus(s) {
|
|
|
2240
2281
|
if (phase === 'done') color = '#16a34a';
|
|
2241
2282
|
else if (phase === 'error') color = '#ef4444';
|
|
2242
2283
|
else if (phase === 'downloading' || phase === 'installing') color = '#3b82f6';
|
|
2284
|
+
if (!text && phase === 'idle') {
|
|
2285
|
+
// 初始/空闲状态给出明确文案,而不是“未知状态”
|
|
2286
|
+
text = s.installed ? ('已安装 v' + (s.version || '未知版本')) : '未安装';
|
|
2287
|
+
}
|
|
2243
2288
|
el.innerHTML = '<span style="color:' + color + '">' + (text ? text : '未知状态') + '</span>';
|
|
2244
2289
|
if (s.error) el.innerHTML += '<br><span style="color:#ef4444;font-size:12px">' + s.error + '</span>';
|
|
2245
2290
|
}
|
|
@@ -2259,18 +2304,19 @@ function claudeEngineRenderProgress(p) {
|
|
|
2259
2304
|
function claudeEngineRefreshStatus() {
|
|
2260
2305
|
api('/api/claude-sdk/status', 'GET').then(function(s){
|
|
2261
2306
|
if (!s || !s.phase) return;
|
|
2307
|
+
claudeEngineState = s;
|
|
2262
2308
|
claudeEngineRenderStatus(s);
|
|
2263
2309
|
claudeEngineRenderProgress(s);
|
|
2264
2310
|
var btn = claudeEngineEl('claude-engine-install-btn');
|
|
2265
2311
|
if (btn) {
|
|
2266
2312
|
if (s.phase === 'done' || s.installed) {
|
|
2267
|
-
btn.textContent = '
|
|
2313
|
+
btn.textContent = '重新安装 Claude Code SDK';
|
|
2268
2314
|
btn.disabled = false;
|
|
2269
2315
|
} else if (s.phase === 'downloading' || s.phase === 'installing' || s.phase === 'detecting') {
|
|
2270
2316
|
btn.textContent = '安装中…';
|
|
2271
2317
|
btn.disabled = true;
|
|
2272
2318
|
} else {
|
|
2273
|
-
btn.textContent = '
|
|
2319
|
+
btn.textContent = '安装 Claude Code SDK';
|
|
2274
2320
|
btn.disabled = false;
|
|
2275
2321
|
}
|
|
2276
2322
|
}
|