one-code-extension 0.4.1 → 0.6.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 +40 -28
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +26 -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 +124 -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 +642 -0
- package/extensions/background/index.ts +614 -248
- package/extensions/background/loop-fire.ts +173 -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 +56 -0
- package/extensions/background/wakeup.ts +178 -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 +66 -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 +250 -78
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/cache-marks/index.ts +42 -0
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +436 -63
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +219 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/budget.ts +16 -2
- package/extensions/context-budget/index.ts +35 -15
- package/extensions/context-management/index.ts +36 -28
- 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 +141 -25
- 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 +420 -42
- package/extensions/file-tracker/replay.ts +137 -24
- package/extensions/file-tracker/restore.ts +66 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +45 -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 +209 -58
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/prompt-context.ts +59 -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 +26 -12
- 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/ledger/index.ts +46 -0
- package/extensions/ledger/wire.ts +154 -0
- package/extensions/lib/agent-cron.ts +146 -0
- package/extensions/lib/agent-loader.ts +18 -3
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-auth.ts +98 -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 +52 -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 +97 -0
- package/extensions/lib/covered-messages.ts +23 -0
- package/extensions/lib/deferred.ts +359 -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/ledger/fold-memo.ts +49 -0
- package/extensions/lib/ledger/fold.ts +265 -0
- package/extensions/lib/ledger/layout.ts +72 -0
- package/extensions/lib/ledger/plan.ts +102 -0
- package/extensions/lib/ledger/records.ts +276 -0
- package/extensions/lib/ledger/state.ts +47 -0
- package/extensions/lib/ledger/validate.ts +141 -0
- package/extensions/lib/ledger/wire.ts +632 -0
- package/extensions/lib/local-command.ts +71 -19
- 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 +178 -0
- package/extensions/lib/model-catalog/models-dev.json +18533 -0
- package/extensions/lib/model-catalog/openrouter.json +5050 -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 +357 -73
- 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 +38 -2
- package/extensions/lib/plan-mode-channels.ts +42 -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/previous-mark.ts +101 -0
- package/extensions/lib/process-state.ts +48 -0
- package/extensions/lib/process-tree.ts +132 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +370 -358
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +547 -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 +24 -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 +112 -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 +129 -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 +159 -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 +76 -12
- 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 +90 -43
- 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 +220 -121
- 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 +164 -73
- 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/model-default/index.ts +86 -19
- 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 +1426 -398
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +11 -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 +276 -92
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/run-state.ts +26 -0
- 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 +64 -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 +14 -7
- package/extensions/recap/index.ts +73 -71
- 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/announce.ts +30 -0
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +469 -132
- package/extensions/skill/invoke.ts +118 -75
- 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 +146 -31
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +82 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1594 -852
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +5 -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/parent-mode.ts +50 -0
- package/extensions/subagents/pending-claim.ts +18 -1
- package/extensions/subagents/runner.ts +135 -80
- package/extensions/subagents/runs.ts +44 -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 +165 -47
- package/extensions/system-prompt/template.ts +34 -51
- 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 +204 -70
- 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/index.ts +366 -144
- 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 +108 -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 +157 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +33 -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 +162 -44
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +21 -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
- package/extensions/tool-search/announce.ts +0 -42
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a compaction cuts the conversation. Pure: no pi extension API.
|
|
3
|
+
*
|
|
4
|
+
* pi keeps the last ~20k tokens verbatim after its summary (`keepRecentTokens`).
|
|
5
|
+
* Claude Code keeps only the final assistant reply (findings §47), and
|
|
6
|
+
* so does One Code: the summary, that reply, then whatever came after it (the
|
|
7
|
+
* results of a tool call it made, on an overflow mid-turn). A short kept tail
|
|
8
|
+
* also keeps little signed thinking across the boundary (findings §46).
|
|
9
|
+
*
|
|
10
|
+
* pi's own cut is moved to the last assistant message that reaches the
|
|
11
|
+
* provider. When pi cut earlier (a keep window above 0), the messages between
|
|
12
|
+
* the two cuts join the doomed span. When pi cut later (with the window at 0,
|
|
13
|
+
* pi cuts at the last cut point, which can be a queued prompt or a harness
|
|
14
|
+
* notification that arrived after the reply), the reply and what follows it
|
|
15
|
+
* leave the doomed span, so the summary does not repeat what stays verbatim.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { latestCompaction } from "../lib/compaction-boundary.ts";
|
|
19
|
+
|
|
20
|
+
/** The fields of a session entry the cut reads; pi's `SessionEntry` fits it. */
|
|
21
|
+
export type CutEntry = {
|
|
22
|
+
id: string;
|
|
23
|
+
type: string;
|
|
24
|
+
message?: { role?: string; stopReason?: string; content?: unknown };
|
|
25
|
+
targetId?: string;
|
|
26
|
+
replacement?: { content: unknown } | null;
|
|
27
|
+
firstKeptEntryId?: string | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type Span<M> = { messagesToSummarize: M[]; turnPrefixMessages: M[]; isSplitTurn: boolean };
|
|
31
|
+
type Preparation<M> = Span<M> & { firstKeptEntryId: string };
|
|
32
|
+
|
|
33
|
+
/** What pi summarizes for a preparation: the messages before the cut, a split turn's prefix included. */
|
|
34
|
+
export function doomedMessages<M>(preparation: Span<M>): M[] {
|
|
35
|
+
return preparation.isSplitTurn ? [...preparation.messagesToSummarize, ...preparation.turnPrefixMessages] : preparation.messagesToSummarize;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* pi's preparation moved to Claude Code's cut, with the entry the kept tail
|
|
40
|
+
* starts at. pi's own when its cut already is the reply, no reply is still in
|
|
41
|
+
* context, or the messages to take off are not the doomed span's tail.
|
|
42
|
+
*/
|
|
43
|
+
export function claudeCodeCut<E extends CutEntry, M extends { role: string }, P extends Preparation<M>>(
|
|
44
|
+
entries: readonly E[],
|
|
45
|
+
preparation: P,
|
|
46
|
+
toMessages: (entry: E) => M[],
|
|
47
|
+
): { preparation: P; firstKeptEntryId: string } {
|
|
48
|
+
const unchanged = { preparation, firstKeptEntryId: preparation.firstKeptEntryId };
|
|
49
|
+
const cut = lastReplyCut(entries, preparation.firstKeptEntryId);
|
|
50
|
+
if (!cut) return unchanged;
|
|
51
|
+
const moved = moveCut(preparation, spanMessages(cut.span, entries, toMessages), cut.later);
|
|
52
|
+
return moved ? { preparation: moved, firstKeptEntryId: cut.firstKeptEntryId } : unchanged;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The entry Claude Code's cut keeps from, and the entries between it and pi's
|
|
57
|
+
* cut: summarized too when the reply is `later` than pi's cut, kept when it is
|
|
58
|
+
* earlier. Undefined when pi's entry is not on the branch, no reply is still
|
|
59
|
+
* in context, or pi already cut at the reply.
|
|
60
|
+
*
|
|
61
|
+
* An assistant message that ended in an error or an abort never reaches the
|
|
62
|
+
* provider (pi-ai's `transformMessages` drops it), and one whose content (as
|
|
63
|
+
* a context edit leaves it) has no text or tool call is omitted or skipped by
|
|
64
|
+
* its converter, so none of them is a reply to keep. A reply an
|
|
65
|
+
* earlier compaction already summarized is out of reach.
|
|
66
|
+
*/
|
|
67
|
+
export function lastReplyCut<E extends CutEntry>(
|
|
68
|
+
entries: readonly E[],
|
|
69
|
+
piFirstKeptEntryId: string,
|
|
70
|
+
): { firstKeptEntryId: string; span: E[]; later: boolean } | undefined {
|
|
71
|
+
const piIndex = entries.findIndex((entry) => entry.id === piFirstKeptEntryId);
|
|
72
|
+
if (piIndex < 0) return undefined;
|
|
73
|
+
const edits = contextEdits(entries);
|
|
74
|
+
const start = latestCompaction(entries)?.keptStart ?? 0;
|
|
75
|
+
let replyIndex = -1;
|
|
76
|
+
for (let i = entries.length - 1; i >= start; i--) {
|
|
77
|
+
const entry = entries[i];
|
|
78
|
+
const message = entry.message;
|
|
79
|
+
if (entry.type !== "message" || message?.role !== "assistant") continue;
|
|
80
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") continue;
|
|
81
|
+
// The content the provider gets: a context edit's replacement, or none when it omits the message.
|
|
82
|
+
const content = edits.has(entry.id) ? edits.get(entry.id)?.content : message.content;
|
|
83
|
+
if (!sendsContent(content)) continue;
|
|
84
|
+
replyIndex = i;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
if (replyIndex < 0 || replyIndex === piIndex) return undefined;
|
|
88
|
+
const later = replyIndex > piIndex;
|
|
89
|
+
return {
|
|
90
|
+
firstKeptEntryId: entries[replyIndex].id,
|
|
91
|
+
span: later ? entries.slice(piIndex, replyIndex) : entries.slice(replyIndex, piIndex),
|
|
92
|
+
later,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Each edited entry's replacement (null when the edit omits it), the latest edit winning, as in pi's projection. */
|
|
97
|
+
function contextEdits(entries: readonly CutEntry[]): Map<string, CutEntry["replacement"]> {
|
|
98
|
+
const edits = new Map<string, CutEntry["replacement"]>();
|
|
99
|
+
for (const entry of entries) if (entry.type === "context_edit" && entry.targetId) edits.set(entry.targetId, entry.replacement);
|
|
100
|
+
return edits;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Whether an assistant message carries text or a tool call (thinking alone is not a reply). */
|
|
104
|
+
function sendsContent(content: unknown): boolean {
|
|
105
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
106
|
+
return (
|
|
107
|
+
Array.isArray(content) &&
|
|
108
|
+
content.some((block: { type?: string; text?: string }) => block?.type === "toolCall" || (block?.type === "text" && !!block.text?.trim()))
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The context messages of entries in branch order, as pi projects them: an
|
|
114
|
+
* omitted entry contributes nothing, a replaced one its replacement content
|
|
115
|
+
* (`buildSessionProjection` in pi 0.87 and later; earlier pi has no edits), and
|
|
116
|
+
* an older compaction entry inside the span nothing (only the newest
|
|
117
|
+
* compaction's summary is context).
|
|
118
|
+
*/
|
|
119
|
+
export function spanMessages<E extends CutEntry, M extends { role: string }>(span: readonly E[], all: readonly E[], toMessages: (entry: E) => M[]): M[] {
|
|
120
|
+
const edits = contextEdits(all);
|
|
121
|
+
return span.flatMap((entry) => {
|
|
122
|
+
if (entry.type === "compaction" || entry.type === "context_edit") return [];
|
|
123
|
+
const messages = toMessages(entry);
|
|
124
|
+
if (!edits.has(entry.id)) return messages;
|
|
125
|
+
const replacement = edits.get(entry.id);
|
|
126
|
+
if (!replacement) return [];
|
|
127
|
+
return messages.map((message) => {
|
|
128
|
+
if (!["user", "assistant", "toolResult", "custom"].includes(message.role)) return message;
|
|
129
|
+
const content =
|
|
130
|
+
(message.role === "assistant" || message.role === "toolResult") && typeof replacement.content === "string"
|
|
131
|
+
? [{ type: "text", text: replacement.content }]
|
|
132
|
+
: replacement.content;
|
|
133
|
+
return { ...message, content };
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* pi's preparation with the doomed span moved to the new cut, the split turn's
|
|
140
|
+
* prefix folded in: `messages` appended when the reply is `later` than pi's
|
|
141
|
+
* cut, else taken off the span's end. Undefined when those are not the span's
|
|
142
|
+
* tail (role and timestamp, message by message) or would leave nothing to
|
|
143
|
+
* summarize: pi's cut stays.
|
|
144
|
+
*/
|
|
145
|
+
export function moveCut<P extends Span<unknown>>(preparation: P, messages: P["messagesToSummarize"], later: boolean): P | undefined {
|
|
146
|
+
const doomed = doomedMessages(preparation);
|
|
147
|
+
const moved = (messagesToSummarize: P["messagesToSummarize"]): P => ({ ...preparation, messagesToSummarize, turnPrefixMessages: [], isSplitTurn: false });
|
|
148
|
+
if (later) return moved([...doomed, ...messages]);
|
|
149
|
+
const keep = doomed.length - messages.length;
|
|
150
|
+
if (keep <= 0) return undefined;
|
|
151
|
+
const same = (a: unknown, b: unknown) => {
|
|
152
|
+
const [x, y] = [a as { role?: string; timestamp?: number } | null, b as { role?: string; timestamp?: number } | null];
|
|
153
|
+
return x?.role === y?.role && x?.timestamp === y?.timestamp;
|
|
154
|
+
};
|
|
155
|
+
if (messages.some((message, i) => !same(message, doomed[keep + i]))) return undefined;
|
|
156
|
+
return moved(doomed.slice(0, keep));
|
|
157
|
+
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* compaction extension — replaces pi's summarizer with Claude Code's.
|
|
3
3
|
*
|
|
4
4
|
* Hooks `session_before_compact` (manual /compact, threshold, and overflow
|
|
5
|
-
* alike),
|
|
6
|
-
* compaction instruction; only the <summary> block of the
|
|
7
|
-
* context.
|
|
5
|
+
* alike), moves pi's cut point to the last assistant reply (`cut.ts`), and
|
|
6
|
+
* sends Claude Code's compaction instruction; only the <summary> block of the
|
|
7
|
+
* reply survives as context, followed by that reply.
|
|
8
8
|
*
|
|
9
9
|
* The call is built to hit the provider prompt cache, which is why Claude
|
|
10
10
|
* Code compacts on the *session model* rather than something cheaper: it
|
|
@@ -41,20 +41,27 @@ import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
|
41
41
|
import {
|
|
42
42
|
convertToLlm,
|
|
43
43
|
type ExtensionAPI,
|
|
44
|
-
type ExtensionContext,
|
|
45
44
|
type SessionBeforeCompactEvent,
|
|
45
|
+
type SessionEntry,
|
|
46
|
+
sessionEntryToContextMessages,
|
|
46
47
|
} from "@earendil-works/pi-coding-agent";
|
|
47
48
|
import {
|
|
48
49
|
type AnthropicModelCompat,
|
|
49
|
-
anthropicBetas,
|
|
50
50
|
clearThinkingApplies,
|
|
51
51
|
clearThinkingEnabled,
|
|
52
|
-
|
|
52
|
+
sessionRequestHeaders,
|
|
53
53
|
withClearThinking,
|
|
54
54
|
} from "../context-management/index.ts";
|
|
55
|
+
import { PRECOMPACT_INSTRUCTIONS_CHANNEL, type PreCompactInstructions } from "../hooks/compaction.ts";
|
|
55
56
|
import { looksLikeAnthropicRequest } from "../lib/anthropic-payload.ts";
|
|
56
57
|
import { forcedReasoningLevel } from "../lib/model-policy.ts";
|
|
58
|
+
import { captureRequest, extendPayload, LAST_REQUEST_CHANNEL, LastExchange, lastCovered, type RequestCapture, replayOutputCap } from "../lib/request-replay.ts";
|
|
59
|
+
import { withoutSystemMessages } from "../lib/side-call.ts";
|
|
60
|
+
import { followCoveredMessages } from "../lib/covered-messages.ts";
|
|
61
|
+
import { LEDGER_SENT_CHANNEL, type LedgerSent } from "../lib/ledger/plan.ts";
|
|
62
|
+
import { claudeCodeCut, type CutEntry, doomedMessages } from "./cut.ts";
|
|
57
63
|
import { fitToBudget, replayFits, withoutUsage } from "./fit.ts";
|
|
64
|
+
import { withoutThinking } from "./kept-thinking.ts";
|
|
58
65
|
import { buildCompactionInstruction, COMPACTION_MAX_TOKENS, continuationSummary, extractSummary } from "./prompt.ts";
|
|
59
66
|
|
|
60
67
|
/** Compaction reads a whole context window; give it more room than the classifier's 30s. */
|
|
@@ -71,6 +78,11 @@ export const STANDALONE_SYSTEM_PROMPT =
|
|
|
71
78
|
type Preparation = Pick<SessionBeforeCompactEvent["preparation"], "messagesToSummarize" | "turnPrefixMessages" | "isSplitTurn" | "previousSummary">;
|
|
72
79
|
|
|
73
80
|
export default function compactionExtension(pi: ExtensionAPI) {
|
|
81
|
+
const hookInstructions = new WeakMap<AbortSignal, string>();
|
|
82
|
+
pi.events.on(PRECOMPACT_INSTRUCTIONS_CHANNEL, (data) => {
|
|
83
|
+
const { signal, instructions } = data as PreCompactInstructions;
|
|
84
|
+
hookInstructions.set(signal, instructions);
|
|
85
|
+
});
|
|
74
86
|
/**
|
|
75
87
|
* The exact message array the session last handed the provider, captured
|
|
76
88
|
* from the `context` event AFTER every extension has mutated it — most
|
|
@@ -87,38 +99,84 @@ export default function compactionExtension(pi: ExtensionAPI) {
|
|
|
87
99
|
* standalone path) diverges at message one, because those injected reminders
|
|
88
100
|
* never become entries — so it cannot reproduce the cached prefix at all.
|
|
89
101
|
*
|
|
90
|
-
* We hold the
|
|
91
|
-
* structuredClone the session never mutates again, and
|
|
92
|
-
* last extension with a `context` handler, so
|
|
93
|
-
*
|
|
102
|
+
* We hold the messages themselves, not copies: `emitContext` hands each
|
|
103
|
+
* handler a fresh structuredClone the session never mutates again, and
|
|
104
|
+
* compaction is the last extension with a `context` handler, so these are
|
|
105
|
+
* exactly the messages the turn sent. Returning nothing keeps the handler a
|
|
106
|
+
* pure observer.
|
|
107
|
+
*
|
|
108
|
+
* The system prompt is captured at the same moment. `ctx.getSystemPrompt()`
|
|
109
|
+
* reflects the prompt a run forced through `before_agent_start` only while
|
|
110
|
+
* that run lasts; pi drops the run's options when it settles, so read from an
|
|
111
|
+
* idle `/compact` it is pi's default prompt and the replay misses the cache
|
|
112
|
+
* (findings §27).
|
|
113
|
+
*/
|
|
114
|
+
let captured: { messages: AgentMessage[]; systemPrompt: string; model: string | undefined } | undefined;
|
|
115
|
+
followCoveredMessages(pi, (messages, ctx) => {
|
|
116
|
+
captured = { messages, systemPrompt: ctx.getSystemPrompt(), model: modelKey(ctx.model) };
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The same request as it went on the wire, and the reply that answered it
|
|
121
|
+
* (lib/request-replay.ts): the body the ledger's payload hook publishes once
|
|
122
|
+
* it has spliced and validated it, so it is final; it is published for the
|
|
123
|
+
* btw and recap side calls too.
|
|
94
124
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
const exchange = new LastExchange<AgentMessage & { role: string }>();
|
|
126
|
+
const publish = (capture: ReturnType<typeof captureRequest>) => {
|
|
127
|
+
exchange.setCapture(capture);
|
|
128
|
+
pi.events.emit(LAST_REQUEST_CHANNEL, capture);
|
|
129
|
+
};
|
|
130
|
+
pi.events.on(LEDGER_SENT_CHANNEL, (data) => {
|
|
131
|
+
const sent = data as LedgerSent;
|
|
132
|
+
// The context this body was built from ends where a fork's own messages start.
|
|
133
|
+
const covers = captured ? lastCovered(captured.messages as { role: string; timestamp?: number }[]) : undefined;
|
|
134
|
+
publish(captureRequest(sent.model, sent.payload, covers, sent.sessionId));
|
|
135
|
+
});
|
|
136
|
+
pi.on("message_end", (event) => {
|
|
137
|
+
exchange.noteMessage(event.message as AgentMessage & { role: string });
|
|
138
|
+
});
|
|
139
|
+
pi.on("session_start", () => {
|
|
140
|
+
captured = undefined;
|
|
141
|
+
publish(undefined);
|
|
98
142
|
});
|
|
143
|
+
|
|
99
144
|
// After a compaction the capture describes the pre-compaction request. A
|
|
100
145
|
// second /compact before any turn would otherwise re-summarize history the
|
|
101
146
|
// first one already folded away (and mis-scope the kept tail); the standalone
|
|
102
147
|
// reconstruction from entries serves until the next request recaptures.
|
|
103
148
|
pi.on("session_compact", () => {
|
|
104
|
-
|
|
149
|
+
captured = undefined;
|
|
150
|
+
publish(undefined);
|
|
105
151
|
});
|
|
106
152
|
// A /tree branch switch stays in the same process and fires no `context`
|
|
107
153
|
// event (navigateTree rebuilds the messages itself), so the capture would
|
|
108
154
|
// still describe the abandoned branch — and a /compact before the next turn
|
|
109
155
|
// summarized work the kept branch never did (review H1). Same remedy.
|
|
110
156
|
pi.on("session_tree", () => {
|
|
111
|
-
|
|
157
|
+
captured = undefined;
|
|
158
|
+
publish(undefined);
|
|
112
159
|
});
|
|
113
160
|
|
|
114
161
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
162
|
+
const instructions = hookInstructions.get(event.signal);
|
|
163
|
+
hookInstructions.delete(event.signal);
|
|
164
|
+
if (instructions) event = { ...event, customInstructions: [event.customInstructions, instructions].filter(Boolean).join("\n\n") };
|
|
115
165
|
if (process.env.CC_COMPACTION === "0") return undefined;
|
|
116
166
|
|
|
117
167
|
const model = ctx.model;
|
|
118
168
|
if (!model) return undefined;
|
|
119
169
|
// Snapshot before the first await so the capture and pi's preparation
|
|
120
170
|
// describe the same request even if a context event lands meanwhile.
|
|
121
|
-
|
|
171
|
+
// A replay exists to read the session model's cache, and the prompt is built
|
|
172
|
+
// per model, so a capture made under another model (a `/model` switch with
|
|
173
|
+
// no turn since) is not replayed: the standalone shape serves.
|
|
174
|
+
const capture =
|
|
175
|
+
captured && captured.model === modelKey(model) ? { ...captured, messages: withoutSystemMessages(captured.messages) } : undefined;
|
|
176
|
+
const wire = exchange.forModel(model);
|
|
177
|
+
// Claude Code's cut: keep only the last assistant reply, and summarize
|
|
178
|
+
// exactly what lies before it (cut.ts).
|
|
179
|
+
const { preparation, firstKeptEntryId } = claudeCodeCut(event.branchEntries as (SessionEntry & CutEntry)[], event.preparation, sessionEntryToContextMessages);
|
|
122
180
|
|
|
123
181
|
try {
|
|
124
182
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
@@ -133,16 +191,24 @@ export default function compactionExtension(pi: ExtensionAPI) {
|
|
|
133
191
|
// Otherwise (no capture yet, an overflow, a nearly full window) the
|
|
134
192
|
// standalone shape summarizes the doomed span pi isolated. An overflow
|
|
135
193
|
// never replays, so its replay request is not even built.
|
|
136
|
-
const replay =
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
194
|
+
const replay = capture && event.reason !== "overflow" ? replayRequest(pi, capture, event, preparation, wire?.reply, model.api) : undefined;
|
|
195
|
+
const replaying = replay !== undefined && replayFits(event.reason, model, replay, maxTokens);
|
|
196
|
+
const request = replaying
|
|
197
|
+
? replay
|
|
198
|
+
: standaloneRequest(
|
|
199
|
+
model,
|
|
200
|
+
preparation,
|
|
201
|
+
buildCompactionInstruction({ reason: event.reason, customInstructions: event.customInstructions }),
|
|
202
|
+
maxTokens,
|
|
203
|
+
);
|
|
204
|
+
// A replay goes out as the session's own last request body with the
|
|
205
|
+
// reply and the instruction appended, so it reads the cache that request
|
|
206
|
+
// wrote: rebuilt from `request`, the tools array (tool-search reshapes it
|
|
207
|
+
// on the wire) never matched on first-party Claude (measured on Sonnet 5:
|
|
208
|
+
// 0 read, 65k written). `request` still sizes the call (fit.ts), and
|
|
209
|
+
// pi-ai converts only the tail. No capture for this model, or too little
|
|
210
|
+
// room left under the captured cap: `request` as is.
|
|
211
|
+
const plan = replaying && wire ? wirePlan(wire, request, maxTokens) : undefined;
|
|
146
212
|
|
|
147
213
|
// When context-management (clear_thinking) is active for this session,
|
|
148
214
|
// the agent loop's cached message prefix has old thinking blocks cleared
|
|
@@ -159,17 +225,32 @@ export default function compactionExtension(pi: ExtensionAPI) {
|
|
|
159
225
|
// the Anthropic-shaped fields, and are gated to Anthropic anyway.
|
|
160
226
|
const cmModel = model as { api?: string; provider?: string; baseUrl?: string; compat?: AnthropicModelCompat };
|
|
161
227
|
const clearThinking = clearThinkingEnabled(process.env.CC_CLEAR_THINKING, cmModel);
|
|
162
|
-
const headers =
|
|
163
|
-
|
|
164
|
-
|
|
228
|
+
const headers = sessionRequestHeaders(cmModel, auth.headers);
|
|
229
|
+
|
|
230
|
+
// A replay sends the captured body (which already carries the
|
|
231
|
+
// clear_thinking edit) with the tail pi-ai just built appended.
|
|
232
|
+
// Otherwise the clear_thinking body edit, matching the session's
|
|
233
|
+
// requests (see the header note above), only on Anthropic requests
|
|
234
|
+
// that carry thinking, exactly as the context-management extension gates it.
|
|
235
|
+
let onPayload: ((payload: unknown) => unknown) | undefined;
|
|
236
|
+
if (plan) {
|
|
237
|
+
onPayload = (payload) => extendPayload(plan.capture, payload as Record<string, unknown>, plan.maxTokens);
|
|
238
|
+
} else if (clearThinking) {
|
|
239
|
+
onPayload = (payload) =>
|
|
240
|
+
looksLikeAnthropicRequest(payload) &&
|
|
241
|
+
clearThinkingApplies(payload as Record<string, unknown>, cmModel.compat?.forceAdaptiveThinking === true)
|
|
242
|
+
? withClearThinking(payload as Record<string, unknown>)
|
|
243
|
+
: payload;
|
|
244
|
+
}
|
|
165
245
|
|
|
166
246
|
const timeout = AbortSignal.timeout(COMPACTION_TIMEOUT_MS);
|
|
167
|
-
const
|
|
247
|
+
const callContext: Context = plan ? { systemPrompt: "", messages: plan.tail, tools: [] } : request;
|
|
248
|
+
const result = await completeSimple(baseUrl ? ({ ...model, baseUrl } as Model<Api>) : model, callContext, {
|
|
168
249
|
apiKey: auth.apiKey,
|
|
169
250
|
headers,
|
|
170
251
|
env: auth.env,
|
|
171
252
|
signal: AbortSignal.any([event.signal, timeout]),
|
|
172
|
-
maxTokens,
|
|
253
|
+
maxTokens: plan?.maxTokens ?? maxTokens,
|
|
173
254
|
// The session id is the prompt-cache key / routing affinity on the
|
|
174
255
|
// providers that use one (openai-codex prompt_cache_key, session
|
|
175
256
|
// headers). Without it the replayed prefix cannot hit the session's
|
|
@@ -189,18 +270,13 @@ export default function compactionExtension(pi: ExtensionAPI) {
|
|
|
189
270
|
// unless the model cannot disable thinking, where an off-request
|
|
190
271
|
// would 400 (forcedReasoningLevel sends its lowest level instead).
|
|
191
272
|
reasoning: ctx.thinkingLevel === "off" ? forcedReasoningLevel(model) : ctx.thinkingLevel,
|
|
192
|
-
|
|
193
|
-
// the header note above). Only attached on Anthropic requests that
|
|
194
|
-
// carry thinking, exactly as the context-management extension gates it.
|
|
195
|
-
onPayload: clearThinking
|
|
196
|
-
? (payload: unknown) =>
|
|
197
|
-
looksLikeAnthropicRequest(payload) &&
|
|
198
|
-
clearThinkingApplies(payload as Record<string, unknown>, cmModel.compat?.forceAdaptiveThinking === true)
|
|
199
|
-
? withClearThinking(payload as Record<string, unknown>)
|
|
200
|
-
: payload
|
|
201
|
-
: undefined,
|
|
273
|
+
onPayload,
|
|
202
274
|
});
|
|
203
275
|
|
|
276
|
+
// A failed or capped stream can carry partial text. Never replace the
|
|
277
|
+
// conversation with that incomplete checkpoint; let pi retry its summary.
|
|
278
|
+
if (result.stopReason === "error" || result.stopReason === "aborted" || result.stopReason === "length") return undefined;
|
|
279
|
+
|
|
204
280
|
const text = result.content
|
|
205
281
|
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
|
206
282
|
.map((block) => block.text)
|
|
@@ -212,7 +288,7 @@ export default function compactionExtension(pi: ExtensionAPI) {
|
|
|
212
288
|
compaction: {
|
|
213
289
|
// Undefined in --no-session runs; the pointer line is dropped.
|
|
214
290
|
summary: continuationSummary(summary, ctx.sessionManager.getSessionFile()),
|
|
215
|
-
firstKeptEntryId
|
|
291
|
+
firstKeptEntryId,
|
|
216
292
|
tokensBefore: event.preparation.tokensBefore,
|
|
217
293
|
usage: result.usage,
|
|
218
294
|
},
|
|
@@ -224,20 +300,50 @@ export default function compactionExtension(pi: ExtensionAPI) {
|
|
|
224
300
|
});
|
|
225
301
|
}
|
|
226
302
|
|
|
303
|
+
/** A model's identity for matching a capture to the current model. */
|
|
304
|
+
function modelKey(model: { provider?: string; id?: string } | undefined): string | undefined {
|
|
305
|
+
return model ? `${model.provider}/${model.id}` : undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* A replay's wire form: the tail pi-ai converts (the reply, if any, and the
|
|
310
|
+
* instruction: the last messages of `request`) and the output cap left under
|
|
311
|
+
* the captured request's own, or undefined when too little room is left.
|
|
312
|
+
*/
|
|
313
|
+
function wirePlan(
|
|
314
|
+
wire: { capture: RequestCapture; reply: unknown },
|
|
315
|
+
request: Context,
|
|
316
|
+
maxTokens: number,
|
|
317
|
+
): { capture: RequestCapture; tail: Message[]; maxTokens: number } | undefined {
|
|
318
|
+
const tail = request.messages.slice(-(wire.reply ? 2 : 1));
|
|
319
|
+
const cap = replayOutputCap(wire.capture, tail, maxTokens);
|
|
320
|
+
return cap === undefined ? undefined : { capture: wire.capture, tail, maxTokens: cap };
|
|
321
|
+
}
|
|
322
|
+
|
|
227
323
|
/**
|
|
228
324
|
* The cache-aligned shape: the session's system prompt, the active tool
|
|
229
325
|
* definitions in their active order — kept purely so the cached prefix (tools
|
|
230
326
|
* come first) still matches; a mismatch costs cache hits, never correctness —
|
|
231
327
|
* the captured request verbatim, and the instruction as one more user message.
|
|
232
328
|
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* names it (`keptTailOf`). Cutting the tail off the request instead was
|
|
329
|
+
* The kept tail (the last assistant reply and anything after it, `cut.ts`)
|
|
330
|
+
* stays verbatim after the summary; the captured request still carries it, so
|
|
331
|
+
* the instruction names it (`keptTailOf`). Cutting the tail off the request instead was
|
|
236
332
|
* rejected: Anthropic checks cache hits only ~20 blocks back from the
|
|
237
333
|
* breakpoint, so a request ending well before the last cached block misses
|
|
238
334
|
* the cache the whole replay exists to hit.
|
|
239
335
|
*/
|
|
240
|
-
function replayRequest(
|
|
336
|
+
function replayRequest(
|
|
337
|
+
pi: ExtensionAPI,
|
|
338
|
+
captured: { messages: AgentMessage[]; systemPrompt: string },
|
|
339
|
+
event: SessionBeforeCompactEvent,
|
|
340
|
+
preparation: Preparation,
|
|
341
|
+
reply: AgentMessage | undefined,
|
|
342
|
+
api: string | undefined,
|
|
343
|
+
): Context {
|
|
344
|
+
// The reply that answered the captured request is part of the conversation
|
|
345
|
+
// pi is compacting, and the last message the replayed body gains.
|
|
346
|
+
const conversation = reply ? [...captured.messages, reply] : captured.messages;
|
|
241
347
|
const byName = new Map(pi.getAllTools().map((tool) => [tool.name, tool]));
|
|
242
348
|
const tools = pi
|
|
243
349
|
.getActiveTools()
|
|
@@ -247,25 +353,38 @@ function replayRequest(pi: ExtensionAPI, ctx: ExtensionContext, captured: AgentM
|
|
|
247
353
|
const instruction = buildCompactionInstruction({
|
|
248
354
|
reason: event.reason,
|
|
249
355
|
customInstructions: event.customInstructions,
|
|
250
|
-
keptTail: keptTailOf(
|
|
356
|
+
keptTail: keptTailOf(conversation, preparation, api),
|
|
251
357
|
});
|
|
252
|
-
return { systemPrompt:
|
|
358
|
+
return { systemPrompt: captured.systemPrompt, messages: [...convertToLlm(conversation), instructionMessage(instruction)], tools };
|
|
253
359
|
}
|
|
254
360
|
|
|
255
361
|
/**
|
|
256
362
|
* The standalone shape: the doomed span reconstructed from session entries,
|
|
257
|
-
* its assistant usage zeroed
|
|
258
|
-
*
|
|
259
|
-
*
|
|
363
|
+
* its assistant usage zeroed, its signed thinking removed (it would fail the
|
|
364
|
+
* check under this system prompt; `kept-thinking.ts`), and its largest tool
|
|
365
|
+
* results cleared until pi's clamp leaves room for the summary (`fit.ts`), no
|
|
366
|
+
* tools, a one-line system prompt. Gets no kept-tail note: it holds only the doomed span. If it fails,
|
|
260
367
|
* pi's own compaction serves. Exported for the unit test.
|
|
261
368
|
*/
|
|
262
369
|
export function standaloneRequest(model: Model<Api>, preparation: Preparation, instruction: string, maxTokens: number): Context {
|
|
263
|
-
const
|
|
370
|
+
const span = withoutThinking(withoutUsage(convertToLlm(reconstructFromEntries(preparation))), model.api);
|
|
371
|
+
const messages = [...span, instructionMessage(instruction)];
|
|
264
372
|
return fitToBudget(model, { systemPrompt: STANDALONE_SYSTEM_PROMPT, messages }, maxTokens).request;
|
|
265
373
|
}
|
|
266
374
|
|
|
267
375
|
const instructionMessage = (instruction: string): Message => ({ role: "user", content: instruction, timestamp: Date.now() });
|
|
268
376
|
|
|
377
|
+
/**
|
|
378
|
+
* APIs whose request puts a run of consecutive tool results into ONE user
|
|
379
|
+
* message (pi-ai's converters: Anthropic and Bedrock require it, Gemini merges
|
|
380
|
+
* function responses into one turn). The model counts messages on the wire, so
|
|
381
|
+
* the kept-tail note counts such a run once.
|
|
382
|
+
*/
|
|
383
|
+
const MERGES_TOOL_RESULTS = new Set(["anthropic-messages", "bedrock-converse-stream", "google-generative-ai", "google-vertex"]);
|
|
384
|
+
const GOOGLE_APIS = new Set(["google-generative-ai", "google-vertex"]);
|
|
385
|
+
|
|
386
|
+
type TailMessage = { role: string; content?: unknown; toolName?: unknown };
|
|
387
|
+
|
|
269
388
|
/**
|
|
270
389
|
* The verbatim-kept tail of the captured request: everything after the doomed
|
|
271
390
|
* span. pi builds the context as `[compactionSummary?] + one message per entry`
|
|
@@ -273,37 +392,64 @@ const instructionMessage = (instruction: string): Message => ({ role: "user", co
|
|
|
273
392
|
* `messagesToSummarize`/`turnPrefixMessages` with the same mapping, so the
|
|
274
393
|
* doomed span is a prefix of the captured array. Alignment is checked role by
|
|
275
394
|
* role; on any mismatch (an extension that inserted a message) no note is made
|
|
276
|
-
* rather than a wrong one.
|
|
395
|
+
* rather than a wrong one.
|
|
396
|
+
*
|
|
397
|
+
* `count` is in the provider's messages (`api`): a parallel batch's results
|
|
398
|
+
* are one message on Anthropic. `landmark` names the first kept message so the
|
|
399
|
+
* model need not count: how its text opens, else the tool it calls, else the
|
|
400
|
+
* tool whose result it is. Exported for the unit test.
|
|
277
401
|
*/
|
|
278
402
|
export function keptTailOf(
|
|
279
|
-
captured: readonly
|
|
403
|
+
captured: readonly TailMessage[],
|
|
280
404
|
preparation: Preparation,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
...preparation.messagesToSummarize,
|
|
285
|
-
...(preparation.isSplitTurn ? preparation.turnPrefixMessages : []),
|
|
286
|
-
];
|
|
405
|
+
api?: string,
|
|
406
|
+
): { count: number; landmark?: string } | undefined {
|
|
407
|
+
const doomed: { role: string }[] = [...(preparation.previousSummary ? [{ role: "compactionSummary" }] : []), ...doomedMessages(preparation)];
|
|
287
408
|
if (doomed.length === 0 || doomed.length > captured.length) return undefined;
|
|
288
409
|
for (let i = 0; i < doomed.length; i++) {
|
|
289
410
|
if (captured[i].role !== doomed[i].role) return undefined;
|
|
290
411
|
}
|
|
291
|
-
const
|
|
292
|
-
if (
|
|
293
|
-
|
|
412
|
+
const tail = captured.slice(doomed.length);
|
|
413
|
+
if (tail.length === 0) return { count: 0 };
|
|
414
|
+
// On Google a tool result's image can become a user message of its own,
|
|
415
|
+
// depending on the model: the count cannot be known, so no note.
|
|
416
|
+
const imageResult = tail.some((m) => m.role === "toolResult" && Array.isArray(m.content) && (m.content as { type?: string }[]).some((b) => b?.type === "image"));
|
|
417
|
+
if (imageResult && api !== undefined && GOOGLE_APIS.has(api)) return undefined;
|
|
418
|
+
const merges = api !== undefined && MERGES_TOOL_RESULTS.has(api);
|
|
419
|
+
const count = tail.filter((m, i) => !(merges && m.role === "toolResult" && tail[i - 1]?.role === "toolResult")).length;
|
|
420
|
+
return { count, landmark: landmarkOf(tail[0]) };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** How the note points at the first kept message, or undefined when nothing identifies it. */
|
|
424
|
+
function landmarkOf(message: TailMessage): string | undefined {
|
|
425
|
+
if (message.role === "toolResult" && typeof message.toolName === "string") return `the result of the ${message.toolName} call`;
|
|
426
|
+
const content = Array.isArray(message.content) ? (message.content as { type?: string; text?: string; name?: unknown; arguments?: unknown }[]) : [];
|
|
427
|
+
const text = typeof message.content === "string" ? message.content : content.find((b) => b?.type === "text" && b.text?.trim())?.text;
|
|
428
|
+
const opening = oneLine(text);
|
|
429
|
+
if (opening) return `the message that opens "${opening}"`;
|
|
430
|
+
const call = content.find((b) => b?.type === "toolCall" && typeof b.name === "string");
|
|
431
|
+
if (message.role === "assistant" && call) {
|
|
432
|
+
const hint = oneLine(callHint(call.arguments));
|
|
433
|
+
return `the assistant message that calls ${call.name as string}${hint ? ` (${hint})` : ""}`;
|
|
434
|
+
}
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** The argument that says what a call is about: its path, command or pattern, else its first string argument. */
|
|
439
|
+
function callHint(args: unknown): string | undefined {
|
|
440
|
+
if (!args || typeof args !== "object") return undefined;
|
|
441
|
+
const record = args as Record<string, unknown>;
|
|
442
|
+
for (const key of ["path", "file_path", "command", "pattern", "url", "query"]) {
|
|
443
|
+
if (typeof record[key] === "string") return record[key] as string;
|
|
444
|
+
}
|
|
445
|
+
return Object.values(record).find((value): value is string => typeof value === "string");
|
|
294
446
|
}
|
|
295
447
|
|
|
296
|
-
/** The first ~80 characters of a
|
|
297
|
-
function
|
|
298
|
-
const content = message.content;
|
|
299
|
-
const text =
|
|
300
|
-
typeof content === "string"
|
|
301
|
-
? content
|
|
302
|
-
: Array.isArray(content)
|
|
303
|
-
? (content.find((b) => b && typeof b === "object" && (b as { type?: string }).type === "text") as { text?: string } | undefined)?.text
|
|
304
|
-
: undefined;
|
|
448
|
+
/** The first ~80 characters of a text, single-line, for a landmark. */
|
|
449
|
+
function oneLine(text: string | undefined): string | undefined {
|
|
305
450
|
if (!text) return undefined;
|
|
306
451
|
const line = text.replace(/\s+/g, " ").trim();
|
|
452
|
+
if (!line) return undefined;
|
|
307
453
|
return line.length > 80 ? `${line.slice(0, 80)}…` : line;
|
|
308
454
|
}
|
|
309
455
|
|
|
@@ -319,9 +465,7 @@ function openingText(message: { content?: unknown }): string | undefined {
|
|
|
319
465
|
* summarized with the rest.
|
|
320
466
|
*/
|
|
321
467
|
function reconstructFromEntries(preparation: Preparation): AgentMessage[] {
|
|
322
|
-
const doomed = preparation
|
|
323
|
-
? [...preparation.messagesToSummarize, ...preparation.turnPrefixMessages]
|
|
324
|
-
: preparation.messagesToSummarize;
|
|
468
|
+
const doomed = doomedMessages(preparation);
|
|
325
469
|
return preparation.previousSummary
|
|
326
470
|
? [
|
|
327
471
|
{
|