chatccc 0.2.270 → 0.2.276
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 +16 -10
- package/config.sample.json +4 -3
- package/deepccc-agent/README.md +147 -61
- package/deepccc-agent/package.json +5 -2
- package/dist/deepccc-agent/src/attachments.js +192 -0
- package/dist/deepccc-agent/src/cli.js +59 -13
- package/dist/deepccc-agent/src/config.js +57 -4
- package/dist/deepccc-agent/src/context.js +299 -16
- package/dist/deepccc-agent/src/file-tools.js +33 -0
- package/dist/deepccc-agent/src/index.js +68 -21
- package/dist/deepccc-agent/src/tool-protocol.js +14 -3
- package/dist/deepccc-agent/src/web-entry.js +72 -0
- package/dist/deepccc-agent/src/web-page.js +414 -0
- package/dist/deepccc-agent/src/web-runtime.js +331 -0
- package/dist/deepccc-agent/src/web-server.js +476 -0
- package/dist/deepccc-agent/src/web-session-store.js +162 -0
- package/dist/deepccc-agent/src/web-tool-presentation.js +123 -0
- package/dist/src/adapters/ccc-adapter.js +5 -1
- package/dist/src/agent-capability-grants.js +26 -0
- package/dist/src/agent-delegate-task.js +5 -2
- package/dist/src/agent-file-rpc.js +6 -1
- package/dist/src/agent-image-rpc.js +6 -1
- package/dist/src/agent-team/application/task-execution-service.js +330 -97
- package/dist/src/agent-team/domain/task-run.js +14 -1
- package/dist/src/agent-team/infrastructure/task-execution-runtime.js +7 -2
- package/dist/src/agent-team/main-agent-bootstrap.js +24 -1
- package/dist/src/agent-team/repositories/json-task-run-repository.js +22 -4
- package/dist/src/agent-team/web/agent-team-page.js +14 -7
- package/dist/src/cards.js +7 -4
- package/dist/src/config.js +12 -0
- package/dist/src/im-skills.js +9 -2
- package/dist/src/orchestrator.js +117 -29
- package/dist/src/safe-maintenance.js +4 -1
- package/dist/src/session-name.js +15 -0
- package/dist/src/session.js +54 -9
- package/dist/src/web-ui.js +76 -32
- package/im-skills/feishu-skill/receive-send-file.md +3 -2
- package/im-skills/feishu-skill/receive-send-image.md +3 -2
- package/im-skills/feishu-skill/send-file.mjs +6 -5
- package/im-skills/feishu-skill/send-image.mjs +6 -5
- package/im-skills/feishu-skill/skill.md +4 -2
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
7
7
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
8
|
-
import { generateText, isLoopFinished, stepCountIs, streamText } from "ai";
|
|
8
|
+
import { generateText, isLoopFinished, stepCountIs, streamText, } from "ai";
|
|
9
9
|
import { existsSync, readFileSync } from "node:fs";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { isAbsolute, join, resolve } from "node:path";
|
|
@@ -77,6 +77,7 @@ const COMPACTION_RECOVERY_HINT_DISABLED = [
|
|
|
77
77
|
].join("\n");
|
|
78
78
|
export const DEFAULT_COMPACTION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
79
79
|
const MAX_COMPACTION_OUTPUT_TOKENS = 16_384;
|
|
80
|
+
const OPENAI_COMPATIBLE_PROVIDER_NAME = "deepccc";
|
|
80
81
|
/** task 子代理工具:子代理单轮对话的最大工具步数(防失控循环,结果收敛后即结束) */
|
|
81
82
|
const TASK_MAX_STEPS = 20;
|
|
82
83
|
/** 子代理最终输出截断:保留前 MAX_TASK_OUTPUT_CHARS 字符,尾部注明截断信息 */
|
|
@@ -143,7 +144,7 @@ function addAnthropicToolJsonCompatibilityNote(messages) {
|
|
|
143
144
|
// 部分 Anthropic→OpenAI/Ark 转换器会用 response_format=json_object
|
|
144
145
|
// 实现工具调用,却只在 messages 中校验 JSON 关键词、不读取顶层 system。
|
|
145
146
|
// 这里仅声明工具参数的编码方式,并明确不要求普通最终回复输出 JSON。
|
|
146
|
-
return messages.map((message, index) => (index === lastUserIndex
|
|
147
|
+
return messages.map((message, index) => (index === lastUserIndex && message.role === "user" && typeof message.content === "string"
|
|
147
148
|
? {
|
|
148
149
|
...message,
|
|
149
150
|
content: `${message.content}\n\n${ANTHROPIC_TOOL_JSON_COMPATIBILITY_NOTE}`,
|
|
@@ -160,11 +161,13 @@ function addAnthropicToolJsonCompatibilityNote(messages) {
|
|
|
160
161
|
function maybeAppendCompactionRecoveryHint(messages, summary, rawLogsEnabled, sessionId) {
|
|
161
162
|
if (!summary.trim())
|
|
162
163
|
return messages;
|
|
163
|
-
const summaryIndex = messages.findIndex((message) => message.role === "user"
|
|
164
|
+
const summaryIndex = messages.findIndex((message) => message.role === "user"
|
|
165
|
+
&& typeof message.content === "string"
|
|
166
|
+
&& message.content.startsWith("以下是更早的对话摘要"));
|
|
164
167
|
if (summaryIndex < 0)
|
|
165
168
|
return messages;
|
|
166
169
|
const hint = rawLogsEnabled ? buildCompactionRecoveryHint(sessionId) : COMPACTION_RECOVERY_HINT_DISABLED;
|
|
167
|
-
return messages.map((message, index) => (index === summaryIndex
|
|
170
|
+
return messages.map((message, index) => (index === summaryIndex && message.role === "user" && typeof message.content === "string"
|
|
168
171
|
? { ...message, content: `${message.content}\n\n${hint}` }
|
|
169
172
|
: message));
|
|
170
173
|
}
|
|
@@ -219,6 +222,14 @@ function normalizeMaxSteps(value) {
|
|
|
219
222
|
}
|
|
220
223
|
return value;
|
|
221
224
|
}
|
|
225
|
+
function normalizeMaxOutputTokens(value) {
|
|
226
|
+
if (value === undefined)
|
|
227
|
+
return undefined;
|
|
228
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
229
|
+
throw new Error("maxOutputTokens must be a positive integer when provided");
|
|
230
|
+
}
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
222
233
|
function normalizeAnthropicBaseURL(baseURL) {
|
|
223
234
|
// 完全按用户填写的地址使用,不自动补 /v1(AI SDK 仅对官方 api.anthropic.com
|
|
224
235
|
// 特判补一次 /v1,其他地址原样拼接 /messages)。
|
|
@@ -239,6 +250,8 @@ export class ChatSession {
|
|
|
239
250
|
compactionTimeoutMs;
|
|
240
251
|
maxSteps;
|
|
241
252
|
effort;
|
|
253
|
+
maxOutputTokens;
|
|
254
|
+
streaming;
|
|
242
255
|
permissionMode;
|
|
243
256
|
permissionResolver;
|
|
244
257
|
permissionGate;
|
|
@@ -267,6 +280,7 @@ export class ChatSession {
|
|
|
267
280
|
baseURL: this.baseURL,
|
|
268
281
|
model: this.subModelId || this.modelId,
|
|
269
282
|
effort: this.effort,
|
|
283
|
+
maxOutputTokens: this.maxOutputTokens,
|
|
270
284
|
}, {
|
|
271
285
|
cwd: taskCwd,
|
|
272
286
|
persist: false,
|
|
@@ -304,6 +318,8 @@ export class ChatSession {
|
|
|
304
318
|
this.subModelId = (overrides.subModel ?? appConfig.subModel ?? "").trim();
|
|
305
319
|
this.provider = normalizeDeepCccProvider(overrides.provider ?? appConfig.provider);
|
|
306
320
|
this.effort = (overrides.effort ?? appConfig.effort ?? "").trim();
|
|
321
|
+
this.maxOutputTokens = normalizeMaxOutputTokens(overrides.maxOutputTokens ?? appConfig.maxOutputTokens);
|
|
322
|
+
this.streaming = overrides.streaming ?? appConfig.streaming;
|
|
307
323
|
this.apiKey = apiKey;
|
|
308
324
|
this.baseURL = baseURL;
|
|
309
325
|
const provider = this.provider === "anthropic"
|
|
@@ -312,7 +328,7 @@ export class ChatSession {
|
|
|
312
328
|
apiKey,
|
|
313
329
|
})
|
|
314
330
|
: createOpenAICompatible({
|
|
315
|
-
name:
|
|
331
|
+
name: OPENAI_COMPATIBLE_PROVIDER_NAME,
|
|
316
332
|
baseURL,
|
|
317
333
|
apiKey,
|
|
318
334
|
includeUsage: true,
|
|
@@ -339,6 +355,7 @@ export class ChatSession {
|
|
|
339
355
|
cwd: this.cwd,
|
|
340
356
|
contextWindow: options.contextWindow ?? appConfig.contextWindow,
|
|
341
357
|
compactAtTokens: options.compactAtTokens,
|
|
358
|
+
maxToolContextTokens: options.maxToolContextTokens,
|
|
342
359
|
keepRecentMessages: options.keepRecentMessages,
|
|
343
360
|
});
|
|
344
361
|
this.permissionGate = new PermissionGate(this.permissionMode, this.permissionResolver);
|
|
@@ -379,6 +396,8 @@ export class ChatSession {
|
|
|
379
396
|
// assistant 消息 toolCalls 字段;[Tool transcript] 文本视图仍按原格式生成。
|
|
380
397
|
const toolCallsById = new Map();
|
|
381
398
|
const toolCallOrder = [];
|
|
399
|
+
const timeline = [];
|
|
400
|
+
let toolContext = [];
|
|
382
401
|
try {
|
|
383
402
|
if (this.context.planCompaction()) {
|
|
384
403
|
yield { type: "status", phase: "compacting" };
|
|
@@ -415,14 +434,14 @@ export class ChatSession {
|
|
|
415
434
|
? addAnthropicToolJsonCompatibilityNote(hintedMessages)
|
|
416
435
|
: hintedMessages;
|
|
417
436
|
// effort 按协议映射:
|
|
418
|
-
// - OpenAI 兼容:providerOptions.
|
|
419
|
-
//
|
|
437
|
+
// - OpenAI 兼容:providerOptions 的 key 必须与 createOpenAICompatible.name 一致;
|
|
438
|
+
// reasoningEffort 由 SDK 映射为请求体 reasoning_effort 字段;
|
|
420
439
|
// - Anthropic:providerOptions.anthropic.effort 由 @ai-sdk/anthropic 组装为请求体
|
|
421
440
|
// output_config.effort(官方 Effort API,见 platform.claude.com/docs/en/build-with-claude/effort)
|
|
422
441
|
let effortProviderOptions;
|
|
423
442
|
if (this.effort) {
|
|
424
443
|
effortProviderOptions = this.provider === "openai"
|
|
425
|
-
? {
|
|
444
|
+
? { [OPENAI_COMPATIBLE_PROVIDER_NAME]: { reasoningEffort: this.effort } }
|
|
426
445
|
: { anthropic: { effort: this.effort } };
|
|
427
446
|
}
|
|
428
447
|
const baseGenerationOptions = {
|
|
@@ -438,14 +457,18 @@ export class ChatSession {
|
|
|
438
457
|
}),
|
|
439
458
|
stopWhen: maxSteps !== undefined ? stepCountIs(maxSteps) : isLoopFinished(),
|
|
440
459
|
abortSignal: signal,
|
|
460
|
+
...(this.maxOutputTokens !== undefined
|
|
461
|
+
? { maxOutputTokens: this.maxOutputTokens }
|
|
462
|
+
: {}),
|
|
441
463
|
...(effortProviderOptions ? { providerOptions: effortProviderOptions } : {}),
|
|
442
464
|
};
|
|
443
465
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
444
466
|
fullText = "";
|
|
445
467
|
safeAccumulated = "";
|
|
446
|
-
|
|
468
|
+
toolContext = [];
|
|
447
469
|
toolCallsById.clear();
|
|
448
470
|
toolCallOrder.length = 0;
|
|
471
|
+
timeline.length = 0;
|
|
449
472
|
let lastReasoningProgressAt;
|
|
450
473
|
const attemptMessages = attempt === 0
|
|
451
474
|
? modelMessages
|
|
@@ -455,7 +478,7 @@ export class ChatSession {
|
|
|
455
478
|
messages: attemptMessages,
|
|
456
479
|
};
|
|
457
480
|
let stream;
|
|
458
|
-
if (
|
|
481
|
+
if (this.streaming) {
|
|
459
482
|
const result = streamText(generationOptions);
|
|
460
483
|
stream = result.fullStream ?? textStreamToFullStream(result.textStream);
|
|
461
484
|
}
|
|
@@ -476,6 +499,11 @@ export class ChatSession {
|
|
|
476
499
|
}
|
|
477
500
|
else if (part.type === "text-delta") {
|
|
478
501
|
fullText += part.text;
|
|
502
|
+
const previous = timeline[timeline.length - 1];
|
|
503
|
+
if (previous?.type === "text")
|
|
504
|
+
previous.text += part.text;
|
|
505
|
+
else
|
|
506
|
+
timeline.push({ type: "text", text: part.text });
|
|
479
507
|
// 隐私替换只在展示层:safeAccumulated 供事件消费者(终端/JSONL)使用,
|
|
480
508
|
// fullText 原文用于持久化上下文,避免替换结果回流污染上下文。
|
|
481
509
|
const safeText = applyPrivacy(part.text);
|
|
@@ -483,9 +511,11 @@ export class ChatSession {
|
|
|
483
511
|
yield { type: "text", text: safeText, accumulated: safeAccumulated };
|
|
484
512
|
}
|
|
485
513
|
else if (part.type === "tool-call") {
|
|
486
|
-
|
|
487
|
-
|
|
514
|
+
const input = safeJson(part.input);
|
|
515
|
+
toolContext.push(`tool_call ${part.toolName}: ${input}`);
|
|
516
|
+
toolCallsById.set(part.toolCallId, { id: part.toolCallId, name: part.toolName, input });
|
|
488
517
|
toolCallOrder.push(part.toolCallId);
|
|
518
|
+
timeline.push({ type: "tool_use", id: part.toolCallId, name: part.toolName, input });
|
|
489
519
|
yield {
|
|
490
520
|
type: "tool_use",
|
|
491
521
|
id: part.toolCallId,
|
|
@@ -494,10 +524,12 @@ export class ChatSession {
|
|
|
494
524
|
};
|
|
495
525
|
}
|
|
496
526
|
else if (part.type === "tool-result") {
|
|
497
|
-
|
|
527
|
+
const output = truncateToolContext(safeJson(part.output));
|
|
528
|
+
toolContext.push(`tool_result ${part.toolName}: ${output}`);
|
|
498
529
|
const call = toolCallsById.get(part.toolCallId);
|
|
499
530
|
if (call)
|
|
500
|
-
call.output =
|
|
531
|
+
call.output = output;
|
|
532
|
+
timeline.push({ type: "tool_result", tool_use_id: part.toolCallId, name: part.toolName, output });
|
|
501
533
|
yield {
|
|
502
534
|
type: "tool_result",
|
|
503
535
|
tool_use_id: part.toolCallId,
|
|
@@ -514,6 +546,7 @@ export class ChatSession {
|
|
|
514
546
|
call.output = message;
|
|
515
547
|
call.is_error = true;
|
|
516
548
|
}
|
|
549
|
+
timeline.push({ type: "tool_result", tool_use_id: part.toolCallId, name: part.toolName, output: message, is_error: true });
|
|
517
550
|
yield {
|
|
518
551
|
type: "tool_result",
|
|
519
552
|
tool_use_id: part.toolCallId,
|
|
@@ -529,7 +562,7 @@ export class ChatSession {
|
|
|
529
562
|
}
|
|
530
563
|
}
|
|
531
564
|
if (hasMalformedToolProtocolText(fullText)) {
|
|
532
|
-
console.warn(`[DeepCCC] malformed
|
|
565
|
+
console.warn(`[DeepCCC] malformed tool protocol text detected for ${this.context.sessionId} `
|
|
533
566
|
+ `(attempt ${attempt + 1}/2, structuredToolCalls=${toolCallOrder.length})`);
|
|
534
567
|
rawLog?.writeLine(safeRawStreamJson({
|
|
535
568
|
type: "deepccc_tool_protocol_recovery",
|
|
@@ -542,8 +575,8 @@ export class ChatSession {
|
|
|
542
575
|
continue;
|
|
543
576
|
}
|
|
544
577
|
throw new Error(toolCallOrder.length > 0
|
|
545
|
-
? "
|
|
546
|
-
: "
|
|
578
|
+
? "工具调用协议异常:检测到混合的结构化调用与伪造工具文本,为避免重复执行工具,本轮已安全终止"
|
|
579
|
+
: "工具调用协议异常:模型重试后仍输出了无效或伪造的工具调用文本");
|
|
547
580
|
}
|
|
548
581
|
completed = true;
|
|
549
582
|
const collectedToolCalls = toolCallOrder
|
|
@@ -553,6 +586,7 @@ export class ChatSession {
|
|
|
553
586
|
fullText,
|
|
554
587
|
transcriptLines: toolContext,
|
|
555
588
|
toolCalls: collectedToolCalls,
|
|
589
|
+
timeline,
|
|
556
590
|
}));
|
|
557
591
|
yield { type: "done", text: safeAccumulated };
|
|
558
592
|
return;
|
|
@@ -564,9 +598,22 @@ export class ChatSession {
|
|
|
564
598
|
if (malformedProtocolOutput)
|
|
565
599
|
yield { type: "text_reset" };
|
|
566
600
|
if (err.name === "AbortError" || signal?.aborted) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
601
|
+
if ((fullText || toolCallOrder.length > 0) && !malformedProtocolOutput) {
|
|
602
|
+
const collectedToolCalls = toolCallOrder
|
|
603
|
+
.map((id) => toolCallsById.get(id))
|
|
604
|
+
.filter((call) => call !== undefined);
|
|
605
|
+
const interruptedTimeline = timeline.map((entry) => ({ ...entry }));
|
|
606
|
+
const previous = interruptedTimeline[interruptedTimeline.length - 1];
|
|
607
|
+
if (previous?.type === "text")
|
|
608
|
+
previous.text += "\n[interrupted]";
|
|
609
|
+
else
|
|
610
|
+
interruptedTimeline.push({ type: "text", text: "[interrupted]" });
|
|
611
|
+
this.context.appendMessage(buildPersistedAssistantMessage({
|
|
612
|
+
fullText: `${fullText}\n[interrupted]`,
|
|
613
|
+
transcriptLines: toolContext,
|
|
614
|
+
toolCalls: collectedToolCalls,
|
|
615
|
+
timeline: interruptedTimeline,
|
|
616
|
+
}));
|
|
570
617
|
}
|
|
571
618
|
yield { type: "done", text: safeAccumulated };
|
|
572
619
|
return;
|
|
@@ -630,7 +677,7 @@ export class ChatSession {
|
|
|
630
677
|
// output_config.effort=low),避免继承主对话的高 effort 拖慢"压缩上下文中"阶段。
|
|
631
678
|
maxOutputTokens: MAX_COMPACTION_OUTPUT_TOKENS,
|
|
632
679
|
providerOptions: this.provider === "openai"
|
|
633
|
-
? {
|
|
680
|
+
? { [OPENAI_COMPATIBLE_PROVIDER_NAME]: { reasoningEffort: "none" } }
|
|
634
681
|
: { anthropic: { effort: "low" } },
|
|
635
682
|
});
|
|
636
683
|
if (!result.text.trim()) {
|
|
@@ -6,12 +6,23 @@
|
|
|
6
6
|
const MALFORMED_DSML_TRAILER = /<\/[||]{2}DSML[||]{2}(?:parameter|invoke)>\s*$/iu;
|
|
7
7
|
const RENDERED_TOOL_CALL = /\[调用\s+[A-Za-z_][\w.-]*\]/u;
|
|
8
8
|
const DSML_INVOKE_TAG = /<[||]{2}DSML[||]{2}invoke\b/iu;
|
|
9
|
+
const TOOL_TRANSCRIPT_HEADER = /(?:^|\n)\s*\[工具记录\]\s*(?:\n|$)/u;
|
|
10
|
+
const TOOL_TRANSCRIPT_EVENT = /(?:^|\n)\s*tool_(?:call|result|error)\s+[A-Za-z_][\w.-]*\s*:/u;
|
|
11
|
+
/**
|
|
12
|
+
* Detects a model copying DeepCCC's persisted/debug transcript syntax into
|
|
13
|
+
* normal assistant text. Both the dedicated header and an event line are
|
|
14
|
+
* required so documentation and ordinary discussion remain valid.
|
|
15
|
+
*/
|
|
16
|
+
export function hasImitatedToolTranscriptText(text) {
|
|
17
|
+
return TOOL_TRANSCRIPT_HEADER.test(text) && TOOL_TRANSCRIPT_EVENT.test(text);
|
|
18
|
+
}
|
|
9
19
|
export function hasMalformedToolProtocolText(text) {
|
|
10
|
-
|
|
20
|
+
const malformedDsml = MALFORMED_DSML_TRAILER.test(text)
|
|
11
21
|
&& (RENDERED_TOOL_CALL.test(text) || DSML_INVOKE_TAG.test(text));
|
|
22
|
+
return malformedDsml || hasImitatedToolTranscriptText(text);
|
|
12
23
|
}
|
|
13
24
|
export const TOOL_PROTOCOL_RECOVERY_PROMPT = [
|
|
14
25
|
"[系统恢复提示] 上一次响应泄漏了内部工具调用协议,因此已被丢弃。",
|
|
15
|
-
"请重新完成当前用户请求。需要调用工具时,只能使用 API 提供的结构化工具调用;不要把 DSML 或工具参数作为普通文本输出。",
|
|
16
|
-
"
|
|
26
|
+
"请重新完成当前用户请求。需要调用工具时,只能使用 API 提供的结构化工具调用;不要把 DSML、[工具调用]、[工具记录] 或工具参数作为普通文本输出。",
|
|
27
|
+
"不要提及本恢复提示,不要伪造工具已执行,也不要根据未执行的命令声称得到了结果。",
|
|
17
28
|
].join("\n");
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { startDeepCccWebServer } from "./web-server.js";
|
|
4
|
+
const WEB_FLAGS = new Set(["web", "--reuse-existing", "--port", "--no-open", "--help", "-h"]);
|
|
5
|
+
export function isLegacyCliInvocation(args) {
|
|
6
|
+
if (!args.length)
|
|
7
|
+
return false;
|
|
8
|
+
if (args[0] === "web")
|
|
9
|
+
return false;
|
|
10
|
+
return !WEB_FLAGS.has(args[0]);
|
|
11
|
+
}
|
|
12
|
+
export function parseWebEntryArgs(rawArgs) {
|
|
13
|
+
const args = rawArgs[0] === "web" ? rawArgs.slice(1) : [...rawArgs];
|
|
14
|
+
if (args.includes("--help") || args.includes("-h"))
|
|
15
|
+
return {};
|
|
16
|
+
let port;
|
|
17
|
+
let openBrowser;
|
|
18
|
+
let reuseExisting = false;
|
|
19
|
+
for (let index = 0; index < args.length; index++) {
|
|
20
|
+
const arg = args[index];
|
|
21
|
+
if (arg === "--reuse-existing")
|
|
22
|
+
reuseExisting = true;
|
|
23
|
+
else if (arg === "--no-open")
|
|
24
|
+
openBrowser = false;
|
|
25
|
+
else if (arg === "--port") {
|
|
26
|
+
const value = Number(args[++index]);
|
|
27
|
+
if (!Number.isInteger(value) || value < 1 || value > 65_535)
|
|
28
|
+
throw new Error("--port must be an integer between 1 and 65535");
|
|
29
|
+
port = value;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw new Error(`Unknown DeepCCC Web option: ${arg}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
reuseExisting,
|
|
37
|
+
...(port ? { port } : {}),
|
|
38
|
+
...(openBrowser === false ? { openBrowser: false } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function printWebHelp() {
|
|
42
|
+
console.log([
|
|
43
|
+
"DeepCCC Web UI",
|
|
44
|
+
"",
|
|
45
|
+
"Usage: deepccc [web] [options]",
|
|
46
|
+
"",
|
|
47
|
+
" --reuse-existing Reuse an existing verified DeepCCC Web instance",
|
|
48
|
+
" --port <port> Override config.web.port for this run",
|
|
49
|
+
" --no-open Do not open the browser automatically",
|
|
50
|
+
" --help Show this help",
|
|
51
|
+
"",
|
|
52
|
+
"Terminal CLI: deepccc-cli [options]",
|
|
53
|
+
].join("\n"));
|
|
54
|
+
}
|
|
55
|
+
export async function runWebEntry(rawArgs = process.argv.slice(2)) {
|
|
56
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
57
|
+
printWebHelp();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const options = parseWebEntryArgs(rawArgs);
|
|
61
|
+
const handle = await startDeepCccWebServer(options);
|
|
62
|
+
console.log(`DeepCCC Web UI: ${handle.url}${handle.reused ? " (reused existing server)" : ""}`);
|
|
63
|
+
}
|
|
64
|
+
function isDirectInvocation() {
|
|
65
|
+
return resolve(fileURLToPath(import.meta.url)) === resolve(process.argv[1] ?? "");
|
|
66
|
+
}
|
|
67
|
+
if (isDirectInvocation()) {
|
|
68
|
+
runWebEntry().catch((err) => {
|
|
69
|
+
console.error(`DeepCCC Web startup failed: ${err.message}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
|
72
|
+
}
|