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
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import {
|
|
29
29
|
gitSubcommand,
|
|
30
30
|
leadTokens,
|
|
31
|
+
LOOPS,
|
|
31
32
|
parseCommand,
|
|
32
33
|
resolvePayload,
|
|
33
34
|
type Segment,
|
|
@@ -37,18 +38,14 @@ import {
|
|
|
37
38
|
export const clip = (text: string, max = 200): string => (text.length > max ? `${text.slice(0, max)}…` : text);
|
|
38
39
|
|
|
39
40
|
export function bashGuardReason(command: string, opts: { background: boolean }): string | undefined {
|
|
40
|
-
// A heredoc
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const lead = command.split(/<</)[0];
|
|
46
|
-
if (lead !== command && !lead.trim()) return undefined;
|
|
47
|
-
const { segments, parseFailed } = parseCommand(lead);
|
|
48
|
-
if (parseFailed || segments.length === 0) return undefined;
|
|
41
|
+
// A heredoc body is data to the grammar, so a literal `vim` or `while …
|
|
42
|
+
// sleep` inside a document never trips a guard (review T14).
|
|
43
|
+
const parsed = parseCommand(command);
|
|
44
|
+
if (parsed.parseFailed || parsed.segments.length === 0) return undefined;
|
|
45
|
+
const { segments } = parsed;
|
|
49
46
|
const interactive = interactiveReason(segments);
|
|
50
47
|
if (interactive || opts.background) return interactive;
|
|
51
|
-
return waitReason(segments) ?? pollLoopReason(
|
|
48
|
+
return waitReason(segments) ?? pollLoopReason(command, segments) ?? orphanReason(parsed.background, segments);
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
// ---------------------------------------------------------------------------
|
|
@@ -123,15 +120,13 @@ function waitReason(segments: Segment[]): string | undefined {
|
|
|
123
120
|
// ---------------------------------------------------------------------------
|
|
124
121
|
// Poll-loop guard
|
|
125
122
|
|
|
123
|
+
const inLoop = (seg: Segment) => seg.enclosing.some((construct) => LOOPS.has(construct));
|
|
124
|
+
|
|
126
125
|
function pollLoopReason(command: string, segments: Segment[]): string | undefined {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
const sleeps = segments.some((seg) => {
|
|
132
|
-
const { command: cmd } = resolvePayload(leadTokens(seg));
|
|
133
|
-
return cmd === "sleep";
|
|
134
|
-
});
|
|
126
|
+
// Wherever the loop sits in the line: `echo start; while true; do sleep 5;
|
|
127
|
+
// done` stalls the session as surely as the loop alone. `sleep` counts only
|
|
128
|
+
// as a command inside the loop: `echo sleep` is data.
|
|
129
|
+
const sleeps = segments.some((seg) => inLoop(seg) && resolvePayload(leadTokens(seg)).command === "sleep");
|
|
135
130
|
if (!sleeps) return undefined;
|
|
136
131
|
return (
|
|
137
132
|
`Blocked: a foreground polling loop (\`${clip(command, 160)}\`). It occupies the whole session while it spins. ` +
|
|
@@ -143,54 +138,7 @@ function pollLoopReason(command: string, segments: Segment[]): string | undefine
|
|
|
143
138
|
// ---------------------------------------------------------------------------
|
|
144
139
|
// Orphan guard
|
|
145
140
|
|
|
146
|
-
|
|
147
|
-
export function hasBackgroundAmp(command: string): boolean {
|
|
148
|
-
let inSingle = false;
|
|
149
|
-
let inDouble = false;
|
|
150
|
-
let inAnsiC = false; // $'…' honors backslash-escaped quotes, unlike '…'
|
|
151
|
-
let escape = false;
|
|
152
|
-
for (let i = 0; i < command.length; i++) {
|
|
153
|
-
const ch = command[i];
|
|
154
|
-
if (escape) {
|
|
155
|
-
escape = false;
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
if (inAnsiC) {
|
|
159
|
-
if (ch === "\\") escape = true;
|
|
160
|
-
else if (ch === "'") inAnsiC = false;
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
if (ch === "'" && !inSingle && !inDouble && command[i - 1] === "$") {
|
|
164
|
-
inAnsiC = true;
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
if (ch === "\\" && !inSingle) {
|
|
168
|
-
escape = true;
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
if (ch === "'" && !inDouble) {
|
|
172
|
-
inSingle = !inSingle;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (ch === '"' && !inSingle) {
|
|
176
|
-
inDouble = !inDouble;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
if (inSingle || inDouble || ch !== "&") continue;
|
|
180
|
-
const prev = command[i - 1];
|
|
181
|
-
const next = command[i + 1];
|
|
182
|
-
if (next === "&") {
|
|
183
|
-
i++; // logical &&
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
if (next === ">") continue; // &> redirect
|
|
187
|
-
if (prev === ">" || prev === "|") continue; // 2>&1 fd duplication, |& pipe
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function orphanReason(command: string, segments: Segment[]): string | undefined {
|
|
141
|
+
function orphanReason(background: boolean, segments: Segment[]): string | undefined {
|
|
194
142
|
const message = (via: string) =>
|
|
195
143
|
`Blocked: this command detaches a process with ${via}, leaving an orphan this session cannot manage — its output and exit status would be lost. ` +
|
|
196
144
|
"Run it with run_in_background: true instead: it returns a task id immediately, completion arrives as a task notification, output stays readable with task_output, and it can be stopped with task_stop. " +
|
|
@@ -204,7 +152,7 @@ function orphanReason(command: string, segments: Segment[]): string | undefined
|
|
|
204
152
|
// Only a `wait` that comes LAST reaps the children — `wait; job &` still
|
|
205
153
|
// orphans the job it precedes.
|
|
206
154
|
const lastCmd = resolvePayload(leadTokens(segments[segments.length - 1])).command;
|
|
207
|
-
if (
|
|
155
|
+
if (background && lastCmd !== "wait") return message("`&`");
|
|
208
156
|
return undefined;
|
|
209
157
|
}
|
|
210
158
|
|
package/extensions/bash/index.ts
CHANGED
|
@@ -5,77 +5,130 @@
|
|
|
5
5
|
* Registering a tool named `bash` overrides the built-in (findings §2), which
|
|
6
6
|
* is how pi's official sandbox example does it too. The foreground path
|
|
7
7
|
* delegates to pi's real executor (`createBashToolDefinition`) so upstream
|
|
8
|
-
* bash behavior — timeout handling,
|
|
9
|
-
*
|
|
8
|
+
* bash behavior — timeout handling, PI_* env, spawn hooks — stays exactly
|
|
9
|
+
* pi's (its tail cut of a long output is replaced by Claude Code's head-first
|
|
10
|
+
* one, lib/spill-file.ts); the shared body in lib/shell-tool.ts adds the background
|
|
10
11
|
* branch (spool to `<sessionDir>/bash/<taskId>/output.log`, the shared
|
|
11
12
|
* background registry, a steered completion notification), and this file only
|
|
12
13
|
* supplies what is bash's: pi's definition, the bash the session resolved
|
|
13
14
|
* (lib/shell-spawn.ts — Git Bash on Windows, `CLAUDE_CODE_GIT_BASH_PATH`
|
|
14
|
-
* honoured),
|
|
15
|
+
* honoured), Claude Code's description (description.ts) and the bash guards. The permission
|
|
15
16
|
* gate and auto-mode classifier run before execute like any bash call — a
|
|
16
17
|
* background command is NOT auto-allowed, and the gate fires before anything
|
|
17
18
|
* detaches.
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import type { BashSpawnContext, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
23
|
+
import { createBashToolDefinition, createLocalBashOperations, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
22
24
|
import { Type } from "typebox";
|
|
25
|
+
import { childProcessEnv, LAUNCHER_ENV_VAR } from "../lib/app-launch.mjs";
|
|
23
26
|
import { perCwd } from "../lib/per-cwd.ts";
|
|
24
|
-
import { bashSpawn } from "../lib/shell-spawn.ts";
|
|
27
|
+
import { bashSpawn, piShellEnv, withChildProcessEnv } from "../lib/shell-spawn.ts";
|
|
25
28
|
import { registerShellTool } from "../lib/shell-tool.ts";
|
|
29
|
+
import { type DescriptionForm, followDescriptionForm } from "../lib/tool-variants.ts";
|
|
30
|
+
import { CONTEXT_BASELINE_CHANNEL, restoredContext } from "../lib/context-stack.ts";
|
|
31
|
+
import { PERMISSION_STATUS_CHANNEL, type PermissionStatus } from "../permissions/modes.ts";
|
|
32
|
+
import { BASH_PARAMS, bashDescription } from "./description.ts";
|
|
26
33
|
import { bashGuardReason } from "./guards.ts";
|
|
27
34
|
|
|
28
35
|
const BashParams = Type.Object({
|
|
29
|
-
command: Type.String({ description:
|
|
30
|
-
timeout: Type.Optional(Type.Number({ description:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
description:
|
|
34
|
-
"Run detached and return a task id immediately instead of waiting. Completion arrives as a task notification; inspect with task_output, stop with task_stop. Use this instead of nohup/'&' — those leave an unmanaged orphan process",
|
|
35
|
-
}),
|
|
36
|
-
),
|
|
37
|
-
description: Type.Optional(
|
|
38
|
-
Type.String({ description: "5-10 word description of what the command does (shown in notifications)" }),
|
|
39
|
-
),
|
|
36
|
+
command: Type.String({ description: BASH_PARAMS.command }),
|
|
37
|
+
timeout: Type.Optional(Type.Number({ description: BASH_PARAMS.timeout })),
|
|
38
|
+
description: Type.Optional(Type.String({ description: BASH_PARAMS.description })),
|
|
39
|
+
run_in_background: Type.Optional(Type.Boolean({ description: BASH_PARAMS.run_in_background })),
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
export default function bashExtension(pi: ExtensionAPI) {
|
|
43
|
+
bashTool(pi, { announce: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The tool itself. `announce: false` is the in-process child's shape
|
|
48
|
+
* (bash/child.ts): the same tool, but a child has no terminal of its own
|
|
49
|
+
* (stderr is the parent's TUI), so it prints nothing.
|
|
50
|
+
*/
|
|
51
|
+
export function bashTool(pi: ExtensionAPI, { announce }: { announce: boolean }) {
|
|
43
52
|
// The session's bash, resolved once (lib/shell-spawn.ts). An ignored
|
|
44
53
|
// CLAUDE_CODE_GIT_BASH_PATH is reported once, at the first session start.
|
|
45
54
|
const bash = bashSpawn();
|
|
46
55
|
const shellPath = bash.spawn?.commandTransport === "stdin" ? undefined : bash.spawn?.shell;
|
|
47
56
|
let warned = false;
|
|
48
|
-
pi.on("session_start", (_event, ctx) => {
|
|
57
|
+
if (announce) pi.on("session_start", (_event, ctx) => {
|
|
49
58
|
if (warned || !bash.warning) return;
|
|
50
59
|
warned = true;
|
|
51
60
|
if (ctx.hasUI) ctx.ui.notify(bash.warning, "warning");
|
|
52
61
|
else process.stderr.write(`${bash.warning}\n`);
|
|
53
62
|
});
|
|
54
63
|
|
|
55
|
-
// pi's definition supplies the
|
|
56
|
-
//
|
|
57
|
-
//
|
|
64
|
+
// pi's definition supplies the TUI renderers; the executor is re-created
|
|
65
|
+
// per working directory because it closes over cwd (worktree switches
|
|
66
|
+
// change ctx.cwd mid-session). The same bash drives pi's foreground
|
|
58
67
|
// executor, so the override applies there too.
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// contradict, or a model that trusts the description sends `timeout: 120`
|
|
64
|
-
// and gets a 120 ms deadline (TOOL-FIDELITY-REVIEW-2026-09-07 H3). Swap in
|
|
65
|
-
// CC's Bash sentence.
|
|
66
|
-
const baseDescription = base.description.replace(
|
|
67
|
-
"Optionally provide a timeout in seconds.",
|
|
68
|
-
"`timeout` is in milliseconds: default 120000, max 600000.",
|
|
69
|
-
);
|
|
68
|
+
// Every command the model runs gets the user's own environment back under
|
|
69
|
+
// the bundled app (lib/app-launch.mjs), so a `pi` it starts is the user's pi.
|
|
70
|
+
const spawnHook = (context: BashSpawnContext): BashSpawnContext => ({ ...context, env: childProcessEnv(context.env) });
|
|
71
|
+
const base = createBashToolDefinition(process.cwd(), { shellPath, spawnHook });
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
// The tier follows the model; the permission-mode variant freezes when the
|
|
74
|
+
// first request starts. Re-registering on a mode switch changes both the
|
|
75
|
+
// description and pi's tool order, losing the entire cached prefix. Mode
|
|
76
|
+
// reminders and the live permission gate carry later changes instead.
|
|
77
|
+
let form: DescriptionForm = "short";
|
|
78
|
+
// Tiny's dedicated search tools replace the long form's shell-find advice.
|
|
79
|
+
let searchTools = false;
|
|
80
|
+
let autoMode = false;
|
|
81
|
+
let liveAutoMode = false;
|
|
82
|
+
let modeFrozen = false;
|
|
83
|
+
const restoredAutoMode = (): boolean | undefined => {
|
|
84
|
+
const baselines = restoredContext(pi.events)?.baselines;
|
|
85
|
+
// Older snapshots' last mode chose the last request's description.
|
|
86
|
+
const saved = baselines?.["bash-auto-mode"];
|
|
87
|
+
if (typeof saved === "boolean") return saved;
|
|
88
|
+
return typeof baselines?.["permission-mode"] === "string" ? baselines["permission-mode"] === "auto" : undefined;
|
|
89
|
+
};
|
|
90
|
+
const describe = () => bashDescription(form, autoMode, searchTools);
|
|
91
|
+
const setDescription = registerShellTool(pi, {
|
|
72
92
|
name: "bash",
|
|
73
93
|
ccLabel: "Bash",
|
|
74
|
-
description:
|
|
94
|
+
description: describe(),
|
|
75
95
|
parameters: BashParams,
|
|
76
96
|
base,
|
|
77
|
-
foreground: perCwd((cwd: string) => createBashToolDefinition(cwd, { shellPath })),
|
|
97
|
+
foreground: perCwd((cwd: string) => createBashToolDefinition(cwd, { shellPath, spawnHook })),
|
|
78
98
|
guard: bashGuardReason,
|
|
79
99
|
backgroundShell: () => bash.spawn,
|
|
80
100
|
});
|
|
101
|
+
followDescriptionForm(pi, (next, tier) => {
|
|
102
|
+
form = next;
|
|
103
|
+
searchTools = tier === "tiny";
|
|
104
|
+
setDescription(describe());
|
|
105
|
+
});
|
|
106
|
+
pi.events.on(PERMISSION_STATUS_CHANNEL, (data) => {
|
|
107
|
+
liveAutoMode = (data as PermissionStatus).mode === "auto";
|
|
108
|
+
if (modeFrozen) return;
|
|
109
|
+
// Permissions' startup broadcast precedes this extension's session_start.
|
|
110
|
+
// Honor a restored variant even here: a temporary re-registration would
|
|
111
|
+
// move the tool despite putting its original description back afterwards.
|
|
112
|
+
autoMode = restoredAutoMode() ?? liveAutoMode;
|
|
113
|
+
setDescription(describe());
|
|
114
|
+
});
|
|
115
|
+
pi.on("session_start", () => {
|
|
116
|
+
const saved = restoredAutoMode();
|
|
117
|
+
modeFrozen = saved !== undefined;
|
|
118
|
+
autoMode = saved ?? liveAutoMode;
|
|
119
|
+
setDescription(describe());
|
|
120
|
+
});
|
|
121
|
+
pi.on("turn_start", () => {
|
|
122
|
+
modeFrozen = true;
|
|
123
|
+
pi.events.emit(CONTEXT_BASELINE_CHANNEL, { key: "bash-auto-mode", value: autoMode });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// The user's own `!` commands, under the bundled app only: pi runs them with
|
|
127
|
+
// its full process environment, so they get the same restore. Outside the app
|
|
128
|
+
// pi's default path runs untouched. They run in the session's bash, the one
|
|
129
|
+
// the bash tool uses.
|
|
130
|
+
pi.on("user_bash", () => {
|
|
131
|
+
if (process.env[LAUNCHER_ENV_VAR] === undefined) return undefined;
|
|
132
|
+
return { operations: withChildProcessEnv(createLocalBashOperations({ shellPath }), () => piShellEnv(join(getAgentDir(), "bin"))) };
|
|
133
|
+
});
|
|
81
134
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi's compaction keep window, at startup (lib/compaction-keep.mjs has the
|
|
3
|
+
* rule and why).
|
|
4
|
+
*
|
|
5
|
+
* On the user's own pi, while neither the user's nor the project's settings set
|
|
6
|
+
* `compaction.keepRecentTokens`, One Code asks once whether to set it to 0 in
|
|
7
|
+
* the user's settings. "Yes" writes that one key (pi merges nested settings
|
|
8
|
+
* key by key when it saves its own, so the value survives); pi reads it at
|
|
9
|
+
* the next start. "No" is remembered in `~/.onecode/settings.json` and never
|
|
10
|
+
* asked again; Escape asks again next start. An explicit value is the user's
|
|
11
|
+
* and is never questioned; `/doctor` shows it. The bundled app sets 0 itself.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import os from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { type ExtensionContext, getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { installKind } from "../doctor/builtins.ts";
|
|
18
|
+
import {
|
|
19
|
+
compactionKeepView,
|
|
20
|
+
KEEP_NO,
|
|
21
|
+
KEEP_YES,
|
|
22
|
+
keepWindowPrompt,
|
|
23
|
+
ONE_CODE_KEEP_RECENT_TOKENS,
|
|
24
|
+
withCompactionKeepBackfilled,
|
|
25
|
+
} from "../lib/compaction-keep.mjs";
|
|
26
|
+
import { readSettingsForWrite, writeSettings } from "../lib/one-code-settings.ts";
|
|
27
|
+
import { tildify } from "../lib/paths.ts";
|
|
28
|
+
import { declined, recordDeclined } from "./declined.ts";
|
|
29
|
+
|
|
30
|
+
/** `~/.onecode/settings.json` key: the user answered "No" to the question. */
|
|
31
|
+
export const KEEP_DECLINED_KEY = "compactionKeepDeclined";
|
|
32
|
+
|
|
33
|
+
/** Ask once, when nothing sets the keep window. Never throws. */
|
|
34
|
+
export async function handleCompactionKeep(ctx: ExtensionContext): Promise<void> {
|
|
35
|
+
try {
|
|
36
|
+
if (installKind() === "app" || !ctx.hasUI || process.env.CC_COMPACTION === "0") return;
|
|
37
|
+
if (declined(KEEP_DECLINED_KEY)) return;
|
|
38
|
+
const agentDir = getAgentDir();
|
|
39
|
+
const manager = SettingsManager.create(ctx.cwd, agentDir);
|
|
40
|
+
const modelKey = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
41
|
+
if (compactionKeepView(manager.getGlobalSettings(), manager.getProjectSettings(), modelKey).source !== "default") return;
|
|
42
|
+
|
|
43
|
+
const userPath = join(agentDir, "settings.json");
|
|
44
|
+
const choice = await ctx.ui.select(keepWindowPrompt(tildify(userPath, os.homedir())), [KEEP_YES, KEEP_NO]);
|
|
45
|
+
if (choice === KEEP_YES) {
|
|
46
|
+
const { settings, changed } = withCompactionKeepBackfilled(readSettingsForWrite(userPath));
|
|
47
|
+
if (changed) writeSettings(userPath, settings);
|
|
48
|
+
ctx.ui.notify(`Set compaction.keepRecentTokens to ${ONE_CODE_KEEP_RECENT_TOKENS}; /compact works on short sessions from the next start.`, "info");
|
|
49
|
+
} else if (choice === KEEP_NO) {
|
|
50
|
+
try {
|
|
51
|
+
recordDeclined(KEEP_DECLINED_KEY);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
ctx.ui.notify(`Could not remember the answer in ~/.onecode/settings.json: ${(error as Error).message}. One Code asks again next start.`, "warning");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
try {
|
|
58
|
+
ctx.ui.notify(`Could not set pi's compaction keep window: ${(error as Error).message}. Run /doctor for the settings change.`, "warning");
|
|
59
|
+
} catch {
|
|
60
|
+
// The session ended while the dialog was open: nothing is left to tell.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The startup questions' remembered "No" (replaced-builtins.ts,
|
|
3
|
+
* compaction-keep.ts): a key set to true in `~/.onecode/settings.json`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import { oneCodeSettingsPath, readSettingsForWrite, writeSettings } from "../lib/one-code-settings.ts";
|
|
8
|
+
|
|
9
|
+
/** Whether the user answered "No" to the question kept under `key`; a malformed settings file reads as not declined. */
|
|
10
|
+
export function declined(key: string): boolean {
|
|
11
|
+
try {
|
|
12
|
+
return readSettingsForWrite(oneCodeSettingsPath(os.homedir()))[key] === true;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Remember a "No" to the question kept under `key`. */
|
|
19
|
+
export function recordDeclined(key: string): void {
|
|
20
|
+
const path = oneCodeSettingsPath(os.homedir());
|
|
21
|
+
writeSettings(path, { ...readSettingsForWrite(path), [key]: true });
|
|
22
|
+
}
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import { ARGUMENT_HINT_CHANNEL, type ArgumentHint, type CommandHint, frontmatterCommandHint, hintForInput, PI_BUILTIN_HINTS } from "../lib/argument-hints.ts";
|
|
18
|
+
import { parseFrontmatterLoosely } from "../lib/frontmatter.ts";
|
|
19
|
+
import { promptTemplateFiles } from "../lib/skill-scan.ts";
|
|
17
20
|
import { modeCycleKey } from "../lib/keys.ts";
|
|
18
21
|
import os from "node:os";
|
|
19
22
|
import { fileURLToPath } from "node:url";
|
|
@@ -21,6 +24,8 @@ import { basename, dirname, join } from "node:path";
|
|
|
21
24
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
22
25
|
import { getAgentDir, SettingsManager, VERSION as PI_VERSION } from "@earendil-works/pi-coding-agent";
|
|
23
26
|
import { piVersionWarning } from "../lib/pi-version.ts";
|
|
27
|
+
import { handleCompactionKeep } from "./compaction-keep.ts";
|
|
28
|
+
import { handleReplacedBuiltins } from "./replaced-builtins.ts";
|
|
24
29
|
import { extensionVersion } from "../lib/package-version.ts";
|
|
25
30
|
import {
|
|
26
31
|
formatModel,
|
|
@@ -38,9 +43,11 @@ import {
|
|
|
38
43
|
shouldDefaultHideThinking,
|
|
39
44
|
type StartupSection,
|
|
40
45
|
} from "./startup.ts";
|
|
41
|
-
import { linesComponent, safeThemePaint, truncateLine } from "../lib/tui-render.ts";
|
|
46
|
+
import { linesComponent, safeThemeInverse, safeThemePaint, truncateLine } from "../lib/tui-render.ts";
|
|
47
|
+
import { AGENT_VIEW_CHANNEL, type AgentViewAnnouncement, type AgentViewTarget } from "../lib/agent-view.ts";
|
|
42
48
|
import { markAssistantMarkdown } from "./assistant-marker.ts";
|
|
43
49
|
import { PROMPT_PADDING, PromptEditor } from "./prompt-editor.ts";
|
|
50
|
+
import { sanitizeTitle } from "../lib/terminal-text.ts";
|
|
44
51
|
|
|
45
52
|
export { truncateLine };
|
|
46
53
|
|
|
@@ -138,6 +145,19 @@ export function sectionSummary(sections: StartupSection[], paint: (color: string
|
|
|
138
145
|
.join(paint("muted", " · "));
|
|
139
146
|
}
|
|
140
147
|
|
|
148
|
+
/** The mode line's words for each permission mode, so the banner never says "default" beside "manual mode on". */
|
|
149
|
+
const MODE_WORDS: Record<string, string> = {
|
|
150
|
+
default: "manual",
|
|
151
|
+
acceptEdits: "accept edits",
|
|
152
|
+
bypassPermissions: "bypass permissions",
|
|
153
|
+
dontAsk: "don't ask",
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** The banner's mode field: the permission display with the mode named as the mode line names it. */
|
|
157
|
+
export function bannerModeLabel(display: string): string {
|
|
158
|
+
return MODE_WORDS[display] ?? display;
|
|
159
|
+
}
|
|
160
|
+
|
|
141
161
|
/**
|
|
142
162
|
* Banner layout: the wordmark block, then a one-space-indented text column
|
|
143
163
|
* (tagline + version, context, hints, sections). Kept pure so the layout is
|
|
@@ -213,7 +233,11 @@ interface BrandingEditorUI {
|
|
|
213
233
|
* a pi-tui render change ever makes the marker misplace (cosmetic only — typing
|
|
214
234
|
* is never affected; see prompt-marker.ts).
|
|
215
235
|
*/
|
|
216
|
-
function installPromptMarker(
|
|
236
|
+
function installPromptMarker(
|
|
237
|
+
ctx: { hasUI: boolean; mode: string; ui: BrandingEditorUI; isIdle(): boolean; hasPendingMessages(): boolean },
|
|
238
|
+
argumentHints: ReadonlyMap<string, CommandHint>,
|
|
239
|
+
agentView: { target?: AgentViewTarget },
|
|
240
|
+
): void {
|
|
217
241
|
if (process.env.CC_NO_INPUT_MARKER === "1") return;
|
|
218
242
|
if (!ctx.hasUI || ctx.mode !== "tui") return;
|
|
219
243
|
const ui = ctx.ui;
|
|
@@ -223,7 +247,26 @@ function installPromptMarker(ctx: { hasUI: boolean; mode: string; ui: BrandingEd
|
|
|
223
247
|
// Bold makes the chevron read heavier than the input text beside it.
|
|
224
248
|
return `\x1b[1m${safeThemePaint(ui.theme)("accent", PROMPT_GLYPH)}\x1b[0m${" ".repeat(PROMPT_PADDING - 1)}`;
|
|
225
249
|
};
|
|
226
|
-
|
|
250
|
+
const renderHint = (text: string) => {
|
|
251
|
+
// While an agent is viewed the empty editor addresses it (lib/agent-view.ts).
|
|
252
|
+
const placeholder = text === "" && agentView.target ? agentView.target.placeholder : hintForInput(text, argumentHints);
|
|
253
|
+
return placeholder === undefined ? undefined : safeThemePaint(ui.theme)("dim", placeholder);
|
|
254
|
+
};
|
|
255
|
+
const badge = () => agentView.target?.badge;
|
|
256
|
+
const paintBadge = (label: string) => safeThemeInverse(ui.theme)(safeThemePaint(ui.theme)("accent", ` ${label} `));
|
|
257
|
+
// The editor can outlive this context across a session replacement; a stale
|
|
258
|
+
// context throws, and ↑ must still reach the editor then.
|
|
259
|
+
const queued = () => {
|
|
260
|
+
try {
|
|
261
|
+
return !ctx.isIdle() && ctx.hasPendingMessages();
|
|
262
|
+
} catch {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
ui.setEditorComponent(
|
|
267
|
+
(tui: unknown, theme: unknown, keybindings: unknown) =>
|
|
268
|
+
new PromptEditor(tui as never, theme as never, keybindings as never, renderMarker, renderHint, badge, paintBadge, queued),
|
|
269
|
+
);
|
|
227
270
|
}
|
|
228
271
|
|
|
229
272
|
/**
|
|
@@ -268,6 +311,25 @@ function applyOutputPadDefault(): void {
|
|
|
268
311
|
}
|
|
269
312
|
|
|
270
313
|
export default function brandingExtension(pi: ExtensionAPI) {
|
|
314
|
+
/**
|
|
315
|
+
* pi's merged `quietStartup` (global settings with the project's
|
|
316
|
+
* `.pi/settings.json` over them), so the banner and pi agree on whether pi
|
|
317
|
+
* lists its own startup sections. `pi.getSettings()` is in pi 1.0.1; a pi
|
|
318
|
+
* near the 0.84.3 floor without it falls back to the global file alone.
|
|
319
|
+
*/
|
|
320
|
+
const effectiveQuietStartup = (agentDir: string): unknown => {
|
|
321
|
+
try {
|
|
322
|
+
if (typeof (pi as { getSettings?: unknown }).getSettings === "function") return pi.getSettings().quietStartup;
|
|
323
|
+
} catch {
|
|
324
|
+
// The banner must still install: fall back to the global file.
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
return (JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8")) as { quietStartup?: unknown }).quietStartup;
|
|
328
|
+
} catch {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
271
333
|
applyThinkingDefault();
|
|
272
334
|
applyOutputPadDefault();
|
|
273
335
|
// Mark each block of assistant prose with Claude Code's "●" bullet. Only
|
|
@@ -296,7 +358,9 @@ export default function brandingExtension(pi: ExtensionAPI) {
|
|
|
296
358
|
const retitle = (ctx: { hasUI: boolean; cwd: string; ui: { setTitle(title: string): void } }) => {
|
|
297
359
|
if (!live || !ctx.hasUI) return;
|
|
298
360
|
const name = pi.getSessionName?.()?.trim();
|
|
299
|
-
|
|
361
|
+
// pi writes the title inside its OSC 0 and strips only CR and LF; a BEL or
|
|
362
|
+
// ESC in the session name (any source) or the directory would end it early.
|
|
363
|
+
const title = sanitizeTitle(name ? `${NAME} - ${name} - ${basename(ctx.cwd)}` : NAME);
|
|
300
364
|
if (title === lastTitle) return; // agent_start fires every turn; write only on change
|
|
301
365
|
lastTitle = title;
|
|
302
366
|
ctx.ui.setTitle(title);
|
|
@@ -307,18 +371,76 @@ export default function brandingExtension(pi: ExtensionAPI) {
|
|
|
307
371
|
live = false;
|
|
308
372
|
});
|
|
309
373
|
|
|
310
|
-
|
|
374
|
+
// Commands' argument placeholders (lib/argument-hints.ts), drawn by the prompt editor.
|
|
375
|
+
// Declared before the CC_NO_BANNER return below: session_start reads it.
|
|
376
|
+
const argumentHints = new Map<string, CommandHint>(Object.entries(PI_BUILTIN_HINTS).map(([command, hint]) => [command, { hint }]));
|
|
377
|
+
// The agent the user is viewing, if any: the editor then targets it (lib/agent-view.ts).
|
|
378
|
+
const agentView: { target?: AgentViewTarget } = {};
|
|
379
|
+
pi.events.on(AGENT_VIEW_CHANNEL, (data) => {
|
|
380
|
+
agentView.target = (data as AgentViewAnnouncement | undefined)?.target;
|
|
381
|
+
});
|
|
382
|
+
pi.events.on(ARGUMENT_HINT_CHANNEL, (data) => {
|
|
383
|
+
const { command, ...hint } = data as ArgumentHint;
|
|
384
|
+
argumentHints.set(command, hint);
|
|
385
|
+
});
|
|
386
|
+
// pi's prompt templates and `/skill:` commands carry their file's
|
|
387
|
+
// `argument-hint` frontmatter. pi lists them only once a turn resolves them,
|
|
388
|
+
// so the template folders are scanned at session_start (the skill extension
|
|
389
|
+
// announces `/skill:` hints from its own scan) and pi's list read each turn.
|
|
390
|
+
// pi runs a built-in or extension command before a command file of the same
|
|
391
|
+
// name, so such a file's hint is never read.
|
|
392
|
+
const hintedFiles = new Set<string>();
|
|
393
|
+
const readFileHints = (files: { name: string; path: string }[], commands = pi.getCommands()) => {
|
|
394
|
+
const extensionCommands = new Set(commands.filter((c) => c.source === "extension").map((c) => c.name));
|
|
395
|
+
for (const { name, path } of files) {
|
|
396
|
+
if (hintedFiles.has(path)) continue;
|
|
397
|
+
if (Object.hasOwn(PI_BUILTIN_HINTS, name) || extensionCommands.has(name)) continue;
|
|
398
|
+
try {
|
|
399
|
+
const hint = frontmatterCommandHint(parseFrontmatterLoosely(readFileSync(path, "utf-8")).frontmatter);
|
|
400
|
+
if (hint) argumentHints.set(name, hint);
|
|
401
|
+
hintedFiles.add(path);
|
|
402
|
+
} catch {
|
|
403
|
+
// Unreadable (mid-replace, say): no hint now; a later scan retries it.
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
pi.on("before_agent_start", () => {
|
|
408
|
+
try {
|
|
409
|
+
const commands = pi.getCommands();
|
|
410
|
+
readFileHints(
|
|
411
|
+
commands.filter((c) => c.source !== "extension").map((c) => ({ name: c.name, path: c.sourceInfo.path })),
|
|
412
|
+
commands,
|
|
413
|
+
);
|
|
414
|
+
} catch {
|
|
415
|
+
// Not bound (a torn-down session): the next turn looks again.
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
pi.on("session_start", (event, ctx) => {
|
|
311
419
|
// pi's own write lands after this handler, so only the deferred write
|
|
312
420
|
// sticks; a new session may carry a name (resume), so forget the last.
|
|
313
421
|
lastTitle = undefined;
|
|
314
422
|
live = true;
|
|
315
423
|
setTimeout(() => retitle(ctx), 0).unref?.();
|
|
316
424
|
ctx.ui.setHiddenThinkingLabel(THINKING_LABEL);
|
|
317
|
-
installPromptMarker(ctx as unknown as
|
|
425
|
+
installPromptMarker(ctx as unknown as Parameters<typeof installPromptMarker>[0], argumentHints, agentView);
|
|
426
|
+
try {
|
|
427
|
+
readFileHints(promptTemplateFiles(ctx.cwd, os.homedir(), getAgentDir()));
|
|
428
|
+
} catch {
|
|
429
|
+
// Commands not bound: nothing was marked read, so the next turn reads pi's list.
|
|
430
|
+
}
|
|
318
431
|
// Soft drift guard: warn once at startup when the hosting pi is outside
|
|
319
432
|
// the range this release was tested against (see lib/pi-version.ts).
|
|
320
433
|
const versionWarning = piVersionWarning(PI_VERSION);
|
|
321
434
|
if (versionWarning) ctx.ui.notify(versionWarning, "warning");
|
|
435
|
+
// pi 0.99 skips its built-in tool search and MCP for ours and warns at
|
|
436
|
+
// every start, advising the user to remove One Code: offer to turn them
|
|
437
|
+
// off once, else answer with the notice (replaced-builtins.ts). Only
|
|
438
|
+
// when pi resolves its built-ins again (startup, /reload), not on a
|
|
439
|
+
// /new, resume or fork, which re-run this handler too. Not awaited: a
|
|
440
|
+
// dialog must not hold up startup. The compaction keep-window question
|
|
441
|
+
// (compaction-keep.ts) follows it, so the two dialogs never overlap.
|
|
442
|
+
const reason = (event as { reason?: string }).reason ?? "startup";
|
|
443
|
+
if (reason === "startup" || reason === "reload") void handleReplacedBuiltins(ctx).then(() => handleCompactionKeep(ctx));
|
|
322
444
|
});
|
|
323
445
|
|
|
324
446
|
if (process.env.CC_NO_BANNER === "1") return;
|
|
@@ -363,9 +485,11 @@ export default function brandingExtension(pi: ExtensionAPI) {
|
|
|
363
485
|
// instead (minus the internal [Extensions] noise).
|
|
364
486
|
const home = os.homedir();
|
|
365
487
|
// Resolve pi's live agent dir (honours PI_CODING_AGENT_DIR) so an
|
|
366
|
-
// isolated one-code app reads its own
|
|
488
|
+
// isolated one-code app reads its own skills, never ~/.pi.
|
|
367
489
|
const agentDir = getAgentDir();
|
|
368
|
-
|
|
490
|
+
// Use pi's effective settings, including project overrides, so the two
|
|
491
|
+
// listings agree on whether quiet startup is enabled.
|
|
492
|
+
const sections = quietStartupEnabled(effectiveQuietStartup(agentDir))
|
|
369
493
|
? collectStartupSections(ctx.cwd, home, join(dirname(fileURLToPath(import.meta.url)), "..", "..", "themes"), agentDir)
|
|
370
494
|
: undefined;
|
|
371
495
|
|
|
@@ -387,7 +511,7 @@ export default function brandingExtension(pi: ExtensionAPI) {
|
|
|
387
511
|
version,
|
|
388
512
|
model: modelLabel ?? "none",
|
|
389
513
|
cwd: ctx.cwd,
|
|
390
|
-
mode: permissionModeDisplay(permissionStatus ?? { mode: "default", paused: false }),
|
|
514
|
+
mode: bannerModeLabel(permissionModeDisplay(permissionStatus ?? { mode: "default", paused: false })),
|
|
391
515
|
subagents:
|
|
392
516
|
modelLabel === undefined
|
|
393
517
|
? "none"
|