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
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
* every line goes through `truncateLine` — never skip it.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { isAbsolute, relative } from "node:path";
|
|
18
19
|
import { framesToText, notificationBody, parseNotificationFrames } from "./notifications.ts";
|
|
19
20
|
import { fitPainted, hardWrapColumns, sliceColumns, visibleWidth } from "./text-width.ts";
|
|
21
|
+
import { sanitizeDisplayText } from "./terminal-text.ts";
|
|
22
|
+
import { userDenialKind } from "./user-denial.ts";
|
|
20
23
|
export { notificationBody };
|
|
21
24
|
export { visibleWidth } from "./text-width.ts";
|
|
22
25
|
|
|
@@ -148,6 +151,41 @@ export function cutPlainText(text: string, width: number): string {
|
|
|
148
151
|
return `${sliceColumns(text, Math.max(0, width - 1)).text}…`;
|
|
149
152
|
}
|
|
150
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Fit a key-hint row (`a · b · c`) into `width` columns by breaking it at its
|
|
156
|
+
* ` · ` separators onto as many lines as it needs, so a narrow terminal shows
|
|
157
|
+
* every key instead of cutting the last ones off. A single hint wider than
|
|
158
|
+
* the row is cut with an ellipsis.
|
|
159
|
+
*/
|
|
160
|
+
export function wrapHints(text: string, width: number, separator = " · "): string[] {
|
|
161
|
+
if (width <= 0) return [""];
|
|
162
|
+
if (visibleWidth(text) <= width) return [text];
|
|
163
|
+
const lines: string[] = [];
|
|
164
|
+
let current = "";
|
|
165
|
+
for (const hint of text.split(separator)) {
|
|
166
|
+
const joined = current ? `${current}${separator}${hint}` : hint;
|
|
167
|
+
if (!current || visibleWidth(joined) <= width) {
|
|
168
|
+
current = joined;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
lines.push(cutPlainText(current, width));
|
|
172
|
+
current = hint;
|
|
173
|
+
}
|
|
174
|
+
if (current) lines.push(cutPlainText(current, width));
|
|
175
|
+
return lines;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* A panel's key-hint rows: each hint line wrapped at its separators to fit
|
|
180
|
+
* `width` after `indent`, so no key is cut off on a narrow terminal, and
|
|
181
|
+
* returned indented (plain; the caller paints). A list panel counts the rows
|
|
182
|
+
* in its body budget.
|
|
183
|
+
*/
|
|
184
|
+
export function hintLines(hints: string | readonly string[], width: number, indent = ""): string[] {
|
|
185
|
+
const room = Math.max(1, width - visibleWidth(indent));
|
|
186
|
+
return (typeof hints === "string" ? [hints] : hints).flatMap((line) => wrapHints(line, room).map((part) => `${indent}${part}`));
|
|
187
|
+
}
|
|
188
|
+
|
|
151
189
|
/** Cut then pad plain (unpainted) text to exactly `width` terminal columns. */
|
|
152
190
|
export function padPlainText(text: string, width: number): string {
|
|
153
191
|
const shortened = cutPlainText(text, width);
|
|
@@ -172,17 +210,19 @@ export function panelTopRule(paint: (color: string, text: string) => string, wid
|
|
|
172
210
|
* A rounded, bordered single-line search input, Claude Code style. Returns three
|
|
173
211
|
* lines (top border, the input row, bottom border), already indented one column.
|
|
174
212
|
* The placeholder shows dim until the user types. `paint` colors the border with
|
|
175
|
-
* the "border" token; the query text stays in the default foreground.
|
|
213
|
+
* the "border" token; the query text stays in the default foreground. `icon`
|
|
214
|
+
* leads the text (a plain text input passes ""); the box keeps its width.
|
|
176
215
|
*/
|
|
177
216
|
export function searchBoxLines(
|
|
178
217
|
query: string,
|
|
179
218
|
placeholder: string,
|
|
180
219
|
paint: (color: string, text: string) => string,
|
|
181
220
|
width: number,
|
|
221
|
+
icon = "⌕ ",
|
|
182
222
|
): string[] {
|
|
183
223
|
const boxWidth = Math.max(12, width - 2); // one column of margin each side
|
|
184
224
|
const inner = boxWidth - 2; // space between the vertical borders
|
|
185
|
-
const label =
|
|
225
|
+
const label = `${icon}${query || placeholder}`;
|
|
186
226
|
const cut = cutPlainText(label, inner - 2); // one space padding each side
|
|
187
227
|
const content = ` ${cut}${" ".repeat(Math.max(0, inner - 2 - visibleWidth(cut)))} `;
|
|
188
228
|
const v = paint("border", "│");
|
|
@@ -251,6 +291,50 @@ export function wrapPlainText(text: string, width: number): string[] {
|
|
|
251
291
|
return out;
|
|
252
292
|
}
|
|
253
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Word-aware wrap for prose (task text, assistant text, the dim transcript lines, a plan): breaks at spaces,
|
|
296
|
+
* hard-breaking only words longer than the width; preserves blank lines. A
|
|
297
|
+
* line's leading indent stays and its continuation lines hang under it (a
|
|
298
|
+
* nested list, an indented code line), unless the indent takes half the width.
|
|
299
|
+
* Plain text in, plain lines out — painting happens after, per line.
|
|
300
|
+
*/
|
|
301
|
+
export function wrapProse(text: string, width: number): string[] {
|
|
302
|
+
const columns = Math.max(1, width);
|
|
303
|
+
const out: string[] = [];
|
|
304
|
+
for (const raw of text.replace(/\r\n/g, "\n").split("\n")) {
|
|
305
|
+
if (raw.trim() === "") {
|
|
306
|
+
out.push("");
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const lead = /^[ \t]*/.exec(raw)![0];
|
|
310
|
+
const indent = lead.replace(/\t/g, " ");
|
|
311
|
+
const hang = visibleWidth(indent) * 2 < columns ? indent : "";
|
|
312
|
+
const room = columns - visibleWidth(hang);
|
|
313
|
+
const push = (part: string) => out.push(`${hang}${part}`);
|
|
314
|
+
let line = "";
|
|
315
|
+
for (const word of raw.slice(lead.length).split(" ")) {
|
|
316
|
+
if (visibleWidth(word) > room) {
|
|
317
|
+
// A word wider than the screen (path, hash): flush, then hard-break it.
|
|
318
|
+
// The last chunk becomes the running line so later words can join it.
|
|
319
|
+
if (line) push(line);
|
|
320
|
+
const chunks = hardWrapColumns(word, room);
|
|
321
|
+
line = chunks.pop() ?? "";
|
|
322
|
+
for (const chunk of chunks) push(chunk);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const candidate = line ? `${line} ${word}` : word;
|
|
326
|
+
if (visibleWidth(candidate) > room) {
|
|
327
|
+
push(line);
|
|
328
|
+
line = word;
|
|
329
|
+
} else {
|
|
330
|
+
line = candidate;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
push(line);
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
|
|
254
338
|
/**
|
|
255
339
|
* Cut a painted line to `width` terminal columns without splitting ANSI escape
|
|
256
340
|
* sequences or a wide/emoji grapheme, ending with an ellipsis and a reset so
|
|
@@ -296,13 +380,18 @@ export function linesComponent(build: (width: number) => string[]): TuiComponent
|
|
|
296
380
|
}
|
|
297
381
|
|
|
298
382
|
/**
|
|
299
|
-
* A
|
|
383
|
+
* A dim transcript line led by a dim mark ("✻ Cooked for 5m 12s",
|
|
300
384
|
* "※ recap: …") — the shared shape behind the turn-duration and recap
|
|
301
|
-
* display-only entries.
|
|
385
|
+
* display-only entries. Text wider than the terminal word-wraps under itself
|
|
386
|
+
* (a hanging indent past the mark) instead of being cut at the edge.
|
|
387
|
+
* Width-memoized via linesComponent.
|
|
302
388
|
*/
|
|
303
389
|
export function dimMarkedLine(theme: unknown, mark: string, text: string): TuiComponent {
|
|
304
390
|
const paint = safeThemePaint(theme);
|
|
305
|
-
return linesComponent(() =>
|
|
391
|
+
return linesComponent((width) => {
|
|
392
|
+
const indent = visibleWidth(mark) + 1;
|
|
393
|
+
return wrapProse(text, width - indent).map((line, index) => `${index === 0 ? paint("dim", mark) : " ".repeat(indent - 1)} ${paint("dim", line)}`);
|
|
394
|
+
});
|
|
306
395
|
}
|
|
307
396
|
|
|
308
397
|
/**
|
|
@@ -329,9 +418,9 @@ const PRIMARY_ARG_KEYS = [
|
|
|
329
418
|
"task",
|
|
330
419
|
] as const;
|
|
331
420
|
|
|
332
|
-
/** Collapse whitespace/newlines so a summary always fits on one line. */
|
|
421
|
+
/** Collapse whitespace/newlines so a summary always fits on one line (control characters removed). */
|
|
333
422
|
function oneLine(text: string, max = 96): string {
|
|
334
|
-
const flat = text.replace(/\s+/g, " ").trim();
|
|
423
|
+
const flat = sanitizeDisplayText(text).replace(/\s+/g, " ").trim();
|
|
335
424
|
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
336
425
|
}
|
|
337
426
|
|
|
@@ -445,16 +534,20 @@ export function callLine(
|
|
|
445
534
|
bulletColorOverride?: string,
|
|
446
535
|
): string {
|
|
447
536
|
const bullet = theme.fg(bulletColorOverride ?? bulletColor(isPartial, isError), "●");
|
|
448
|
-
|
|
449
|
-
|
|
537
|
+
// A label can carry an MCP server's or tool's own name, a summary the model's arguments.
|
|
538
|
+
const name = theme.bold(sanitizeDisplayText(label));
|
|
539
|
+
const shown = sanitizeDisplayText(summary);
|
|
540
|
+
return shown ? `${bullet} ${name}(${theme.fg("muted", shown)})` : `${bullet} ${name}`;
|
|
450
541
|
}
|
|
451
542
|
|
|
452
543
|
/**
|
|
453
544
|
* ` ⎿ line…` — the result lines. First line carries the elbow, the rest are
|
|
454
545
|
* aligned under it; a `… +N lines` trailer advertises ctrl+o when collapsed.
|
|
546
|
+
* The text is a tool's output (a shell, an MCP server, a web page), so its
|
|
547
|
+
* escape sequences and control characters are removed before it is painted.
|
|
455
548
|
*/
|
|
456
549
|
export function resultLines(theme: ThemeLike, text: string, expanded: boolean, isError: boolean, maxCollapsed = 5): string[] {
|
|
457
|
-
const { lines, hidden } = collapseLines(text, expanded, maxCollapsed);
|
|
550
|
+
const { lines, hidden } = collapseLines(sanitizeDisplayText(text), expanded, maxCollapsed);
|
|
458
551
|
const color = isError ? "error" : "muted";
|
|
459
552
|
const out = lines.map((line, i) => (i === 0 ? ` ⎿ ${theme.fg(color, line)}` : ` ${theme.fg(color, line)}`));
|
|
460
553
|
if (hidden > 0) out.push(` ${theme.fg("dim", `… +${hidden} lines (${EXPAND_HINT})`)}`);
|
|
@@ -471,6 +564,47 @@ export function textContent(result: { content?: Array<{ type: string; text?: str
|
|
|
471
564
|
.trim();
|
|
472
565
|
}
|
|
473
566
|
|
|
567
|
+
/** The file operation a rejected file tool names: "User rejected write to …", "User rejected update to …". */
|
|
568
|
+
export type RejectedFileOp = "write" | "update";
|
|
569
|
+
|
|
570
|
+
/** Claude Code's transcript line for a call the user rejected with a reason, or for a tool with no line of its own. */
|
|
571
|
+
export const TOOL_USE_REJECTED = "Tool use rejected";
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* A file tool's `path` for the transcript, as Claude Code shows it: relative to
|
|
575
|
+
* the working directory when it is absolute, as the model wrote it otherwise.
|
|
576
|
+
*/
|
|
577
|
+
export function displayToolPath(raw: string, cwd: string | undefined): string {
|
|
578
|
+
if (!cwd || !isAbsolute(raw)) return raw;
|
|
579
|
+
return relative(cwd, raw) || raw;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* The result line for a call the user rejected, or undefined when the result
|
|
584
|
+
* is not a rejection. The model reads the whole rejection text; the transcript
|
|
585
|
+
* shows Claude Code's short line instead: "User rejected write to <path>" for a
|
|
586
|
+
* file tool rejected without a reason, "Tool use rejected" otherwise. A
|
|
587
|
+
* rejection without a reason also stops the turn, and the turn's own
|
|
588
|
+
* "Interrupted" line follows it.
|
|
589
|
+
*/
|
|
590
|
+
export function rejectionLine(
|
|
591
|
+
theme: ThemeLike,
|
|
592
|
+
result: { content?: Array<{ type: string; text?: string }> },
|
|
593
|
+
isError: boolean,
|
|
594
|
+
file?: { op: RejectedFileOp; path: string | undefined },
|
|
595
|
+
): string | undefined {
|
|
596
|
+
if (!isError) return undefined;
|
|
597
|
+
const kind = userDenialKind(textContent(result));
|
|
598
|
+
if (!kind) return undefined;
|
|
599
|
+
if (kind === "stop" && file?.path) return ` ⎿ ${rejectedFileHead(theme, file.op, file.path)}`;
|
|
600
|
+
return ` ⎿ ${theme.fg("dim", TOOL_USE_REJECTED)}`;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** "User rejected <op> to <path>", dimmed with the path in bold: a file tool's reasonless rejection, before the `⎿` elbow. */
|
|
604
|
+
export function rejectedFileHead(theme: ThemeLike, op: RejectedFileOp, path: string): string {
|
|
605
|
+
return `${theme.fg("dim", `User rejected ${op} to `)}${theme.bold(theme.fg("dim", sanitizeDisplayText(path)))}`;
|
|
606
|
+
}
|
|
607
|
+
|
|
474
608
|
export interface CcRenderSpec<TArgs = unknown, TDetails = unknown> {
|
|
475
609
|
/** One-line arg summary shown in parentheses. Default: `summarizeArgs`. */
|
|
476
610
|
title?: (args: TArgs | undefined) => string | undefined;
|
|
@@ -485,6 +619,12 @@ export interface CcRenderSpec<TArgs = unknown, TDetails = unknown> {
|
|
|
485
619
|
) => string | undefined;
|
|
486
620
|
/** Lines shown before the `… +N lines` trailer kicks in. Default 5. */
|
|
487
621
|
maxCollapsedLines?: number;
|
|
622
|
+
/**
|
|
623
|
+
* Already-painted text (an OSC 8 hyperlink, say) appended to the first
|
|
624
|
+
* result line after the result is sanitized. Build it only from values the
|
|
625
|
+
* tool controls, never from its output: nothing strips its escapes.
|
|
626
|
+
*/
|
|
627
|
+
link?: (result: { details?: TDetails }, isError: boolean) => string | undefined;
|
|
488
628
|
}
|
|
489
629
|
|
|
490
630
|
/**
|
|
@@ -524,15 +664,33 @@ export function ccToolRenderers<TArgs = any, TDetails = any>(
|
|
|
524
664
|
theme: ThemeLike,
|
|
525
665
|
context: { args?: TArgs; isError: boolean },
|
|
526
666
|
) {
|
|
667
|
+
const stripped = stripReminderBlocks(result);
|
|
668
|
+
const rejected = rejectionLine(theme, stripped, context.isError);
|
|
669
|
+
if (rejected) return linesComponent(() => [rejected]);
|
|
527
670
|
let text: string | undefined;
|
|
528
671
|
try {
|
|
529
672
|
text = spec.result?.(result, context.args, context.isError);
|
|
530
673
|
} catch {
|
|
531
674
|
text = undefined;
|
|
532
675
|
}
|
|
533
|
-
text ??= textContent(
|
|
676
|
+
text ??= textContent(stripped);
|
|
534
677
|
if (!text) return linesComponent(() => []);
|
|
535
|
-
|
|
678
|
+
let link: string | undefined;
|
|
679
|
+
try {
|
|
680
|
+
link = spec.link?.(result, context.isError);
|
|
681
|
+
} catch {
|
|
682
|
+
link = undefined;
|
|
683
|
+
}
|
|
684
|
+
return linesComponent((width) => {
|
|
685
|
+
const lines = resultLines(theme, text, options.expanded, context.isError, spec.maxCollapsedLines);
|
|
686
|
+
if (link && lines.length > 0) {
|
|
687
|
+
// Truncation drops a cut link's closing escape and leaves the
|
|
688
|
+
// hyperlink open, so a link that does not fit goes in as plain text.
|
|
689
|
+
const joined = `${lines[0]} ${link}`;
|
|
690
|
+
lines[0] = visibleWidth(joined) <= width ? joined : `${lines[0]} ${sanitizeDisplayText(link)}`;
|
|
691
|
+
}
|
|
692
|
+
return lines;
|
|
693
|
+
});
|
|
536
694
|
},
|
|
537
695
|
};
|
|
538
696
|
}
|
|
@@ -558,6 +716,8 @@ export interface CcWrapOptions<TArgs = any> {
|
|
|
558
716
|
* content below the header (edit's diff preview, shown before approval).
|
|
559
717
|
*/
|
|
560
718
|
keepBaseCall?: boolean;
|
|
719
|
+
/** A file tool: a rejection without a reason reads "User rejected <op> to <path>" (the path from `title`). */
|
|
720
|
+
rejectedAs?: RejectedFileOp;
|
|
561
721
|
}
|
|
562
722
|
|
|
563
723
|
/**
|
|
@@ -620,6 +780,17 @@ export function stripReminderBlocks<T extends { content?: unknown }>(result: T):
|
|
|
620
780
|
return changed ? { ...result, content: kept } : result;
|
|
621
781
|
}
|
|
622
782
|
|
|
783
|
+
/**
|
|
784
|
+
* The context pi's built-in renderers get under the wrapper. pi 1.1.0 added
|
|
785
|
+
* two fields that would change our rows: the shell's `durationMs` prints a
|
|
786
|
+
* "Took Ns" line Claude Code's rows do not have, and `outputPad` sets the edit
|
|
787
|
+
* preview's and diff's indent. Hide the first and pin the second to the 1 pi
|
|
788
|
+
* hard-coded before, so a row looks the same on every pi and setting.
|
|
789
|
+
*/
|
|
790
|
+
function baseContext(context: any, lastComponent: unknown): any {
|
|
791
|
+
return { ...context, durationMs: undefined, outputPad: 1, lastComponent };
|
|
792
|
+
}
|
|
793
|
+
|
|
623
794
|
export function ccWrapBuiltinRenderers<TArgs = any>(
|
|
624
795
|
label: string,
|
|
625
796
|
base: {
|
|
@@ -632,14 +803,16 @@ export function ccWrapBuiltinRenderers<TArgs = any>(
|
|
|
632
803
|
renderCall: (args: TArgs, theme: any, context: any) => TuiComponent;
|
|
633
804
|
renderResult: (result: any, options: { expanded: boolean; isPartial: boolean }, theme: any, context: any) => TuiComponent;
|
|
634
805
|
} {
|
|
635
|
-
const
|
|
806
|
+
const rawTitle = (args: TArgs | undefined): string | undefined => {
|
|
636
807
|
try {
|
|
637
|
-
|
|
638
|
-
if (custom !== undefined) return summarizeArgs(custom);
|
|
808
|
+
return opts.title?.(args);
|
|
639
809
|
} catch {
|
|
640
|
-
|
|
810
|
+
return undefined;
|
|
641
811
|
}
|
|
642
|
-
|
|
812
|
+
};
|
|
813
|
+
const titleOf = (args: TArgs | undefined): string => {
|
|
814
|
+
const custom = rawTitle(args);
|
|
815
|
+
return summarizeArgs(custom !== undefined ? custom : args);
|
|
643
816
|
};
|
|
644
817
|
|
|
645
818
|
return {
|
|
@@ -652,7 +825,7 @@ export function ccWrapBuiltinRenderers<TArgs = any>(
|
|
|
652
825
|
}
|
|
653
826
|
let inner: any;
|
|
654
827
|
try {
|
|
655
|
-
inner = base.renderCall(args, theme,
|
|
828
|
+
inner = base.renderCall(args, theme, baseContext(context, context.state.ccInnerCall));
|
|
656
829
|
context.state.ccInnerCall = inner;
|
|
657
830
|
} catch {
|
|
658
831
|
return linesComponent(() => [head()]);
|
|
@@ -683,9 +856,15 @@ export function ccWrapBuiltinRenderers<TArgs = any>(
|
|
|
683
856
|
},
|
|
684
857
|
renderResult(rawResult: any, options: { expanded: boolean; isPartial: boolean }, theme: ThemeLike, context: any) {
|
|
685
858
|
const result = stripReminderBlocks(rawResult);
|
|
859
|
+
const path = rawTitle(context.args);
|
|
860
|
+
const rejected = rejectionLine(theme, result, context.isError, opts.rejectedAs && {
|
|
861
|
+
op: opts.rejectedAs,
|
|
862
|
+
path: path ? displayToolPath(path, context.cwd) : undefined,
|
|
863
|
+
});
|
|
864
|
+
if (rejected) return linesComponent(() => [rejected]);
|
|
686
865
|
let inner: any;
|
|
687
866
|
try {
|
|
688
|
-
inner = base.renderResult?.(result, options, theme,
|
|
867
|
+
inner = base.renderResult?.(result, options, theme, baseContext(context, context.state.ccInnerResult));
|
|
689
868
|
context.state.ccInnerResult = inner;
|
|
690
869
|
} catch {
|
|
691
870
|
inner = undefined;
|
|
@@ -744,9 +923,9 @@ export function formatFireTime(ms: number): string {
|
|
|
744
923
|
* `✻ Running scheduled task (Sep 11 12:03pm)` line, the prompt itself under it
|
|
745
924
|
* on ctrl+o (the prompt is the turn's input, verbatim — findings §21).
|
|
746
925
|
*/
|
|
747
|
-
export function scheduledTaskComponent(theme: ThemeLike, prompt: string, firedAt: number, expanded: boolean): TuiComponent {
|
|
748
|
-
const lines = prompt.trim().split("\n");
|
|
749
|
-
const headline =
|
|
926
|
+
export function scheduledTaskComponent(theme: ThemeLike, prompt: string, firedAt: number, expanded: boolean, label = "Running scheduled task", suffix = ""): TuiComponent {
|
|
927
|
+
const lines = sanitizeDisplayText(prompt).trim().split("\n");
|
|
928
|
+
const headline = `${label} (${formatFireTime(firedAt)})${suffix}`;
|
|
750
929
|
return linesComponent(() => {
|
|
751
930
|
if (!expanded) {
|
|
752
931
|
return [`${theme.fg("dim", "✻")} ${theme.fg("dim", headline)}${theme.fg("dim", ` (+${lines.length} lines, ${EXPAND_HINT})`)}`];
|
|
@@ -764,10 +943,48 @@ export function scheduledTaskComponent(theme: ThemeLike, prompt: string, firedAt
|
|
|
764
943
|
* wire frames (`<task-notification>`, the agent-message envelope) are
|
|
765
944
|
* model-only; `parseNotificationFrames` takes them apart for display.
|
|
766
945
|
*/
|
|
767
|
-
|
|
946
|
+
/** An agent task's `<summary>`: `Agent "<description>" finished` / `failed: …` / `was stopped…` (findings §24). */
|
|
947
|
+
function isAgentCompletion(summary: string): boolean {
|
|
948
|
+
return summary.startsWith('Agent "');
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* A notification that opens a session's first turn goes out as a user message
|
|
953
|
+
* (lib/notifications.ts, "First turn of a session"), and pi draws a user
|
|
954
|
+
* message as markdown with no per-message renderer, so the wire frames would
|
|
955
|
+
* show in full. This is that message's display text: the headlines
|
|
956
|
+
* `notificationComponent` shows collapsed, one paragraph per frame, without
|
|
957
|
+
* the expand hint (a user message does not expand). `undefined` when the text
|
|
958
|
+
* is not wholly harness frames, so a real prompt renders as typed.
|
|
959
|
+
*/
|
|
960
|
+
export function notificationUserMarkdown(text: string): string | undefined {
|
|
961
|
+
// Every user message renders through here: a typed prompt skips the parse.
|
|
962
|
+
if (!text.includes("<task-notification>") && !text.includes("<agent-message ")) return undefined;
|
|
963
|
+
const parsed = parseNotificationFrames(sanitizeDisplayText(text));
|
|
964
|
+
if (parsed.length === 0 || parsed.some((frame) => frame.kind === "text")) return undefined;
|
|
965
|
+
const frames = parsed.filter((frame) => !(frame.kind === "task" && frame.pointer));
|
|
966
|
+
return (frames.length > 0 ? frames : parsed)
|
|
967
|
+
.map((frame) => {
|
|
968
|
+
if (frame.kind === "agent-message") return `› Message from @${frame.from}`;
|
|
969
|
+
if (frame.kind === "task" && isAgentCompletion(frame.summary)) {
|
|
970
|
+
const took = frame.durationMs === undefined ? "" : ` · ${formatDuration(0, frame.durationMs)}`;
|
|
971
|
+
return `● ${oneLine(frame.summary)}${took}`;
|
|
972
|
+
}
|
|
973
|
+
return frame.kind === "task" ? `✳ ${oneLine(frame.summary)}` : "";
|
|
974
|
+
})
|
|
975
|
+
.join("\n\n");
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* `agentName` names the agent a message comes from (its task id is the
|
|
980
|
+
* wire address): the headline reads `› Message from @<name>`, falling back to
|
|
981
|
+
* the address when the name is unknown.
|
|
982
|
+
*/
|
|
983
|
+
export function notificationComponent(theme: ThemeLike, text: string, expanded: boolean, agentName?: (from: string) => string | undefined): TuiComponent {
|
|
768
984
|
// An agent's completion pointer says nothing the message above it does not;
|
|
769
|
-
// CC's transcript shows the message alone.
|
|
770
|
-
|
|
985
|
+
// CC's transcript shows the message alone. Bodies carry shell output and
|
|
986
|
+
// agent replies, so control characters are removed before parsing.
|
|
987
|
+
const parsed = parseNotificationFrames(sanitizeDisplayText(text));
|
|
771
988
|
const frames = parsed.filter((frame) => !(frame.kind === "task" && frame.pointer));
|
|
772
989
|
if (frames.length === 0) frames.push({ kind: "text", text: framesToText(parsed) });
|
|
773
990
|
const dim = (s: string) => theme.fg("dim", s);
|
|
@@ -785,8 +1002,19 @@ export function notificationComponent(theme: ThemeLike, text: string, expanded:
|
|
|
785
1002
|
// the header, then the body as delivered (preamble, indented report),
|
|
786
1003
|
// indented under it, when expanded. The guard is never shown.
|
|
787
1004
|
const bodyLines = frame.body.split("\n");
|
|
788
|
-
|
|
789
|
-
|
|
1005
|
+
const sender = agentName?.(frame.from) ?? frame.from;
|
|
1006
|
+
if (expanded) out.push(dim(`› Message from ${sender}`), ...body(bodyLines));
|
|
1007
|
+
else out.push(dim(`› Message from @${sender}`) + hint(bodyLines));
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (frame.kind === "task" && isAgentCompletion(frame.summary)) {
|
|
1011
|
+
// Claude Code 2.1.283's line for an agent's turn: `⏺ Agent "<desc>"
|
|
1012
|
+
// finished · 3s`, the dot tinted by outcome, no expand hint (findings
|
|
1013
|
+
// §40). ctrl+o still shows the report under it.
|
|
1014
|
+
const dot = theme.fg(frame.status === "failed" ? "error" : frame.status === "completed" ? "success" : "dim", "●");
|
|
1015
|
+
const took = frame.durationMs === undefined ? "" : dim(` · ${formatDuration(0, frame.durationMs)}`);
|
|
1016
|
+
out.push(`${dot} ${oneLine(frame.summary)}${took}`);
|
|
1017
|
+
if (expanded && frame.body) out.push(...body(frame.body.split("\n")));
|
|
790
1018
|
continue;
|
|
791
1019
|
}
|
|
792
1020
|
const headline = frame.kind === "task" ? frame.summary : oneLine(frame.text.split("\n")[0] ?? "");
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code's per-turn `<total_tokens>` placements (pure). One Code stores
|
|
3
|
+
* the line the way it always has, so a resumed session rebuilds the same
|
|
4
|
+
* bytes: a raw marker block after the text of every user message after the
|
|
5
|
+
* first (context-budget's `sticky-append`), and the countdown as its own
|
|
6
|
+
* block at the end of every tool result (persisted by system-reminder). Two
|
|
7
|
+
* steps turn those into Claude Code's shapes:
|
|
8
|
+
*
|
|
9
|
+
* - at `context` time, while a tool result's blocks are still separate and
|
|
10
|
+
* keyed by its call id (`resolveCountdowns`): on a model that takes a
|
|
11
|
+
* mid-conversation system message the countdown block leaves the result and
|
|
12
|
+
* its value is kept by call id; elsewhere it is framed, a blank line after
|
|
13
|
+
* the output once pi joins the blocks (Claude Code's Haiku shape);
|
|
14
|
+
* - on the final body (`withTurnBudgetMessages`): on a system-role model a
|
|
15
|
+
* system message with the line follows each later prompt and each
|
|
16
|
+
* tool-result message (the lowest countdown it carries), and on first-party
|
|
17
|
+
* Fable the `clear_at` nudge follows the latter; elsewhere the framed block
|
|
18
|
+
* goes before the text of each later prompt, with a trailing newline.
|
|
19
|
+
*
|
|
20
|
+
* Nothing is parsed out of joined text, so a tool's own output that contains
|
|
21
|
+
* such a line is never touched.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { reseatMessageMark } from "./anthropic-payload.ts";
|
|
25
|
+
import { wrapReminder } from "./reminders.ts";
|
|
26
|
+
import { messagesKey, type SystemRoleLayout, systemMessage, type WireMessage, type WirePart, byWireCallId, withoutParts } from "./system-role.ts";
|
|
27
|
+
|
|
28
|
+
/** The stored line, exactly. */
|
|
29
|
+
const LINE = /^<total_tokens>(\d+) tokens left<\/total_tokens>$/;
|
|
30
|
+
|
|
31
|
+
/** Claude Code's Fable nudge after a tool result's budget message. */
|
|
32
|
+
export const CLEAR_AT_NUDGE =
|
|
33
|
+
"First privately list what you need next; then request every item that doesn't depend on another's result in this one response.";
|
|
34
|
+
|
|
35
|
+
type StoredBlock = { type?: string; text?: string };
|
|
36
|
+
type StoredMessage = { role: string; toolCallId?: string; content?: unknown };
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The context-time step over pi's messages: each tool result's countdown block
|
|
40
|
+
* (the last whole block that is the line) is lifted out on a system-role model,
|
|
41
|
+
* its value kept under the call id, or framed elsewhere. Returns the messages
|
|
42
|
+
* (the input when nothing changed) and the countdowns by call id.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveCountdowns<T extends StoredMessage>(messages: T[], systemRole: boolean): { messages: T[]; left: Map<string, number> } {
|
|
45
|
+
const left = new Map<string, number>();
|
|
46
|
+
let changed = false;
|
|
47
|
+
const out = messages.map((message) => {
|
|
48
|
+
if (message.role !== "toolResult" || !Array.isArray(message.content) || !message.toolCallId) return message;
|
|
49
|
+
const blocks = message.content as StoredBlock[];
|
|
50
|
+
const at = blocks.findLastIndex((block) => block?.type === "text" && typeof block.text === "string" && LINE.test(block.text));
|
|
51
|
+
if (at === -1) return message;
|
|
52
|
+
const line = blocks[at].text as string;
|
|
53
|
+
changed = true;
|
|
54
|
+
if (systemRole) {
|
|
55
|
+
left.set(message.toolCallId, Number(LINE.exec(line)?.[1]));
|
|
56
|
+
return { ...message, content: blocks.filter((_, index) => index !== at) };
|
|
57
|
+
}
|
|
58
|
+
return { ...message, content: blocks.map((block, index) => (index === at ? { ...block, text: `\n${wrapReminder(line)}` } : block)) };
|
|
59
|
+
});
|
|
60
|
+
return { messages: changed ? out : messages, left };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TurnBudgetOptions {
|
|
64
|
+
/** The wire shape. */
|
|
65
|
+
shape: SystemRoleLayout;
|
|
66
|
+
/** True on a model that takes a mid-conversation system message. */
|
|
67
|
+
systemRole: boolean;
|
|
68
|
+
/** The role a system message takes on the OpenAI APIs. */
|
|
69
|
+
role: "system" | "developer";
|
|
70
|
+
/** The countdowns `resolveCountdowns` lifted, by call id. */
|
|
71
|
+
left: ReadonlyMap<string, number>;
|
|
72
|
+
/** Send Claude Code's `clear_at` nudge after a tool result's budget message (first-party Fable). */
|
|
73
|
+
nudge?: boolean;
|
|
74
|
+
/** False when the budget is off: no prompt carries a marker, so a user's own tag stays text. */
|
|
75
|
+
markers?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The call ids a wire message answers: Anthropic `tool_result` blocks, an OpenAI output item or tool message. */
|
|
79
|
+
function answeredCalls(message: WireMessage): string[] {
|
|
80
|
+
if (message?.role === "user" && Array.isArray(message.content)) {
|
|
81
|
+
return (message.content as WirePart[]).flatMap((part) => (part?.type === "tool_result" && typeof part.tool_use_id === "string" ? [part.tool_use_id] : []));
|
|
82
|
+
}
|
|
83
|
+
if (message?.type === "function_call_output" && typeof message.call_id === "string") return [message.call_id];
|
|
84
|
+
if (message?.role === "tool" && typeof message.tool_call_id === "string") return [message.tool_call_id];
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function withTurnBudgetMessages(payload: Record<string, unknown>, opts: TurnBudgetOptions): Record<string, unknown> | undefined {
|
|
89
|
+
const key = messagesKey(opts.shape);
|
|
90
|
+
const messages = payload[key];
|
|
91
|
+
if (!Array.isArray(messages)) return undefined;
|
|
92
|
+
const out: WireMessage[] = [];
|
|
93
|
+
let changed = false;
|
|
94
|
+
/** The lowest countdown of the tool results not yet followed by their message. */
|
|
95
|
+
let pending: number | undefined;
|
|
96
|
+
/** The first user message carries the context stack, never a marker (context-budget's `skipStackCarrier`). */
|
|
97
|
+
let stackCarrierSeen = false;
|
|
98
|
+
for (let i = 0; i < messages.length; i++) {
|
|
99
|
+
let message = messages[i] as WireMessage;
|
|
100
|
+
const after: WireMessage[] = [];
|
|
101
|
+
const stackCarrier = !stackCarrierSeen && message?.role === "user";
|
|
102
|
+
if (stackCarrier) stackCarrierSeen = true;
|
|
103
|
+
// A later prompt's marker: a system message after it, or the framed block before its text.
|
|
104
|
+
// The marker comes after the user's text, so the last match is it: a
|
|
105
|
+
// prompt that is itself such a tag stays the user's, and so does the
|
|
106
|
+
// first prompt, which has no marker to find.
|
|
107
|
+
if (opts.markers !== false && !stackCarrier && message?.role === "user" && Array.isArray(message.content)) {
|
|
108
|
+
const parts = message.content as WirePart[];
|
|
109
|
+
const at = parts.findLastIndex((part) => typeof part?.text === "string" && LINE.test(part.text));
|
|
110
|
+
// The marker ends the message, so it carries pi's cache mark; withoutParts keeps it.
|
|
111
|
+
const rest = at === -1 ? undefined : withoutParts(parts, (_, index) => index === at);
|
|
112
|
+
if (rest) {
|
|
113
|
+
const line = parts[at].text as string;
|
|
114
|
+
if (opts.systemRole) {
|
|
115
|
+
message = { ...message, content: rest };
|
|
116
|
+
after.push(systemMessage(opts.shape, opts.role, line));
|
|
117
|
+
} else {
|
|
118
|
+
message = { ...message, content: [{ type: opts.shape === "responses" ? "input_text" : "text", text: `${wrapReminder(line)}\n` }, ...rest] };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (opts.systemRole) {
|
|
123
|
+
for (const id of answeredCalls(message)) {
|
|
124
|
+
const value = byWireCallId(opts.left, id);
|
|
125
|
+
if (value !== undefined) pending = pending === undefined ? value : Math.min(pending, value);
|
|
126
|
+
}
|
|
127
|
+
// On the OpenAI APIs a batch of results is a run of items; one message follows the run.
|
|
128
|
+
const next = messages[i + 1] as WireMessage | undefined;
|
|
129
|
+
const runContinues = opts.shape !== "anthropic" && (next?.type === "function_call_output" || next?.role === "tool");
|
|
130
|
+
if (pending !== undefined && !runContinues) {
|
|
131
|
+
after.push(systemMessage(opts.shape, opts.role, `<total_tokens>${pending} tokens left</total_tokens>`));
|
|
132
|
+
if (opts.nudge) {
|
|
133
|
+
after.push(opts.shape === "anthropic" ? ({ role: "system", content: CLEAR_AT_NUDGE, clear_at: "next_user_message" } as WireMessage) : systemMessage(opts.shape, opts.role, CLEAR_AT_NUDGE));
|
|
134
|
+
}
|
|
135
|
+
pending = undefined;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (message !== messages[i] || after.length > 0) changed = true;
|
|
139
|
+
out.push(message, ...after);
|
|
140
|
+
}
|
|
141
|
+
if (!changed) return undefined;
|
|
142
|
+
return { ...payload, [key]: opts.shape === "anthropic" ? reseatMessageMark(out) : out };
|
|
143
|
+
}
|
|
@@ -23,13 +23,26 @@ export const APP_PACKAGE = "@one-ai/one-code";
|
|
|
23
23
|
export const EXTENSION_PACKAGE = "one-code-extension";
|
|
24
24
|
/** The Homebrew formula (IsuruMaduranga/homebrew-one-ai), renamed from one-code at 0.3.0. */
|
|
25
25
|
export const HOMEBREW_FORMULA = "onecode";
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* How each install method upgrades One Code; the doctor and the update notice
|
|
28
|
+
* quote these. On the user's own pi a bare `pi update` updates pi itself and
|
|
29
|
+
* skips packages, so the package is named.
|
|
30
|
+
*/
|
|
27
31
|
export const UPGRADE_COMMANDS = {
|
|
28
32
|
npm: `npm install -g ${APP_PACKAGE}`,
|
|
29
33
|
brew: `brew upgrade ${HOMEBREW_FORMULA}`,
|
|
30
|
-
"pi-package":
|
|
34
|
+
"pi-package": `pi update npm:${EXTENSION_PACKAGE}`,
|
|
31
35
|
};
|
|
32
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The upgrade command for a running install: the pi package's, or the app's by
|
|
39
|
+
* the install method the launcher published (`ONECODE_INSTALL_METHOD`).
|
|
40
|
+
*/
|
|
41
|
+
export function upgradeCommandFor(install, env = process.env) {
|
|
42
|
+
if (install !== "app") return UPGRADE_COMMANDS["pi-package"];
|
|
43
|
+
return env.ONECODE_INSTALL_METHOD === "brew" ? UPGRADE_COMMANDS.brew : UPGRADE_COMMANDS.npm;
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
/**
|
|
34
47
|
* The registry packument for a package (scoped slash percent-encoded). The
|
|
35
48
|
* full document, not `/latest`: its `time` map dates every version, which
|
|
@@ -22,7 +22,7 @@ export const USAGE_CHANNEL = "one-code:usage-recorded";
|
|
|
22
22
|
*/
|
|
23
23
|
export const USAGE_ENTRY_TYPE = "one-code:usage";
|
|
24
24
|
|
|
25
|
-
export type UsageSource = "subagent" | "classifier" | "reader" | "recap" | "session-title" | "setup" | "btw";
|
|
25
|
+
export type UsageSource = "subagent" | "classifier" | "reader" | "web-fetch" | "web-search" | "recap" | "session-title" | "setup" | "btw";
|
|
26
26
|
|
|
27
27
|
export interface UsageRecord {
|
|
28
28
|
source: UsageSource;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The result a tool call gets when the user rejects it (pure): Claude Code's
|
|
3
|
+
* denial text, shared by the permission prompt and plan-mode approval. With
|
|
4
|
+
* the user's typed reason it carries their words (decisions/tools.md, "Denial
|
|
5
|
+
* feedback carries the user's words") in Claude Code's "To tell you how to
|
|
6
|
+
* proceed, the user said:" form; without one it tells the model to stop and
|
|
7
|
+
* wait for the user.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const REJECTED =
|
|
11
|
+
"The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file).";
|
|
12
|
+
|
|
13
|
+
const MEMORY_NOTE =
|
|
14
|
+
"Note: The user's next message may contain a correction or preference. Pay close attention — if they explain what went wrong or how they'd prefer you to work, consider saving that to memory for future sessions.";
|
|
15
|
+
|
|
16
|
+
const STOP = "STOP what you are doing and wait for the user to tell you how to proceed.";
|
|
17
|
+
const USER_SAID = "To tell you how to proceed, the user said:\n";
|
|
18
|
+
|
|
19
|
+
/** Claude Code's rejection text, with the user's reason when they typed one. */
|
|
20
|
+
export function userDenialText(feedback?: string): string {
|
|
21
|
+
const said = feedback?.trim();
|
|
22
|
+
const next = said ? `${USER_SAID}${said}` : STOP;
|
|
23
|
+
return `${REJECTED} ${next}\n\n${MEMORY_NOTE}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether a tool result is the user's rejection, for the transcript: `stop`
|
|
28
|
+
* when they rejected without a reason, `reason` when they gave one, undefined
|
|
29
|
+
* for anything else. The transcript shows a short line for either (Claude
|
|
30
|
+
* Code's); the model still reads the whole text.
|
|
31
|
+
*/
|
|
32
|
+
export function userDenialKind(text: string): "stop" | "reason" | undefined {
|
|
33
|
+
if (!text.startsWith(`${REJECTED} `)) return undefined;
|
|
34
|
+
const next = text.slice(REJECTED.length + 1);
|
|
35
|
+
if (next.startsWith(STOP)) return "stop";
|
|
36
|
+
if (next.startsWith(USER_SAID)) return "reason";
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|