lark-remote 0.4.2 → 0.4.4
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/CHANGELOG.md +33 -0
- package/dist/bridge/approval-coordinator.d.ts +2 -0
- package/dist/bridge/approval-coordinator.js +16 -7
- package/dist/bridge/index.d.ts +22 -0
- package/dist/bridge/index.js +138 -56
- package/dist/card/card-budget.js +39 -26
- package/dist/card/run-renderer.js +9 -2
- package/dist/card/run-state.js +81 -31
- package/dist/card/text-truncate.d.ts +9 -10
- package/dist/card/text-truncate.js +48 -43
- package/dist/card/tool-render.js +6 -2
- package/dist/cli.js +17 -0
- package/dist/config/codex-config.d.ts +3 -0
- package/dist/config/codex-config.js +4 -1
- package/dist/config/dir.js +6 -6
- package/dist/config/index.d.ts +7 -5
- package/dist/config/index.js +52 -13
- package/dist/config/kimi-config.d.ts +4 -1
- package/dist/config/kimi-config.js +5 -2
- package/dist/config/pi-config.d.ts +2 -7
- package/dist/config/pi-config.js +6 -6
- package/dist/config/wizard.js +8 -4
- package/dist/connector/index.d.ts +19 -0
- package/dist/connector/index.js +134 -23
- package/dist/inbound/placeholder.d.ts +3 -2
- package/dist/inbound/placeholder.js +17 -7
- package/dist/index.js +28 -6
- package/dist/instance-lock.d.ts +31 -15
- package/dist/instance-lock.js +64 -62
- package/dist/platform/agent-stopper.d.ts +29 -7
- package/dist/platform/agent-stopper.js +40 -8
- package/dist/platform/identity.d.ts +59 -3
- package/dist/platform/identity.js +105 -30
- package/dist/platform/path.d.ts +0 -7
- package/dist/platform/path.js +1 -51
- package/dist/platform/shell.d.ts +14 -0
- package/dist/platform/shell.js +35 -2
- package/dist/platform/sleep-blocker.d.ts +6 -0
- package/dist/platform/sleep-blocker.js +31 -5
- package/dist/platform/spawn.d.ts +1 -1
- package/dist/platform/spawn.js +1 -1
- package/dist/platform/terminator-posix.d.ts +1 -29
- package/dist/platform/terminator-posix.js +32 -30
- package/dist/platform/terminator-win32.js +4 -2
- package/dist/platform/terminator.d.ts +2 -2
- package/dist/platform/terminator.js +17 -2
- package/dist/platform/types.d.ts +3 -0
- package/dist/router/card-action-feedback.d.ts +17 -0
- package/dist/router/card-action-feedback.js +31 -0
- package/dist/router/config/claude.js +8 -5
- package/dist/router/index.d.ts +2 -0
- package/dist/router/index.js +35 -15
- package/dist/runner/claude/runner.js +1 -1
- package/dist/runner/claude/session.d.ts +2 -6
- package/dist/runner/claude/session.js +7 -8
- package/dist/runner/codex/app-server/protocol-types.d.ts +4 -0
- package/dist/runner/codex/app-server/protocol-types.js +4 -0
- package/dist/runner/codex/app-server/runner.js +3 -0
- package/dist/runner/common/acp/base-acp-runner.js +7 -0
- package/dist/runner/common/acp/base-translator.d.ts +43 -0
- package/dist/runner/common/acp/base-translator.js +83 -18
- package/dist/runner/common/connection-based-runner.d.ts +18 -0
- package/dist/runner/common/connection-based-runner.js +31 -0
- package/dist/runner/common/jsonrpc/client.js +6 -1
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +20 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +27 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +29 -1
- package/dist/runner/common/jsonrpc/transport.js +79 -10
- package/dist/runner/common/spawning-runner.d.ts +49 -19
- package/dist/runner/common/spawning-runner.js +94 -54
- package/dist/runner/dsh/client.d.ts +1 -3
- package/dist/runner/dsh/client.js +0 -4
- package/dist/runner/dsh/types.d.ts +0 -36
- package/dist/runner/kimi/acp/runner.d.ts +18 -1
- package/dist/runner/kimi/acp/runner.js +100 -42
- package/dist/runner/kimi/acp/translator.d.ts +0 -1
- package/dist/runner/opencode/acp/translator.d.ts +22 -2
- package/dist/runner/opencode/acp/translator.js +60 -0
- package/dist/runner/pi/rpc/protocol-types.d.ts +4 -0
- package/dist/runner/pi/rpc/runner.js +3 -0
- package/dist/runner/resolve-agent-choices.js +0 -5
- package/package.json +2 -2
- package/dist/runner/common/process-stopper.d.ts +0 -7
- package/dist/runner/common/process-stopper.js +0 -8
- package/dist/runner/dsh/fake-dsh-server.d.ts +0 -66
- package/dist/runner/dsh/fake-dsh-server.js +0 -162
package/dist/card/run-state.js
CHANGED
|
@@ -388,31 +388,67 @@ function reduceAssistantEvent(state, event) {
|
|
|
388
388
|
// string as the old per-render parse, so truncation-broken JSON still
|
|
389
389
|
// yields null (over-cap input renders no summary — behavior preserved).
|
|
390
390
|
const inputStr = truncateDetail(stringifyUnknown(content.input));
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
parsedInput: tryParseRecord(inputStr),
|
|
400
|
-
...(content.summary !== undefined ? { summary: content.summary } : {}),
|
|
401
|
-
status: 'running',
|
|
402
|
-
startedAt: event.timestamp,
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
|
-
];
|
|
406
|
-
next = {
|
|
407
|
-
...next,
|
|
408
|
-
blocks: keepLatestBlocks(capped),
|
|
409
|
-
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
|
|
410
|
-
footer: 'tool_running',
|
|
391
|
+
const incomingTool = {
|
|
392
|
+
id: content.id,
|
|
393
|
+
name: content.name,
|
|
394
|
+
input: inputStr,
|
|
395
|
+
parsedInput: tryParseRecord(inputStr),
|
|
396
|
+
...(content.summary !== undefined ? { summary: content.summary } : {}),
|
|
397
|
+
status: 'running',
|
|
398
|
+
startedAt: event.timestamp,
|
|
411
399
|
};
|
|
400
|
+
const blocks = markThinkingInactive(next.blocks);
|
|
401
|
+
// A tool_use patch (same id) enriches an existing tool block in place:
|
|
402
|
+
// opencode emits the initial tool_call while args are still streaming,
|
|
403
|
+
// then re-reports the command/path in later tool_call_updates. Merging
|
|
404
|
+
// keeps one panel per tool call; a first-seen id still appends.
|
|
405
|
+
const existingIndex = blocks.findIndex((block) => block.kind === 'tool' && block.tool.id === content.id);
|
|
406
|
+
if (existingIndex >= 0) {
|
|
407
|
+
const existing = blocks[existingIndex];
|
|
408
|
+
const merged = blocks.slice();
|
|
409
|
+
merged[existingIndex] = {
|
|
410
|
+
kind: 'tool',
|
|
411
|
+
tool: mergeToolUse(existing.tool, incomingTool),
|
|
412
|
+
};
|
|
413
|
+
next = { ...next, blocks: keepLatestBlocks(merged), footer: 'tool_running' };
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
const capped = [...blocks, { kind: 'tool', tool: incomingTool }];
|
|
417
|
+
next = {
|
|
418
|
+
...next,
|
|
419
|
+
blocks: keepLatestBlocks(capped),
|
|
420
|
+
omittedBlocks: (next.omittedBlocks ?? 0) + Math.max(0, capped.length - MAX_BLOCKS),
|
|
421
|
+
footer: 'tool_running',
|
|
422
|
+
};
|
|
423
|
+
}
|
|
412
424
|
}
|
|
413
425
|
}
|
|
414
426
|
return next;
|
|
415
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* Merge a same-id tool_use patch into the existing tool block. Only fields the
|
|
430
|
+
* patch can legitimately enrich are overwritten:
|
|
431
|
+
* - `input`/`parsedInput` — replaced only when the patch carries a non-empty
|
|
432
|
+
* object (an empty `{}` patch must not erase a command already rendered);
|
|
433
|
+
* - `summary` — replaced only when the patch supplies one;
|
|
434
|
+
* - `name` — kept from first sight (a completed opencode update has no kind,
|
|
435
|
+
* so its patch cannot know the mapped name).
|
|
436
|
+
* Everything else (`status`, `output`, `completedAt`, `toolHint`) is preserved,
|
|
437
|
+
* so an out-of-order patch after the tool_result cannot reset status to running.
|
|
438
|
+
*/
|
|
439
|
+
function mergeToolUse(existing, incoming) {
|
|
440
|
+
const incomingHasInput = incoming.parsedInput !== undefined &&
|
|
441
|
+
incoming.parsedInput !== null &&
|
|
442
|
+
Object.keys(incoming.parsedInput).length > 0;
|
|
443
|
+
return {
|
|
444
|
+
...existing,
|
|
445
|
+
input: incomingHasInput ? incoming.input : existing.input,
|
|
446
|
+
parsedInput: incomingHasInput ? incoming.parsedInput : existing.parsedInput,
|
|
447
|
+
name: existing.name || incoming.name,
|
|
448
|
+
...(incoming.summary !== undefined ? { summary: incoming.summary } : {}),
|
|
449
|
+
startedAt: existing.startedAt ?? incoming.startedAt,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
416
452
|
/** Handle tool_result content blocks (user message with tool results).
|
|
417
453
|
* P3-4: rebuild ONLY the block matching `tool_use_id`, copying every other
|
|
418
454
|
* block by reference. The old `.map()` recreated all block objects per
|
|
@@ -589,26 +625,40 @@ function tryParseRecord(input) {
|
|
|
589
625
|
function keepLatestBlocks(blocks) {
|
|
590
626
|
return blocks.length <= MAX_BLOCKS ? blocks : blocks.slice(-MAX_BLOCKS);
|
|
591
627
|
}
|
|
628
|
+
/** 截断标记前缀(`keepTailMarked` 产出,含已丢弃正文计数)。 */
|
|
629
|
+
const TRUNCATION_MARKER_RE = /^…(前 (\d+) 字符已省略)\n/;
|
|
592
630
|
/**
|
|
593
631
|
* keepTail + 截断标记:被去头的存储内容以前缀如实标注(信息保真红线:禁止静默丢弃)。
|
|
594
632
|
* 标记计入 maxChars 预算(存储总量仍 ≤ maxChars,保持既有长度不变量)。
|
|
633
|
+
*
|
|
634
|
+
* 计数口径(2026-10 修正):只统计**真实丢弃的正文**,不含标记自身开销。
|
|
635
|
+
* 关键点是增量流下 `value` 往往已带上一次的标记(`marker + kept`);若直接
|
|
636
|
+
* 用 `value.length - kept.length` 计费,会把上一帧的标记文本也算进「已省略」,
|
|
637
|
+
* 造成每帧显示十几字符的虚高。这里先剥离旧标记,把 N 定义为**从该块起始累计
|
|
638
|
+
* 丢弃的正文数**(单调不减),再按「marker 计入预算」的不动点求最小 N。
|
|
595
639
|
*/
|
|
596
640
|
function keepTailMarked(value, maxChars) {
|
|
597
|
-
|
|
641
|
+
const prior = TRUNCATION_MARKER_RE.exec(value);
|
|
642
|
+
const priorDropped = prior ? Number(prior[1]) : 0;
|
|
643
|
+
const body = prior ? value.slice(prior[0].length) : value;
|
|
644
|
+
// 从未截断且当前正文未超预算:原样返回(不误标)。
|
|
645
|
+
if (priorDropped === 0 && body.length <= maxChars)
|
|
598
646
|
return value;
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
|
|
647
|
+
// 求最小累计丢弃数 N(≥ priorDropped),使 marker(N) + 保留正文 ≤ maxChars:
|
|
648
|
+
// body.length - (N - priorDropped) + (12 + digits(N)) ≤ maxChars
|
|
649
|
+
// ⟺ N ≥ priorDropped + body.length - maxChars + 12 + digits(N)
|
|
650
|
+
// 即 N = K + digits(N) 的不动点(K 为固定项,位数至多几次即收敛)。
|
|
651
|
+
const K = priorDropped + body.length - maxChars + 12;
|
|
652
|
+
let totalDropped = Math.max(priorDropped, K);
|
|
603
653
|
for (;;) {
|
|
604
|
-
const next =
|
|
605
|
-
if (next ===
|
|
654
|
+
const next = Math.max(priorDropped, K + String(totalDropped).length);
|
|
655
|
+
if (next === totalDropped)
|
|
606
656
|
break;
|
|
607
|
-
|
|
657
|
+
totalDropped = next;
|
|
608
658
|
}
|
|
609
|
-
const
|
|
610
|
-
const kept =
|
|
611
|
-
return
|
|
659
|
+
const newlyDropped = totalDropped - priorDropped;
|
|
660
|
+
const kept = body.slice(newlyDropped);
|
|
661
|
+
return `…(前 ${totalDropped} 字符已省略)\n${kept}`;
|
|
612
662
|
}
|
|
613
663
|
function truncateDetail(value) {
|
|
614
664
|
return value.length <= MAX_TOOL_DETAIL_CHARS
|
|
@@ -30,19 +30,18 @@ export declare const CARD_BUDGET_BYTES = 28000;
|
|
|
30
30
|
* Each separator line corresponds to exactly one table.
|
|
31
31
|
*/
|
|
32
32
|
export declare function countMarkdownTables(text: string): number;
|
|
33
|
+
/**
|
|
34
|
+
* 删掉 `text` 中文档顺序**最旧**的至多 `maxDrop` 个 table 块(保留其余,含最新
|
|
35
|
+
* 的那些),并在最后一个删除点插入省略提示。`maxDrop <= 0` 时原样返回。
|
|
36
|
+
*
|
|
37
|
+
* 与 `truncateMarkdownTables` 同一口径:删最旧、留最新。
|
|
38
|
+
*/
|
|
39
|
+
export declare function dropOldestMarkdownTables(text: string, maxDrop: number): string;
|
|
33
40
|
/**
|
|
34
41
|
* Truncate markdown tables in a string to at most `maxTables`, keeping the
|
|
35
42
|
* **newest** (last) tables and removing the oldest ones.
|
|
36
43
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* each such table entirely (header row + separator row + data rows).
|
|
40
|
-
*
|
|
41
|
-
* A "table block" is a contiguous run of lines where the first line is the
|
|
42
|
-
* header row, the second is the separator row (`|---|`), and subsequent lines
|
|
43
|
-
* are data rows. We remove entire table blocks from the text.
|
|
44
|
-
*
|
|
45
|
-
* When tables are removed, a hint is inserted at the removal point indicating
|
|
46
|
-
* how many earlier tables were omitted.
|
|
44
|
+
* 注意:`maxTables` 是**这一个字符串**的上限。飞书 11310 数的是**整卡**,
|
|
45
|
+
* 表分散在多个字段时必须走 `card-budget.ts` 的整卡预算,不能逐字段调用本函数。
|
|
47
46
|
*/
|
|
48
47
|
export declare function truncateMarkdownTables(text: string, maxTables?: number): string;
|
|
@@ -22,6 +22,8 @@ export const FEISHU_MAX_TABLES = 5;
|
|
|
22
22
|
* 共用,避免三处各自定义后数值漂移。
|
|
23
23
|
*/
|
|
24
24
|
export const CARD_BUDGET_BYTES = 28_000;
|
|
25
|
+
/** markdown table 分隔行判据:以 `|` 开头,只含 `| - : 空格`。 */
|
|
26
|
+
const TABLE_SEPARATOR_RE = /^\|[-: |]+$/;
|
|
25
27
|
/**
|
|
26
28
|
* Count the number of markdown tables in a string.
|
|
27
29
|
*
|
|
@@ -32,43 +34,24 @@ export const CARD_BUDGET_BYTES = 28_000;
|
|
|
32
34
|
export function countMarkdownTables(text) {
|
|
33
35
|
let count = 0;
|
|
34
36
|
for (const line of text.split('\n')) {
|
|
35
|
-
if (
|
|
37
|
+
if (TABLE_SEPARATOR_RE.test(line.trim())) {
|
|
36
38
|
count++;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
return count;
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
42
|
-
*
|
|
43
|
-
* **newest** (last) tables and removing the oldest ones.
|
|
44
|
-
*
|
|
45
|
-
* Strategy: find all table separator lines, determine which tables to remove
|
|
46
|
-
* (those whose separator is among the oldest `count - maxTables`), then remove
|
|
47
|
-
* each such table entirely (header row + separator row + data rows).
|
|
48
|
-
*
|
|
49
|
-
* A "table block" is a contiguous run of lines where the first line is the
|
|
50
|
-
* header row, the second is the separator row (`|---|`), and subsequent lines
|
|
51
|
-
* are data rows. We remove entire table blocks from the text.
|
|
44
|
+
* 按文档顺序定位字符串里的 markdown table 块。
|
|
52
45
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
46
|
+
* `truncateMarkdownTables`(单串)与 `enforceCardBudget`(整卡预算)共用这一份
|
|
47
|
+
* 识别口径——两处各写一遍分隔行正数是下一个 bug 的温床。
|
|
55
48
|
*/
|
|
56
|
-
|
|
57
|
-
if (maxTables <= 0)
|
|
58
|
-
return text;
|
|
59
|
-
const tableCount = countMarkdownTables(text);
|
|
60
|
-
if (tableCount <= maxTables)
|
|
61
|
-
return text;
|
|
49
|
+
function findMarkdownTableBlocks(text) {
|
|
62
50
|
const lines = text.split('\n');
|
|
63
|
-
// Step 1: Find all table separator line indices
|
|
64
|
-
const separatorIndices = [];
|
|
65
|
-
for (let i = 0; i < lines.length; i++) {
|
|
66
|
-
if (/^\|[-: |]+$/.test(lines[i].trim())) {
|
|
67
|
-
separatorIndices.push(i);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
51
|
const blocks = [];
|
|
71
|
-
for (
|
|
52
|
+
for (let sepIdx = 0; sepIdx < lines.length; sepIdx++) {
|
|
53
|
+
if (!TABLE_SEPARATOR_RE.test(lines[sepIdx].trim()))
|
|
54
|
+
continue;
|
|
72
55
|
// Header is the line just before the separator
|
|
73
56
|
let start = sepIdx - 1;
|
|
74
57
|
if (start < 0)
|
|
@@ -90,30 +73,37 @@ export function truncateMarkdownTables(text, maxTables = FEISHU_MAX_TABLES) {
|
|
|
90
73
|
while (end < lines.length && /^\|/.test(lines[end].trim())) {
|
|
91
74
|
end++;
|
|
92
75
|
}
|
|
93
|
-
blocks.push({ start, end
|
|
76
|
+
blocks.push({ start, end });
|
|
94
77
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
78
|
+
return blocks;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 删掉 `text` 中文档顺序**最旧**的至多 `maxDrop` 个 table 块(保留其余,含最新
|
|
82
|
+
* 的那些),并在最后一个删除点插入省略提示。`maxDrop <= 0` 时原样返回。
|
|
83
|
+
*
|
|
84
|
+
* 与 `truncateMarkdownTables` 同一口径:删最旧、留最新。
|
|
85
|
+
*/
|
|
86
|
+
export function dropOldestMarkdownTables(text, maxDrop) {
|
|
87
|
+
if (maxDrop <= 0)
|
|
88
|
+
return text;
|
|
89
|
+
const lines = text.split('\n');
|
|
90
|
+
const blocksToRemove = findMarkdownTableBlocks(text).slice(0, maxDrop);
|
|
91
|
+
if (blocksToRemove.length === 0)
|
|
92
|
+
return text;
|
|
100
93
|
const blockByStart = new Map();
|
|
101
|
-
for (const b of
|
|
94
|
+
for (const b of blocksToRemove)
|
|
102
95
|
blockByStart.set(b.start, b);
|
|
103
|
-
|
|
104
|
-
// Step 4: Rebuild the text, skipping removed table blocks and inserting hints
|
|
96
|
+
const toRemove = blocksToRemove.length;
|
|
105
97
|
const result = [];
|
|
106
98
|
let i = 0;
|
|
107
|
-
let
|
|
99
|
+
let dropped = 0;
|
|
108
100
|
while (i < lines.length) {
|
|
109
101
|
const block = blockByStart.get(i);
|
|
110
|
-
if (block
|
|
111
|
-
|
|
112
|
-
removedSoFar++;
|
|
102
|
+
if (block) {
|
|
103
|
+
dropped++;
|
|
113
104
|
i = block.end;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
result.push(`_💡 前 ${toRemoveCount} 个表格已省略_`);
|
|
105
|
+
if (dropped === toRemove) {
|
|
106
|
+
result.push(`_💡 前 ${dropped} 个表格已省略_`);
|
|
117
107
|
}
|
|
118
108
|
continue;
|
|
119
109
|
}
|
|
@@ -122,4 +112,19 @@ export function truncateMarkdownTables(text, maxTables = FEISHU_MAX_TABLES) {
|
|
|
122
112
|
}
|
|
123
113
|
return result.join('\n');
|
|
124
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Truncate markdown tables in a string to at most `maxTables`, keeping the
|
|
117
|
+
* **newest** (last) tables and removing the oldest ones.
|
|
118
|
+
*
|
|
119
|
+
* 注意:`maxTables` 是**这一个字符串**的上限。飞书 11310 数的是**整卡**,
|
|
120
|
+
* 表分散在多个字段时必须走 `card-budget.ts` 的整卡预算,不能逐字段调用本函数。
|
|
121
|
+
*/
|
|
122
|
+
export function truncateMarkdownTables(text, maxTables = FEISHU_MAX_TABLES) {
|
|
123
|
+
if (maxTables <= 0)
|
|
124
|
+
return text;
|
|
125
|
+
const dropCount = findMarkdownTableBlocks(text).length - maxTables;
|
|
126
|
+
if (dropCount <= 0)
|
|
127
|
+
return text;
|
|
128
|
+
return dropOldestMarkdownTables(text, dropCount);
|
|
129
|
+
}
|
|
125
130
|
//# sourceMappingURL=text-truncate.js.map
|
package/dist/card/tool-render.js
CHANGED
|
@@ -39,9 +39,13 @@ export function toolHeaderText(tool) {
|
|
|
39
39
|
const blockSummary = tool.summary
|
|
40
40
|
? truncate(tool.summary.replace(/\s+/g, ' ').trim(), HEADER_SUMMARY_MAX, { suffix: '' })
|
|
41
41
|
: '';
|
|
42
|
+
// opencode carries the same value in both slots (title = command/path, and
|
|
43
|
+
// the args-derived summary resolves to it too); dedupe so the header reads
|
|
44
|
+
// `✅ **Bash** — ls` instead of `✅ **Bash** — ls — ls`.
|
|
42
45
|
const parts = [summary, blockSummary].filter(Boolean);
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
const uniqueParts = parts.filter((part, i) => parts.indexOf(part) === i);
|
|
47
|
+
return uniqueParts.length > 0
|
|
48
|
+
? `${icon} **${tool.name}** — ${uniqueParts.join(' — ')}`
|
|
45
49
|
: `${icon} **${tool.name}**`;
|
|
46
50
|
}
|
|
47
51
|
/**
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI 引导层(bootstrap):在 import 应用模块图之前决定运行时(bun/node)并拉起它。
|
|
4
|
+
*
|
|
5
|
+
* **这是全仓唯一被认可的 spawn 收口例外**(即 AGENTS.md「子进程 spawn 只走
|
|
6
|
+
* platform/spawn.ts」红线的显式豁免)。三个理由缺一不可:
|
|
7
|
+
* 1. 时序:preflight(Node 版本守卫、-v/-h/--advance-help)必须在 import 应用
|
|
8
|
+
* 模块图之前跑完——dist/cli.js 刻意不使用 ES2020+ 语法以便在 node 12 上仍
|
|
9
|
+
* 可被解析,而 dist/index.js 是 ES2022;先 import platform/spawn 就等于先把
|
|
10
|
+
* 应用模块图拖进来,守卫当场失效(报错堆栈全落在 Node 内部)。
|
|
11
|
+
* 2. 依赖:platform/spawn.ts 依赖 cross-spawn(应用依赖树的一部分),在「刚
|
|
12
|
+
* 诊断出 Node 太老」的那个时刻并不可加载。
|
|
13
|
+
* 3. 语义:这里 spawn 的是**另一份运行时**(bun 重启自己),不是 agent/工具
|
|
14
|
+
* 子进程,不参与组杀、pid 文件、windowsHide 注入这整套 runner 语义。
|
|
15
|
+
*
|
|
16
|
+
* 结论:不要为了“统一收口”把这里改走 platform/spawn;反过来,除本文件之外
|
|
17
|
+
* 任何地方新增子进程都必须走 platform/spawn.ts。
|
|
18
|
+
*/
|
|
2
19
|
import fs from 'node:fs';
|
|
3
20
|
import path from 'node:path';
|
|
4
21
|
import { spawn, spawnSync } from 'node:child_process';
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
/**
|
|
12
12
|
* Resolve codex home directory.
|
|
13
13
|
* Priority: explicit codexHome param > $CODEX_HOME env > ~/.codex
|
|
14
|
+
*
|
|
15
|
+
* 空串/纯空白按「未设置」处理:`??` 只挡 undefined,放过空串会让
|
|
16
|
+
* `path.join('', 'config.toml')` 变成相对**进程 cwd** 的路径。
|
|
14
17
|
*/
|
|
15
18
|
export declare function resolveCodexHome(codexHome?: string): string;
|
|
16
19
|
/** loadCodexConfig() return type */
|
|
@@ -22,9 +22,12 @@ const CODEX_MAX_BUFFER = 4 * 1024 * 1024;
|
|
|
22
22
|
/**
|
|
23
23
|
* Resolve codex home directory.
|
|
24
24
|
* Priority: explicit codexHome param > $CODEX_HOME env > ~/.codex
|
|
25
|
+
*
|
|
26
|
+
* 空串/纯空白按「未设置」处理:`??` 只挡 undefined,放过空串会让
|
|
27
|
+
* `path.join('', 'config.toml')` 变成相对**进程 cwd** 的路径。
|
|
25
28
|
*/
|
|
26
29
|
export function resolveCodexHome(codexHome) {
|
|
27
|
-
return codexHome
|
|
30
|
+
return codexHome?.trim() || process.env.CODEX_HOME?.trim() || path.join(os.homedir(), '.codex');
|
|
28
31
|
}
|
|
29
32
|
/**
|
|
30
33
|
* Fallback lists
|
package/dist/config/dir.js
CHANGED
|
@@ -133,20 +133,20 @@ export function printAdvanceHelp() {
|
|
|
133
133
|
'',
|
|
134
134
|
'Codex(agents.codex.appServer.*):',
|
|
135
135
|
' binary (默认 codex) codex 二进制路径',
|
|
136
|
-
' requestTimeoutMs (默认 60000) 单次 RPC 请求超时(ms)',
|
|
137
|
-
' idleTtlMs (默认 1800000) 连接空闲回收(ms)',
|
|
136
|
+
' requestTimeoutMs (默认 60000) 单次 RPC 请求超时(ms),0=不超时',
|
|
137
|
+
' idleTtlMs (默认 1800000) 连接空闲回收(ms),0=不回收',
|
|
138
138
|
' turnIdleTimeoutMinutes(默认 30) turn 无输出超时(分钟),0=关闭(卡片也可改)',
|
|
139
139
|
'',
|
|
140
140
|
'Kimi(agents.kimi.acp.*):',
|
|
141
141
|
' binary (默认 kimi) kimi 二进制路径',
|
|
142
|
-
' requestTimeoutMs (默认 60000) 单次请求超时(ms)',
|
|
143
|
-
' idleTtlMs (默认 1800000) 连接空闲回收(ms)',
|
|
142
|
+
' requestTimeoutMs (默认 60000) 单次请求超时(ms),0=不超时',
|
|
143
|
+
' idleTtlMs (默认 1800000) 连接空闲回收(ms),0=不回收',
|
|
144
144
|
' turnIdleTimeoutMinutes(默认 30) turn 无输出超时(分钟),0=关闭(卡片也可改)',
|
|
145
145
|
'',
|
|
146
146
|
'OpenCode(agents.opencode.acp.*):',
|
|
147
147
|
' binary (默认 opencode) opencode 二进制路径',
|
|
148
|
-
' requestTimeoutMs (默认 60000) 单次请求超时(ms)',
|
|
149
|
-
' idleTtlMs (默认 1800000) 连接空闲回收(ms)',
|
|
148
|
+
' requestTimeoutMs (默认 60000) 单次请求超时(ms),0=不超时',
|
|
149
|
+
' idleTtlMs (默认 1800000) 连接空闲回收(ms),0=不回收',
|
|
150
150
|
' turnIdleTimeoutMinutes(默认 30) turn 无输出超时(分钟),0=关闭(卡片也可改)',
|
|
151
151
|
'',
|
|
152
152
|
'Pi:',
|
package/dist/config/index.d.ts
CHANGED
|
@@ -3,11 +3,13 @@ export { getConfigDir } from './dir.js';
|
|
|
3
3
|
/** Valid effort values for Claude */
|
|
4
4
|
export declare const CLAUDE_EFFORTS: readonly ["low", "medium", "high", "xhigh", "max"];
|
|
5
5
|
/**
|
|
6
|
-
* Claude
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* '
|
|
10
|
-
*
|
|
6
|
+
* Claude 的权限模式取值:'default' 之外都是 CLI `--permission-mode` 的真实枚举
|
|
7
|
+
* (2026-09-20 实测 claude --help:acceptEdits | auto | bypassPermissions | manual
|
|
8
|
+
* | dontAsk | plan)。
|
|
9
|
+
* 'default' 是本项目自造的值,不是 CLI 的取值 —— 它表示"不传 --permission-mode",
|
|
10
|
+
* 由 CLI 自己决定默认模式,转换在 runner/claude/session.ts 的 spawn 参数处。
|
|
11
|
+
* 'manual' 与 'default' 不是别名关系:一个是显式传给 CLI 的取值,一个是省略该参数,
|
|
12
|
+
* schema 保留两者只为兼容旧配置(UI 下拉的处理见 router/config/claude.ts)。
|
|
11
13
|
*/
|
|
12
14
|
export declare const CLAUDE_PERMISSION_MODES: readonly ["default", "acceptEdits", "auto", "bypassPermissions", "manual", "dontAsk", "plan"];
|
|
13
15
|
/** Valid thinking levels for Pi */
|
package/dist/config/index.js
CHANGED
|
@@ -10,11 +10,13 @@ import { KIMI_THINKING_EFFORTS } from './kimi-config.js';
|
|
|
10
10
|
/** Valid effort values for Claude */
|
|
11
11
|
export const CLAUDE_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
12
12
|
/**
|
|
13
|
-
* Claude
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* '
|
|
17
|
-
*
|
|
13
|
+
* Claude 的权限模式取值:'default' 之外都是 CLI `--permission-mode` 的真实枚举
|
|
14
|
+
* (2026-09-20 实测 claude --help:acceptEdits | auto | bypassPermissions | manual
|
|
15
|
+
* | dontAsk | plan)。
|
|
16
|
+
* 'default' 是本项目自造的值,不是 CLI 的取值 —— 它表示"不传 --permission-mode",
|
|
17
|
+
* 由 CLI 自己决定默认模式,转换在 runner/claude/session.ts 的 spawn 参数处。
|
|
18
|
+
* 'manual' 与 'default' 不是别名关系:一个是显式传给 CLI 的取值,一个是省略该参数,
|
|
19
|
+
* schema 保留两者只为兼容旧配置(UI 下拉的处理见 router/config/claude.ts)。
|
|
18
20
|
*/
|
|
19
21
|
export const CLAUDE_PERMISSION_MODES = [
|
|
20
22
|
'default',
|
|
@@ -96,9 +98,9 @@ const ClaudeConfigSchema = z.object({
|
|
|
96
98
|
const CodexAppServerConfigSchema = z.object({
|
|
97
99
|
/** Path to the codex binary for app-server mode. */
|
|
98
100
|
binary: z.string().default('codex'),
|
|
99
|
-
/** Request timeout in milliseconds. */
|
|
101
|
+
/** Request timeout in milliseconds. 0 = 不设超时(不是「0ms 即超时」)。 */
|
|
100
102
|
requestTimeoutMs: z.number().int().min(0).default(60000),
|
|
101
|
-
/** Idle TTL
|
|
103
|
+
/** Idle TTL in milliseconds. 0 = 不回收常驻连接(对齐 claude.idleTtlMinutes)。 */
|
|
102
104
|
idleTtlMs: z.number().int().min(0).default(1800000),
|
|
103
105
|
/** Turn idle timeout in minutes: no app-server output for this long triggers
|
|
104
106
|
* turn/interrupt and fails the run. 0 disables the timeout. */
|
|
@@ -141,9 +143,9 @@ const OpencodeConfigSchema = z.object({
|
|
|
141
143
|
.object({
|
|
142
144
|
/** Path to opencode binary for ACP mode. */
|
|
143
145
|
binary: z.string().default('opencode'),
|
|
144
|
-
/** Request timeout in milliseconds. */
|
|
146
|
+
/** Request timeout in milliseconds. 0 = 不设超时(不是「0ms 即超时」)。 */
|
|
145
147
|
requestTimeoutMs: z.number().int().min(0).default(60000),
|
|
146
|
-
/** Idle TTL
|
|
148
|
+
/** Idle TTL in milliseconds. 0 = 不回收常驻连接(对齐 claude.idleTtlMinutes)。 */
|
|
147
149
|
idleTtlMs: z.number().int().min(0).default(1800000),
|
|
148
150
|
/** Turn idle timeout in minutes (0 disables). */
|
|
149
151
|
turnIdleTimeoutMinutes: z.number().int().min(0).default(DEFAULT_TURN_IDLE_TIMEOUT_MINUTES),
|
|
@@ -165,7 +167,7 @@ const PiConfigSchema = z.object({
|
|
|
165
167
|
const KimiConfigSchema = z.object({
|
|
166
168
|
/** Model ID or alias, e.g. 'kimi-code/k3'. */
|
|
167
169
|
model: z.string().default('kimi-code/k3'),
|
|
168
|
-
/** Thinking effort
|
|
170
|
+
/** Thinking effort,取值见 KIMI_THINKING_EFFORTS('low' | 'high' | 'max')。 */
|
|
169
171
|
thinkingEffort: z.enum(KIMI_THINKING_EFFORTS).default('max'),
|
|
170
172
|
/** Permission mode: 'manual' (approve each), 'auto' (engine decides), 'yolo' (allow all). */
|
|
171
173
|
permissionMode: z.enum(['manual', 'auto', 'yolo']).default('manual'),
|
|
@@ -174,9 +176,9 @@ const KimiConfigSchema = z.object({
|
|
|
174
176
|
.object({
|
|
175
177
|
/** Path to kimi binary for ACP mode. */
|
|
176
178
|
binary: z.string().default('kimi'),
|
|
177
|
-
/** Request timeout in milliseconds. */
|
|
179
|
+
/** Request timeout in milliseconds. 0 = 不设超时(不是「0ms 即超时」)。 */
|
|
178
180
|
requestTimeoutMs: z.number().int().min(0).default(60000),
|
|
179
|
-
/** Idle TTL
|
|
181
|
+
/** Idle TTL in milliseconds. 0 = 不回收常驻连接(对齐 claude.idleTtlMinutes)。 */
|
|
180
182
|
idleTtlMs: z.number().int().min(0).default(1800000),
|
|
181
183
|
/** Turn idle timeout in minutes (0 disables). */
|
|
182
184
|
turnIdleTimeoutMinutes: z.number().int().min(0).default(DEFAULT_TURN_IDLE_TIMEOUT_MINUTES),
|
|
@@ -442,9 +444,46 @@ export function setConfigValues(configPath, config, updates) {
|
|
|
442
444
|
const errors = result.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\n');
|
|
443
445
|
throw new Error(`[lark-remote] config validation failed:\n${errors}`);
|
|
444
446
|
}
|
|
445
|
-
atomicWrite(configPath,
|
|
447
|
+
atomicWrite(configPath, renderConfigPatch(configPath, updates, result.data));
|
|
446
448
|
return result.data;
|
|
447
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* 写盘内容:读磁盘原文,把改动逐条打在 YAML Document 上(setIn 会按需补中间层),
|
|
452
|
+
* 而不是把校验结果整份 stringify —— 后者会把 dir.ts 正引导用户手写的注释、以及
|
|
453
|
+
* schema 不认识的键(Zod 默认剥离)在第一次保存时清光,还会把每个默认值都落进文件。
|
|
454
|
+
*
|
|
455
|
+
* 原文读不到或解析不了(进程启动后被改坏)时退回校验结果:那份原文已无保留价值,
|
|
456
|
+
* 与修复前的行为一致。
|
|
457
|
+
*/
|
|
458
|
+
function renderConfigPatch(configPath, updates, validated) {
|
|
459
|
+
let doc;
|
|
460
|
+
try {
|
|
461
|
+
doc = YAML.parseDocument(fs.readFileSync(configPath, 'utf-8'), { prettyErrors: true });
|
|
462
|
+
if (doc.errors.length > 0)
|
|
463
|
+
throw new Error(doc.errors[0].message);
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
getLogger().warn(`[config] existing file unusable, rewriting from validated config: ${err.message}`);
|
|
467
|
+
return YAML.stringify(validated);
|
|
468
|
+
}
|
|
469
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
470
|
+
const parts = mapAgentKey(key).split('.');
|
|
471
|
+
if (value === undefined) {
|
|
472
|
+
doc.deleteIn(parts);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
// 保存载荷里只写"校验结果中存活下来"的键:schema 不认识的键(§P2-24 的 foo.bar)
|
|
476
|
+
// 会被 Zod 剥掉,跟着打在 Document 上就是把垃圾永久写进用户配置文件。
|
|
477
|
+
// 磁盘上原有的未知键不受影响 —— 那是用户手写的,本函数不碰。
|
|
478
|
+
const accepted = getConfigValue(validated, parts.join('.'));
|
|
479
|
+
if (accepted === undefined) {
|
|
480
|
+
getLogger().warn(`[config] save payload key not in schema, not written: ${key}`);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
doc.setIn(parts, accepted);
|
|
484
|
+
}
|
|
485
|
+
return doc.toString();
|
|
486
|
+
}
|
|
448
487
|
/**
|
|
449
488
|
* 按 dot-separated key 删除嵌套值(不存在的键静默忽略)。
|
|
450
489
|
*/
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
/** Valid thinking effort values for kimi (Layer 1: Schema enum source) */
|
|
8
8
|
export declare const KIMI_THINKING_EFFORTS: readonly ["low", "high", "max"];
|
|
9
9
|
export type KimiThinkingEffort = (typeof KIMI_THINKING_EFFORTS)[number];
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* 兜底 effort 全集就是 schema 枚举本身(不是另一份字面量,否则新增取值要改两处)。
|
|
12
|
+
* 类型放宽为 readonly string[]:消费侧拿的是外部输入过滤后的兜底列表,不做枚举窄化。
|
|
13
|
+
*/
|
|
11
14
|
export declare const FALLBACK_EFFORTS: readonly string[];
|
|
12
15
|
/** Load Kimi config and return available models */
|
|
13
16
|
interface KimiConfigResult {
|
|
@@ -8,8 +8,11 @@ import { getLogger } from '../logger/index.js';
|
|
|
8
8
|
import { spawnProcessSync } from '../platform/spawn.js';
|
|
9
9
|
/** Valid thinking effort values for kimi (Layer 1: Schema enum source) */
|
|
10
10
|
export const KIMI_THINKING_EFFORTS = ['low', 'high', 'max'];
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* 兜底 effort 全集就是 schema 枚举本身(不是另一份字面量,否则新增取值要改两处)。
|
|
13
|
+
* 类型放宽为 readonly string[]:消费侧拿的是外部输入过滤后的兜底列表,不做枚举窄化。
|
|
14
|
+
*/
|
|
15
|
+
export const FALLBACK_EFFORTS = KIMI_THINKING_EFFORTS;
|
|
13
16
|
/** 清空缓存(仅供测试使用,生产代码无调用点) */
|
|
14
17
|
export function invalidateKimiConfigCache() {
|
|
15
18
|
kimiConfigCache = null;
|
|
@@ -6,14 +6,8 @@
|
|
|
6
6
|
interface PiProviderInfo {
|
|
7
7
|
/** Provider 名称 */
|
|
8
8
|
name: string;
|
|
9
|
-
/** API 端点(可选) */
|
|
10
|
-
baseUrl?: string;
|
|
11
|
-
/** API 类型 */
|
|
12
|
-
api?: string;
|
|
13
9
|
/** 该 provider 下的模型列表 */
|
|
14
10
|
models: string[];
|
|
15
|
-
/** 是否有认证信息 */
|
|
16
|
-
isAuthenticated: boolean;
|
|
17
11
|
}
|
|
18
12
|
/** loadPiConfig() 的返回类型,包含 provider 和 model 的聚合信息 */
|
|
19
13
|
export interface PiConfigResult {
|
|
@@ -29,7 +23,8 @@ export declare function _getAuthFilePath(): string;
|
|
|
29
23
|
/**
|
|
30
24
|
* 读取 pi 的 provider 和 model 配置
|
|
31
25
|
*
|
|
32
|
-
* @returns provider
|
|
26
|
+
* @returns provider 列表,每项含名称与其模型列表;auth.json 里有凭据但
|
|
27
|
+
* models.json 未声明的 provider 以空模型列出(只为让 provider 选项不丢)
|
|
33
28
|
*/
|
|
34
29
|
export declare function loadPiProviderConfig(): PiProviderInfo[];
|
|
35
30
|
/**
|
package/dist/config/pi-config.js
CHANGED
|
@@ -11,9 +11,12 @@ import path from 'node:path';
|
|
|
11
11
|
* 默认 ~/.pi/agent。每次调用时动态读取环境变量,避免模块级缓存导致
|
|
12
12
|
* singleFork 模式下测试设置 env 过晚(与 CLAUDE_SETTINGS_PATH 模式类似,
|
|
13
13
|
* 但 pi-config 的常量原本在模块级求值,改为惰性求值以支持运行时切换)。
|
|
14
|
+
*
|
|
15
|
+
* 空串/纯空白按「未设置」处理:`??` 只挡 undefined,放过空串会让 models.json
|
|
16
|
+
* 解析成相对**进程 cwd** 的路径。
|
|
14
17
|
*/
|
|
15
18
|
function getPiConfigDir() {
|
|
16
|
-
return process.env.PI_CONFIG_DIR
|
|
19
|
+
return process.env.PI_CONFIG_DIR?.trim() || path.join(os.homedir(), '.pi', 'agent');
|
|
17
20
|
}
|
|
18
21
|
/** @internal Exported for testing only — verify env var is respected. */
|
|
19
22
|
export function _getModelsFilePath() {
|
|
@@ -55,7 +58,8 @@ function getPiModelFallback(provider) {
|
|
|
55
58
|
/**
|
|
56
59
|
* 读取 pi 的 provider 和 model 配置
|
|
57
60
|
*
|
|
58
|
-
* @returns provider
|
|
61
|
+
* @returns provider 列表,每项含名称与其模型列表;auth.json 里有凭据但
|
|
62
|
+
* models.json 未声明的 provider 以空模型列出(只为让 provider 选项不丢)
|
|
59
63
|
*/
|
|
60
64
|
export function loadPiProviderConfig() {
|
|
61
65
|
const results = new Map();
|
|
@@ -87,10 +91,7 @@ export function loadPiProviderConfig() {
|
|
|
87
91
|
const cfg = config;
|
|
88
92
|
results.set(name, {
|
|
89
93
|
name,
|
|
90
|
-
baseUrl: cfg.baseUrl,
|
|
91
|
-
api: cfg.api,
|
|
92
94
|
models: (cfg.models ?? []).map((m) => m.id),
|
|
93
|
-
isAuthenticated: !!authData[name],
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
// 4. 对于 auth.json 中有凭据但未在 models.json 中定义的 provider
|
|
@@ -100,7 +101,6 @@ export function loadPiProviderConfig() {
|
|
|
100
101
|
results.set(name, {
|
|
101
102
|
name,
|
|
102
103
|
models: [],
|
|
103
|
-
isAuthenticated: true,
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
}
|