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,76 @@
|
|
|
1
|
+
import { ansiColor } from '../../engine/index.js';
|
|
2
|
+
import { ACCENT_INDEX } from '../theme.js';
|
|
3
|
+
/** 转轮帧序(braille 十帧——accent 呈现形态随组件批定形,本批定形) */
|
|
4
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
5
|
+
/** 转轮样式(accent 定值——theme 单源) */
|
|
6
|
+
const SPINNER_STYLE = Object.freeze({ fg: ansiColor(ACCENT_INDEX) });
|
|
7
|
+
/** 状态行动画件(量高恒 1 行——底部固定区状态行) */
|
|
8
|
+
export class StatusLine {
|
|
9
|
+
/** 状态变更通知(装配层接重绘请求——start/stop/setTool/setStatus/tick) */
|
|
10
|
+
onChange;
|
|
11
|
+
busy = false;
|
|
12
|
+
/** 忙态文案(agent_start 附带的活动描述——工具名在场时让位) */
|
|
13
|
+
busyText = '';
|
|
14
|
+
/** 闲态文案(setStatus last-writer-wins——忙时写入挂起、闲时呈现) */
|
|
15
|
+
idleText = '';
|
|
16
|
+
/** 实时工具名(tool_execution_start 写入;null = 无工具在场) */
|
|
17
|
+
toolName = null;
|
|
18
|
+
frameIndex = 0;
|
|
19
|
+
/** 量高:恒 1(状态行单行制) */
|
|
20
|
+
measure(width) {
|
|
21
|
+
void width;
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
/** 落位:忙态 = 转轮(accent)+ 工具段/活动文案;闲态 = 文案行(空则空行) */
|
|
25
|
+
render(buffer, region) {
|
|
26
|
+
if (this.busy) {
|
|
27
|
+
const frame = SPINNER_FRAMES[this.frameIndex % SPINNER_FRAMES.length];
|
|
28
|
+
buffer.writeText(region.row, region.col, frame, SPINNER_STYLE);
|
|
29
|
+
// 转轮后文案段(rest 自带前导空格——起点 = 转轮格 +1;工具名优先——件 3 语义)
|
|
30
|
+
const rest = this.toolName !== null ? ` ⚙ ${this.toolName} …` : this.busyText !== '' ? ` ${this.busyText}` : '';
|
|
31
|
+
if (rest !== '')
|
|
32
|
+
buffer.writeText(region.row, region.col + 1, rest);
|
|
33
|
+
}
|
|
34
|
+
else if (this.idleText !== '') {
|
|
35
|
+
buffer.writeText(region.row, region.col, this.idleText);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** 进入忙态(agent_start 驱动;text = 可选活动描述) */
|
|
39
|
+
start(text = '') {
|
|
40
|
+
this.busy = true;
|
|
41
|
+
this.busyText = text;
|
|
42
|
+
this.toolName = null;
|
|
43
|
+
this.onChange?.();
|
|
44
|
+
}
|
|
45
|
+
/** 退出忙态(agent_end 驱动——工具名一并清) */
|
|
46
|
+
stop() {
|
|
47
|
+
this.busy = false;
|
|
48
|
+
this.toolName = null;
|
|
49
|
+
this.onChange?.();
|
|
50
|
+
}
|
|
51
|
+
/** 忙态观测(装配层布防抖逻辑用) */
|
|
52
|
+
get isBusy() {
|
|
53
|
+
return this.busy;
|
|
54
|
+
}
|
|
55
|
+
/** 实时工具名(tool_execution_start → name;null 清——工具退场) */
|
|
56
|
+
setTool(name) {
|
|
57
|
+
this.toolName = name;
|
|
58
|
+
this.onChange?.();
|
|
59
|
+
}
|
|
60
|
+
/** 闲态文案(last-writer-wins——usage 行与自定义状态同载体共存) */
|
|
61
|
+
setStatus(text) {
|
|
62
|
+
this.idleText = text;
|
|
63
|
+
this.onChange?.();
|
|
64
|
+
}
|
|
65
|
+
/** 推帧(装配层定时驱动——仅忙态有意义;闲态推帧零变化零通知) */
|
|
66
|
+
tick() {
|
|
67
|
+
if (!this.busy)
|
|
68
|
+
return;
|
|
69
|
+
this.frameIndex++;
|
|
70
|
+
this.onChange?.();
|
|
71
|
+
}
|
|
72
|
+
/** 当前转轮帧观测面(测试断言帧推进用) */
|
|
73
|
+
get frame() {
|
|
74
|
+
return SPINNER_FRAMES[this.frameIndex % SPINNER_FRAMES.length];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { truncateToWidth, wrapText } from '../engine/index.js';
|
|
2
|
+
/** 单行文本件(measure 恒 1;宽字符截断不产半字) */
|
|
3
|
+
export class Text {
|
|
4
|
+
content;
|
|
5
|
+
style;
|
|
6
|
+
constructor(props) {
|
|
7
|
+
this.content = props.content;
|
|
8
|
+
this.style = props.style;
|
|
9
|
+
}
|
|
10
|
+
measure(_width) {
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
render(buffer, region) {
|
|
14
|
+
if (region.width <= 0 || region.height <= 0)
|
|
15
|
+
return;
|
|
16
|
+
buffer.writeText(region.row, region.col, truncateToWidth(this.content, region.width), this.style);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** 折行文本件(显式 \n + 列宽折行;measure = 折行行数——两段同一 wrapText 单源) */
|
|
20
|
+
export class Paragraph {
|
|
21
|
+
content;
|
|
22
|
+
style;
|
|
23
|
+
constructor(props) {
|
|
24
|
+
this.content = props.content;
|
|
25
|
+
this.style = props.style;
|
|
26
|
+
}
|
|
27
|
+
measure(width) {
|
|
28
|
+
return wrapText(this.content, Math.max(1, width)).length;
|
|
29
|
+
}
|
|
30
|
+
render(buffer, region) {
|
|
31
|
+
if (region.width <= 0 || region.height <= 0)
|
|
32
|
+
return;
|
|
33
|
+
const lines = wrapText(this.content, region.width);
|
|
34
|
+
for (let i = 0; i < Math.min(lines.length, region.height); i++) {
|
|
35
|
+
buffer.writeText(region.row + i, region.col, lines[i] ?? '', this.style);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI 呈现配色面(07 篇 §4.1 引擎节件 3「着色克制」条款的定值件)。
|
|
3
|
+
*
|
|
4
|
+
* 着色纪律单源(引擎节件 3):accent 只用于焦点指示面(现载体 = 对话输入件
|
|
5
|
+
* 焦点边框、状态行转轮、overlay 占焦族〔select 面板标题 + 补全弹层空态〕
|
|
6
|
+
* ——2026-09-07 实装对账补列),正文不混用(唯一例外 = 非聚焦摘要行的会话区分色
|
|
7
|
+
* ——非 accent 家族的第二着色位,见 sessionColor);accent 无用户配置面——
|
|
8
|
+
* v1 固定内置色板、缺省索引在本件定值(「随组件批定值」的兑现位)。
|
|
9
|
+
*/
|
|
10
|
+
import { ansiColor } from '../engine/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* accent 缺省色号(v1 定值 = cyan 6)。
|
|
13
|
+
*
|
|
14
|
+
* 选型依据:cyan 是 16 色板经典强调色,与常见终端配色(暗底浅字)对比稳定;
|
|
15
|
+
* 索引 6 属标准 8 色,8 色降级终端(07 §4.1 支持矩阵——bold 亮色映射兜底)
|
|
16
|
+
* 下仍可辨。真彩 / 256 色升级挂账引擎节件 3,届时本值随色板扩展再裁。
|
|
17
|
+
*/
|
|
18
|
+
export const ACCENT_INDEX = ansiColor(6);
|
|
19
|
+
/**
|
|
20
|
+
* FNV-1a 32 位散列(会话区分色的确定性散列源——无依赖、分布均匀、同输入
|
|
21
|
+
* 恒同输出;charCodeAt 按 UTF-16 码元——短 id 十六进制串场景无代理对歧义)。
|
|
22
|
+
*/
|
|
23
|
+
function fnv1a(text) {
|
|
24
|
+
let hash = 0x811c9dc5;
|
|
25
|
+
for (let i = 0; i < text.length; i++) {
|
|
26
|
+
hash ^= text.charCodeAt(i);
|
|
27
|
+
hash = Math.imul(hash, 0x01000193);
|
|
28
|
+
}
|
|
29
|
+
return hash >>> 0;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 会话区分色:会话短 id 散列映射 16 色板索引(07 引擎节件 3——确定性纯
|
|
33
|
+
* 函数、零配置零存储;berry themeFor「各归各色」的等价承接,主题配置面
|
|
34
|
+
* 不预造)。消费面 = 非聚焦摘要行着色(呈现面件 9),与 accent 家族互斥。
|
|
35
|
+
*
|
|
36
|
+
* 映射域 = 16 色板全域(含索引 0——黑底终端上可读性弱是已知取舍,按规范
|
|
37
|
+
* 字面全域映射;真实痛感出现时随色板升级挂账一并再裁)。
|
|
38
|
+
*/
|
|
39
|
+
export function sessionColor(sessionShortId) {
|
|
40
|
+
return ansiColor(fnv1a(sessionShortId) & 0x0f);
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* channels 件类型契约(L4 宿主固定件——02 篇席 10;机制真源 07 篇 §4.1/§4.3、
|
|
3
|
+
* 03 篇 §2.2 命令面、04 篇 §4 队列、04 篇 §9 审批归属)。
|
|
4
|
+
*
|
|
5
|
+
* 批 10a 落通道核(多会话信封分流 + 投影拉取注入 + 命令面 + ui 原语分发 +
|
|
6
|
+
* 提问队列——通道无关逻辑,零渲染依赖〔2026-09-06 改裁弃 pi-tui 后天然
|
|
7
|
+
* 满足——TUI 实装已落件内自研栈,批 10e〕)。
|
|
8
|
+
* 单向 DAG:channels → contracts, context, agent(02 §4.1——投影类型经装配侧
|
|
9
|
+
* 泛型钉入,不 import session)。
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ui 原语分发核(07 §4.3 签名定稿的通道无关编排面——2026-09-06 channels 纵切批)。
|
|
3
|
+
*
|
|
4
|
+
* 职责三块:①降级判定——通道不支持的原语按「notify 化」降级
|
|
5
|
+
* (select→input→notify、confirm→input→notify——插件不感知通道能力差异);
|
|
6
|
+
* ②阻塞原语编排——入提问队列(与审批 ask 同队列)+ 多后端并发竞速
|
|
7
|
+
* (04 §9 跨入口先答先得,败腿经 signal 撤销);③widget 会话级单槽
|
|
8
|
+
* (后写胜前写、置 null 清空——07 §4.3 setWidget 多写者语义)。
|
|
9
|
+
*
|
|
10
|
+
* 撤销面(07 §4.3):外部 signal abort 或队列收口 → 保守值收场
|
|
11
|
+
* (confirm→false / select·input→'')——曾在屏者的撤销说明行归后端
|
|
12
|
+
* signal 消费实现,核只负责 abort 传播与结算。
|
|
13
|
+
*
|
|
14
|
+
* 核级 API 一律显式 sessionId(多会话归属的机器位);ctx.ui 薄包装绑
|
|
15
|
+
* 「当前会话」归 host 装配批。后端移除(removeBackend)时在飞问询的收口
|
|
16
|
+
* 是后端自身义务(移除前 settle 或经 signal abort)——装配级事件,核不代管。
|
|
17
|
+
*/
|
|
18
|
+
import { AskQueue } from './ask-queue.js';
|
|
19
|
+
export class UiCore {
|
|
20
|
+
backendsGetter;
|
|
21
|
+
askQueue;
|
|
22
|
+
/** 审批 always 的策略表回写注入(07 §4.3 提问队列条款——装配接线) */
|
|
23
|
+
onApprovalAlways;
|
|
24
|
+
/** widget 会话级单槽(07 §4.3——per-session 恰一槽,后写胜前写) */
|
|
25
|
+
widgets = new Map();
|
|
26
|
+
constructor(backendsGetter, askQueue, onApprovalAlways) {
|
|
27
|
+
this.backendsGetter = backendsGetter;
|
|
28
|
+
this.askQueue = askQueue;
|
|
29
|
+
this.onApprovalAlways = onApprovalAlways;
|
|
30
|
+
}
|
|
31
|
+
backends() {
|
|
32
|
+
return this.backendsGetter();
|
|
33
|
+
}
|
|
34
|
+
/** 能力过滤:声明且实现俱在才 capable(防后端声明能力而缺实现——防御位) */
|
|
35
|
+
capable(cap) {
|
|
36
|
+
// 能力键与方法名同名惯例的唯一例外:approval 位的方法面叫 askApproval
|
|
37
|
+
//(与服务面同名——UiBackend 上 confirm/select/input 三件方法名即能力名)
|
|
38
|
+
const method = cap === 'approval' ? 'askApproval' : cap;
|
|
39
|
+
return this.backends().filter((b) => b.capabilities[cap] === true && typeof b[method] === 'function');
|
|
40
|
+
}
|
|
41
|
+
// ---- 非阻塞三件(纯活体层——07 §4.3 语义纪律:不落日志不入队) ----
|
|
42
|
+
/** 一次性通知:扇出全部后端(level 档识别与 info 归一归后端呈现侧) */
|
|
43
|
+
notify(message, opts) {
|
|
44
|
+
for (const b of this.backends()) {
|
|
45
|
+
if (b.capabilities.notify)
|
|
46
|
+
b.notify(message, opts);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** 状态行更新:扇出 capable 后端(last-writer-wins 天然);零 capable no-op */
|
|
50
|
+
setStatus(sessionId, status) {
|
|
51
|
+
for (const b of this.capable('setStatus'))
|
|
52
|
+
b.setStatus?.(sessionId, status);
|
|
53
|
+
}
|
|
54
|
+
/** widget 单槽:后写胜前写、置 null 清空;扇出 capable 后端 */
|
|
55
|
+
setWidget(sessionId, node) {
|
|
56
|
+
if (node === null) {
|
|
57
|
+
this.widgets.delete(sessionId);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
this.widgets.set(sessionId, { node });
|
|
61
|
+
}
|
|
62
|
+
for (const b of this.capable('setWidget'))
|
|
63
|
+
b.setWidget?.(sessionId, node);
|
|
64
|
+
}
|
|
65
|
+
/** 当前槽值(repaint 载荷消费——焦点切换重画带上该会话槽) */
|
|
66
|
+
widgetOf(sessionId) {
|
|
67
|
+
return this.widgets.get(sessionId) ?? null;
|
|
68
|
+
}
|
|
69
|
+
/** 观众探针:任一后端自报有观众(TUI 恒真 / webui 在线连接数 > 0 / 零后端即假) */
|
|
70
|
+
hasAudience() {
|
|
71
|
+
return this.backends().some((b) => b.hasAudience());
|
|
72
|
+
}
|
|
73
|
+
// ---- 阻塞三件(入提问队列——与审批 ask 同队列 per-session FIFO) ----
|
|
74
|
+
/** 是/否确认(降级链:confirm 后端 → input 后端〔(y/n) 形〕→ notify 化 + 保守值 false) */
|
|
75
|
+
confirm(sessionId, message, opts) {
|
|
76
|
+
return this.ask(sessionId, 'confirm', opts?.signal, () => false, (signal) => {
|
|
77
|
+
const direct = this.capable('confirm');
|
|
78
|
+
if (direct.length > 0) {
|
|
79
|
+
return Promise.race(direct.map((b) => b.confirm(message, { signal })));
|
|
80
|
+
}
|
|
81
|
+
const viaInput = this.capable('input');
|
|
82
|
+
if (viaInput.length > 0) {
|
|
83
|
+
// confirm→input 降级(07 §4.3 降级路径同链透传):(y/n) 形、答 y/yes 为真
|
|
84
|
+
const prompt = `${message} (y/n)`;
|
|
85
|
+
return Promise.race(viaInput.map((b) => b.input(prompt, { signal }))).then((answer) => /^y(es)?$/i.test(answer.trim()));
|
|
86
|
+
}
|
|
87
|
+
// notify 化到底:呈现问句后保守值收场(无人可答——fail-closed)
|
|
88
|
+
this.notify(message);
|
|
89
|
+
return Promise.resolve(false);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** 单选(降级链:select 后端 → input 后端〔编号列表形〕→ notify 化 + 保守值 '') */
|
|
93
|
+
select(sessionId, message, choices, opts) {
|
|
94
|
+
return this.ask(sessionId, 'select', opts?.signal, () => '', (signal) => {
|
|
95
|
+
const direct = this.capable('select');
|
|
96
|
+
if (direct.length > 0) {
|
|
97
|
+
return Promise.race(direct.map((b) => b.select(message, choices, { signal }))).then((answer) => validateChoice(answer, choices));
|
|
98
|
+
}
|
|
99
|
+
const viaInput = this.capable('input');
|
|
100
|
+
if (viaInput.length > 0) {
|
|
101
|
+
// select→input 降级(Web 通道维持同款——07 §4.3):编号列表提示、手输 value
|
|
102
|
+
const prompt = `${message}\n${choices.map((c, i) => `${i + 1}. ${c.label} → ${c.value}`).join('\n')}`;
|
|
103
|
+
return Promise.race(viaInput.map((b) => b.input(prompt, { signal }))).then((answer) => validateChoice(answer, choices));
|
|
104
|
+
}
|
|
105
|
+
this.notify(`${message}\n${choices.map((c) => `- ${c.label} → ${c.value}`).join('\n')}`);
|
|
106
|
+
return Promise.resolve('');
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/** 自由文本输入(降级链:input 后端 → notify 化 + 保守值 '') */
|
|
110
|
+
input(sessionId, message, opts) {
|
|
111
|
+
return this.ask(sessionId, 'input', opts?.signal, () => '', (signal) => {
|
|
112
|
+
const viaInput = this.capable('input');
|
|
113
|
+
if (viaInput.length > 0) {
|
|
114
|
+
return Promise.race(viaInput.map((b) => b.input(message, { placeholder: opts?.placeholder, signal })));
|
|
115
|
+
}
|
|
116
|
+
this.notify(message);
|
|
117
|
+
return Promise.resolve('');
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 审批 ask(07 §4.3 提问队列条款——与阻塞三件同队同收口律):
|
|
122
|
+
* 多后端竞速先答先得(跨入口单漏斗——decide durable 单漏斗在
|
|
123
|
+
* ApprovalService 不变);收口两语义分立(对齐 04 §9 run 信号透传
|
|
124
|
+
* ask 链)——会话关闭 / run 打断 → `'cancel'`(用户主动终止);降级
|
|
125
|
+
* 到底(全部后端无 approval capability)→ `'unavailable'`(呈现面
|
|
126
|
+
* 结构性无人可答——ApprovalService 侧落 timeout 源;2026-09-13 真模型
|
|
127
|
+
* 实测批修,修前误答 'cancel' 错标用户主动取消);`always` + 草案 →
|
|
128
|
+
* onApprovalAlways 回写;无草案 always 防御收口视同 approve(零草案
|
|
129
|
+
* 零副作用)。
|
|
130
|
+
*/
|
|
131
|
+
askApproval(sessionId, request, opts) {
|
|
132
|
+
return this.ask(sessionId, 'approval', opts?.signal, () => 'cancel', (signal) => {
|
|
133
|
+
const direct = this.capable('approval');
|
|
134
|
+
if (direct.length > 0) {
|
|
135
|
+
return Promise.race(direct.map((b) => b.askApproval(sessionId, request, { signal }))).then((answer) => this.settleApprovalAlways(answer, request));
|
|
136
|
+
}
|
|
137
|
+
// notify 化到底:呈现摘要后自报结构性无人(fail-closed——04 §9 无应答
|
|
138
|
+
// 者语义,ApprovalService 落 unavailable/timeout;与打断路保守值 'cancel'
|
|
139
|
+
// 分立——修前误答 'cancel' 致 decided 错标 cancel/user 双失真)
|
|
140
|
+
this.notify(request.summary);
|
|
141
|
+
return Promise.resolve('unavailable');
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** always 收口路:带草案回写策略表注入面;无草案防御视同 approve */
|
|
145
|
+
settleApprovalAlways(answer, request) {
|
|
146
|
+
if (answer !== 'always')
|
|
147
|
+
return answer;
|
|
148
|
+
if (request.suggestedEntry === undefined)
|
|
149
|
+
return 'approve'; // 零草案零副作用(04 §9 ③)
|
|
150
|
+
this.onApprovalAlways?.(request.suggestedEntry);
|
|
151
|
+
return 'always';
|
|
152
|
+
}
|
|
153
|
+
// ---- 收口 ----
|
|
154
|
+
/**
|
|
155
|
+
* 会话收口(通道侧——unregisterSession 联动):清提问队列(余项保守值)+
|
|
156
|
+
* 清 widget 槽。后端各自的会话呈现清理由其后端自身负责(onEnvelope 流断)。
|
|
157
|
+
*/
|
|
158
|
+
closeSession(sessionId) {
|
|
159
|
+
this.askQueue.clearSession(sessionId);
|
|
160
|
+
this.widgets.delete(sessionId);
|
|
161
|
+
}
|
|
162
|
+
/** 提问队列观察面(透传——诊断面/测试消费) */
|
|
163
|
+
pending(sessionId) {
|
|
164
|
+
return this.askQueue.pending(sessionId);
|
|
165
|
+
}
|
|
166
|
+
// ---- 编舞内核 ----
|
|
167
|
+
/**
|
|
168
|
+
* ask 统一编排:入队 → 队首呈现(多后端竞速先答先得)→ 落定出队。
|
|
169
|
+
* 入口先收副屏(07 §4.1 件 8 条款:注意力优先级 ask > 回看——ask 到来时
|
|
170
|
+
* 在场副屏先收起再入提问队列;能力缺席〔无副屏后端〕零义务跳过)。
|
|
171
|
+
* 三条收口路(全部 once):首答(含降级解析值)/ 呈现异常(保守值——
|
|
172
|
+
* fail-closed)/ 外部 signal abort 或队列取消(保守值)。
|
|
173
|
+
*/
|
|
174
|
+
ask(sessionId, kind, externalSignal, conservative, present) {
|
|
175
|
+
// ask 强制收起(件 8 条款——批 10f-4):扇出可选钩,先于入队
|
|
176
|
+
for (const b of this.backends())
|
|
177
|
+
b.collapseAltScreen?.();
|
|
178
|
+
let resolve;
|
|
179
|
+
const promise = new Promise((r) => {
|
|
180
|
+
resolve = r;
|
|
181
|
+
});
|
|
182
|
+
let done = false;
|
|
183
|
+
// 内部 signal:败腿撤销 + 队列收口的统一传播位(后端只认这一个信号源——
|
|
184
|
+
// 外部 signal 经本核折入 finish,不直传后端)
|
|
185
|
+
const controller = new AbortController();
|
|
186
|
+
const finish = (value) => {
|
|
187
|
+
if (done)
|
|
188
|
+
return;
|
|
189
|
+
done = true;
|
|
190
|
+
controller.abort(); // 败腿撤销传播(已落定后端再收 abort 是无害 no-op)
|
|
191
|
+
resolve(value);
|
|
192
|
+
this.askQueue.settled(sessionId);
|
|
193
|
+
};
|
|
194
|
+
const externalAbort = () => finish(conservative());
|
|
195
|
+
externalSignal?.addEventListener('abort', externalAbort, { once: true });
|
|
196
|
+
this.askQueue.enqueue(sessionId, kind, {
|
|
197
|
+
start: () => {
|
|
198
|
+
present(controller.signal).then(finish, () => finish(conservative()));
|
|
199
|
+
},
|
|
200
|
+
cancel: () => finish(conservative()),
|
|
201
|
+
});
|
|
202
|
+
return promise;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** select 降级答案校验:手输不在 value 集即保守值 ''(fail-closed——不猜最近匹配) */
|
|
206
|
+
function validateChoice(answer, choices) {
|
|
207
|
+
const trimmed = answer.trim();
|
|
208
|
+
return choices.some((c) => c.value === trimmed) ? trimmed : '';
|
|
209
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* checkpoint 捕获执行(05 §5.3 批 15d——walk+hash+落仓+裁剪的单遍编舞)。
|
|
3
|
+
*
|
|
4
|
+
* 捕获序:枚举 walk 域(walk.ts 判据)→ 逐文件单遍读+哈希(读一次同时喂
|
|
5
|
+
* blob 写与 manifest 条目——无双读)→ blob 落仓(内容寻址去重)→ manifest
|
|
6
|
+
* 落仓 → prune(per workspace 保留帽 + blob 引用计数 GC)。
|
|
7
|
+
*
|
|
8
|
+
* 失败语义:walk/读文件/blob 写任一失败抛 CHECKPOINT_CAPTURE_FAILED——
|
|
9
|
+
* gate 侧折 fail-closed block(快照是 rewind 语义承诺的前提,拍不了就放行
|
|
10
|
+
* 变异 = 伪承诺)。空工作区合法(files: [] 的 manifest——恢复到空也是回退点)。
|
|
11
|
+
*/
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
13
|
+
import { contentHash } from './store.js';
|
|
14
|
+
import { readWorkspaceFile, walkWorkspaceFiles } from './walk.js';
|
|
15
|
+
/** 组装捕获面(openCheckpointStore 的伴生件——装配根一并接线) */
|
|
16
|
+
export function createCapture(store, opts = {}) {
|
|
17
|
+
const now = opts.now ?? Date.now;
|
|
18
|
+
const newId = opts.newId ?? randomUUID;
|
|
19
|
+
return async ({ sessionId, boundarySeq, workspaceRoot, trigger }) => {
|
|
20
|
+
// 段一:枚举 + 单遍读哈希(IO 失败由 walk/readWorkspaceFile 折 CAPTURE_FAILED)
|
|
21
|
+
const walked = await walkWorkspaceFiles(workspaceRoot);
|
|
22
|
+
const entries = [];
|
|
23
|
+
for (const file of walked) {
|
|
24
|
+
const content = await readWorkspaceFile(file.absPath);
|
|
25
|
+
const hash = contentHash(content);
|
|
26
|
+
await store.writeBlob(hash, content);
|
|
27
|
+
entries.push({ path: file.path, hash, bytes: content.byteLength });
|
|
28
|
+
}
|
|
29
|
+
// 段二:manifest 落仓(id = uuid——/rewind 作用目标键)
|
|
30
|
+
const manifest = {
|
|
31
|
+
id: newId(),
|
|
32
|
+
sessionId,
|
|
33
|
+
boundarySeq,
|
|
34
|
+
workspaceRoot,
|
|
35
|
+
capturedAt: now(),
|
|
36
|
+
trigger,
|
|
37
|
+
files: entries,
|
|
38
|
+
};
|
|
39
|
+
await store.saveManifest(manifest);
|
|
40
|
+
// 段三:裁剪编舞(每次捕获后跑——保留帽 + 引用计数 GC;失败不回滚本次
|
|
41
|
+
// 落仓:prune 是维护面,异常上抛交调用方诊断,快照本体已原子在册)
|
|
42
|
+
await store.prune();
|
|
43
|
+
return manifest;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* checkpoint 域错误码注册(05 §5.3——CHECKPOINT_ 前缀族;02 §5.3 批 15d
|
|
3
|
+
* 明列四码)。
|
|
4
|
+
*
|
|
5
|
+
* 码语义分层:CAPTURE_FAILED 管 pre-mutation 捕获 fail-closed(快照是
|
|
6
|
+
* rewind 语义承诺的前提);NOT_FOUND 管回退点缺席守卫;STORE_CORRUPT 管
|
|
7
|
+
* 快照仓坏形 fail-loud(宁拒不误读);RESTORE_FAILED 管恢复执行 IO 失败
|
|
8
|
+
* (pre-rewind 保底快照在场可重跑收敛)。本文件由模块公开面 index.ts
|
|
9
|
+
* 引入(注册纪律:import 发生才注册)。
|
|
10
|
+
*/
|
|
11
|
+
import { registerErrorCodes } from '../contracts/index.js';
|
|
12
|
+
registerErrorCodes([
|
|
13
|
+
{
|
|
14
|
+
code: 'CHECKPOINT_CAPTURE_FAILED',
|
|
15
|
+
module: 'checkpoint',
|
|
16
|
+
description: 'pre-mutation 捕获失败——守门段 fail-closed block 位(拍不了就放行变异 = 伪承诺;含 walk 超文件帽)',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
code: 'CHECKPOINT_NOT_FOUND',
|
|
20
|
+
module: 'checkpoint',
|
|
21
|
+
description: '回退点缺席——已裁剪或坏 id(/rewind 作用目标守卫)',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
code: 'CHECKPOINT_STORE_CORRUPT',
|
|
25
|
+
module: 'checkpoint',
|
|
26
|
+
description: '快照仓坏形——manifest 坏形或 blob 哈希不符,fail-loud 拒误读',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
code: 'CHECKPOINT_RESTORE_FAILED',
|
|
30
|
+
module: 'checkpoint',
|
|
31
|
+
description: '恢复执行 IO 失败——pre-rewind 保底快照在场,重跑同 manifest 幂等收敛',
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /rewind 命令处理器(05 §5.3 批 15d——两段事务的用户呈现面)。
|
|
3
|
+
*
|
|
4
|
+
* 形态律与 /goal、/tick 同族:argv → 人读文本;服务面守卫错(BaseError:
|
|
5
|
+
* NOT_FOUND / STORE_CORRUPT / RESTORE_FAILED)折文本不抛(命令面是用户面
|
|
6
|
+
* 不是异常面)。动词三形:
|
|
7
|
+
* /rewind list 按当前会话工作区锚列点(id/时刻/触发形/规模)
|
|
8
|
+
* /rewind preview <id> 段一 preview——恢复 N/删除 M/不动 U 零改动对账
|
|
9
|
+
* /rewind restore <id> 段二 restore——保底拍+文件恢复+fork 三步序
|
|
10
|
+
*
|
|
11
|
+
* TUI 命令注册已落(批 19c-4 ctx.channels.registerCommand);adopt 切前台
|
|
12
|
+
* 编舞 v1 未落——restore 回执的 forkedSessionId 先经输出面呈报,焦点切换随
|
|
13
|
+
* TUI adopt 命令立题〔2026-09-11 勘正:原注指向已飞的批 12,失锚〕。
|
|
14
|
+
*/
|
|
15
|
+
import { BaseError } from '../contracts/index.js';
|
|
16
|
+
import { previewRewind, restoreRewind } from './restore.js';
|
|
17
|
+
export const REWIND_USAGE = [
|
|
18
|
+
'用法:/rewind list —— 列当前工作区的回退点',
|
|
19
|
+
' /rewind preview <id> —— 预演(恢复 N/删除 M/不动 U,零改动)',
|
|
20
|
+
' /rewind restore <id> —— 回退(保底快照 → 文件恢复 → fork 新会话)',
|
|
21
|
+
].join('\n');
|
|
22
|
+
/** manifest 行渲染(list 用——id 截 8 位短形 + 触发形中文 + 规模) */
|
|
23
|
+
function manifestLine(id, capturedAt, trigger, files, boundarySeq) {
|
|
24
|
+
const when = new Date(capturedAt).toISOString().replace('T', ' ').slice(0, 19);
|
|
25
|
+
const kind = trigger === 'mutation' ? '变异前拍' : '回退保底拍';
|
|
26
|
+
return `- ${id.slice(0, 8)}… ${when}〔${kind}〕${files} 文件 · 回退点 seq=${boundarySeq}`;
|
|
27
|
+
}
|
|
28
|
+
/** /rewind 处理器(argv → 人读文本;守卫错折文本) */
|
|
29
|
+
export async function runRewindCommand(argv, deps) {
|
|
30
|
+
const [verb, ...rest] = argv;
|
|
31
|
+
try {
|
|
32
|
+
switch (verb) {
|
|
33
|
+
case 'list': {
|
|
34
|
+
const ctx = deps.session.contextOf(deps.sessionId);
|
|
35
|
+
if (ctx === undefined || ctx.workspaceRoot === '') {
|
|
36
|
+
return '当前会话无工作区锚——无从列点(快照特性按工作区适用)。';
|
|
37
|
+
}
|
|
38
|
+
const rows = (await deps.store.listManifests()).filter((m) => m.workspaceRoot === ctx.workspaceRoot);
|
|
39
|
+
if (rows.length === 0)
|
|
40
|
+
return '本工作区暂无回退点(首个变异前拍在工作区内的写工具执行时落)。';
|
|
41
|
+
const lines = rows.map((m) => manifestLine(m.id, m.capturedAt, m.trigger, m.files.length, m.boundarySeq));
|
|
42
|
+
return `本工作区共 ${rows.length} 个回退点(新在前):\n${lines.join('\n')}`;
|
|
43
|
+
}
|
|
44
|
+
case 'preview': {
|
|
45
|
+
const id = rest[0];
|
|
46
|
+
if (!id)
|
|
47
|
+
return `缺回退点 id。\n${REWIND_USAGE}`;
|
|
48
|
+
const result = await previewRewind(deps.store, id);
|
|
49
|
+
return [
|
|
50
|
+
`回退点 ${result.manifest.id.slice(0, 8)}…(${new Date(result.manifest.capturedAt).toISOString().replace('T', ' ').slice(0, 19)} 拍摄,${result.manifest.files.length} 文件,回退点 seq=${result.manifest.boundarySeq})`,
|
|
51
|
+
`预演对账(零改动):恢复 ${result.restoreCount} · 删除 ${result.deleteCount} · 不动 ${result.untouchedCount}`,
|
|
52
|
+
`确认执行:/rewind restore ${result.manifest.id}`,
|
|
53
|
+
].join('\n');
|
|
54
|
+
}
|
|
55
|
+
case 'restore': {
|
|
56
|
+
const id = rest[0];
|
|
57
|
+
if (!id)
|
|
58
|
+
return `缺回退点 id。\n${REWIND_USAGE}`;
|
|
59
|
+
const receipt = await restoreRewind(deps, id);
|
|
60
|
+
const forkLine = receipt.forkedSessionId !== undefined
|
|
61
|
+
? `已 fork 新会话 ${receipt.forkedSessionId}(旧史保留,自回退点净边界起跑)。`
|
|
62
|
+
: `fork 未成:${receipt.vetoReason}(文件已恢复——痕迹链完整,可重试或手动接续)`;
|
|
63
|
+
return [
|
|
64
|
+
`已回退至 ${receipt.id.slice(0, 8)}…:恢复 ${receipt.restoredCount} · 删除 ${receipt.deletedCount} · 不动 ${receipt.untouchedCount}`,
|
|
65
|
+
`保底快照 ${receipt.preRewindId.slice(0, 8)}…(本次回退自身可回退)。`,
|
|
66
|
+
forkLine,
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
|
69
|
+
case undefined:
|
|
70
|
+
case 'help':
|
|
71
|
+
return REWIND_USAGE;
|
|
72
|
+
default:
|
|
73
|
+
return `未知动词「${verb}」。\n${REWIND_USAGE}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
// 守卫错折文本(命令面是用户面——BaseError 码与人读原因直呈)
|
|
78
|
+
if (err instanceof BaseError)
|
|
79
|
+
return `${err.code}:${err.message}`;
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组装守门监听器(工厂——监听器闭包内持 per-session 边界游标 Map,跨调用
|
|
3
|
+
* 存活;同一 dispatch 生命周期内复用一个实例,重复挂载 = 各自独立游标)。
|
|
4
|
+
*/
|
|
5
|
+
export function createCheckpointGate(deps) {
|
|
6
|
+
const warn = deps.warn ?? ((message) => console.warn(message));
|
|
7
|
+
// per-session 上次捕获边界(per-run 判据的游标——turn 在飞边界不动即同 run)
|
|
8
|
+
const lastCapturedBoundary = new Map();
|
|
9
|
+
return async (input, next) => {
|
|
10
|
+
// 判据 1:只管写意图(read 工具不触发快照——03 §2.3 effect 面)
|
|
11
|
+
if (input.tool.effect !== 'write')
|
|
12
|
+
return next(input);
|
|
13
|
+
// 判据 2:无会话键 = 非本件域(测试/系统调用形态——静默放行)
|
|
14
|
+
const sessionId = input.sessionId;
|
|
15
|
+
if (sessionId === undefined)
|
|
16
|
+
return next(input);
|
|
17
|
+
// 判据 2b:会话语境不可解(contextOf undefined)同上放行——不 warn
|
|
18
|
+
// (会话未知是装配面事实,非配置缺口;两分见判据 3)
|
|
19
|
+
const ctx = deps.session.contextOf(sessionId);
|
|
20
|
+
if (ctx === undefined)
|
|
21
|
+
return next(input);
|
|
22
|
+
// 判据 3:无 workspaceRoot 锚 = 配置缺口——放行 + warn(非仓故障)
|
|
23
|
+
if (ctx.workspaceRoot === '') {
|
|
24
|
+
warn(`[checkpoint] 会话 ${sessionId} 无工作区锚——快照不适用,写工具放行(配置缺口:会话建时未带 workspaceRoot)。`);
|
|
25
|
+
return next(input);
|
|
26
|
+
}
|
|
27
|
+
// 判据 4:边界未推进 = 同 run 段——不重拍(per-run 一 manifest)
|
|
28
|
+
const boundary = ctx.lastClosedBoundary;
|
|
29
|
+
const last = lastCapturedBoundary.get(sessionId);
|
|
30
|
+
if (last !== undefined && boundary <= last)
|
|
31
|
+
return next(input);
|
|
32
|
+
// 捕获(mutation 拍):失败折 fail-closed block——reason 首缀专属码
|
|
33
|
+
try {
|
|
34
|
+
await deps.capture({ sessionId, boundarySeq: boundary, workspaceRoot: ctx.workspaceRoot, trigger: 'mutation' });
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
38
|
+
input.outcome = {
|
|
39
|
+
action: 'block',
|
|
40
|
+
reason: `[CHECKPOINT_CAPTURE_FAILED] pre-mutation 快照失败,fail-closed 拒变异(快照是回退承诺的前提):${message}`,
|
|
41
|
+
};
|
|
42
|
+
return input; // 不调 next:短路整链(守门段 block 语义)
|
|
43
|
+
}
|
|
44
|
+
// 成功:推进游标(后续同 run 写工具不再重拍)+ 放行交棒
|
|
45
|
+
lastCapturedBoundary.set(sessionId, boundary);
|
|
46
|
+
return next(input);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* checkpoint 模块公开面(05 §5.3 批 15d——工作区快照/回退件)。
|
|
3
|
+
*
|
|
4
|
+
* 消费面(批 19c-4 装载态入册兑现):gate 监听器(core:checkpoint 件经
|
|
5
|
+
* 插件钩子正门 ctx.on('tools_pre_execute') 挂 waterfall——装载期注册先于
|
|
6
|
+
* per-session safety 行,序差无害注记见 core-plugins.ts 装配序注记)+
|
|
7
|
+
* /rewind 命令(ctx.channels.registerCommand——发起会话 = 焦点会话;
|
|
8
|
+
* adopt 切前台编舞 v1 未落——restore 回执的 forkedSessionId 先经命令输出
|
|
9
|
+
* 面呈报,焦点切换随 TUI adopt 命令立题〔2026-09-11 遗漏扫描批勘正——
|
|
10
|
+
* 原注指向已飞的 run 入口批,失锚〕)。codes.js 的 import 副作用 = 错误码
|
|
11
|
+
* 注册纪律(import 发生才注册)。
|
|
12
|
+
*/
|
|
13
|
+
import './codes.js';
|
|
14
|
+
export { CHECKPOINT_RETENTION_PER_WORKSPACE, CHECKPOINT_WALK_FILE_CAP, } from './types.js';
|
|
15
|
+
export { readWorkspaceFile, walkWorkspaceFiles } from './walk.js';
|
|
16
|
+
export { contentHash, openCheckpointStore } from './store.js';
|
|
17
|
+
export { createCapture } from './capture.js';
|
|
18
|
+
export { createCheckpointGate } from './gate.js';
|
|
19
|
+
export { previewRewind, restoreRewind, } from './restore.js';
|
|
20
|
+
export { REWIND_USAGE, runRewindCommand } from './command.js';
|