@bolloon/bolloon-agent 0.4.33 → 0.5.1
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/agents/contacts/chain.js +27 -7
- package/dist/agents/execution-supervisor.js +580 -40
- package/dist/agents/external-events.js +35 -6
- package/dist/agents/gateway-group.js +100 -12
- package/dist/agents/goal-criteria.js +10 -3
- package/dist/agents/goal-flywheel/block-executor.js +346 -0
- package/dist/agents/goal-flywheel/continuation-decision.js +592 -0
- package/dist/agents/goal-flywheel/goal-change.js +754 -0
- package/dist/agents/goal-flywheel/index.js +26 -0
- package/dist/agents/goal-flywheel/memory-layers.js +227 -0
- package/dist/agents/goal-flywheel/run-closure.js +809 -0
- package/dist/agents/goal-flywheel/skill-candidate.js +628 -0
- package/dist/agents/goal-flywheel/types.js +329 -0
- package/dist/agents/goal-flywheel/wiring/change.js +288 -0
- package/dist/agents/goal-flywheel/wiring/closure.js +697 -0
- package/dist/agents/goal-flywheel/wiring/continuation.js +432 -0
- package/dist/agents/goal-flywheel/wiring/contract.js +270 -0
- package/dist/agents/goal-flywheel/wiring/index.js +26 -0
- package/dist/agents/goal-flywheel/wiring/monitor.js +254 -0
- package/dist/agents/goal-flywheel/wiring/seams.js +592 -0
- package/dist/agents/goal-flywheel/work-contract.js +478 -0
- package/dist/agents/goal-flywheel/work-monitor.js +488 -0
- package/dist/agents/goal-flywheel-wiring.js +1870 -0
- package/dist/agents/goal-state-reducer.js +406 -0
- package/dist/agents/goal-store.js +35 -1
- package/dist/agents/mobile-flywheel-view.js +144 -0
- package/dist/agents/mobile-task-actions-verify.js +74 -0
- package/dist/agents/mobile-task-actions.js +178 -0
- package/dist/agents/mobile-task-views.js +199 -0
- package/dist/agents/model-policy.js +651 -0
- package/dist/agents/network-pulse.js +304 -18
- package/dist/agents/pi-sdk-tools.js +84 -33
- package/dist/agents/pi-sdk.js +157 -42
- package/dist/agents/run-store.js +71 -5
- package/dist/agents/skill-readiness.js +52 -10
- package/dist/agents/subagent-manager.js +209 -3
- package/dist/agents/task/task-runner.js +17 -4
- package/dist/agents/task-group.js +8 -51
- package/dist/agents/task-public-text.js +89 -0
- package/dist/agents/workflow-pivot-loop.js +19 -12
- package/dist/cli/clipboard.js +65 -0
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/tasks.js +350 -2
- package/dist/cli/identity-command.js +177 -0
- package/dist/cli/ink-app.js +145 -17
- package/dist/cli/log-gate.js +386 -0
- package/dist/cli/mention-data.js +2 -0
- package/dist/cli/model-selector.js +538 -0
- package/dist/cli/reply-hygiene.js +54 -0
- package/dist/cli/setup-wizard.js +757 -69
- package/dist/cli/startup-notice.js +160 -0
- package/dist/cli/stores.js +22 -1
- package/dist/cli/theme.js +49 -0
- package/dist/cli/tui-select.js +970 -0
- package/dist/cli/update-commands.js +150 -24
- package/dist/cli-entry.js +137 -20
- package/dist/electron-build/agents/contacts/chain.js +26 -6
- package/dist/electron-build/agents/contacts/chain.js.map +1 -1
- package/dist/electron-build/agents/execution-supervisor.js +578 -38
- package/dist/electron-build/agents/execution-supervisor.js.map +1 -1
- package/dist/electron-build/agents/external-events.js +34 -5
- package/dist/electron-build/agents/external-events.js.map +1 -1
- package/dist/electron-build/agents/goal-criteria.js +10 -3
- package/dist/electron-build/agents/goal-criteria.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel/continuation-decision.js +598 -0
- package/dist/electron-build/agents/goal-flywheel/continuation-decision.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/goal-change.js +773 -0
- package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/memory-layers.js +269 -0
- package/dist/electron-build/agents/goal-flywheel/memory-layers.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/run-closure.js +818 -0
- package/dist/electron-build/agents/goal-flywheel/run-closure.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/skill-candidate.js +642 -0
- package/dist/electron-build/agents/goal-flywheel/skill-candidate.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/types.js +333 -0
- package/dist/electron-build/agents/goal-flywheel/types.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/change.js +294 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/change.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/closure.js +742 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/closure.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js +444 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/contract.js +279 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/contract.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/index.js +111 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/index.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js +264 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/seams.js +612 -0
- package/dist/electron-build/agents/goal-flywheel/wiring/seams.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/work-contract.js +488 -0
- package/dist/electron-build/agents/goal-flywheel/work-contract.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel/work-monitor.js +501 -0
- package/dist/electron-build/agents/goal-flywheel/work-monitor.js.map +1 -0
- package/dist/electron-build/agents/goal-flywheel-wiring.js +1962 -0
- package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -0
- package/dist/electron-build/agents/goal-state-reducer.js +414 -0
- package/dist/electron-build/agents/goal-state-reducer.js.map +1 -0
- package/dist/electron-build/agents/goal-store.js +35 -1
- package/dist/electron-build/agents/goal-store.js.map +1 -1
- package/dist/electron-build/agents/model-policy.js +706 -0
- package/dist/electron-build/agents/model-policy.js.map +1 -0
- package/dist/electron-build/agents/network-pulse.js +307 -19
- package/dist/electron-build/agents/network-pulse.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +84 -33
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +156 -41
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/run-store.js +75 -6
- package/dist/electron-build/agents/run-store.js.map +1 -1
- package/dist/electron-build/agents/skill-readiness.js +52 -9
- package/dist/electron-build/agents/skill-readiness.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js +16 -3
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +19 -12
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +146 -6
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/connection-probe.js +764 -0
- package/dist/electron-build/llm/connection-probe.js.map +1 -0
- package/dist/electron-build/llm/custom-provider-store.js +272 -0
- package/dist/electron-build/llm/custom-provider-store.js.map +1 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js +23 -0
- package/dist/electron-build/llm/data/provider-catalog-baked.js.map +1 -0
- package/dist/electron-build/llm/model-catalog.js +778 -0
- package/dist/electron-build/llm/model-catalog.js.map +1 -0
- package/dist/electron-build/llm/model-discovery.js +1202 -0
- package/dist/electron-build/llm/model-discovery.js.map +1 -0
- package/dist/electron-build/llm/model-selection.js +1315 -0
- package/dist/electron-build/llm/model-selection.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +70 -11
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/llm/provider-catalog.js +969 -0
- package/dist/electron-build/llm/provider-catalog.js.map +1 -0
- package/dist/electron-build/llm/provider-registry.js +950 -0
- package/dist/electron-build/llm/provider-registry.js.map +1 -0
- package/dist/electron-build/llm/tool-name.js +266 -0
- package/dist/electron-build/llm/tool-name.js.map +1 -0
- package/dist/electron-build/orbitdb/cid-database.js +83 -17
- package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
- package/dist/electron-build/orbitdb/ipfs-node.js +31 -2
- package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -1
- package/dist/electron-build/utils/dual-source-facts.js +236 -0
- package/dist/electron-build/utils/dual-source-facts.js.map +1 -0
- package/dist/electron-build/utils/dual-source.js +347 -0
- package/dist/electron-build/utils/dual-source.js.map +1 -0
- package/dist/electron-build/utils/update-manager.js +328 -104
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/electron-build/utils/update-state.js +9 -11
- package/dist/electron-build/utils/update-state.js.map +1 -1
- package/dist/electron-build/utils/version-identity.js +193 -0
- package/dist/electron-build/utils/version-identity.js.map +1 -0
- package/dist/electron-build/utils/version-info.js +57 -31
- package/dist/electron-build/utils/version-info.js.map +1 -1
- package/dist/index.js +385 -108
- package/dist/ios/bolloon-web.json +10 -0
- package/dist/ios/chain-explorer.js +617 -0
- package/dist/ios/explorer.html +163 -0
- package/dist/ios/index.html +96 -0
- package/dist/ios/mobile-agent.js +2 -2
- package/dist/ios/mobile-contacts.js +362 -0
- package/dist/ios/mobile-core.js +3102 -310
- package/dist/ios/mobile-privacy.js +185 -0
- package/dist/ios/mobile-tasks.js +339 -0
- package/dist/ios/mobile-update.js +1442 -0
- package/dist/ios/mobile.css +19 -0
- package/dist/ios/mobile.html +96 -0
- package/dist/ios/mobile.js +1013 -41
- package/dist/ios/routes-chain-index.js +108 -0
- package/dist/ios/routes-contacts.js +420 -0
- package/dist/ios/routes-llm-config.js +344 -52
- package/dist/ios/routes-mobile-tasks.js +353 -0
- package/dist/ios/routes-onchain-trade.js +99 -0
- package/dist/ios/routes-x402-info.js +1 -0
- package/dist/ios/server.js +921 -0
- package/dist/ios/sw.js +1 -1
- package/dist/ios/task-frame-server.js +211 -0
- package/dist/llm/config-store.js +146 -6
- package/dist/llm/connection-probe.js +744 -0
- package/dist/llm/custom-provider-store.js +224 -0
- package/dist/llm/data/provider-catalog-baked.js +19 -0
- package/dist/llm/model-catalog.js +717 -0
- package/dist/llm/model-discovery.js +1137 -0
- package/dist/llm/model-selection.js +1241 -0
- package/dist/llm/pi-ai.js +70 -11
- package/dist/llm/provider-catalog.js +898 -0
- package/dist/llm/provider-registry.js +917 -0
- package/dist/llm/tool-name.js +220 -0
- package/dist/orbitdb/cid-database.js +81 -16
- package/dist/orbitdb/ipfs-node.js +31 -2
- package/dist/setup/onboard.js +34 -8
- package/dist/utils/dual-source-facts.js +223 -0
- package/dist/utils/dual-source.js +297 -0
- package/dist/utils/update-cli.js +16 -21
- package/dist/utils/update-health.js +35 -5
- package/dist/utils/update-manager.js +325 -96
- package/dist/utils/update-state.js +13 -10
- package/dist/utils/version-identity.js +176 -0
- package/dist/utils/version-info.js +27 -22
- package/dist/web/bolloon-web.json +10 -0
- package/dist/web/mobile-contacts.js +44 -11
- package/dist/web/mobile-core.js +2901 -580
- package/dist/web/mobile-tasks.js +339 -0
- package/dist/web/mobile-update.js +1442 -0
- package/dist/web/mobile.css +19 -0
- package/dist/web/mobile.html +64 -0
- package/dist/web/mobile.js +838 -35
- package/dist/web/routes-llm-config.js +344 -52
- package/dist/web/routes-mobile-tasks.js +353 -0
- package/dist/web/server.js +163 -4
- package/dist/web/sw.js +1 -1
- package/package.json +3 -1
- package/scripts/build-web.ts +36 -0
package/dist/cli/ink-app.js
CHANGED
|
@@ -8,6 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
* ↑/↓ 导航, Tab/Enter 选中, Esc 关闭, 弹出窗打开时 TextInput 让出焦点 (focus=false)
|
|
9
9
|
*/
|
|
10
10
|
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
11
|
+
import * as fs from 'fs';
|
|
11
12
|
import { render, Box, Text, useInput, useApp, useStdout } from 'ink';
|
|
12
13
|
import TextInput from 'ink-text-input';
|
|
13
14
|
import { dispWidth, LOADING_FRAMES as KAOMOJI } from './loading-tui.js';
|
|
@@ -29,23 +30,64 @@ function msgVisualLines(text, width) {
|
|
|
29
30
|
}
|
|
30
31
|
return n;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* 分区账本 (2026-09-27 leo 口径: **面板高度必须确立 + 底部输入框打字不许抖**) ——
|
|
35
|
+
* 一屏就是终端的真实行数, 且**每一块的行数是常数**:
|
|
36
|
+
*
|
|
37
|
+
* 行 1 … history ← 历史区 (占满剩余高度; 消息窗口 + 弹层都在这里面)
|
|
38
|
+
* 行 history+1 ← 活动行 (思考/自动整理/暂停跟随提示; **固定预留 1 行**)
|
|
39
|
+
* 行…… ← 分隔线 / 状态栏 / 分隔线 / 输入栏 / 分隔线 (固定 5 行)
|
|
40
|
+
*
|
|
41
|
+
* 三条硬边界:
|
|
42
|
+
* · 几何恒定: 打字 / 提示出现 / 补全弹窗出现或消失, **输入行所在行号与其余区域位置一个字不动**
|
|
43
|
+
* (弹窗**覆盖**在历史区底部 —— 从 history 里扣, 不与固定栏抢位置);
|
|
44
|
+
* · **渲染总行数 == 终端高** —— 消息窗口切片 + 根容器 `overflow="hidden"` 兜底。
|
|
45
|
+
* (注: 修前帧高忽高忽低, 一旦某帧 > 终端高, Ink 的 `shouldClearTerminalForFrame` 会在**之后每一帧**
|
|
46
|
+
* 发 `ESC[2J ESC[3J ESC[H` —— 连回滚缓冲一起清, 于是"选不中 / 滚不回"同时出现。帧高恒定 = 那些清屏不再发。)
|
|
47
|
+
* · 暂停跟随时冻结整帧 (状态栏时钟也不 tick) ⇒ 帧字节不变 ⇒ 终端里的框选/复制不被打断。
|
|
48
|
+
*/
|
|
49
|
+
const CHROME_LINES = 5; // 3 条全宽分隔线 + 状态栏 + 输入栏
|
|
50
|
+
const ACTIVITY_LINES = 1; // 活动行 (固定预留, 免得状态一变就推挤历史区)
|
|
51
|
+
const RESERVED_LINES = CHROME_LINES + ACTIVITY_LINES;
|
|
52
|
+
const MIN_HISTORY_LINES = 1;
|
|
53
|
+
/** 弹层最多占几行 (给历史区留出至少 1 行消息, 免得弹窗把历史区吃光) */
|
|
54
|
+
const POPUP_MAX_ROWS = 8;
|
|
55
|
+
/** 一屏布局账本 (纯函数 —— 验收门直接喂尺寸复核) */
|
|
56
|
+
export function layoutBudget(opts) {
|
|
57
|
+
const rows = Math.max(RESERVED_LINES + MIN_HISTORY_LINES, Math.floor(opts.rows || 24));
|
|
58
|
+
const cols = Math.max(20, Math.floor(opts.cols || 80));
|
|
59
|
+
const history = Math.max(MIN_HISTORY_LINES, rows - RESERVED_LINES);
|
|
60
|
+
const popupRows = Math.max(0, Math.min(Math.floor(opts.popupRows || 0), POPUP_MAX_ROWS, Math.max(0, history - MIN_HISTORY_LINES)));
|
|
61
|
+
return {
|
|
62
|
+
rows, cols,
|
|
63
|
+
chrome: CHROME_LINES, activity: ACTIVITY_LINES,
|
|
64
|
+
history, msgH: history - popupRows, popupRows,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
32
67
|
// 2026-09-08: React.memo — msgs 引用不变时跳过重渲染 (配合虚拟化 slice, status tick 不再整表重绘)
|
|
33
68
|
const Messages = React.memo(({ msgs }) => (_jsx(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "flex-start", children: msgs.map((m, i) => {
|
|
34
69
|
const clean = m.replace(/\x1b\[[0-9;]*m/g, '');
|
|
35
70
|
return clean.trim() ? _jsx(Text, { children: clean ? m : '' }, i) : null;
|
|
36
71
|
}) })));
|
|
37
|
-
const MentionPopup = ({ title, items, sel, width, loading }) => {
|
|
38
|
-
const MAX_ROWS = 8;
|
|
39
|
-
// 2026-08-08: 滑动窗口 — 选中项始终可见 (原实现 fix 屏幕顶部, sel 超窗口时无高亮行)
|
|
40
|
-
const offset = Math.max(0, Math.min(sel - Math.floor(MAX_ROWS / 2), Math.max(0, items.length - MAX_ROWS)));
|
|
41
|
-
const shown = items.slice(offset, offset + MAX_ROWS);
|
|
72
|
+
const MentionPopup = ({ title, items, sel, width, loading, maxRows }) => {
|
|
42
73
|
const innerW = Math.max(width - 2, 10);
|
|
43
|
-
|
|
74
|
+
// 行账本: 2 行边框 + 正文若干 + (还有更多?1:0) —— 正文裁剪到刚好填满 maxRows
|
|
75
|
+
const totalRows = Math.max(1, maxRows);
|
|
76
|
+
const emptyNote = !loading && items.length === 0;
|
|
77
|
+
let bodyRows = Math.max(0, totalRows - 2 - (emptyNote ? 1 : 0));
|
|
78
|
+
let hasMore = items.length > bodyRows;
|
|
79
|
+
if (hasMore && bodyRows > 0)
|
|
80
|
+
bodyRows -= 1; // 给"还有 N 项"让一行
|
|
81
|
+
hasMore = items.length > bodyRows;
|
|
82
|
+
// 2026-08-08: 滑动窗口 — 选中项始终可见 (原实现 fix 屏幕顶部, sel 超窗口时无高亮行)
|
|
83
|
+
const offset = Math.max(0, Math.min(sel - Math.floor(bodyRows / 2), Math.max(0, items.length - bodyRows)));
|
|
84
|
+
const shown = items.slice(offset, offset + bodyRows);
|
|
85
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsx(Text, { color: THEME.accent, bold: true, children: `╭─ ${title} ${'─'.repeat(Math.max(2, innerW - dispWidth(title) - 4))}╮` }), loading && items.length === 0 ? (_jsx(Text, { color: "dim", children: "\u2502 \u626B\u63CF\u4E2D..." })) : emptyNote ? (_jsx(Text, { color: "dim", children: "\u2502 \u65E0\u5339\u914D" })) : (shown.map((it, i) => {
|
|
44
86
|
const active = i === sel;
|
|
45
87
|
const label = it.kind === 'file' ? it.label : `${it.kind === 'skill' ? '⚡' : it.kind === 'plugin' ? '🔌' : ''}${it.label}`;
|
|
46
88
|
const hint = it.hint ? `${it.hint}` : it.kind === 'file' ? '文件' : '';
|
|
47
89
|
return (_jsxs(Box, { width: innerW, children: [_jsx(Text, { color: active ? 'black' : undefined, backgroundColor: active ? 'cyan' : undefined, children: `${active ? '❯ ' : ' '}${label}` }), _jsx(Text, { color: active ? 'black' : 'dim', backgroundColor: active ? 'cyan' : undefined, dimColor: !active, children: ` ${hint}` })] }, `${it.kind}:${it.label}`));
|
|
48
|
-
})),
|
|
90
|
+
})), hasMore && (_jsxs(Text, { color: "dim", children: ["\u2502 ", offset + 1, "-", offset + shown.length, "/", items.length, " \u00B7 \u8FD8\u6709 ", items.length - (offset + shown.length), " \u9879..."] })), _jsx(Text, { color: THEME.accent, children: `╰${'─'.repeat(innerW)}╯` })] }));
|
|
49
91
|
};
|
|
50
92
|
const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH }) => {
|
|
51
93
|
const [input, setInput] = useState('');
|
|
@@ -90,15 +132,27 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
90
132
|
const [picker, setPicker] = useState(null);
|
|
91
133
|
const pickerCb = useRef(null);
|
|
92
134
|
const pickerSelRef = useRef(0);
|
|
135
|
+
/**
|
|
136
|
+
* 2026-09-26: 取消回调。此前 Esc 只把 `pickerCb` 置空并关窗 —— 对"等一个选择结果"的调用方
|
|
137
|
+
* (分步选择器要按步往下走) 来说就是**永远等不到**。现在 Esc/显式关闭都会回调它。
|
|
138
|
+
*/
|
|
139
|
+
const pickerCancel = useRef(null);
|
|
93
140
|
// 全局钩子: index.ts 命令打开/关闭选择器
|
|
94
141
|
useEffect(() => {
|
|
95
|
-
globalThis.__inkOpenPicker = (itemsArg, title, onPick) => {
|
|
142
|
+
globalThis.__inkOpenPicker = (itemsArg, title, onPick, onCancel) => {
|
|
96
143
|
pickerSelRef.current = 0;
|
|
97
144
|
pickerCb.current = onPick;
|
|
145
|
+
pickerCancel.current = onCancel ?? null;
|
|
98
146
|
setPicker({ title: title || '选择', items: itemsArg, sel: 0 });
|
|
99
147
|
setInput('');
|
|
100
148
|
};
|
|
101
|
-
globalThis.__inkClosePicker = () => {
|
|
149
|
+
globalThis.__inkClosePicker = () => {
|
|
150
|
+
const c = pickerCancel.current;
|
|
151
|
+
pickerCb.current = null;
|
|
152
|
+
pickerCancel.current = null;
|
|
153
|
+
setPicker(null);
|
|
154
|
+
c?.();
|
|
155
|
+
};
|
|
102
156
|
return () => { delete globalThis.__inkOpenPicker; delete globalThis.__inkClosePicker; };
|
|
103
157
|
}, []);
|
|
104
158
|
// ── 输入历史 (↑/↓ 切换) ───────────────────────────────────────────────────
|
|
@@ -392,13 +446,18 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
392
446
|
const it = itemsArg[Math.min(pickerSelRef.current, itemsArg.length - 1)];
|
|
393
447
|
const cb = pickerCb.current;
|
|
394
448
|
pickerCb.current = null;
|
|
449
|
+
pickerCancel.current = null;
|
|
395
450
|
setPicker(null);
|
|
396
451
|
cb?.(it);
|
|
397
452
|
return;
|
|
398
453
|
}
|
|
399
454
|
if (key.escape) {
|
|
455
|
+
// 取消也要**回调**调用方 (否则等结果的调用方永远挂着)
|
|
456
|
+
const c = pickerCancel.current;
|
|
400
457
|
pickerCb.current = null;
|
|
458
|
+
pickerCancel.current = null;
|
|
401
459
|
setPicker(null);
|
|
460
|
+
c?.();
|
|
402
461
|
return;
|
|
403
462
|
}
|
|
404
463
|
return; // 其余键忽略
|
|
@@ -528,7 +587,9 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
528
587
|
return;
|
|
529
588
|
}
|
|
530
589
|
// 双击 Esc 退出当前进程: 第一击提示, 500ms 内第二击退出
|
|
531
|
-
|
|
590
|
+
// ⚠ 判据含**字节级兜底** (`_input === '\u001b'`): Ink 对"孤独的 Esc"要先攒 20ms 再吐,
|
|
591
|
+
// 重挂之后实测 `key.escape` 不再为真 —— 与上面 Enter 那条同一个道理 (不依赖 Ink 的键解析)。
|
|
592
|
+
if (key.escape || _input === '\u001b') {
|
|
532
593
|
const now = Date.now();
|
|
533
594
|
if (now - lastEscRef.current < DOUBLE_ESC_MS) {
|
|
534
595
|
requestExit();
|
|
@@ -558,7 +619,8 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
558
619
|
// 自动更新状态栏 (每秒)
|
|
559
620
|
// 2026-08-07 修复: 依赖必须为空 [] — [getStatusUpdate] 在渲染间引用变化 (Ink 内部元素重建),
|
|
560
621
|
// effect 每次渲染 cleanup+setup → setInterval 刚建立就被清除 → 永不 tick → 状态栏恒初始值.
|
|
561
|
-
//
|
|
622
|
+
// 2026-09-27: **暂停跟随时不 tick** —— 用户上滚后整帧冻结 (Ink 的 log-update 在输出相同时
|
|
623
|
+
// 一个字节都不写), 于是终端里的框选/复制不会被打断, 也保证"底部行字节不再变化"。
|
|
562
624
|
useEffect(() => {
|
|
563
625
|
// 挂载时立即同步刷新一次状态栏 (不等 1s 后第一个 tick)
|
|
564
626
|
try {
|
|
@@ -568,6 +630,8 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
568
630
|
}
|
|
569
631
|
catch { /* 状态栏更新失败不致命 */ }
|
|
570
632
|
const timer = setInterval(() => {
|
|
633
|
+
if (!stickRef.current)
|
|
634
|
+
return; // 暂停跟随: 冻结整帧 (回到最底自动恢复)
|
|
571
635
|
try {
|
|
572
636
|
const s = getStatusUpdate();
|
|
573
637
|
if (s)
|
|
@@ -603,7 +667,16 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
603
667
|
// ── 虚拟化 transcript (消息级窗口 + 自动跟随底部) ───────────────────────────
|
|
604
668
|
// #1 分区预留: chrome 固定行 = 分隔线×3 + 状态栏 + 输入栏 = 5; transcript 严格不超区
|
|
605
669
|
// (content Box flexGrow=1 但内容超宽会把 status/composer 挤走 → 可见窗口行数钳制 ≤ availH)
|
|
606
|
-
|
|
670
|
+
// 2026-09-27: 高度**全部来自真终端尺寸** (`layoutBudget`) —— 帧高 == 终端高 (常数),
|
|
671
|
+
// 弹层从历史区里扣 (覆盖式, 不推挤固定栏) ⇒ 打字 / 提示出现都不动输入行位置。
|
|
672
|
+
const sticky = stickRef.current;
|
|
673
|
+
const popupOpenNow = !!(popupOpen || picker);
|
|
674
|
+
const budget = layoutBudget({
|
|
675
|
+
rows: termSize.h, cols: W,
|
|
676
|
+
popupRows: popupOpenNow ? POPUP_MAX_ROWS : 0,
|
|
677
|
+
});
|
|
678
|
+
const msgH = budget.msgH; // 消息窗口高度 (弹层占的那几行已经扣掉)
|
|
679
|
+
const availH = budget.history; // 滚动按"历史区总高"算 (含被弹层覆盖的几行)
|
|
607
680
|
const heights = useMemo(() => msgs.map(m => msgVisualLines(m, W)), [msgs, W]);
|
|
608
681
|
const cumulative = useMemo(() => {
|
|
609
682
|
const c = [0];
|
|
@@ -612,26 +685,47 @@ const InkApp = ({ onPrompt, initialStatus, getStatusUpdate, terminalW, terminalH
|
|
|
612
685
|
return c;
|
|
613
686
|
}, [heights]);
|
|
614
687
|
const totalLines = cumulative[cumulative.length - 1] || 0;
|
|
615
|
-
const maxTop = Math.max(0, totalLines -
|
|
616
|
-
const sticky = stickRef.current;
|
|
688
|
+
const maxTop = Math.max(0, totalLines - msgH);
|
|
617
689
|
const top = sticky ? maxTop : Math.min(scrollTop, maxTop);
|
|
618
690
|
let start = 0;
|
|
619
691
|
while (start < msgs.length && cumulative[start + 1] <= top + 0.5)
|
|
620
692
|
start++;
|
|
621
693
|
let end = start;
|
|
622
|
-
// 严格钳制: 端界不越过
|
|
623
|
-
while (end + 1 < msgs.length && cumulative[end + 1] <= top +
|
|
694
|
+
// 严格钳制: 端界不越过 msgH, 保证 transcript 内容行数 ≤ 分区高度 (互不覆盖)
|
|
695
|
+
while (end + 1 < msgs.length && cumulative[end + 1] <= top + msgH)
|
|
624
696
|
end++;
|
|
625
697
|
const visible = useMemo(() => msgs.slice(start, end + 1), [msgs, start, end]);
|
|
626
698
|
const scrolledOut = maxTop > 0 && !sticky;
|
|
627
|
-
|
|
699
|
+
// 测试/诊断探针: 把**真实用到的**布局与滚动状态写一份 JSONL (验收门按它做精确断言;
|
|
700
|
+
// 不设 BOLLOON_TUI_PROBE 时零开销 —— 只在渲染后追加一行)。
|
|
701
|
+
// 注: 走静态 import —— dist 是 ESM, 这里 `require()` 不存在 (踩过一次: 探针静默不写 + 树停更)。
|
|
702
|
+
useEffect(() => {
|
|
703
|
+
const p = process.env.BOLLOON_TUI_PROBE;
|
|
704
|
+
if (!p)
|
|
705
|
+
return;
|
|
706
|
+
try {
|
|
707
|
+
fs.appendFileSync(p, JSON.stringify({
|
|
708
|
+
t: Date.now(), rows: budget.rows, cols: budget.cols, chrome: budget.chrome,
|
|
709
|
+
activity: budget.activity, history: budget.history, msgH, popupRows: budget.popupRows,
|
|
710
|
+
inputLine: budget.rows - 1, // 输入栏行号 (1-based; 最后一行是底部分隔线)
|
|
711
|
+
totalMsgs: msgs.length, totalLines, top, maxTop, stick: sticky,
|
|
712
|
+
firstVisible: start, lastVisible: end, visibleCount: visible.length,
|
|
713
|
+
input, pausedHint: scrolledOut ? '已暂停跟随 · PgDn/End 回到底部' : '',
|
|
714
|
+
}) + '\n');
|
|
715
|
+
}
|
|
716
|
+
catch { /* 探针写不进去绝不影响界面 */ }
|
|
717
|
+
});
|
|
718
|
+
return (_jsxs(Box, { flexDirection: "column", height: budget.rows, width: budget.cols, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", height: budget.history, children: [_jsxs(Box, { height: msgH, overflow: "hidden", flexDirection: hasRails ? 'row' : 'column', children: [_jsx(Box, { flexGrow: 1, flexDirection: "column", justifyContent: "flex-start", overflow: "hidden", children: _jsx(Messages, { msgs: visible }) }), hasRails && (_jsx(Box, { width: 36, marginLeft: 1, flexDirection: "column", justifyContent: "flex-start", children: railNames.map((n) => _jsxs(Text, { color: THEME.muted, children: [n, "\\n", widgets[n]] }, n)) }))] }), popupOpenNow && tabState && (_jsx(MentionPopup, { title: POPUP_TITLE_TAB, items: filtered, sel: safeSel, width: W, maxRows: budget.popupRows })), popupOpenNow && !tabState && mention && (_jsx(MentionPopup, { title: popupTitle, items: filtered, sel: safeSel, width: W, loading: loadingFiles, maxRows: budget.popupRows })), popupOpenNow && !tabState && !mention && picker && (_jsx(MentionPopup, { title: picker.title, items: picker.items, sel: Math.min(picker.sel, picker.items.length - 1), width: W, maxRows: budget.popupRows }))] }), _jsx(Box, { height: budget.activity, width: budget.cols, overflow: "hidden", children: scrolledOut ? (_jsxs(Text, { color: THEME.warn, children: ["\u5DF2\u6682\u505C\u8DDF\u968F \u00B7 PgDn/End \u56DE\u5230\u5E95\u90E8 (\u2191", top, " \u884C \u00B7 Ctrl+U/D \u7FFB\u9875 \u00B7 Home \u5230\u9876)"] })) : transient ? (_jsx(Text, { children: transient })) : thinking ? (_jsxs(Text, { color: "yellow", children: [KAOMOJI[thinkingIdx.current], " \u601D\u8003\u4E2D..."] })) : (_jsx(Text, { children: " " })) }), _jsx(Box, { children: _jsx(Text, { bold: true, color: THEME.accent, children: '─'.repeat(W) }) }), _jsx(Box, { height: 1, width: budget.cols, overflow: "hidden", children: _jsx(Text, { children: status }) }), _jsx(Box, { children: _jsx(Text, { bold: true, color: THEME.accent, children: '─'.repeat(W) }) }), _jsxs(Box, { width: budget.cols, height: 1, overflow: "hidden", children: [_jsx(Text, { bold: true, color: THEME.accent, children: "\u276F " }), _jsx(Box, { width: Math.max(10, budget.cols - 2), height: 1, overflow: "hidden", flexShrink: 0, children: _jsx(TextInput, { value: input, onChange: setInput, onSubmit: onSubmit, focus: !popupOpenNow, placeholder: COMPOSER_PLACEHOLDER }, tiKey) })] }), _jsx(Box, { children: _jsx(Text, { bold: true, color: THEME.accent, children: '─'.repeat(W) }) })] }));
|
|
628
719
|
};
|
|
629
720
|
export { InkApp };
|
|
630
721
|
// ─── 启动 ────────────────────────────────────────────────────────────────────
|
|
631
722
|
let _inkInstance = null;
|
|
723
|
+
/** 上次 render 的参数 (suspend → resume 要原样重挂, 所以必须留着) */
|
|
724
|
+
let _lastInkArgs = null;
|
|
632
725
|
export function startInk(onPrompt, initialStatus, getStatusUpdate) {
|
|
633
726
|
const tw = process.stdout.columns || 80;
|
|
634
727
|
const th = process.stdout.rows || 24;
|
|
728
|
+
_lastInkArgs = { onPrompt, initialStatus, getStatusUpdate };
|
|
635
729
|
_inkInstance = render(_jsx(InkApp, { onPrompt: onPrompt, initialStatus: initialStatus, getStatusUpdate: getStatusUpdate, terminalW: tw, terminalH: th }), {
|
|
636
730
|
stdout: process.stdout,
|
|
637
731
|
stdin: process.stdin,
|
|
@@ -639,12 +733,46 @@ export function startInk(onPrompt, initialStatus, getStatusUpdate) {
|
|
|
639
733
|
patchConsole: false, // 重要: 阻止 Ink 劫持 console.log
|
|
640
734
|
});
|
|
641
735
|
}
|
|
736
|
+
/**
|
|
737
|
+
* 暂时让出终端 (2026-09-27): 会话内 `/model` 要用**与 `bolloon model` 同一个**全屏选择器组件
|
|
738
|
+
* (`tui-select.ts` —— 固定高度视窗 + 跟随 + 折叠 + 搜索 + 光标高亮), 而它自己是 raw-mode
|
|
739
|
+
* 逐键渲染: 两个渲染器同时抢同一个终端必然互相撕。
|
|
740
|
+
*
|
|
741
|
+
* 语义: 先把 Ink 卸掉并清掉它那一帧 (终端干净), 交给选择器画; 用完 `resumeInk()` 原样挂回来。
|
|
742
|
+
* 为什么可以这么做: 会话内容活在**外部 store** (`stores.ts` 的 transcriptStore/uiStore), 不在 React 里,
|
|
743
|
+
* 所以重挂之后历史一字不少 (只丢"输入框草稿"这种真正的临时态)。
|
|
744
|
+
*/
|
|
745
|
+
export function suspendInk() {
|
|
746
|
+
if (!_inkInstance)
|
|
747
|
+
return;
|
|
748
|
+
try {
|
|
749
|
+
_inkInstance.unmount();
|
|
750
|
+
_inkInstance.clear();
|
|
751
|
+
}
|
|
752
|
+
catch { /* 卸不掉也让调用方继续 (选择器会自己清干净) */ }
|
|
753
|
+
_inkInstance = null;
|
|
754
|
+
}
|
|
755
|
+
/** 把 Ink 按上次的参数挂回来 (suspendInk 之后用); 没挂过则忽略 */
|
|
756
|
+
export function resumeInk() {
|
|
757
|
+
if (_inkInstance || !_lastInkArgs)
|
|
758
|
+
return;
|
|
759
|
+
const a = _lastInkArgs;
|
|
760
|
+
startInk(a.onPrompt, a.initialStatus, a.getStatusUpdate);
|
|
761
|
+
// ⚠ 必须把 stdin **重新拉起来**: 选择器的 `RawSession.close()` 会 `stdin.pause()`,
|
|
762
|
+
// 而 Ink 挂载时只 `setRawMode(true)`、**自己不会 resume** ⇒ 少这一下, 面板照常重绘,
|
|
763
|
+
// 但按键再也不进来 —— 实测: 打字不回显、Esc 双击退出也没反应 (2026-09-27 验收门抓到)。
|
|
764
|
+
try {
|
|
765
|
+
process.stdin.resume();
|
|
766
|
+
}
|
|
767
|
+
catch { /* 不支持的宿主, 不影响渲染 */ }
|
|
768
|
+
}
|
|
642
769
|
export function stopInk() {
|
|
643
770
|
if (_inkInstance) {
|
|
644
771
|
_inkInstance.unmount();
|
|
645
772
|
_inkInstance.clear();
|
|
646
773
|
_inkInstance = null;
|
|
647
774
|
}
|
|
775
|
+
_lastInkArgs = null;
|
|
648
776
|
}
|
|
649
777
|
export function inkAppendLine(line) {
|
|
650
778
|
appendMsg(line);
|