berry-agent 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { ScrollView } from '../scroll/scroll-view.js';
|
|
2
|
+
import { Editor } from '../editor/editor.js';
|
|
3
|
+
import { prefixDisplayWidth } from '../editor/visual-lines.js';
|
|
4
|
+
import { shortIdOf } from '../backend/transcript.js';
|
|
5
|
+
import { tokenize } from '../../commands.js';
|
|
6
|
+
/** 筛选四态循环序(全部→活体→冻结→终态→全部——Tab 单步右移) */
|
|
7
|
+
const FILTER_CYCLE = ['all', 'active', 'frozen', 'terminal'];
|
|
8
|
+
/** 分区呈现名(分区头 + 筛选注记共用单源) */
|
|
9
|
+
const SECTION_LABEL = Object.freeze({
|
|
10
|
+
active: '活体',
|
|
11
|
+
frozen: '冻结',
|
|
12
|
+
terminal: '终态',
|
|
13
|
+
});
|
|
14
|
+
/** 提示行样式(dim——存在感弱于正文;分区头/投影行同载体) */
|
|
15
|
+
const DIM_STYLE = Object.freeze({ dim: true });
|
|
16
|
+
/** 光标行反色样式(整行最强存在感——SelectPanel 高亮行同载体) */
|
|
17
|
+
const CURSOR_STYLE = Object.freeze({ inverse: true });
|
|
18
|
+
/** 常态底行键面提示(按光标行分区动态铸造——零条目可见时行动词消隐) */
|
|
19
|
+
function hintText(section) {
|
|
20
|
+
const verbs = section === null
|
|
21
|
+
? ''
|
|
22
|
+
: section === 'active'
|
|
23
|
+
? 'f 冻结 · d 忘掉 · '
|
|
24
|
+
: section === 'frozen'
|
|
25
|
+
? 'f 解冻 · '
|
|
26
|
+
: 'r 恢复 · ';
|
|
27
|
+
return `${verbs}e 导出 · tab 筛选 · q/esc 返回`;
|
|
28
|
+
}
|
|
29
|
+
/** owner 键短显(头行——project 键 16 hex 截前 8 位,引用短 id 同款 8 位惯例) */
|
|
30
|
+
function ownerLabel(key) {
|
|
31
|
+
if (key.startsWith('project:'))
|
|
32
|
+
return `project:${key.slice('project:'.length, 'project:'.length + 8)}`;
|
|
33
|
+
return key; // global(及未来他形)原样——project 之外的键无哈希面
|
|
34
|
+
}
|
|
35
|
+
/** epoch 毫秒 → ISO UTC(tools.ts fmt 同式——呈现换算面同源注记) */
|
|
36
|
+
function fmtTs(ts) {
|
|
37
|
+
return new Date(ts).toISOString().replace('.000Z', 'Z');
|
|
38
|
+
}
|
|
39
|
+
/** key 事件窄化(text/ime/paste 归各分路) */
|
|
40
|
+
function asKey(event) {
|
|
41
|
+
return event.kind === 'key' ? event : null;
|
|
42
|
+
}
|
|
43
|
+
/** 无修饰命名键判 */
|
|
44
|
+
function isPlainKey(e, key) {
|
|
45
|
+
return !e.ctrl && !e.alt && !e.shift && !e.meta && e.key === key;
|
|
46
|
+
}
|
|
47
|
+
/** 错误折底行文案(守卫错人读面——码与人读原因直呈,同命令面 idiom) */
|
|
48
|
+
function errorText(err) {
|
|
49
|
+
if (err instanceof Error && 'code' in err && typeof err.code === 'string') {
|
|
50
|
+
return `${err.code}:${err.message}`;
|
|
51
|
+
}
|
|
52
|
+
return err instanceof Error ? err.message : String(err);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 管理面内容件:ScrollView 子类(滚动/折叠/偏移算术继承)+ OverlayContent
|
|
56
|
+
* (副屏 root——render 直收全屏 region)。
|
|
57
|
+
*/
|
|
58
|
+
export class MemoryViewer extends ScrollView {
|
|
59
|
+
ownerKeys;
|
|
60
|
+
dao;
|
|
61
|
+
sanitize;
|
|
62
|
+
exportCommand;
|
|
63
|
+
onExit;
|
|
64
|
+
onInterrupt;
|
|
65
|
+
onQuit;
|
|
66
|
+
/* ---- 视图态(rebuild 全量重建——动词后整表重取) ---- */
|
|
67
|
+
styledLines = [];
|
|
68
|
+
entries = [];
|
|
69
|
+
/** 光标(entries 下标;-1 = 零条目可见无处可指) */
|
|
70
|
+
cursor = -1;
|
|
71
|
+
/** 健康五计数(投影两行数据源) */
|
|
72
|
+
health = { active: 0, dismissed: 0, expired: 0, frozen: 0, total: 0 };
|
|
73
|
+
/** 筛选态(Tab 循环) */
|
|
74
|
+
filter = 'all';
|
|
75
|
+
/* ---- 模态两态(互斥:确认态与输入态不并在场——开其一先收另一) ---- */
|
|
76
|
+
/** 确认态(d 两段式——目标条目非空即在态) */
|
|
77
|
+
confirmTarget = null;
|
|
78
|
+
/** 输入态(e 导出参数行在场位) */
|
|
79
|
+
exportOpen = false;
|
|
80
|
+
exportEditor;
|
|
81
|
+
/** 底行一次性提示(守卫错/导出回执/解冻指引——常态下次按键即清) */
|
|
82
|
+
notice = null;
|
|
83
|
+
/** 退出闭锁(同批多事件只退一次——q 与 Esc 竞发的防御位) */
|
|
84
|
+
exited = false;
|
|
85
|
+
constructor(options) {
|
|
86
|
+
super(); // 无 maxHeight——副屏 root 直收 region 全高
|
|
87
|
+
this.ownerKeys = options.ownerKeys;
|
|
88
|
+
this.dao = options.dao;
|
|
89
|
+
this.sanitize = options.sanitize;
|
|
90
|
+
this.exportCommand = options.exportCommand;
|
|
91
|
+
this.onExit = options.onExit;
|
|
92
|
+
this.onInterrupt = options.onInterrupt;
|
|
93
|
+
this.onQuit = options.onQuit;
|
|
94
|
+
this.exportEditor = new Editor({ maxVisibleLines: 1 }); // 单行档——导出参数行
|
|
95
|
+
this.exportEditor.setFocused(false);
|
|
96
|
+
this.rebuild(); // 开屏快照(光标落首条——顶部对齐)
|
|
97
|
+
}
|
|
98
|
+
/** 量高:头行 + 视口全量 + 底铬(输入态 = Editor;其余恒 1 行) */
|
|
99
|
+
measure(width) {
|
|
100
|
+
return 1 + super.measure(width) + (this.exportOpen ? this.exportEditor.measure(width) : 1);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 落位(副屏全屏 region 三段):头行(档名 + owner 短显 + 筛选注记)→
|
|
104
|
+
* 滚动视口(super.render——投影两行/分区头/条目行全在内,折叠与偏移恒归
|
|
105
|
+
* ScrollView)→ 底铬(输入态 = 单行 Editor;确认态 = 确认文案;否则
|
|
106
|
+
* 一次性提示 ?? 动态键面提示)。
|
|
107
|
+
*/
|
|
108
|
+
render(buffer, region) {
|
|
109
|
+
if (region.height < 2)
|
|
110
|
+
return; // 防御位(极小终端——头行 + 视口都不够)
|
|
111
|
+
// 头行(筛选态在场时附注记——Tab 动作的可视反馈位)
|
|
112
|
+
const filterTag = this.filter === 'all' ? '' : ` ·〔筛选:${SECTION_LABEL[this.filter]}〕`;
|
|
113
|
+
buffer.writeText(region.row, region.col, `❄ 记忆管理 · ${this.ownerKeys.map(ownerLabel).join(' · ')}${filterTag}`);
|
|
114
|
+
// 滚动视口
|
|
115
|
+
const chromeBottom = this.exportOpen ? this.exportEditor.measure(region.width) : 1;
|
|
116
|
+
const viewHeight = region.height - 1 - chromeBottom;
|
|
117
|
+
if (viewHeight > 0) {
|
|
118
|
+
super.render(buffer, { row: region.row + 1, col: region.col, width: region.width, height: viewHeight });
|
|
119
|
+
}
|
|
120
|
+
// 底铬
|
|
121
|
+
if (this.exportOpen) {
|
|
122
|
+
this.exportEditor.setFocused(true);
|
|
123
|
+
this.exportEditor.render(buffer, {
|
|
124
|
+
row: region.row + region.height - chromeBottom,
|
|
125
|
+
col: region.col,
|
|
126
|
+
width: region.width,
|
|
127
|
+
height: chromeBottom,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const bottom = this.confirmTarget !== null
|
|
132
|
+
? `忘掉 [m:${shortIdOf(this.confirmTarget.id)}]?enter/y 确认 · esc/n 取消`
|
|
133
|
+
: (this.notice ?? hintText(this.cursorEntry()?.section ?? null));
|
|
134
|
+
buffer.writeText(region.row + region.height - 1, region.col, bottom, DIM_STYLE);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 事件分发(副屏内容终局消费——未消费键不穿透,模态独占)。
|
|
138
|
+
* 序:Ctrl+C/Ctrl+D 副屏键面补丁 → Tab 全局动词(输入态仍可达——Editor
|
|
139
|
+
* 不消费 Tab)→ 输入态模态 → 确认态模态 → 常态(退出键/行动词/光标键
|
|
140
|
+
* → ScrollView 滚动路)。
|
|
141
|
+
*/
|
|
142
|
+
handleEvent(event) {
|
|
143
|
+
if (event.kind === 'mouse')
|
|
144
|
+
return this.handleMouse(event); // mouse 路(终局消费)
|
|
145
|
+
const k = asKey(event);
|
|
146
|
+
if (k !== null && k.phase !== 'release') {
|
|
147
|
+
// Ctrl+C = 打断在飞 run(press/repeat 相动作滤 kitty release——主屏同键面)
|
|
148
|
+
if (k.ctrl && !k.alt && !k.shift && !k.meta && k.key === 'c') {
|
|
149
|
+
this.onInterrupt?.();
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
// Ctrl+D = 退出进程(先收副屏再转退出柄;输入行有文不退——主屏空框闸同律)
|
|
153
|
+
if (k.ctrl && !k.alt && !k.shift && !k.meta && k.key === 'd' && this.exportEditor.model.isEmpty()) {
|
|
154
|
+
this.exit();
|
|
155
|
+
this.onQuit?.();
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
// Tab 全局动词:筛选循环单步(输入态在场仍可达——Editor 不消费 Tab,
|
|
159
|
+
// 与 /history 搜索框键面无冲突的判据面)
|
|
160
|
+
if (isPlainKey(k, 'tab')) {
|
|
161
|
+
this.closeModals();
|
|
162
|
+
this.cycleFilter();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// 输入态(e 导出参数行)——模态:Esc 关 / Enter 执行 / 其余入 Editor
|
|
167
|
+
if (this.exportOpen) {
|
|
168
|
+
if (k !== null && k.phase !== 'release' && !k.ctrl && !k.alt && !k.meta) {
|
|
169
|
+
if (isPlainKey(k, 'escape')) {
|
|
170
|
+
this.closeExport();
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (isPlainKey(k, 'enter')) {
|
|
174
|
+
void this.runExport();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
this.exportEditor.handleEvent(event);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
// 确认态(d 两段式)——模态:Enter/y 确认 / Esc/n 取消(保守值)/ 其余吞
|
|
182
|
+
//(y/n 双轨同判——kitty 轨纯键打字走 text 事件,与常态行动词同律)
|
|
183
|
+
if (this.confirmTarget !== null) {
|
|
184
|
+
const typed = event.kind === 'text' ? event.text : null;
|
|
185
|
+
if (k !== null && k.phase !== 'release') {
|
|
186
|
+
if (isPlainKey(k, 'enter') || isPlainKey(k, 'y')) {
|
|
187
|
+
this.commitForget();
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
if (isPlainKey(k, 'escape') || isPlainKey(k, 'n')) {
|
|
191
|
+
this.confirmTarget = null; // 取消——保守值收场
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (typed === 'y') {
|
|
196
|
+
this.commitForget();
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (typed === 'n') {
|
|
200
|
+
this.confirmTarget = null; // 取消——保守值收场
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
return true; // 未消费键层内终局(模态)
|
|
204
|
+
}
|
|
205
|
+
// 常态:退出键与行动词双轨(kitty disambiguate 轨纯键打字走 text 事件、
|
|
206
|
+
// legacy 轨走 key 事件——同一动作两轨同判,HistoryViewer q 键同律)
|
|
207
|
+
this.notice = null; // 一次性提示读毕即清(动词自身的新提示随后覆写)
|
|
208
|
+
if (k !== null && k.phase !== 'release') {
|
|
209
|
+
if (isPlainKey(k, 'escape')) {
|
|
210
|
+
this.exit();
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (isPlainKey(k, 'up')) {
|
|
214
|
+
this.moveCursor(-1);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
if (isPlainKey(k, 'down')) {
|
|
218
|
+
this.moveCursor(1);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
if (isPlainKey(k, 'f')) {
|
|
222
|
+
this.applyFreezeToggle();
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
if (isPlainKey(k, 'd')) {
|
|
226
|
+
this.beginForget();
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if (isPlainKey(k, 'r')) {
|
|
230
|
+
this.applyRestore();
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (isPlainKey(k, 'e')) {
|
|
234
|
+
this.openExport();
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (event.kind === 'text') {
|
|
239
|
+
const ch = event.text;
|
|
240
|
+
if (ch === 'q') {
|
|
241
|
+
this.exit();
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
if (ch === 'f') {
|
|
245
|
+
this.applyFreezeToggle();
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
if (ch === 'd') {
|
|
249
|
+
this.beginForget();
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
if (ch === 'r') {
|
|
253
|
+
this.applyRestore();
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (ch === 'e') {
|
|
257
|
+
this.openExport();
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// 滚动键(PgUp/PgDn/Home/End——↑/↓ 已被光标消费;未消费键终局吞)
|
|
262
|
+
super.handleEvent(event);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
/* ---------------- 鼠标消费面(07 屏模型双形态节同批回写) ---------------- */
|
|
266
|
+
/**
|
|
267
|
+
* mouse 事件路(终局消费——模态独占):滚轮归 ScrollView(统一滚动路);
|
|
268
|
+
* 左键点条目行 = 光标移至该行(点击反查经视口段映射到逻辑行,再对
|
|
269
|
+
* entries 锚比对——非条目行零动作);头行/底铬/滚动条列/非左键零动作吞。
|
|
270
|
+
*/
|
|
271
|
+
handleMouse(event) {
|
|
272
|
+
if (event.button === 'wheel-up' || event.button === 'wheel-down') {
|
|
273
|
+
super.handleEvent(event); // 滚轮消费路(±3 视觉行——显式滚动路)
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
if (event.phase !== 'press')
|
|
277
|
+
return true; // motion/release 零动作(无拖选面 v1)
|
|
278
|
+
if (this.exportOpen || this.confirmTarget !== null)
|
|
279
|
+
return true; // 模态在场零动作吞
|
|
280
|
+
if (event.button !== 'left')
|
|
281
|
+
return true; // v1 只有左键消费——中/右零动作
|
|
282
|
+
const geo = this.viewportGeometry;
|
|
283
|
+
if (event.row < 1 || event.row >= 1 + geo.height)
|
|
284
|
+
return true; // 头行/底铬命中零动作
|
|
285
|
+
if (this.hitScrollbar(event.col))
|
|
286
|
+
return true; // 滚动条列零动作
|
|
287
|
+
const seg = this.segmentAt(event.row - 1); // 视口首行 = region.row + 1
|
|
288
|
+
if (seg === null)
|
|
289
|
+
return true;
|
|
290
|
+
const idx = this.entries.findIndex((e) => e.lineIndex === seg.line);
|
|
291
|
+
if (idx >= 0)
|
|
292
|
+
this.cursor = idx; // 条目行命中——光标移至该行;分区头/投影行不指
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
/* ---------------- 行动词(f/d/r——单源走 DAO 既有族) ---------------- */
|
|
296
|
+
/** f:冻结切换(呈现层判向——frozen 行 unfreeze、active 行 freeze;终态行零义吞) */
|
|
297
|
+
applyFreezeToggle() {
|
|
298
|
+
const entry = this.cursorEntry();
|
|
299
|
+
if (entry === undefined || entry.section === 'terminal')
|
|
300
|
+
return; // 终态行 f 零义吞
|
|
301
|
+
try {
|
|
302
|
+
if (entry.section === 'frozen')
|
|
303
|
+
this.dao.unfreeze(entry.row.id);
|
|
304
|
+
else
|
|
305
|
+
this.dao.freeze(entry.row.id);
|
|
306
|
+
this.rebuild(entry.row.id); // 同 id 锚定(frozen 位变即换分区呈现)
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
this.notice = errorText(err); // 守卫错折底行
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/** d:忘掉起手(active 行入确认态;终态行零义吞;冻结行零义吞 + 底行指引) */
|
|
313
|
+
beginForget() {
|
|
314
|
+
const entry = this.cursorEntry();
|
|
315
|
+
if (entry === undefined || entry.section === 'terminal')
|
|
316
|
+
return; // 终态行 d 零义吞
|
|
317
|
+
if (entry.section === 'frozen') {
|
|
318
|
+
this.notice = '已冻结——先 f 解冻再忘掉';
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
this.confirmTarget = entry.row; // 确认态(两段式第二段)
|
|
322
|
+
}
|
|
323
|
+
/** 确认态落子:forget(id) 缺省 supersededBy='user'(撤回后条目呈终态区——r 可复) */
|
|
324
|
+
commitForget() {
|
|
325
|
+
const target = this.confirmTarget;
|
|
326
|
+
this.confirmTarget = null; // 单次语义(先出态——守卫错也不再回确认态)
|
|
327
|
+
if (target === null)
|
|
328
|
+
return;
|
|
329
|
+
try {
|
|
330
|
+
this.dao.forget(target.id, { supersededBy: 'user' });
|
|
331
|
+
this.rebuild(target.id); // 同 id 锚定(filter=all 时条目现于终态区)
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
this.notice = errorText(err); // 守卫错折底行(MEMORY_FROZEN 等)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/** r:恢复(终态行免 confirm 直接 restore;active 行零义吞) */
|
|
338
|
+
applyRestore() {
|
|
339
|
+
const entry = this.cursorEntry();
|
|
340
|
+
if (entry === undefined || entry.section !== 'terminal')
|
|
341
|
+
return; // active 行 r 零义吞
|
|
342
|
+
try {
|
|
343
|
+
this.dao.restore(entry.row.id);
|
|
344
|
+
this.rebuild(entry.row.id); // 同 id 锚定(复活后现于活体区)
|
|
345
|
+
}
|
|
346
|
+
catch (err) {
|
|
347
|
+
this.notice = errorText(err);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/* ---------------- 全局动词(e/Tab) ---------------- */
|
|
351
|
+
/** e:开导出参数输入行(/memory-export 真身同一函数——参数面与命令面同形) */
|
|
352
|
+
openExport() {
|
|
353
|
+
this.confirmTarget = null; // 模态互斥(先收另一态)
|
|
354
|
+
this.exportOpen = true;
|
|
355
|
+
this.exportEditor.setFocused(true);
|
|
356
|
+
}
|
|
357
|
+
/** 关输入行(查询文本保留——重开续打的增量延续,/history 搜索框同律) */
|
|
358
|
+
closeExport() {
|
|
359
|
+
this.exportOpen = false;
|
|
360
|
+
this.exportEditor.setFocused(false);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* 执行导出:输入行文本 → 引号感知切分(命令分发同源 tokenize)→ 注入的
|
|
364
|
+
* 处理器闭包;回执折底行(多行以 · 连呈——底行单行面)。fire-and-forget:
|
|
365
|
+
* 命令体同步落盘(writeFileSync),promise 首微task即resolve——引擎帧
|
|
366
|
+
* 合并的 setTimeout 渲染在其后,回执同帧可见。
|
|
367
|
+
*/
|
|
368
|
+
async runExport() {
|
|
369
|
+
const raw = this.exportEditor.getText();
|
|
370
|
+
this.closeExport();
|
|
371
|
+
try {
|
|
372
|
+
const text = await this.exportCommand(tokenize(raw));
|
|
373
|
+
this.notice = text.replace(/\n+/g, ' · ');
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
this.notice = errorText(err); // 非 BaseError 异常面(BaseError 命令内已折文本)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/** Tab:筛选循环单步(全部→活体→冻结→终态→全部)+ 整表按新筛选重建 */
|
|
380
|
+
cycleFilter() {
|
|
381
|
+
const idx = FILTER_CYCLE.indexOf(this.filter);
|
|
382
|
+
this.filter = FILTER_CYCLE[(idx + 1) % FILTER_CYCLE.length];
|
|
383
|
+
this.rebuild(); // 无锚定 id——光标驻留原位就近
|
|
384
|
+
}
|
|
385
|
+
/** 模态全收(Tab 切筛选前的清理位——两模态不跨筛选态存活) */
|
|
386
|
+
closeModals() {
|
|
387
|
+
this.confirmTarget = null;
|
|
388
|
+
if (this.exportOpen)
|
|
389
|
+
this.closeExport();
|
|
390
|
+
}
|
|
391
|
+
/* ---------------- 光标与视图重建 ---------------- */
|
|
392
|
+
/** 光标条目(无条目/零条目可见 = undefined——动词消隐判据面) */
|
|
393
|
+
cursorEntry() {
|
|
394
|
+
return this.cursor >= 0 ? this.entries[this.cursor] : undefined;
|
|
395
|
+
}
|
|
396
|
+
/** 光标移动(条目间循环——非条目行不驻留;移动后视口对齐光标行) */
|
|
397
|
+
moveCursor(delta) {
|
|
398
|
+
if (this.entries.length === 0)
|
|
399
|
+
return; // 零条目可见——光标无处可指
|
|
400
|
+
this.cursor =
|
|
401
|
+
this.cursor < 0
|
|
402
|
+
? delta > 0
|
|
403
|
+
? 0
|
|
404
|
+
: this.entries.length - 1
|
|
405
|
+
: (this.cursor + delta + this.entries.length) % this.entries.length;
|
|
406
|
+
this.ensureCursorVisible();
|
|
407
|
+
}
|
|
408
|
+
/** 视口对齐光标行(顶对齐——近底行由 ScrollView 夹取自然呈现) */
|
|
409
|
+
ensureCursorVisible() {
|
|
410
|
+
const entry = this.cursorEntry();
|
|
411
|
+
if (entry !== undefined)
|
|
412
|
+
this.scrollToLine(entry.lineIndex, 0);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* 整表重取重建(开屏快照与动词成功后的统一路——06 §7 定形注⑤):
|
|
416
|
+
* 三源取数(health 恒全库 / listVisible 双分流 / listForExport 终态双过滤)
|
|
417
|
+
* → 行集三段式构建 → 光标锚定(同 id 优先;锚定目标缺席驻留原位就近;
|
|
418
|
+
* 零条目可见 = -1 行动词消隐)。
|
|
419
|
+
*/
|
|
420
|
+
rebuild(anchorId) {
|
|
421
|
+
// —— 取数三源(零新 DAO 方法——既有动词族单源)——
|
|
422
|
+
this.health = this.dao.overview().health; // 恒全库口径(无参即正形)
|
|
423
|
+
// 管理形取数(批 ev-1 M2 拆形——未生效行在本面可见,行尾「N天后生效」标注)
|
|
424
|
+
const visible = this.dao.listVisibleForManagement(this.ownerKeys);
|
|
425
|
+
const frozenRows = visible.filter((r) => r.frozen); // 冻结区(滤出 frozen 位)
|
|
426
|
+
const activeRows = visible.filter((r) => !r.frozen); // 活体区(滤除 frozen 位)
|
|
427
|
+
const terminalRows = this.dao.listForExport().filter((r) => this.ownerKeys.includes(r.ownerKey) && // ownerKey 过滤
|
|
428
|
+
(r.status === 'dismissed' || r.status === 'expired'));
|
|
429
|
+
// —— 行集三段式构建(投影两行 + 分区列表;头行不在行集——render 直写,
|
|
430
|
+
// 行集进屏不会与屏上头行重复)——
|
|
431
|
+
const lines = [];
|
|
432
|
+
const entries = [];
|
|
433
|
+
const dim = (plain) => ({ plain, runs: [{ start: 0, end: plain.length, style: DIM_STYLE }] });
|
|
434
|
+
lines.push(dim(`记忆库 · 在册 ${this.health.active} · 冻结 ${this.health.frozen} · 共 ${this.health.total}`));
|
|
435
|
+
lines.push(dim(`终态 · 否决 ${this.health.dismissed} · 过期 ${this.health.expired}`));
|
|
436
|
+
const sections = this.filter === 'all'
|
|
437
|
+
? [
|
|
438
|
+
{ key: 'active', rows: activeRows },
|
|
439
|
+
{ key: 'frozen', rows: frozenRows },
|
|
440
|
+
{ key: 'terminal', rows: terminalRows },
|
|
441
|
+
]
|
|
442
|
+
: [
|
|
443
|
+
{
|
|
444
|
+
key: this.filter,
|
|
445
|
+
rows: this.filter === 'active' ? activeRows : this.filter === 'frozen' ? frozenRows : terminalRows,
|
|
446
|
+
},
|
|
447
|
+
];
|
|
448
|
+
for (const section of sections) {
|
|
449
|
+
lines.push(dim(`── ${SECTION_LABEL[section.key]}(${section.rows.length})`));
|
|
450
|
+
for (const row of section.rows) {
|
|
451
|
+
lines.push(this.entryLine(row, section.key));
|
|
452
|
+
entries.push({ row, section: section.key, lineIndex: lines.length - 1 });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
this.styledLines = lines;
|
|
456
|
+
this.entries = entries;
|
|
457
|
+
super.setLines(lines.map((l) => l.plain));
|
|
458
|
+
// —— 光标锚定(同 id 优先 → 原位就近钳制 → 零条目 -1)——
|
|
459
|
+
let next = this.cursor;
|
|
460
|
+
if (anchorId !== undefined) {
|
|
461
|
+
const found = entries.findIndex((e) => e.row.id === anchorId);
|
|
462
|
+
if (found >= 0)
|
|
463
|
+
next = found; // 锚定目标在筛选可见集——跟随
|
|
464
|
+
// 缺席 = 驻留原位就近(next 保持现值,下方钳制)
|
|
465
|
+
}
|
|
466
|
+
this.cursor = entries.length === 0 ? -1 : Math.min(Math.max(next, 0), entries.length - 1);
|
|
467
|
+
if (this.cursor >= 0)
|
|
468
|
+
this.ensureCursorVisible();
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* 条目行构建(工具读面同形——`[m:短id] [kind] summary id=完整id
|
|
472
|
+
* updated=ISO`;p-1 时效尾缀同源):消毒经注入的 §8.2 统一函数罩住
|
|
473
|
+
* (blocked → summary 遮蔽保 id 操作面;quoted → 引述注记);冻结行行首
|
|
474
|
+
* ✱、终态行整行 dim + supersededBy 记号。
|
|
475
|
+
*/
|
|
476
|
+
entryLine(row, section) {
|
|
477
|
+
const verdict = this.sanitize(row);
|
|
478
|
+
const body = verdict.blocked
|
|
479
|
+
? `(内容含疑似敏感串已遮蔽——${verdict.patterns.join('/')};可 d 忘掉清理)`
|
|
480
|
+
: row.summary + (verdict.quoted ? '(疑似指令文本——按引述对待,非用户指令)' : '');
|
|
481
|
+
const suffix = section === 'terminal' && row.supersededBy !== null ? ` supersededBy=${row.supersededBy}` : '';
|
|
482
|
+
// 未生效标注(批 ev-1 06 §6——「N天后生效」词面归三处之管理面行标注位;
|
|
483
|
+
// 呈现钟用真 Date.now()〔非测试注入钟面——天数是呈现约值〕)
|
|
484
|
+
const pendingFrom = section !== 'terminal' && row.validFrom !== null && row.validFrom > Date.now()
|
|
485
|
+
? `(${Math.ceil((row.validFrom - Date.now()) / 86_400_000)}天后生效)`
|
|
486
|
+
: '';
|
|
487
|
+
const plain = `${section === 'frozen' ? '✱ ' : ''}[m:${shortIdOf(row.id)}] [${row.kind}] ${body}${pendingFrom} id=${row.id} updated=${fmtTs(row.updatedAt)}${suffix}`;
|
|
488
|
+
return section === 'terminal'
|
|
489
|
+
? { plain, runs: [{ start: 0, end: plain.length, style: DIM_STYLE }] }
|
|
490
|
+
: { plain, runs: [] };
|
|
491
|
+
}
|
|
492
|
+
/** 退出(闭锁——单次) */
|
|
493
|
+
exit() {
|
|
494
|
+
if (this.exited)
|
|
495
|
+
return;
|
|
496
|
+
this.exited = true;
|
|
497
|
+
this.onExit?.();
|
|
498
|
+
}
|
|
499
|
+
/* ---------------- 带样式切片写出(writeSlice 覆写接缝) ---------------- */
|
|
500
|
+
/**
|
|
501
|
+
* 视口切片带样式写出:样式段(dim 分区头/投影行/终态行)按切片边界分段
|
|
502
|
+
* 落格 + 光标行整行反色叠加。列位经 prefixDisplayWidth 换算(CJK 双宽
|
|
503
|
+
* 对齐与折叠算术同源——HistoryViewer writeSlice 同构的简化形:无搜索
|
|
504
|
+
* 高亮/选区面)。
|
|
505
|
+
*/
|
|
506
|
+
writeSlice(buffer, region, displayRow, seg) {
|
|
507
|
+
const styled = this.styledLines[seg.line];
|
|
508
|
+
if (styled === undefined)
|
|
509
|
+
return;
|
|
510
|
+
const start = seg.startCol;
|
|
511
|
+
const end = seg.startCol + seg.length;
|
|
512
|
+
const cursorLine = this.cursorEntry()?.lineIndex;
|
|
513
|
+
const isCursor = cursorLine === seg.line; // 光标行——整切片反色(叠加在样式段上)
|
|
514
|
+
// 分段边界:切片端点 ∪ 样式段端点(升序去重)
|
|
515
|
+
const cuts = new Set([start, end]);
|
|
516
|
+
for (const run of styled.runs) {
|
|
517
|
+
if (run.start > start && run.start < end)
|
|
518
|
+
cuts.add(run.start);
|
|
519
|
+
if (run.end > start && run.end < end)
|
|
520
|
+
cuts.add(run.end);
|
|
521
|
+
}
|
|
522
|
+
const bounds = [...cuts].sort((a, b) => a - b);
|
|
523
|
+
const baseCols = prefixDisplayWidth(styled.plain, start);
|
|
524
|
+
for (let i = 0; i + 1 < bounds.length; i++) {
|
|
525
|
+
const from = bounds[i];
|
|
526
|
+
const to = bounds[i + 1];
|
|
527
|
+
let style;
|
|
528
|
+
for (const run of styled.runs) {
|
|
529
|
+
if (run.start <= from && from < run.end) {
|
|
530
|
+
style = run.style;
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (isCursor) {
|
|
535
|
+
style = style === undefined ? CURSOR_STYLE : { ...style, inverse: true };
|
|
536
|
+
}
|
|
537
|
+
buffer.writeText(region.row + displayRow, region.col + prefixDisplayWidth(styled.plain, from) - baseCols, styled.plain.slice(from, to), style);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 副屏编舞件(07 §4.1 引擎节件 6(组件与呈现装配件) + §4.3 屏幕模型双形态):AltScreenHost——
|
|
3
|
+
* 备屏 1049 整屏切换的进出编排。
|
|
4
|
+
*
|
|
5
|
+
* 「overlay」专指主屏浮层(OverlayStack);本件是副屏路——件 8 /history
|
|
6
|
+
* 回看器与 06 /memory 的呈现形态(整屏内容值得 1049 备屏切换)。
|
|
7
|
+
*
|
|
8
|
+
* 编舞序(进出对称):
|
|
9
|
+
* - 进:主屏 suspendMain(出屏串 + 卸监听 + pause 流 + raw 复先验)→ 副屏
|
|
10
|
+
* Engine(共享同一 io 实例、screen:'alt-screen')start(1049 备屏进 + raw +
|
|
11
|
+
* 放流——start 的显式放流正是为共享 io 场景补的结构性接缝);
|
|
12
|
+
* - 出:退订转发 → 副屏 dispose(备屏出 + raw 复原 + pause)→ 主屏 resumeMain
|
|
13
|
+
* (进屏串 + raw + 放流 + 全帧重画不走 repaint:停屏期瞬时行靠全帧 + 缓冲
|
|
14
|
+
* 补吐,07 屏模型 / 件 8 条款)→ onReturn 装配钩(树重建等附加动作)。
|
|
15
|
+
*
|
|
16
|
+
* 输入路由切换:共享 io 同一时刻只有一个在听(主屏 suspendMain 已卸监听、
|
|
17
|
+
* 副屏 start 重装)——副屏 input 事件转发 content.handleEvent(回看器键盘滚动)。
|
|
18
|
+
*
|
|
19
|
+
* 鼠标降级接线(2026-09-11 鼠标解码批):open 组合 onMouseLegacy——X10 形首
|
|
20
|
+
* 达(开了 1006 的会话里到达 ⟺ 终端无 SGR 能力,判据可靠)即写 DECRST
|
|
21
|
+
* 1006/1002 关鼠标签听把选区还给终端原生(写序与出屏关序同取先 1006 后
|
|
22
|
+
* 1002)。降级闩 = per-entry:每次 open 新 Engine 新 decoder——重开重新武装、
|
|
23
|
+
* 再吃一次首达(非 SGR 终端上重开 /history 的首个鼠标事件死一次为已知代价)。
|
|
24
|
+
*
|
|
25
|
+
* 构造双注 (primary, io):Engine.io 私有无 getter——装配根同根传两参(io 须与
|
|
26
|
+
* primary 共享同一实例,否则编舞失联)。
|
|
27
|
+
*
|
|
28
|
+
* 批 10f-4 主屏绑收窄:primary 原系 Engine 型——10e 前旧设想残留(主屏
|
|
29
|
+
* TuiBackend 自持输入管线不经 Engine,07 篇屏模型双形态实装对账注)。收窄为
|
|
30
|
+
* AltScreenPrimary 最小宿主介面(本件实际消费面:挂起 / 复起 + 生命周期判定
|
|
31
|
+
* 位);Engine 语义保留给副屏自身的帧管线(生命周期四态仅副屏 Engine 消费)。
|
|
32
|
+
*/
|
|
33
|
+
import { Engine } from '../../engine/index.js';
|
|
34
|
+
/**
|
|
35
|
+
* 副屏宿主:单副屏在场(再 open 拒绝返 null——1049 无嵌套备屏)。
|
|
36
|
+
*/
|
|
37
|
+
export class AltScreenHost {
|
|
38
|
+
primary;
|
|
39
|
+
io;
|
|
40
|
+
engineOptions;
|
|
41
|
+
onReturn;
|
|
42
|
+
alt = null;
|
|
43
|
+
unsubAltInput = null;
|
|
44
|
+
constructor(primary, io, options = {}) {
|
|
45
|
+
this.primary = primary;
|
|
46
|
+
this.io = io;
|
|
47
|
+
this.engineOptions = options.engineOptions;
|
|
48
|
+
this.onReturn = options.onReturn;
|
|
49
|
+
}
|
|
50
|
+
/** 副屏在场态 */
|
|
51
|
+
get isOpen() {
|
|
52
|
+
return this.alt !== null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 进副屏:挂起主屏 → 副屏 Engine 进屏 → 输入转发接线。
|
|
56
|
+
* 拒绝位:已在副屏 / 主屏不在 running 态(无挂起对象)返 null。
|
|
57
|
+
*/
|
|
58
|
+
open(content) {
|
|
59
|
+
if (this.alt !== null)
|
|
60
|
+
return null; // 已在副屏——无嵌套备屏
|
|
61
|
+
if (this.primary.lifecycle !== 'running')
|
|
62
|
+
return null; // 主屏不在场无挂起对象
|
|
63
|
+
this.primary.suspendMain(); // 出主屏(编舞对称的进半场)
|
|
64
|
+
const base = this.engineOptions;
|
|
65
|
+
const alt = new Engine({
|
|
66
|
+
...base,
|
|
67
|
+
io: this.io,
|
|
68
|
+
screen: 'alt-screen',
|
|
69
|
+
// X10 首达降级组合柄:关鼠标签听回终端原生选区 + 链原注入柄(透传面)
|
|
70
|
+
onMouseLegacy: () => {
|
|
71
|
+
this.io.write('\x1b[?1006l\x1b[?1002l'); // DECRST(与出屏关序同取——对称律同序)
|
|
72
|
+
base?.onMouseLegacy?.();
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
alt.start(content); // 1049 备屏进 + 首帧(start 显式放流——共享 io 接缝)
|
|
76
|
+
this.unsubAltInput = alt.on('input', (event) => {
|
|
77
|
+
content.handleEvent(event); // 副屏内容终局消费(回看器键盘滚动)
|
|
78
|
+
});
|
|
79
|
+
this.alt = alt;
|
|
80
|
+
let closed = false;
|
|
81
|
+
return {
|
|
82
|
+
close: () => {
|
|
83
|
+
if (closed)
|
|
84
|
+
return;
|
|
85
|
+
closed = true;
|
|
86
|
+
this.close();
|
|
87
|
+
},
|
|
88
|
+
get closed() {
|
|
89
|
+
return closed;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** 出副屏(幂等):退订 → 副屏 dispose → 主屏 resume(全帧重画)→ onReturn 钩 */
|
|
94
|
+
close() {
|
|
95
|
+
const alt = this.alt;
|
|
96
|
+
if (alt === null)
|
|
97
|
+
return;
|
|
98
|
+
this.unsubAltInput?.(); // 先退订转发(防 dispose 过程竞发)
|
|
99
|
+
this.unsubAltInput = null;
|
|
100
|
+
this.alt = null;
|
|
101
|
+
alt.dispose(); // 备屏出 + raw 复原 + pause 流
|
|
102
|
+
this.primary.resumeMain(); // 主屏复起 + 全帧重画不走 repaint(07 条款)+ 瞬时行补吐
|
|
103
|
+
this.onReturn?.();
|
|
104
|
+
}
|
|
105
|
+
}
|