one-code-extension 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +32 -23
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +27 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +58 -31
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +123 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +515 -0
- package/extensions/background/index.ts +514 -236
- package/extensions/background/loop-fire.ts +136 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +52 -0
- package/extensions/background/wakeup.ts +159 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +88 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +229 -71
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +446 -58
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +216 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/index.ts +24 -5
- package/extensions/context-management/index.ts +25 -0
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +72 -19
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +337 -26
- package/extensions/file-tracker/replay.ts +96 -10
- package/extensions/file-tracker/restore.ts +63 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +32 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +198 -31
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +13 -5
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/lib/agent-cron.ts +130 -0
- package/extensions/lib/agent-loader.ts +15 -1
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +42 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +78 -0
- package/extensions/lib/context-stack.ts +81 -0
- package/extensions/lib/deferred.ts +219 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/local-command.ts +47 -13
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +174 -0
- package/extensions/lib/model-catalog/models-dev.json +18297 -0
- package/extensions/lib/model-catalog/openrouter.json +5252 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +308 -64
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +21 -2
- package/extensions/lib/plan-mode-channels.ts +20 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/process-tree.ts +131 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +364 -58
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +439 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +13 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +113 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +149 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +143 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +70 -4
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +87 -5
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +209 -71
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +151 -67
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1300 -381
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +6 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +156 -69
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +61 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +12 -7
- package/extensions/recap/index.ts +65 -60
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +421 -101
- package/extensions/skill/invoke.ts +128 -18
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +116 -23
- package/extensions/subagents/announce.ts +69 -0
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +76 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1493 -824
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +2 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/runner.ts +108 -80
- package/extensions/subagents/runs.ts +39 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +131 -47
- package/extensions/system-prompt/template.ts +24 -50
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +175 -13
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/announce.ts +16 -0
- package/extensions/tool-search/index.ts +189 -41
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +105 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +145 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +29 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +121 -39
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +18 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /permissions panel state machine (pure), Claude Code's panel (findings §33).
|
|
3
|
+
*
|
|
4
|
+
* `applyPanelKey` mutates the state in place (the wiring owns the object and
|
|
5
|
+
* repaints after every key) and returns an effect when the key needs I/O:
|
|
6
|
+
* saving or deleting a rule, editing the environment, or closing the panel,
|
|
7
|
+
* at which point the wiring reads the approvals and retries off the state.
|
|
8
|
+
*
|
|
9
|
+
* Key routing, as in Claude Code:
|
|
10
|
+
* - Recently denied: Enter toggles approval, `r` toggles retry (which also
|
|
11
|
+
* approves). Both act only when the panel closes.
|
|
12
|
+
* - Allow / Ask / Deny / Auto mode: Enter opens the selected row (`Add a new
|
|
13
|
+
* rule…`, or a rule's detail). Typing any printable key except `j k m i r`
|
|
14
|
+
* and space starts a search with that key, `/` starts an empty one; `j`/`k`
|
|
15
|
+
* move the cursor. Backspace edits the search, Esc clears it.
|
|
16
|
+
* - Workspace: Enter on a directory offers to remove it, on `Add directory…`
|
|
17
|
+
* asks for a path, then whether to keep it for this session or remember it.
|
|
18
|
+
* - ←/→ and Tab switch tabs everywhere outside a dialog; Esc closes the panel.
|
|
19
|
+
*
|
|
20
|
+
* The Auto mode tab differs from Claude Code's in one way: the built-in rules
|
|
21
|
+
* of a section are always in effect, so their row explains that instead of
|
|
22
|
+
* offering to switch them off (working-docs/decisions/auto-mode.md).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { PanelKey } from "./keys.ts";
|
|
26
|
+
|
|
27
|
+
export type Tab = "recent" | "allow" | "ask" | "deny" | "automode" | "workspace";
|
|
28
|
+
export const TABS: Tab[] = ["recent", "allow", "ask", "deny", "automode", "workspace"];
|
|
29
|
+
export type RuleTab = "allow" | "ask" | "deny";
|
|
30
|
+
export const RULE_TABS: RuleTab[] = ["allow", "ask", "deny"];
|
|
31
|
+
/** Tabs with a search box. */
|
|
32
|
+
export type SearchTab = RuleTab | "automode";
|
|
33
|
+
|
|
34
|
+
export type AutoSection = "allow" | "soft_deny" | "hard_deny";
|
|
35
|
+
export const AUTO_SECTIONS: AutoSection[] = ["allow", "soft_deny", "hard_deny"];
|
|
36
|
+
/** Claude Code's names for the auto-mode sections. */
|
|
37
|
+
export const AUTO_SECTION_LABELS: Record<AutoSection, string> = { allow: "Soft allow", soft_deny: "Soft deny", hard_deny: "Hard deny" };
|
|
38
|
+
|
|
39
|
+
export interface DenialRow {
|
|
40
|
+
id: number;
|
|
41
|
+
display: string;
|
|
42
|
+
/** The grounded rule name, shown dim after the call. */
|
|
43
|
+
rule?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Where an entry lives, and whether the panel may change it. */
|
|
47
|
+
interface Sourced {
|
|
48
|
+
/** Unique within its list: the source file and the text. */
|
|
49
|
+
key: string;
|
|
50
|
+
/** `From One Code user settings (~/.onecode/settings.json)`. */
|
|
51
|
+
sourceLabel: string;
|
|
52
|
+
/** Only One Code's own files (and this session's grants) can be edited here. */
|
|
53
|
+
editable: boolean;
|
|
54
|
+
/** Why an uneditable entry cannot be changed here, and where to change it. */
|
|
55
|
+
readOnlyNote?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface RuleRow extends Sourced {
|
|
59
|
+
behavior: RuleTab;
|
|
60
|
+
raw: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AutoEntryRow extends Sourced {
|
|
64
|
+
section: AutoSection;
|
|
65
|
+
text: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface AutoModeView {
|
|
69
|
+
/** How many built-in rules each section holds. */
|
|
70
|
+
builtins: Record<AutoSection, number>;
|
|
71
|
+
/** The user's rules, in section order. */
|
|
72
|
+
entries: AutoEntryRow[];
|
|
73
|
+
environment: {
|
|
74
|
+
/** The environment the classifier gets, first lines first. */
|
|
75
|
+
lines: string[];
|
|
76
|
+
/** `Built-in default`, or where the entries come from. */
|
|
77
|
+
summary: string;
|
|
78
|
+
/** True when the classifier gets only the built-in default. */
|
|
79
|
+
isDefault: boolean;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface WorkspaceDirRow extends Sourced {
|
|
84
|
+
path: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface WorkspaceView {
|
|
88
|
+
/** The session's original working directory, always in the workspace. */
|
|
89
|
+
cwd: string;
|
|
90
|
+
dirs: WorkspaceDirRow[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface Destination {
|
|
94
|
+
id: string;
|
|
95
|
+
label: string;
|
|
96
|
+
description: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface PanelView {
|
|
100
|
+
denials: DenialRow[];
|
|
101
|
+
/** Each tab's rules, already sorted. */
|
|
102
|
+
rules: Record<RuleTab, RuleRow[]>;
|
|
103
|
+
autoMode: AutoModeView;
|
|
104
|
+
workspace: WorkspaceView;
|
|
105
|
+
/** Check a typed directory: its absolute path, or why it cannot be added. */
|
|
106
|
+
validateDir: (input: string) => { path: string } | { error: string };
|
|
107
|
+
/** Where a new permission rule can be saved, first one first. */
|
|
108
|
+
destinations: Destination[];
|
|
109
|
+
/** Why a typed permission rule cannot be saved, or undefined when it parses. */
|
|
110
|
+
ruleError: (raw: string) => string | undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type Dialog =
|
|
114
|
+
| { kind: "addRule"; behavior: RuleTab; draft: string }
|
|
115
|
+
| { kind: "saveRule"; behavior: RuleTab; rule: string; cursor: number }
|
|
116
|
+
| { kind: "ruleDetail"; behavior: RuleTab; key: string; cursor: number }
|
|
117
|
+
| { kind: "pickSection"; cursor: number }
|
|
118
|
+
/** A new rule, or an edit of `editKey`. */
|
|
119
|
+
| { kind: "autoRuleInput"; section: AutoSection; draft: string; editKey?: string }
|
|
120
|
+
/** Edit / Delete for an editable entry; an explanation for a read-only one. */
|
|
121
|
+
| { kind: "autoRuleDetail"; key: string; cursor: number }
|
|
122
|
+
| { kind: "autoRuleDelete"; key: string; cursor: number }
|
|
123
|
+
| { kind: "builtinsInfo"; section: AutoSection }
|
|
124
|
+
| { kind: "envConfirm"; cursor: number }
|
|
125
|
+
| { kind: "addDir"; draft: string }
|
|
126
|
+
/** For this session, remember it, or cancel. */
|
|
127
|
+
| { kind: "rememberDir"; path: string; cursor: number }
|
|
128
|
+
| { kind: "removeDir"; key: string; cursor: number };
|
|
129
|
+
|
|
130
|
+
export interface PanelState {
|
|
131
|
+
tab: Tab;
|
|
132
|
+
cursor: Record<Tab, number>;
|
|
133
|
+
search: Record<SearchTab, string>;
|
|
134
|
+
/** A tab's search box has the keyboard (typed text feeds it). */
|
|
135
|
+
searching: boolean;
|
|
136
|
+
/** Denial ids marked approved (✔). */
|
|
137
|
+
approved: Set<number>;
|
|
138
|
+
/** Denial ids marked for retry; each is also approved. */
|
|
139
|
+
retry: Set<number>;
|
|
140
|
+
dialog?: Dialog;
|
|
141
|
+
notice?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type PanelEffect =
|
|
145
|
+
| { kind: "close" }
|
|
146
|
+
| { kind: "addRule"; behavior: RuleTab; rule: string; destination: string }
|
|
147
|
+
| { kind: "deleteRule"; behavior: RuleTab; key: string }
|
|
148
|
+
| { kind: "addAutoRule"; section: AutoSection; text: string }
|
|
149
|
+
| { kind: "editAutoRule"; key: string; text: string }
|
|
150
|
+
| { kind: "deleteAutoRule"; key: string }
|
|
151
|
+
| { kind: "editEnvironment" }
|
|
152
|
+
| { kind: "addDir"; path: string; remember: boolean }
|
|
153
|
+
| { kind: "removeDir"; key: string };
|
|
154
|
+
|
|
155
|
+
export type RuleListRow = { kind: "add" } | { kind: "rule"; rule: RuleRow };
|
|
156
|
+
export type WorkspaceListRow = { kind: "dir"; dir: WorkspaceDirRow } | { kind: "add" };
|
|
157
|
+
export type AutoListRow = { kind: "add" } | { kind: "builtins"; section: AutoSection } | { kind: "entry"; entry: AutoEntryRow } | { kind: "environment" };
|
|
158
|
+
|
|
159
|
+
export function initialPanelState(hasDenials: boolean): PanelState {
|
|
160
|
+
return {
|
|
161
|
+
// Claude Code opens on Recently denied when there is something to act on.
|
|
162
|
+
tab: hasDenials ? "recent" : "allow",
|
|
163
|
+
cursor: { recent: 0, allow: 0, ask: 0, deny: 0, automode: 0, workspace: 0 },
|
|
164
|
+
search: { allow: "", ask: "", deny: "", automode: "" },
|
|
165
|
+
searching: false,
|
|
166
|
+
approved: new Set(),
|
|
167
|
+
retry: new Set(),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const isRuleTab = (tab: Tab): tab is RuleTab => (RULE_TABS as Tab[]).includes(tab);
|
|
172
|
+
const isSearchTab = (tab: Tab): tab is SearchTab => tab !== "recent" && tab !== "workspace";
|
|
173
|
+
|
|
174
|
+
/** The Workspace tab's rows: the added directories, then `Add directory…` (Claude Code's order). */
|
|
175
|
+
export function workspaceListRows(view: PanelView): WorkspaceListRow[] {
|
|
176
|
+
return [...view.workspace.dirs.map((dir): WorkspaceListRow => ({ kind: "dir", dir })), { kind: "add" }];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** A rule tab's rows: `Add a new rule…` (hidden while a query filters the list), then the matching rules. */
|
|
180
|
+
export function ruleListRows(state: PanelState, view: PanelView, tab: RuleTab): RuleListRow[] {
|
|
181
|
+
const query = state.search[tab].toLowerCase();
|
|
182
|
+
const rules = view.rules[tab].filter((rule) => !query || rule.raw.toLowerCase().includes(query)).map((rule) => ({ kind: "rule" as const, rule }));
|
|
183
|
+
return query ? rules : [{ kind: "add" }, ...rules];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The Auto mode tab's rows: `Add a new rule…`, then per section its built-in
|
|
188
|
+
* row and the user's entries, then the environment. A query keeps only the
|
|
189
|
+
* matching entries.
|
|
190
|
+
*/
|
|
191
|
+
export function autoListRows(state: PanelState, view: PanelView): AutoListRow[] {
|
|
192
|
+
const query = state.search.automode.toLowerCase();
|
|
193
|
+
const entries = view.autoMode.entries;
|
|
194
|
+
if (query) return entries.filter((entry) => entry.text.toLowerCase().includes(query)).map((entry) => ({ kind: "entry", entry }));
|
|
195
|
+
return [
|
|
196
|
+
{ kind: "add" },
|
|
197
|
+
...AUTO_SECTIONS.flatMap((section): AutoListRow[] => [
|
|
198
|
+
{ kind: "builtins", section },
|
|
199
|
+
...entries.filter((entry) => entry.section === section).map((entry): AutoListRow => ({ kind: "entry", entry })),
|
|
200
|
+
]),
|
|
201
|
+
{ kind: "environment" },
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function rowCount(state: PanelState, view: PanelView): number {
|
|
206
|
+
if (state.tab === "recent") return view.denials.length;
|
|
207
|
+
if (state.tab === "automode") return autoListRows(state, view).length;
|
|
208
|
+
if (state.tab === "workspace") return workspaceListRows(view).length;
|
|
209
|
+
return ruleListRows(state, view, state.tab).length;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** How many options a dialog's cursor moves over; 0 for a dialog without a choice list. */
|
|
213
|
+
function dialogOptions(dialog: Dialog, view: PanelView): number {
|
|
214
|
+
switch (dialog.kind) {
|
|
215
|
+
case "saveRule":
|
|
216
|
+
return view.destinations.length;
|
|
217
|
+
case "pickSection":
|
|
218
|
+
return AUTO_SECTIONS.length;
|
|
219
|
+
case "ruleDetail":
|
|
220
|
+
case "autoRuleDetail":
|
|
221
|
+
case "autoRuleDelete":
|
|
222
|
+
case "envConfirm":
|
|
223
|
+
case "removeDir":
|
|
224
|
+
return 2;
|
|
225
|
+
case "rememberDir":
|
|
226
|
+
return 3;
|
|
227
|
+
default:
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function clampPanelState(state: PanelState, view: PanelView): void {
|
|
233
|
+
const max = Math.max(0, rowCount(state, view) - 1);
|
|
234
|
+
state.cursor[state.tab] = Math.min(Math.max(0, state.cursor[state.tab]), max);
|
|
235
|
+
const dialog = state.dialog;
|
|
236
|
+
if (dialog && "cursor" in dialog) dialog.cursor = Math.min(Math.max(0, dialog.cursor), Math.max(0, dialogOptions(dialog, view) - 1));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** The rule a detail dialog shows, resolved against the current view (it may have been reloaded). */
|
|
240
|
+
export function detailRule(dialog: Extract<Dialog, { kind: "ruleDetail" }>, view: PanelView): RuleRow | undefined {
|
|
241
|
+
return view.rules[dialog.behavior].find((rule) => rule.key === dialog.key);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** The workspace directory a dialog names, resolved against the current view. */
|
|
245
|
+
export function workspaceDir(key: string, view: PanelView): WorkspaceDirRow | undefined {
|
|
246
|
+
return view.workspace.dirs.find((dir) => dir.key === key);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The auto-mode entry a dialog names, resolved against the current view. */
|
|
250
|
+
export function autoEntry(key: string, view: PanelView): AutoEntryRow | undefined {
|
|
251
|
+
return view.autoMode.entries.find((entry) => entry.key === key);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const toggle = (set: Set<number>, id: number): boolean => {
|
|
255
|
+
if (set.delete(id)) return false;
|
|
256
|
+
set.add(id);
|
|
257
|
+
return true;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/** Keys that do not start a search in Claude Code (vim navigation, reserved letters, space). */
|
|
261
|
+
const NO_SEARCH_KEYS = new Set(["j", "k", "m", "i", "r", " "]);
|
|
262
|
+
|
|
263
|
+
/** Move a dialog's cursor, wrapping; `y`/`n` pick the first or second of two options. */
|
|
264
|
+
function moveDialogCursor(dialog: Dialog & { cursor: number }, key: PanelKey, options: number): void {
|
|
265
|
+
if (options === 0) return;
|
|
266
|
+
if (key.kind === "up") dialog.cursor = (dialog.cursor - 1 + options) % options;
|
|
267
|
+
else if (key.kind === "down") dialog.cursor = (dialog.cursor + 1) % options;
|
|
268
|
+
else if (key.kind === "text" && options === 2 && /^[yn]$/i.test(key.text)) dialog.cursor = key.text.toLowerCase() === "y" ? 0 : 1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Typing into a draft: text appends, backspace deletes. True when the key was a draft edit. */
|
|
272
|
+
function editDraft(dialog: { draft: string }, key: PanelKey): boolean {
|
|
273
|
+
if (key.kind === "text") dialog.draft += key.text;
|
|
274
|
+
else if (key.kind === "backspace") dialog.draft = dialog.draft.slice(0, -1);
|
|
275
|
+
else return false;
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function applyDialogKey(state: PanelState, dialog: Dialog, key: PanelKey, view: PanelView): PanelEffect | undefined {
|
|
280
|
+
if (key.kind === "close") return { kind: "close" };
|
|
281
|
+
const shut = () => {
|
|
282
|
+
state.dialog = undefined;
|
|
283
|
+
};
|
|
284
|
+
switch (dialog.kind) {
|
|
285
|
+
case "addRule": {
|
|
286
|
+
if (editDraft(dialog, key)) return undefined;
|
|
287
|
+
if (key.kind === "back") shut();
|
|
288
|
+
else if (key.kind === "enter") {
|
|
289
|
+
const rule = dialog.draft.trim();
|
|
290
|
+
if (!rule) return undefined;
|
|
291
|
+
const error = view.ruleError(rule);
|
|
292
|
+
if (error) state.notice = error;
|
|
293
|
+
else state.dialog = { kind: "saveRule", behavior: dialog.behavior, rule, cursor: 0 };
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
case "saveRule": {
|
|
298
|
+
moveDialogCursor(dialog, key, view.destinations.length);
|
|
299
|
+
if (key.kind === "back") state.dialog = { kind: "addRule", behavior: dialog.behavior, draft: dialog.rule };
|
|
300
|
+
else if (key.kind === "enter") {
|
|
301
|
+
const destination = view.destinations[dialog.cursor];
|
|
302
|
+
shut();
|
|
303
|
+
if (destination) return { kind: "addRule", behavior: dialog.behavior, rule: dialog.rule, destination: destination.id };
|
|
304
|
+
}
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
case "ruleDetail": {
|
|
308
|
+
const rule = detailRule(dialog, view);
|
|
309
|
+
if (!rule || key.kind === "back" || (!rule.editable && key.kind === "enter")) {
|
|
310
|
+
shut();
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
if (!rule.editable) return undefined;
|
|
314
|
+
// Yes / No, as in Claude Code's delete confirmation.
|
|
315
|
+
moveDialogCursor(dialog, key, 2);
|
|
316
|
+
if (key.kind === "enter") {
|
|
317
|
+
shut();
|
|
318
|
+
if (dialog.cursor === 0) return { kind: "deleteRule", behavior: dialog.behavior, key: dialog.key };
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
case "pickSection": {
|
|
323
|
+
moveDialogCursor(dialog, key, AUTO_SECTIONS.length);
|
|
324
|
+
if (key.kind === "back") shut();
|
|
325
|
+
else if (key.kind === "enter") state.dialog = { kind: "autoRuleInput", section: AUTO_SECTIONS[dialog.cursor], draft: "" };
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
case "autoRuleInput": {
|
|
329
|
+
if (editDraft(dialog, key)) return undefined;
|
|
330
|
+
if (key.kind === "back") shut();
|
|
331
|
+
else if (key.kind === "enter") {
|
|
332
|
+
const text = dialog.draft.trim();
|
|
333
|
+
if (!text) return undefined;
|
|
334
|
+
if (text === "$defaults") {
|
|
335
|
+
state.notice = '"$defaults" is not a rule: the built-in rules always apply.';
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
shut();
|
|
339
|
+
return dialog.editKey ? { kind: "editAutoRule", key: dialog.editKey, text } : { kind: "addAutoRule", section: dialog.section, text };
|
|
340
|
+
}
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
case "autoRuleDetail": {
|
|
344
|
+
const entry = autoEntry(dialog.key, view);
|
|
345
|
+
if (!entry || key.kind === "back" || (!entry.editable && key.kind === "enter")) {
|
|
346
|
+
shut();
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
if (!entry.editable) return undefined;
|
|
350
|
+
// Edit / Delete.
|
|
351
|
+
moveDialogCursor(dialog, key, 2);
|
|
352
|
+
if (key.kind === "text" && /^[ed]$/i.test(key.text)) dialog.cursor = key.text.toLowerCase() === "e" ? 0 : 1;
|
|
353
|
+
if (key.kind === "enter") {
|
|
354
|
+
state.dialog =
|
|
355
|
+
dialog.cursor === 0
|
|
356
|
+
? { kind: "autoRuleInput", section: entry.section, draft: entry.text, editKey: entry.key }
|
|
357
|
+
: { kind: "autoRuleDelete", key: entry.key, cursor: 1 };
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
case "autoRuleDelete": {
|
|
362
|
+
if (key.kind === "back") {
|
|
363
|
+
shut();
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
moveDialogCursor(dialog, key, 2);
|
|
367
|
+
if (key.kind === "enter") {
|
|
368
|
+
shut();
|
|
369
|
+
if (dialog.cursor === 0) return { kind: "deleteAutoRule", key: dialog.key };
|
|
370
|
+
}
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
case "builtinsInfo": {
|
|
374
|
+
if (key.kind === "back" || key.kind === "enter") shut();
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
case "envConfirm": {
|
|
378
|
+
if (key.kind === "back") {
|
|
379
|
+
shut();
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
moveDialogCursor(dialog, key, 2);
|
|
383
|
+
if (key.kind === "enter") {
|
|
384
|
+
shut();
|
|
385
|
+
if (dialog.cursor === 0) return { kind: "editEnvironment" };
|
|
386
|
+
}
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
case "addDir": {
|
|
390
|
+
if (editDraft(dialog, key)) return undefined;
|
|
391
|
+
if (key.kind === "back") shut();
|
|
392
|
+
else if (key.kind === "enter" && dialog.draft.trim()) {
|
|
393
|
+
const checked = view.validateDir(dialog.draft);
|
|
394
|
+
if ("error" in checked) state.notice = checked.error;
|
|
395
|
+
else state.dialog = { kind: "rememberDir", path: checked.path, cursor: 0 };
|
|
396
|
+
}
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
case "rememberDir": {
|
|
400
|
+
if (key.kind === "back") {
|
|
401
|
+
state.dialog = { kind: "addDir", draft: dialog.path };
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
moveDialogCursor(dialog, key, 3);
|
|
405
|
+
if (key.kind === "enter") {
|
|
406
|
+
shut();
|
|
407
|
+
if (dialog.cursor < 2) return { kind: "addDir", path: dialog.path, remember: dialog.cursor === 1 };
|
|
408
|
+
}
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
case "removeDir": {
|
|
412
|
+
const dir = workspaceDir(dialog.key, view);
|
|
413
|
+
if (!dir || key.kind === "back" || (!dir.editable && key.kind === "enter")) {
|
|
414
|
+
shut();
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
if (!dir.editable) return undefined;
|
|
418
|
+
moveDialogCursor(dialog, key, 2);
|
|
419
|
+
if (key.kind === "enter") {
|
|
420
|
+
shut();
|
|
421
|
+
if (dialog.cursor === 0) return { kind: "removeDir", key: dialog.key };
|
|
422
|
+
}
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Enter on the Auto mode tab's selected row. */
|
|
429
|
+
function openAutoRow(state: PanelState, view: PanelView): PanelEffect | undefined {
|
|
430
|
+
const row = autoListRows(state, view)[state.cursor.automode];
|
|
431
|
+
state.searching = false;
|
|
432
|
+
if (!row) return undefined;
|
|
433
|
+
switch (row.kind) {
|
|
434
|
+
case "add":
|
|
435
|
+
state.dialog = { kind: "pickSection", cursor: 0 };
|
|
436
|
+
return undefined;
|
|
437
|
+
case "builtins":
|
|
438
|
+
state.dialog = { kind: "builtinsInfo", section: row.section };
|
|
439
|
+
return undefined;
|
|
440
|
+
case "entry":
|
|
441
|
+
state.dialog = { kind: "autoRuleDetail", key: row.entry.key, cursor: 0 };
|
|
442
|
+
return undefined;
|
|
443
|
+
case "environment":
|
|
444
|
+
// Replacing the built-in default is confirmed first, as in Claude Code.
|
|
445
|
+
if (view.autoMode.environment.isDefault) {
|
|
446
|
+
state.dialog = { kind: "envConfirm", cursor: 0 };
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
return { kind: "editEnvironment" };
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function applyPanelKey(state: PanelState, key: PanelKey, view: PanelView): PanelEffect | undefined {
|
|
454
|
+
state.notice = undefined;
|
|
455
|
+
if (state.dialog) return applyDialogKey(state, state.dialog, key, view);
|
|
456
|
+
|
|
457
|
+
const tab = state.tab;
|
|
458
|
+
const move = (step: number) => {
|
|
459
|
+
state.cursor[tab] += step;
|
|
460
|
+
clampPanelState(state, view);
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
switch (key.kind) {
|
|
464
|
+
case "close":
|
|
465
|
+
return { kind: "close" };
|
|
466
|
+
case "back":
|
|
467
|
+
if (isSearchTab(tab) && (state.searching || state.search[tab])) {
|
|
468
|
+
state.searching = false;
|
|
469
|
+
state.search[tab] = "";
|
|
470
|
+
state.cursor[tab] = 0;
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
return { kind: "close" };
|
|
474
|
+
case "nextTab":
|
|
475
|
+
case "prevTab": {
|
|
476
|
+
const delta = key.kind === "nextTab" ? 1 : -1;
|
|
477
|
+
state.tab = TABS[(TABS.indexOf(tab) + delta + TABS.length) % TABS.length];
|
|
478
|
+
state.searching = false;
|
|
479
|
+
clampPanelState(state, view);
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
case "up":
|
|
483
|
+
move(-1);
|
|
484
|
+
return undefined;
|
|
485
|
+
case "down":
|
|
486
|
+
move(1);
|
|
487
|
+
return undefined;
|
|
488
|
+
case "pageUp":
|
|
489
|
+
move(-10);
|
|
490
|
+
return undefined;
|
|
491
|
+
case "pageDown":
|
|
492
|
+
move(10);
|
|
493
|
+
return undefined;
|
|
494
|
+
case "backspace":
|
|
495
|
+
if (isSearchTab(tab) && state.searching) {
|
|
496
|
+
state.search[tab] = state.search[tab].slice(0, -1);
|
|
497
|
+
if (!state.search[tab]) state.searching = false;
|
|
498
|
+
state.cursor[tab] = 0;
|
|
499
|
+
}
|
|
500
|
+
return undefined;
|
|
501
|
+
case "enter": {
|
|
502
|
+
if (tab === "recent") {
|
|
503
|
+
const denial = view.denials[state.cursor.recent];
|
|
504
|
+
// Un-approving a row also takes back its retry: a retry is an approval.
|
|
505
|
+
if (denial && !toggle(state.approved, denial.id)) state.retry.delete(denial.id);
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
if (tab === "automode") return openAutoRow(state, view);
|
|
509
|
+
if (tab === "workspace") {
|
|
510
|
+
const row = workspaceListRows(view)[state.cursor.workspace];
|
|
511
|
+
if (row?.kind === "add") state.dialog = { kind: "addDir", draft: "" };
|
|
512
|
+
else if (row?.kind === "dir") state.dialog = { kind: "removeDir", key: row.dir.key, cursor: 1 };
|
|
513
|
+
return undefined;
|
|
514
|
+
}
|
|
515
|
+
const row = ruleListRows(state, view, tab)[state.cursor[tab]];
|
|
516
|
+
state.searching = false;
|
|
517
|
+
if (row?.kind === "add") state.dialog = { kind: "addRule", behavior: tab, draft: "" };
|
|
518
|
+
else if (row?.kind === "rule") state.dialog = { kind: "ruleDetail", behavior: tab, key: row.rule.key, cursor: 0 };
|
|
519
|
+
return undefined;
|
|
520
|
+
}
|
|
521
|
+
case "text": {
|
|
522
|
+
if (tab === "recent") {
|
|
523
|
+
const denial = view.denials[state.cursor.recent];
|
|
524
|
+
if (denial && key.text.toLowerCase() === "r" && toggle(state.retry, denial.id)) state.approved.add(denial.id);
|
|
525
|
+
return undefined;
|
|
526
|
+
}
|
|
527
|
+
if (tab === "workspace") {
|
|
528
|
+
if (key.text === "j") move(1);
|
|
529
|
+
else if (key.text === "k") move(-1);
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
if (state.searching) {
|
|
533
|
+
state.search[tab] += key.text;
|
|
534
|
+
state.cursor[tab] = 0;
|
|
535
|
+
return undefined;
|
|
536
|
+
}
|
|
537
|
+
if (key.text === "j") move(1);
|
|
538
|
+
else if (key.text === "k") move(-1);
|
|
539
|
+
else if (key.text === "/") {
|
|
540
|
+
state.searching = true;
|
|
541
|
+
state.search[tab] = "";
|
|
542
|
+
} else if (!NO_SEARCH_KEYS.has(key.text[0])) {
|
|
543
|
+
state.searching = true;
|
|
544
|
+
state.search[tab] = key.text;
|
|
545
|
+
state.cursor[tab] = 0;
|
|
546
|
+
}
|
|
547
|
+
return undefined;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|