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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One Code's own settings files — the writable half of "own state, borrowed
|
|
3
|
-
* config" (docs/decisions/memory-state.md). `~/.claude` is Claude Code's
|
|
3
|
+
* config" (working-docs/decisions/memory-state.md). `~/.claude` is Claude Code's
|
|
4
4
|
* directory and One Code only ever *reads* it; anything One Code persists that
|
|
5
|
-
* looks like a settings key (its own `subagentModel`
|
|
6
|
-
*
|
|
5
|
+
* looks like a settings key (its own `subagentModel` or the allow rules `/allow`
|
|
6
|
+
* records) lands here instead, so One Code never mutates
|
|
7
7
|
* Claude Code's config and a value that means nothing to Claude Code (a model it
|
|
8
8
|
* cannot run, say) never ends up in Claude Code's file.
|
|
9
9
|
*
|
|
@@ -22,23 +22,43 @@
|
|
|
22
22
|
|
|
23
23
|
import { existsSync, readFileSync } from "node:fs";
|
|
24
24
|
import { join } from "node:path";
|
|
25
|
-
import { writeJsonAtomic } from "./atomic-write.ts";
|
|
25
|
+
import { readJsonFile, writeJsonAtomic } from "./atomic-write.ts";
|
|
26
26
|
import { findProjectRoot } from "./git.ts";
|
|
27
27
|
import { projectSlug } from "./memory.ts";
|
|
28
|
-
import { oneCodeStateDir } from "./paths.ts";
|
|
28
|
+
import { oneCodeStateDir, tryRealpath } from "./paths.ts";
|
|
29
29
|
|
|
30
30
|
/** `~/.onecode/settings.json` — One Code's user-scope settings (writable). */
|
|
31
31
|
export function oneCodeSettingsPath(home: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
32
32
|
return join(oneCodeStateDir(env, home), "settings.json");
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* The project a per-repo setting belongs to: the git repository root when
|
|
37
|
+
* there is one (shared by worktrees and subdirectories), else the cwd, through
|
|
38
|
+
* symlinks so an aliased checkout is the same project.
|
|
39
|
+
*/
|
|
40
|
+
export function oneCodeProjectRoot(cwd: string): string {
|
|
41
|
+
const root = findProjectRoot(cwd) ?? cwd;
|
|
42
|
+
return tryRealpath(root) ?? root;
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
/**
|
|
36
46
|
* `~/.onecode/projects/<slug>/settings.json` — One Code's per-repo settings
|
|
37
|
-
* (writable)
|
|
38
|
-
*
|
|
47
|
+
* (writable), for `oneCodeProjectRoot`. The slug is the memory dir's
|
|
48
|
+
* (`projectSlug`), but of the resolved root, where the memory dir slugs the
|
|
49
|
+
* root as found. It is lossy (`my_app` and `my-app` share a file), so a setting
|
|
50
|
+
* that grants trust records the root it was given for (claude-external-includes.ts).
|
|
39
51
|
*/
|
|
40
52
|
export function oneCodeProjectSettingsPath(cwd: string, home: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
41
|
-
return
|
|
53
|
+
return oneCodeProjectSettingsPathFor(oneCodeProjectRoot(cwd), home, env);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Resolve a project settings path without moving existing permission or trust state. */
|
|
57
|
+
export function oneCodeProjectSettingsPathFor(root: string, home: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
58
|
+
const projects = join(oneCodeStateDir(env, home), "projects");
|
|
59
|
+
const legacy = join(projects, root.replace(/[^A-Za-z0-9-]/g, "-"), "settings.json");
|
|
60
|
+
if (existsSync(legacy)) return legacy;
|
|
61
|
+
return join(projects, projectSlug(root), "settings.json");
|
|
42
62
|
}
|
|
43
63
|
|
|
44
64
|
/**
|
|
@@ -61,3 +81,49 @@ export function readSettingsForWrite(path: string): Record<string, unknown> {
|
|
|
61
81
|
export function writeSettings(path: string, file: Record<string, unknown>): void {
|
|
62
82
|
writeJsonAtomic(path, file);
|
|
63
83
|
}
|
|
84
|
+
|
|
85
|
+
/** User-only upgrade advice, enabled unless explicitly disabled in One Code's settings. */
|
|
86
|
+
export function readSuggestNewerModels(home: string, env: NodeJS.ProcessEnv = process.env): boolean {
|
|
87
|
+
const file = readJsonFile<{ suggestNewerModels?: unknown }>(oneCodeSettingsPath(home, env));
|
|
88
|
+
return file?.suggestNewerModels !== false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Claude Code's `workflowSizeGuideline` values: under 5, 10 or 50 agents, or no guideline. */
|
|
92
|
+
export type WorkflowSizeGuideline = "small" | "medium" | "large" | "unrestricted";
|
|
93
|
+
|
|
94
|
+
const WORKFLOW_SIZES: readonly WorkflowSizeGuideline[] = ["small", "medium", "large", "unrestricted"];
|
|
95
|
+
|
|
96
|
+
export interface WorkflowSettings {
|
|
97
|
+
/** Whether the workflow tool is offered at all. */
|
|
98
|
+
enabled: boolean;
|
|
99
|
+
sizeGuideline: WorkflowSizeGuideline;
|
|
100
|
+
/** True when a settings file set the guideline (the description says "configured"). */
|
|
101
|
+
sizeConfigured: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface WorkflowSettingsFile {
|
|
105
|
+
enableWorkflows?: unknown;
|
|
106
|
+
disableWorkflows?: unknown;
|
|
107
|
+
workflowSizeGuideline?: unknown;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The workflow settings Claude Code reads, from One Code's own files (user,
|
|
112
|
+
* then the project file, which wins): `enableWorkflows` (default true),
|
|
113
|
+
* `disableWorkflows` (true turns workflows off whatever `enableWorkflows`
|
|
114
|
+
* says, as in Claude Code) and `workflowSizeGuideline` (default "medium"; an
|
|
115
|
+
* unknown value is ignored). Read leniently: a malformed file is skipped.
|
|
116
|
+
*/
|
|
117
|
+
export function readWorkflowSettings(cwd: string, home: string, env: NodeJS.ProcessEnv = process.env): WorkflowSettings {
|
|
118
|
+
const files = [oneCodeSettingsPath(home, env), oneCodeProjectSettingsPath(cwd, home, env)].map((path) => readJsonFile<WorkflowSettingsFile>(path));
|
|
119
|
+
let enable = true;
|
|
120
|
+
let disable = false;
|
|
121
|
+
let size: WorkflowSizeGuideline | undefined;
|
|
122
|
+
for (const file of files) {
|
|
123
|
+
if (!file || typeof file !== "object") continue;
|
|
124
|
+
if (typeof file.enableWorkflows === "boolean") enable = file.enableWorkflows;
|
|
125
|
+
if (file.disableWorkflows === true) disable = true;
|
|
126
|
+
if (WORKFLOW_SIZES.includes(file.workflowSizeGuideline as WorkflowSizeGuideline)) size = file.workflowSizeGuideline as WorkflowSizeGuideline;
|
|
127
|
+
}
|
|
128
|
+
return { enabled: enable && !disable, sizeGuideline: size ?? "medium", sizeConfigured: size !== undefined };
|
|
129
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand a URL to the desktop's default handler (no dependency, a platform
|
|
3
|
+
* command). Shared by the MCP sign-in flow (`mcp/oauth/browser.ts`, which also
|
|
4
|
+
* screens the URL it is given) and the artifact viewer (`extensions/artifacts`,
|
|
5
|
+
* which only ever opens `file:` URLs it built itself).
|
|
6
|
+
*
|
|
7
|
+
* The child is detached and unref'd once it starts, so the caller never
|
|
8
|
+
* blocks on the browser process. A launch failure resolves to false: a box
|
|
9
|
+
* with no browser falls back to printing the target. On Windows the URL never passes
|
|
10
|
+
* through `cmd.exe`: `cmd /c start "" <url>` cut a URL at its first `&` and
|
|
11
|
+
* handed the rest to cmd's parser as further commands. `rundll32
|
|
12
|
+
* url.dll,FileProtocolHandler` takes the URL as one argument, by its System32
|
|
13
|
+
* path so no PATH entry can stand in for it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
import { system32Path } from "./paths.ts";
|
|
18
|
+
|
|
19
|
+
export interface OpenerPlan {
|
|
20
|
+
command: string;
|
|
21
|
+
args: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The command and argv that open `url` on `platform`. Pure. */
|
|
25
|
+
export function openerPlan(url: string, platform: NodeJS.Platform = process.platform, env: Record<string, string | undefined> = process.env): OpenerPlan {
|
|
26
|
+
if (platform === "darwin") return { command: "open", args: [url] };
|
|
27
|
+
if (platform === "win32") return { command: system32Path("rundll32.exe", env), args: ["url.dll,FileProtocolHandler", url] };
|
|
28
|
+
return { command: "xdg-open", args: [url] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Why a browser opened from here would not reach the user, or undefined when
|
|
33
|
+
* it would. Over SSH the opener runs on the remote machine's desktop, and a
|
|
34
|
+
* Linux or BSD box with neither X nor Wayland has no desktop at all. Pure.
|
|
35
|
+
*/
|
|
36
|
+
export function noDisplayReason(platform: NodeJS.Platform = process.platform, env: Record<string, string | undefined> = process.env): string | undefined {
|
|
37
|
+
if (env.SSH_CONNECTION || env.SSH_TTY) return "this session runs over SSH";
|
|
38
|
+
if (platform !== "darwin" && platform !== "win32" && !env.DISPLAY && !env.WAYLAND_DISPLAY) return "no graphical display is available";
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Start `plan` detached. Resolves true once the opener has started, false when
|
|
44
|
+
* it could not: a missing `xdg-open` is reported by an `error` event after
|
|
45
|
+
* `spawn` returns, so success waits for the `spawn` event. The child stays
|
|
46
|
+
* ref'd until then, so a one-shot run cannot exit mid-launch.
|
|
47
|
+
*/
|
|
48
|
+
export function launchOpener(plan: OpenerPlan): Promise<boolean> {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
let child: ReturnType<typeof spawn>;
|
|
51
|
+
try {
|
|
52
|
+
child = spawn(plan.command, plan.args, { stdio: "ignore", detached: true });
|
|
53
|
+
} catch {
|
|
54
|
+
resolve(false);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
child.once("error", () => resolve(false));
|
|
58
|
+
child.once("spawn", () => {
|
|
59
|
+
child.unref();
|
|
60
|
+
resolve(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** A best-effort attribution lookup, bounded across auth, retries, and response-body reads. */
|
|
2
|
+
export async function openRouterProviderName(
|
|
3
|
+
generationId: string | undefined,
|
|
4
|
+
getApiKey: () => Promise<string | undefined>,
|
|
5
|
+
signal: AbortSignal,
|
|
6
|
+
): Promise<string | undefined> {
|
|
7
|
+
if (!generationId || signal.aborted) return;
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const cancel = () => controller.abort();
|
|
10
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
11
|
+
const timeout = setTimeout(cancel, 8_000);
|
|
12
|
+
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
13
|
+
let releaseRetry: (() => void) | undefined;
|
|
14
|
+
const stopped = new Promise<undefined>((resolve) => {
|
|
15
|
+
controller.signal.addEventListener("abort", () => {
|
|
16
|
+
clearTimeout(retryTimer);
|
|
17
|
+
releaseRetry?.();
|
|
18
|
+
resolve(undefined);
|
|
19
|
+
}, { once: true });
|
|
20
|
+
});
|
|
21
|
+
const lookup = async (): Promise<string | undefined> => {
|
|
22
|
+
let apiKey: string | undefined;
|
|
23
|
+
try {
|
|
24
|
+
apiKey = await getApiKey();
|
|
25
|
+
} catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!apiKey || controller.signal.aborted) return;
|
|
29
|
+
while (!controller.signal.aborted) {
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(`https://openrouter.ai/api/v1/generation?id=${encodeURIComponent(generationId)}`, {
|
|
32
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
33
|
+
signal: controller.signal,
|
|
34
|
+
});
|
|
35
|
+
if (controller.signal.aborted) return;
|
|
36
|
+
if (response.ok) {
|
|
37
|
+
const body = await response.json() as { data?: { provider_name?: unknown } } | null;
|
|
38
|
+
const provider = body?.data?.provider_name;
|
|
39
|
+
if (typeof provider === "string" && provider.trim()) return provider.trim();
|
|
40
|
+
} else {
|
|
41
|
+
await response.body?.cancel();
|
|
42
|
+
if (response.status === 401 || response.status === 403) return;
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Generation metadata can lag or fail independently of the completed model call.
|
|
46
|
+
}
|
|
47
|
+
if (controller.signal.aborted) return;
|
|
48
|
+
await new Promise<void>((resolve) => {
|
|
49
|
+
releaseRetry = resolve;
|
|
50
|
+
retryTimer = setTimeout(resolve, 1_000);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
return await Promise.race([lookup(), stopped]);
|
|
56
|
+
} finally {
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
signal.removeEventListener("abort", cancel);
|
|
59
|
+
controller.abort();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Per model object: the stream detector asks on every chunk of every provider. */
|
|
64
|
+
const openRouterByModel = new WeakMap<object, boolean>();
|
|
65
|
+
|
|
66
|
+
export function isOpenRouter(model: { provider: string; baseUrl?: string } | undefined): boolean {
|
|
67
|
+
if (!model) return false;
|
|
68
|
+
if (model.provider === "openrouter") return true;
|
|
69
|
+
const known = openRouterByModel.get(model);
|
|
70
|
+
if (known !== undefined) return known;
|
|
71
|
+
let result = false;
|
|
72
|
+
try {
|
|
73
|
+
const hostname = new URL(model.baseUrl ?? "").hostname;
|
|
74
|
+
result = hostname === "openrouter.ai" || hostname.endsWith(".openrouter.ai");
|
|
75
|
+
} catch {
|
|
76
|
+
// No or malformed base URL: not OpenRouter.
|
|
77
|
+
}
|
|
78
|
+
openRouterByModel.set(model, result);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** How the upstream provider failed: a blocked call, a call cut off mid-stream, or a stream gone silent. */
|
|
83
|
+
export type UpstreamFailure = "blocked-call" | "corrupt-stream" | { stalledSeconds: number };
|
|
84
|
+
|
|
85
|
+
export function toolCallCorruptionNotice(modelId: string, providerName: string | undefined, failure: UpstreamFailure = "blocked-call"): string {
|
|
86
|
+
const stalled = typeof failure === "object";
|
|
87
|
+
const what = stalled ? `stopped streaming for ${modelId} (no data for ${failure.stalledSeconds} s)` : `is corrupting tool calls for ${modelId}`;
|
|
88
|
+
const provider = providerName
|
|
89
|
+
? `OpenRouter upstream provider ${providerName} ${what}.`
|
|
90
|
+
: `An OpenRouter upstream provider ${what}; the provider could not be identified.`;
|
|
91
|
+
const outcome = failure === "blocked-call" ? "The call was blocked and the turn stopped." : stalled ? "The turn was stopped." : "The reply was cut off and the turn stopped.";
|
|
92
|
+
const ignore = JSON.stringify([providerName ?? "PROVIDER_NAME"]);
|
|
93
|
+
return `${provider} ${outcome} Add the provider to compat.openRouterRouting.ignore for this model in pi's models.json ("ignore": ${ignore}), or switch models.`;
|
|
94
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Bound an awaited operation, including dependencies that do not accept an abort signal. */
|
|
2
|
+
export async function runWithDeadline<T>(
|
|
3
|
+
call: (signal: AbortSignal) => Promise<T>,
|
|
4
|
+
options: { signal?: AbortSignal; timeoutMs: number; message: string },
|
|
5
|
+
): Promise<T> {
|
|
6
|
+
options.signal?.throwIfAborted();
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
9
|
+
// A one-shot process must live until the awaited operation settles.
|
|
10
|
+
const timer = setTimeout(() => controller.abort(new Error(options.message)), options.timeoutMs);
|
|
11
|
+
let rejectAbort: () => void = () => {};
|
|
12
|
+
const aborted = new Promise<never>((_, reject) => {
|
|
13
|
+
rejectAbort = () => reject(signal.reason);
|
|
14
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
const result = await Promise.race([
|
|
18
|
+
Promise.resolve().then(() => {
|
|
19
|
+
signal.throwIfAborted();
|
|
20
|
+
return call(signal);
|
|
21
|
+
}),
|
|
22
|
+
aborted,
|
|
23
|
+
]);
|
|
24
|
+
signal.throwIfAborted();
|
|
25
|
+
return result;
|
|
26
|
+
} finally {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
signal.removeEventListener("abort", rejectAbort);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -22,10 +22,10 @@ export interface OriginalCommandRecord {
|
|
|
22
22
|
toolCallId: string;
|
|
23
23
|
command: string;
|
|
24
24
|
/**
|
|
25
|
-
* The directory the wrapper `cd`s into (the worktree). The pre-gate
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* The directory the wrapper `cd`s into (the worktree). The pre-gate runs on
|
|
26
|
+
* the original command against THIS cwd, so a worktree session keeps the
|
|
27
|
+
* containment fast path and the prompt shows the command the model wrote,
|
|
28
|
+
* not `cd '…' && (…)` (PERMISSIONS-REVIEW-2026-09-05 L3).
|
|
29
29
|
*/
|
|
30
30
|
cwd?: string;
|
|
31
31
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* banner never hardcodes (and drifts from) the released version.
|
|
4
4
|
*
|
|
5
5
|
* The bundled app sets CC_VERSION to the app package version, which is kept in
|
|
6
|
-
* lockstep with this package (docs/decisions/distribution.md). When the
|
|
6
|
+
* lockstep with this package (working-docs/decisions/distribution.md). When the
|
|
7
7
|
* extension is loaded directly via plain `pi` there is no CC_VERSION, so this
|
|
8
8
|
* reads the extension's own package.json instead of falling back to a stale
|
|
9
9
|
* literal.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which One Code panels hold the keyboard, over the event bus (jiti modules
|
|
3
|
+
* cannot share state), so a permission prompt waits for a panel to close
|
|
4
|
+
* instead of taking its place. Pure: no pi imports.
|
|
5
|
+
*
|
|
6
|
+
* pi shows a dialog (`ctx.ui.select`, `input`, `confirm`) in the slot a
|
|
7
|
+
* `ctx.ui.custom` panel occupies: the dialog replaces the panel and takes its
|
|
8
|
+
* focus, the panel never comes back, and an Enter typed for the panel answers
|
|
9
|
+
* the dialog. Claude Code holds a permission dialog while a command's panel
|
|
10
|
+
* is open. So every panel goes through `panelFactory`, which reports when its
|
|
11
|
+
* component gains and loses focus, and the permissions extension queues its
|
|
12
|
+
* prompts until no panel is focused (`PanelFocus.whenClear`).
|
|
13
|
+
*
|
|
14
|
+
* Focus is read from pi-tui's own `focused` flag, which `setFocus` sets on a
|
|
15
|
+
* component that has the property: a panel that something else displaced
|
|
16
|
+
* reports itself gone at once, so a prompt can never wait on a panel that is
|
|
17
|
+
* no longer on screen.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const PANEL_FOCUS_CHANNEL = "one-code:panel-focus";
|
|
21
|
+
|
|
22
|
+
/** One panel's focus change; `id` is an object unique to the panel. */
|
|
23
|
+
export interface PanelFocusEvent {
|
|
24
|
+
id: object;
|
|
25
|
+
focused: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Emitter {
|
|
29
|
+
emit(channel: string, data: unknown): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function findDescriptor(target: object, key: string): PropertyDescriptor | undefined {
|
|
33
|
+
for (let at: object | null = target; at; at = Object.getPrototypeOf(at)) {
|
|
34
|
+
const found = Object.getOwnPropertyDescriptor(at, key);
|
|
35
|
+
if (found) return found;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Report every write to the component's `focused` flag, keeping whatever the component does with it. */
|
|
41
|
+
function watchFocus(component: object, onChange: (focused: boolean) => void): void {
|
|
42
|
+
const own = findDescriptor(component, "focused");
|
|
43
|
+
let value: unknown = own && "value" in own ? own.value : undefined;
|
|
44
|
+
Object.defineProperty(component, "focused", {
|
|
45
|
+
configurable: true,
|
|
46
|
+
enumerable: true,
|
|
47
|
+
get: () => (own?.get ? own.get.call(component) : value),
|
|
48
|
+
set: (next: unknown) => {
|
|
49
|
+
if (own?.set) own.set.call(component, next);
|
|
50
|
+
else value = next;
|
|
51
|
+
onChange(Boolean(next));
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A `ctx.ui.custom` factory that reports its panel's focus on
|
|
58
|
+
* `PANEL_FOCUS_CHANNEL`: `ctx.ui.custom(panelFactory(pi.events, (tui, theme,
|
|
59
|
+
* keybindings, done) => …))`. The panel counts as gone when it loses focus or
|
|
60
|
+
* calls `done`.
|
|
61
|
+
*/
|
|
62
|
+
export function panelFactory<A extends unknown[], C>(events: Emitter, factory: (...args: A) => C): (...args: A) => C {
|
|
63
|
+
return (...args: A): C => {
|
|
64
|
+
const id = {};
|
|
65
|
+
const report = (focused: boolean) => events.emit(PANEL_FOCUS_CHANNEL, { id, focused } satisfies PanelFocusEvent);
|
|
66
|
+
const done = args[3];
|
|
67
|
+
if (typeof done === "function") {
|
|
68
|
+
args[3] = ((...result: unknown[]) => {
|
|
69
|
+
report(false);
|
|
70
|
+
return done(...result);
|
|
71
|
+
}) as A[3];
|
|
72
|
+
}
|
|
73
|
+
const watch = (component: unknown) => {
|
|
74
|
+
if (component && typeof component === "object") watchFocus(component, report);
|
|
75
|
+
return component;
|
|
76
|
+
};
|
|
77
|
+
const made = factory(...args);
|
|
78
|
+
return (made instanceof Promise ? made.then(watch) : watch(made)) as C;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* `ctx.ui` with a `custom` whose panels report their focus:
|
|
84
|
+
* `panelUi(pi.events, ctx.ui).custom<T>((tui, theme, keybindings, done) => …)`.
|
|
85
|
+
*/
|
|
86
|
+
export function panelUi<U extends { custom: (...args: any[]) => any }>(events: Emitter, ui: U): Pick<U, "custom"> {
|
|
87
|
+
const custom = (factory: (...args: unknown[]) => unknown, ...rest: unknown[]) => ui.custom(panelFactory(events, factory), ...rest);
|
|
88
|
+
return { custom: custom as U["custom"] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The focused panels, fed from `PANEL_FOCUS_CHANNEL`. */
|
|
92
|
+
export class PanelFocus {
|
|
93
|
+
private readonly focused = new Set<object>();
|
|
94
|
+
private waiters = new Set<() => void>();
|
|
95
|
+
|
|
96
|
+
note(data: unknown): void {
|
|
97
|
+
const event = data as Partial<PanelFocusEvent> | undefined;
|
|
98
|
+
if (!event || typeof event.id !== "object" || event.id === null) return;
|
|
99
|
+
if (event.focused) this.focused.add(event.id);
|
|
100
|
+
else this.focused.delete(event.id);
|
|
101
|
+
if (this.focused.size === 0) this.release();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** True while a panel holds the keyboard. */
|
|
105
|
+
get busy(): boolean {
|
|
106
|
+
return this.focused.size > 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Resolves once no panel is focused, or when `signal` aborts. */
|
|
110
|
+
whenClear(signal?: AbortSignal): Promise<void> {
|
|
111
|
+
if (!this.busy || signal?.aborted) return Promise.resolve();
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
const finish = () => {
|
|
114
|
+
this.waiters.delete(finish);
|
|
115
|
+
signal?.removeEventListener("abort", finish);
|
|
116
|
+
resolve();
|
|
117
|
+
};
|
|
118
|
+
this.waiters.add(finish);
|
|
119
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Forget every panel (a new session) and let waiting prompts through. */
|
|
124
|
+
reset(): void {
|
|
125
|
+
this.focused.clear();
|
|
126
|
+
this.release();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private release(): void {
|
|
130
|
+
const waiting = this.waiters;
|
|
131
|
+
this.waiters = new Set();
|
|
132
|
+
for (const finish of waiting) finish();
|
|
133
|
+
}
|
|
134
|
+
}
|
package/extensions/lib/paths.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* (settings, skills, agents, plugins, CLAUDE.md); everything One Code
|
|
7
7
|
* *generates* — plan files, and over time the rest of its state — lands in
|
|
8
8
|
* One Code's own `~/.onecode`, so neither product's artifacts mingle with the
|
|
9
|
-
* other's. See "Own state, borrowed config" in docs/decisions.md.
|
|
9
|
+
* other's. See "Own state, borrowed config" in working-docs/decisions.md.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { realpathSync } from "node:fs";
|
|
@@ -21,6 +21,19 @@ export function claudeConfigDir(env: Record<string, string | undefined> = proces
|
|
|
21
21
|
return env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Claude Code's organization-managed root: `managed-settings.json`
|
|
26
|
+
* (lib/claude-settings.ts) and the managed CLAUDE.md and rules all live here.
|
|
27
|
+
* On Windows it is `C:\Program Files\ClaudeCode`, the only Windows path the
|
|
28
|
+
* Claude Code binary names (2.1.292); `C:\ProgramData\ClaudeCode` is not read.
|
|
29
|
+
* The Windows policy registry keys are a separate source, not read here.
|
|
30
|
+
*/
|
|
31
|
+
export function claudeManagedDir(platform: NodeJS.Platform = process.platform): string {
|
|
32
|
+
if (platform === "darwin") return "/Library/Application Support/ClaudeCode";
|
|
33
|
+
if (platform === "win32") return "C:\\Program Files\\ClaudeCode";
|
|
34
|
+
return "/etc/claude-code";
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
/**
|
|
25
38
|
* The user-scope Claude Code dir for a given `home` — `claudeConfigDir` for
|
|
26
39
|
* callers that thread a `home` (tests, `~`-expansion). `CLAUDE_CONFIG_DIR`
|
|
@@ -158,7 +171,9 @@ export function foldsCase(): boolean {
|
|
|
158
171
|
* the safety floor — so "under this directory" means one thing everywhere.
|
|
159
172
|
*/
|
|
160
173
|
export function comparablePath(path: string): string {
|
|
161
|
-
const
|
|
174
|
+
const absolute = resolve(path);
|
|
175
|
+
// Backslashes separate Windows paths, but name literal sibling files on POSIX.
|
|
176
|
+
const normalized = (process.platform === "win32" ? forwardSlashes(absolute) : absolute).replace(/\/+$/, "");
|
|
162
177
|
return foldsCase() ? normalized.toLowerCase() : normalized;
|
|
163
178
|
}
|
|
164
179
|
|
|
@@ -30,11 +30,20 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { getAgentDir, type InlineExtension } from "@earendil-works/pi-coding-agent";
|
|
33
|
+
import { withKeepAlive } from "../lsp/keep-alive.ts";
|
|
34
|
+
import { bashParserReady } from "./bash-parser.ts";
|
|
33
35
|
import { findProjectRoot } from "./git.ts";
|
|
36
|
+
import { SUBAGENT_HANDBACK } from "./subagent-handback.ts";
|
|
37
|
+
import { autoMemoryEnabled } from "./auto-memory.ts";
|
|
34
38
|
import { memoryDir } from "./memory.ts";
|
|
35
|
-
import {
|
|
39
|
+
import type { PowerShellParse } from "./powershell-parser.ts";
|
|
40
|
+
import { usesClaudeCodeFastPaths } from "./model-tier.ts";
|
|
41
|
+
import { homedir } from "node:os";
|
|
42
|
+
import { join } from "node:path";
|
|
43
|
+
import { claudeConfigDir, claudeJsonPath, oneCodeStateDir } from "./paths.ts";
|
|
36
44
|
import { sessionResultsDir } from "./persisted-output.ts";
|
|
37
|
-
import {
|
|
45
|
+
import { powerShellParser } from "./shell-spawn.ts";
|
|
46
|
+
import { isPrivateScratchpad, sessionScratchpadDir } from "./scratchpad.ts";
|
|
38
47
|
import { decide, extractSubject, isPathSubjectTool, normalizeToolName, type PermissionMode, parseRules } from "../permissions/matcher.ts";
|
|
39
48
|
import { projectAllowApproved } from "../permissions/project-trust.ts";
|
|
40
49
|
import { loadPermissionSettings, normalizePermissionMode } from "../permissions/settings.ts";
|
|
@@ -64,8 +73,33 @@ export function runtimeProtectedDirs(): string[] {
|
|
|
64
73
|
return [...new Set(roots.flatMap((dir) => [dir, resolvedOrSelf(dir)]))];
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
/**
|
|
77
|
+
* The harness's own secret stores, files and directories, which a read tool
|
|
78
|
+
* outside the working space must not read unclassified: One Code's settings
|
|
79
|
+
* (web search API keys, the model tiers) and MCP OAuth token store, pi's
|
|
80
|
+
* `models.json` (custom-provider `apiKey` values) and `auth.json`, Claude
|
|
81
|
+
* Code's user settings (`env` keys, `apiKeyHelper`) and `.claude.json`. The
|
|
82
|
+
* basename denylist (`auto-mode/sensitive.ts`) cannot name these without
|
|
83
|
+
* flagging every project's `settings.json`. Literal and resolved spelling, as
|
|
84
|
+
* for `runtimeProtectedDirs`; `DecideInput.secretPaths` is the consumer.
|
|
85
|
+
*/
|
|
86
|
+
export function runtimeSecretPaths(home: string = homedir()): string[] {
|
|
87
|
+
const stateDir = oneCodeStateDir(process.env, home);
|
|
88
|
+
const agentDir = getAgentDir();
|
|
89
|
+
const paths = [
|
|
90
|
+
join(stateDir, "settings.json"),
|
|
91
|
+
join(stateDir, "mcp-auth"),
|
|
92
|
+
join(agentDir, "models.json"),
|
|
93
|
+
join(agentDir, "auth.json"),
|
|
94
|
+
join(claudeConfigDir(), "settings.json"),
|
|
95
|
+
join(claudeConfigDir(), ".credentials.json"),
|
|
96
|
+
claudeJsonPath(home),
|
|
97
|
+
];
|
|
98
|
+
return [...new Set(paths.flatMap((path) => [path, resolvedOrSelf(path)]))];
|
|
99
|
+
}
|
|
100
|
+
|
|
67
101
|
/** Tools the runtime itself injects; never gate them. */
|
|
68
|
-
const DEFAULT_INTERNAL_TOOLS = new Set(["structured_output"]);
|
|
102
|
+
const DEFAULT_INTERNAL_TOOLS = new Set(["structured_output", SUBAGENT_HANDBACK]);
|
|
69
103
|
|
|
70
104
|
/** The parent permissions extension publishes its live mode here (in-process children read it). */
|
|
71
105
|
export const MODE_ENV = "CC_PERMISSION_MODE";
|
|
@@ -78,6 +112,24 @@ export function localGateMode(envValue: string | undefined, defaultMode: Permiss
|
|
|
78
112
|
return normalizePermissionMode(envValue) ?? defaultMode ?? "acceptEdits";
|
|
79
113
|
}
|
|
80
114
|
|
|
115
|
+
/**
|
|
116
|
+
* PowerShell's own parse of a `powershell` call's command line, for decide(),
|
|
117
|
+
* from the process-wide parse server on the tool's own executable
|
|
118
|
+
* (lib/powershell-parser.ts). Undefined for another tool, in
|
|
119
|
+
* bypassPermissions (nothing is judged), or when the parser cannot answer (no
|
|
120
|
+
* PowerShell, a timeout, a crash, Constrained Language Mode): the gate then
|
|
121
|
+
* clears nothing and the line is classified, prompted or denied. The server's
|
|
122
|
+
* child is unref'd, so the await holds the event loop open itself (a one-shot
|
|
123
|
+
* run would otherwise exit mid-parse).
|
|
124
|
+
*/
|
|
125
|
+
export async function parsePowerShell(tool: string, command: string | undefined, mode: PermissionMode): Promise<PowerShellParse | undefined> {
|
|
126
|
+
if (tool !== "powershell" || !command || mode === "bypassPermissions") return undefined;
|
|
127
|
+
const parser = powerShellParser();
|
|
128
|
+
if (!parser) return undefined;
|
|
129
|
+
const outcome = await withKeepAlive(() => parser.parse(command));
|
|
130
|
+
return outcome.ok ? outcome.parse : undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
81
133
|
export function permissionGateFactory(
|
|
82
134
|
cwd: string,
|
|
83
135
|
home: string,
|
|
@@ -105,9 +157,11 @@ export function permissionGateFactory(
|
|
|
105
157
|
);
|
|
106
158
|
// Memory writes work inside agent sessions too — otherwise the protected
|
|
107
159
|
// `.claude` check turns them into "needs interactive approval" and the
|
|
108
|
-
// harness blocks its own feature (same rationale as in decide()).
|
|
109
|
-
|
|
160
|
+
// harness blocks its own feature (same rationale as in decide()). With
|
|
161
|
+
// auto-memory off the folder gets no grant, as in the parent's gate.
|
|
162
|
+
const memoryDirPath = autoMemoryEnabled(projectRoot) ? memoryDir(home, projectRoot) : undefined;
|
|
110
163
|
const protectedDirs = runtimeProtectedDirs();
|
|
164
|
+
const secretPaths = runtimeSecretPaths(home);
|
|
111
165
|
|
|
112
166
|
return {
|
|
113
167
|
name: "agent-permission-gate",
|
|
@@ -117,6 +171,7 @@ export function permissionGateFactory(
|
|
|
117
171
|
// until the session runs — derived on first tool call, then pinned.
|
|
118
172
|
let scratchpadDirPath: string | undefined;
|
|
119
173
|
pi.on("tool_call", async (event, ctx) => {
|
|
174
|
+
await bashParserReady();
|
|
120
175
|
if (neverGate.has(event.toolName)) return undefined;
|
|
121
176
|
const runCwd = ctx?.cwd ?? cwd;
|
|
122
177
|
const sessionId = ctx?.sessionManager?.getSessionId?.();
|
|
@@ -137,13 +192,17 @@ export function permissionGateFactory(
|
|
|
137
192
|
cwd: runCwd,
|
|
138
193
|
signal: ctx?.signal,
|
|
139
194
|
sessionId,
|
|
195
|
+
model: ctx?.model,
|
|
140
196
|
});
|
|
141
197
|
}
|
|
142
198
|
} catch (error) {
|
|
143
199
|
return { block: true, reason: `Permission bridge failed (${(error as Error).message}); denied to fail safe.` };
|
|
144
200
|
}
|
|
145
201
|
|
|
146
|
-
if (!scratchpadDirPath && sessionId)
|
|
202
|
+
if (!scratchpadDirPath && sessionId) {
|
|
203
|
+
const candidate = sessionScratchpadDir(runCwd, sessionId);
|
|
204
|
+
if (isPrivateScratchpad(candidate)) scratchpadDirPath = candidate;
|
|
205
|
+
}
|
|
147
206
|
const tool = normalizeToolName(event.toolName);
|
|
148
207
|
const subject = extractSubject(tool, event.input as Record<string, unknown>);
|
|
149
208
|
// Path tools (writers and the read tier) are judged by where the path
|
|
@@ -156,6 +215,9 @@ export function permissionGateFactory(
|
|
|
156
215
|
// No classifier is reachable without the bridge, so auto mode is judged
|
|
157
216
|
// as acceptEdits — which decide() confines to the working directory.
|
|
158
217
|
const mode = liveMode === "auto" ? "acceptEdits" : liveMode;
|
|
218
|
+
// PowerShell is judged from its own parse, as in the parent's gate;
|
|
219
|
+
// without one it clears nothing and the call is denied here.
|
|
220
|
+
const powershellParse = await parsePowerShell(tool, subject, mode);
|
|
159
221
|
const result = decide({
|
|
160
222
|
toolName: event.toolName,
|
|
161
223
|
subject,
|
|
@@ -173,6 +235,10 @@ export function permissionGateFactory(
|
|
|
173
235
|
// the subject (the tmpdir fallback sits under a symlinked /var on macOS).
|
|
174
236
|
resultsDirPath: resolvedOrSelf(sessionResultsDir(ctx)),
|
|
175
237
|
protectedDirs,
|
|
238
|
+
secretPaths,
|
|
239
|
+
claudeCodeFastPaths: usesClaudeCodeFastPaths(ctx?.model),
|
|
240
|
+
blockReadsOutsideWorkingDirectories: settings.blockReadsOutsideWorkingDirectories,
|
|
241
|
+
powershellParse,
|
|
176
242
|
});
|
|
177
243
|
if (result.decision === "allow") return undefined;
|
|
178
244
|
const ruleNote = result.rule ? ` (rule: ${result.rule.raw})` : "";
|