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
|
@@ -1,92 +1,91 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* System
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* System reminders: the steering blocks One Code adds to a request, rendered
|
|
3
|
+
* once and then kept as sent (working-docs/features/lifecycle-grid-hunt/design.md
|
|
4
|
+
* section 5). The ledger (extensions/ledger) sends through pi only the
|
|
5
|
+
* messages after the last request's coverage, the tail, and takes everything
|
|
6
|
+
* above it from that request's record. So a reminder is rendered only onto the
|
|
7
|
+
* tail, rides the request that first carried it, and stays in the record
|
|
8
|
+
* byte for byte; nothing here is restored or re-rendered later.
|
|
5
9
|
*
|
|
6
10
|
* Cross-extension contract: emit on the event bus channel
|
|
7
|
-
* `one-code:system-reminder` with `{ text,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
+
* `one-code:system-reminder` with `{ text, placement? }`. The live
|
|
12
|
+
* state lives in the system-reminder extension; do not import one from here
|
|
13
|
+
* (jiti gives every extension its own module instance, so a shared export
|
|
14
|
+
* steers nothing).
|
|
11
15
|
*
|
|
12
|
-
*
|
|
16
|
+
* Four placements, chosen by what kind of fact the reminder is:
|
|
13
17
|
*
|
|
14
|
-
* - `first-prepend
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* a one-
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* command
|
|
41
|
-
*
|
|
42
|
-
* one), then pinned there like a delivered one-shot. They never ride a tool
|
|
43
|
-
* result — a command run mid-turn waits for the next prompt. The caveat
|
|
44
|
-
* block is queued `once` per pending run (a second command joins the first
|
|
45
|
-
* caveat); the command and stdout blocks are never collapsed.
|
|
46
|
-
* `lib/local-command.ts` builds the blocks.
|
|
18
|
+
* - `first-prepend`: SESSION CONTEXT (environment, deferred tools, agent
|
|
19
|
+
* catalog, MCP instructions, skills, `# claudeMd`). Live state, kept current
|
|
20
|
+
* by its owner; rendered only on a request that opens an epoch, in front of
|
|
21
|
+
* the first user message and ordered by `order`: Claude Code's first-message
|
|
22
|
+
* stack. Inside an epoch message 1 is the record, so a change is not
|
|
23
|
+
* rendered; its owner announces the difference at render time
|
|
24
|
+
* (RENDER_CHANNEL: live minus what the epoch told).
|
|
25
|
+
* - `sticky-append`: SESSION STATE that switches on and off (auto mode, plan
|
|
26
|
+
* mode, a worktree session, ultracode). Appended to every user message of
|
|
27
|
+
* the tail stamped at or after the state switched on (`since`). Switched on
|
|
28
|
+
* mid-turn, it first rides the switching call's result (`toolCallId`), or
|
|
29
|
+
* the first result after the switch, never the already-sent turn opener.
|
|
30
|
+
* Removing it closes it: later messages stop carrying it, and the ones
|
|
31
|
+
* already sent keep it in the record.
|
|
32
|
+
* - `last-append`: ONE-SHOT steering about what just happened (a deferred
|
|
33
|
+
* tool miss, a file changed under the model, a mode change). Persisted when
|
|
34
|
+
* emitted as a `one-code:pending` entry, so it survives `/reload` and quit
|
|
35
|
+
* and is gone with an abandoned `/tree` branch; delivered on the tail of the
|
|
36
|
+
* next request (the call's own result when `toolCallId` names one in the
|
|
37
|
+
* tail, the prompt's own message when `promptTimestamp` names one, else
|
|
38
|
+
* the trailing tool result, else the last user message), and
|
|
39
|
+
* recorded as consumed by that request.
|
|
40
|
+
* - `user-prepend`: LOCAL-COMMAND BREADCRUMBS (`/clear`, `/model`, a panel
|
|
41
|
+
* command): pending like a one-shot, but placed only before the text of a
|
|
42
|
+
* prompt that opens a request; mid-turn it waits. The caveat block is
|
|
43
|
+
* queued `once` per run (a second command joins the first caveat); the
|
|
44
|
+
* command and stdout blocks are never collapsed. `lib/local-command.ts`
|
|
45
|
+
* builds them.
|
|
47
46
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* `pi.extensions`, like `context-budget`) or that emits from `tool_call` gets
|
|
53
|
-
* its block written into the result. Emitting from a later `tool_result`
|
|
54
|
-
* handler, or from `tool_execution_end` (pi runs it AFTER the `tool_result`
|
|
55
|
-
* hooks, findings §3), lands the block on the same result but as a pin. Same
|
|
56
|
-
* wording on the wire, so this only matters for `--resume` and for ordering
|
|
57
|
-
* after the `<total_tokens>` line.
|
|
58
|
-
*
|
|
59
|
-
* A compaction summary counts as a user message for anchoring (pi renders it as
|
|
60
|
-
* one), so the context stack survives a compaction that left no user turn. So
|
|
61
|
-
* does a `custom` harness message (task notification, wakeup, hook context):
|
|
62
|
-
* pi sends it as a user message, and a turn it opens has no other user turn.
|
|
63
|
-
*
|
|
64
|
-
* Retry safety falls out of this: pi re-runs the `context` transform per LLM
|
|
65
|
-
* attempt, and both a persisted and a pinned one-shot are present on every
|
|
66
|
-
* attempt by construction.
|
|
47
|
+
* A compaction summary counts as a user message for the stack (pi renders it
|
|
48
|
+
* as one), and so does a `custom` harness message (task notification, wakeup,
|
|
49
|
+
* hook context): pi sends it as a user message, and a turn it opens has no
|
|
50
|
+
* other user turn.
|
|
67
51
|
*/
|
|
68
52
|
|
|
69
53
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
70
54
|
import type { ImageContent, TextContent } from "@earendil-works/pi-ai";
|
|
55
|
+
import type { BlockFact, FileVersion, ToldFact } from "./ledger/records.ts";
|
|
56
|
+
import { contentHash } from "./ledger/wire.ts";
|
|
57
|
+
import type { MovedBlock } from "./system-role.ts";
|
|
71
58
|
|
|
72
59
|
export const REMINDER_CHANNEL = "one-code:system-reminder";
|
|
73
60
|
|
|
74
|
-
/**
|
|
75
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Emitted by the system-reminder extension once per request, while it renders
|
|
63
|
+
* the tail (a `RenderRequest`). An owner of a capability block subscribes and
|
|
64
|
+
* adds the blocks this request needs from live state and what the epoch has
|
|
65
|
+
* told: on a request inside an epoch, the names live now that the epoch has
|
|
66
|
+
* not told yet. Computed when the request goes out, never when a change is
|
|
67
|
+
* queued, so a capability that came and went before the request is never
|
|
68
|
+
* announced.
|
|
69
|
+
*/
|
|
70
|
+
export const RENDER_CHANNEL = "one-code:render";
|
|
76
71
|
|
|
77
72
|
/** See the module header for which placement fits which kind of reminder. */
|
|
78
73
|
export type ReminderPlacement = "first-prepend" | "sticky-append" | "last-append" | "user-prepend";
|
|
79
74
|
|
|
80
|
-
/** Where a delivered one-shot stays: a tool result (by call id) or a user turn (by timestamp). */
|
|
81
|
-
export type PinAnchor = { kind: "toolResult"; toolCallId: string } | { kind: "user"; timestamp: number };
|
|
82
|
-
|
|
83
75
|
/**
|
|
84
76
|
* `order` values for the `first-prepend` context stack, matching Claude Code's
|
|
85
|
-
* fixed sequence on the first user message:
|
|
86
|
-
*
|
|
87
|
-
*
|
|
77
|
+
* fixed sequence on the first user message: environment → model line → deferred
|
|
78
|
+
* tools → agent catalog → MCP instructions → skills → the instructions block →
|
|
79
|
+
* the context block → the date (just before the user's text). Gaps leave room
|
|
80
|
+
* for One Code-specific reminders (e.g. subagent models). On a model that takes
|
|
81
|
+
* a mid-conversation system message, everything below `claudeMd`, and the date,
|
|
82
|
+
* moves into that message instead (`movesToSystemRole`, lib/system-role.ts).
|
|
88
83
|
*/
|
|
89
84
|
export const CONTEXT_ORDER = {
|
|
85
|
+
environment: 1,
|
|
86
|
+
modelLine: 2,
|
|
87
|
+
/** One Code's note that a `-p`/json run ends with its turn (`lib/notifications.ts oneShotSessionNote`); not Claude Code's. */
|
|
88
|
+
oneShot: 3,
|
|
90
89
|
deferredTools: 10,
|
|
91
90
|
subagentModels: 20,
|
|
92
91
|
agents: 21,
|
|
@@ -94,263 +93,364 @@ export const CONTEXT_ORDER = {
|
|
|
94
93
|
delegation: 22,
|
|
95
94
|
mcp: 30,
|
|
96
95
|
skills: 40,
|
|
96
|
+
/** Claude Code's auto-mode note; only in the system message (`systemRoleOnly`). */
|
|
97
|
+
autoModeNote: 45,
|
|
98
|
+
/** The `<total_tokens>` line the first prompt carries (later prompts: lib/turn-budget-layout.ts). */
|
|
99
|
+
totalTokens: 46,
|
|
97
100
|
// CLAUDE.md-family (with AGENTS.md as a per-directory fallback when a directory
|
|
98
101
|
// has no CLAUDE.md) — CLAUDE.md > AGENTS.md.
|
|
99
102
|
claudeMd: 50,
|
|
100
|
-
// One Code's own instructions ride in their own block AFTER the
|
|
103
|
+
// One Code's own instructions ride in their own block AFTER the instructions
|
|
101
104
|
// block (higher `order` = closer to the user text = higher precedence), so
|
|
102
105
|
// ONECODE.md takes precedence over CLAUDE.md/AGENTS.md. Not part of CC.
|
|
103
|
-
oneCodeMd:
|
|
106
|
+
oneCodeMd: 52,
|
|
107
|
+
/** The user's email and the git snapshot. */
|
|
108
|
+
context: 54,
|
|
109
|
+
date: 56,
|
|
104
110
|
} as const;
|
|
105
111
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*/
|
|
116
|
-
suffix?: string;
|
|
117
|
-
/**
|
|
118
|
-
* `sticky-append` only: when the state switched on. The user message that
|
|
119
|
-
* opened the turn in progress at that moment, and every user message after
|
|
120
|
-
* it, carry the block.
|
|
121
|
-
*/
|
|
122
|
-
since?: number;
|
|
123
|
-
/** Set on a pinned one-shot: the exact message it rides on every request. */
|
|
124
|
-
pin?: PinAnchor;
|
|
125
|
-
/**
|
|
126
|
-
* Emit `text` as-is instead of inside a `<system-reminder>` frame — for the
|
|
127
|
-
* few Claude Code blocks that ride bare, like `<total_tokens>` after a tool
|
|
128
|
-
* result. Defaults to false.
|
|
129
|
-
*/
|
|
130
|
-
raw?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Whether a `first-prepend` block of this order leaves the first user message
|
|
114
|
+
* for the mid-conversation system message on a model that takes one: the
|
|
115
|
+
* session facts below the instructions block, and the date, which Claude Code
|
|
116
|
+
* puts last in that message. The instructions, One Code's own block and the
|
|
117
|
+
* context block stay in the user message.
|
|
118
|
+
*/
|
|
119
|
+
export function movesToSystemRole(order: number): boolean {
|
|
120
|
+
return order < CONTEXT_ORDER.claudeMd || order === CONTEXT_ORDER.date;
|
|
131
121
|
}
|
|
132
122
|
|
|
133
123
|
export interface ReminderPayload {
|
|
134
124
|
text?: string;
|
|
135
|
-
|
|
136
|
-
/** Key so a reminder can be replaced (next-turn) or replaced/removed (every-turn). */
|
|
125
|
+
/** Key so a reminder can be replaced (a pending one-shot, a live block) or removed (a live block). */
|
|
137
126
|
key?: string;
|
|
138
|
-
/** Remove the
|
|
127
|
+
/** Remove the live block or close the sticky state under `key`. */
|
|
139
128
|
remove?: boolean;
|
|
140
129
|
/** Where in the message stack this reminder lands. Defaults to `last-append`. */
|
|
141
130
|
placement?: ReminderPlacement;
|
|
142
131
|
/** Sort key among `first-prepend` reminders (Claude Code order). Defaults to 0. */
|
|
143
132
|
order?: number;
|
|
144
|
-
/** Literal text appended after the closing `</system-reminder>` tag. Defaults to "". */
|
|
145
|
-
suffix?: string;
|
|
146
133
|
/** Emit the text bare, with no `<system-reminder>` frame. */
|
|
147
134
|
raw?: boolean;
|
|
148
|
-
/** `
|
|
135
|
+
/** `first-prepend` only: sent only inside the mid-conversation system message, never on a user message. */
|
|
136
|
+
systemRoleOnly?: boolean;
|
|
137
|
+
/** `sticky-append` only: never on the message that carries the context stack, which already says the same. */
|
|
138
|
+
skipStackCarrier?: boolean;
|
|
139
|
+
/** `user-prepend` only: placed once per request, however many are pending (the shared caveat). */
|
|
149
140
|
once?: boolean;
|
|
141
|
+
/** `user-prepend` only: on a request that does not open with a prompt (mid-turn, an overflow retry), the tail carries it instead. */
|
|
142
|
+
orTail?: boolean;
|
|
143
|
+
/** A one-shot dropped, undelivered, at a later compaction (a post-compaction restore). */
|
|
144
|
+
untilCompaction?: boolean;
|
|
145
|
+
/** A one-shot only: the files whose whole content it shows, recorded on its pending entry (`PendingRecordData.observations`). */
|
|
146
|
+
observations?: FileVersion[];
|
|
147
|
+
/** A one-shot only: the changed file versions it names, recorded on its pending entry (`PendingRecordData.notified`). */
|
|
148
|
+
notified?: FileVersion[];
|
|
150
149
|
/**
|
|
151
|
-
* `sticky-append` only:
|
|
152
|
-
* `0` puts it on every user message
|
|
153
|
-
*
|
|
150
|
+
* `sticky-append` only: the state is on from this timestamp instead of now.
|
|
151
|
+
* `0` puts it on every user message (the `<total_tokens>` line rides every
|
|
152
|
+
* user message in Claude Code).
|
|
154
153
|
*/
|
|
155
154
|
since?: number;
|
|
155
|
+
/**
|
|
156
|
+
* The tool call whose result this reminder belongs to. A one-shot rides that
|
|
157
|
+
* result when the request carries it; a sticky state rides it first. Both
|
|
158
|
+
* avoid attaching a switch or countdown to another call in a parallel batch.
|
|
159
|
+
*/
|
|
160
|
+
toolCallId?: string;
|
|
161
|
+
/**
|
|
162
|
+
* A one-shot only: the timestamp of the user message it belongs to (a
|
|
163
|
+
* prompt's hook context). It rides that message when the request carries
|
|
164
|
+
* it, so a prompt whose request failed keeps its own context when the
|
|
165
|
+
* next prompt resends it.
|
|
166
|
+
*/
|
|
167
|
+
promptTimestamp?: number;
|
|
168
|
+
/** `first-prepend` only: what the block tells the model, recorded when an epoch sends it. */
|
|
169
|
+
facts?: ToldFact[];
|
|
156
170
|
}
|
|
157
171
|
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
/** The fact that the whole-text block `key` told `value` (a text, or a snapshot such as a git status). */
|
|
173
|
+
export function toldBlockFact(key: string, value: unknown): BlockFact {
|
|
174
|
+
return { kind: "block", key, hash: contentHash(value) };
|
|
160
175
|
}
|
|
161
176
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
key
|
|
165
|
-
|
|
166
|
-
order
|
|
167
|
-
suffix?: string;
|
|
177
|
+
/** A live `first-prepend` block. */
|
|
178
|
+
export interface StackEntry {
|
|
179
|
+
key: string;
|
|
180
|
+
text: string;
|
|
181
|
+
order: number;
|
|
168
182
|
raw?: boolean;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
once?: boolean;
|
|
173
|
-
};
|
|
183
|
+
systemRoleOnly?: boolean;
|
|
184
|
+
facts?: ToldFact[];
|
|
185
|
+
}
|
|
174
186
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
187
|
+
/** A live `sticky-append` state. */
|
|
188
|
+
export interface StickyEntry {
|
|
189
|
+
key: string;
|
|
190
|
+
text: string;
|
|
191
|
+
raw?: boolean;
|
|
192
|
+
/** When it switched on: user messages stamped at or after it carry it. */
|
|
193
|
+
since: number;
|
|
194
|
+
toolCallId?: string;
|
|
195
|
+
skipStackCarrier?: boolean;
|
|
196
|
+
}
|
|
181
197
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
198
|
+
/** A sticky state as a render places it: `delivered` once an answered request in the epoch carried it (its `stickyFact` is told), so a tool-result pin is only for its first delivery. */
|
|
199
|
+
export type StickyToPlace = StickyEntry & { delivered: boolean };
|
|
185
200
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// every turn) keeps its anchor, so the blocks on earlier messages do not
|
|
200
|
-
// move. Different text under the same key is a new fact (plan → auto
|
|
201
|
-
// under the shared "permission-mode" key) and anchors from now.
|
|
202
|
-
const previous = opts?.key !== undefined ? this.everyTurn.get(opts.key) : undefined;
|
|
203
|
-
entry.since =
|
|
204
|
-
opts?.since ??
|
|
205
|
-
(previous?.placement === "sticky-append" && previous.text === text ? previous.since : undefined) ??
|
|
206
|
-
this.now();
|
|
207
|
-
}
|
|
208
|
-
if (opts?.scope === "every-turn") {
|
|
209
|
-
this.everyTurn.set(opts.key ?? text, entry);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
// A breadcrumb run shares one caveat block (`once`): the same text pending
|
|
213
|
-
// twice would show the model two identical blocks in a row. Only the
|
|
214
|
-
// announcer's caveat asks for this — two commands with the same (empty)
|
|
215
|
-
// stdout are two commands, and both stay.
|
|
216
|
-
if (placement === "user-prepend" && opts?.once && this.nextTurn.some((r) => r.placement === placement && r.text === text)) return;
|
|
217
|
-
// A keyed next-turn reminder replaces its predecessor, so a rapidly
|
|
218
|
-
// re-emitted state change (cycling permission modes) announces only
|
|
219
|
-
// where it settled.
|
|
220
|
-
if (opts?.key) this.nextTurn = this.nextTurn.filter((r) => r.key !== opts.key);
|
|
221
|
-
this.nextTurn.push(entry);
|
|
222
|
-
}
|
|
201
|
+
/** A one-shot to place on this request: a pending entry, or a block an owner rendered for it. */
|
|
202
|
+
export interface OneShot {
|
|
203
|
+
/** The pending entry's id; absent on a render-time block. */
|
|
204
|
+
id?: string;
|
|
205
|
+
text: string;
|
|
206
|
+
raw?: boolean;
|
|
207
|
+
placement: "last-append" | "user-prepend";
|
|
208
|
+
toolCallId?: string;
|
|
209
|
+
promptTimestamp?: number;
|
|
210
|
+
once?: boolean;
|
|
211
|
+
orTail?: boolean;
|
|
212
|
+
facts?: ToldFact[];
|
|
213
|
+
}
|
|
223
214
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
215
|
+
/** What a capability owner adds on RENDER_CHANNEL. */
|
|
216
|
+
export interface RenderedBlock {
|
|
217
|
+
text: string;
|
|
218
|
+
/** What the block tells the model, recorded with the request that carries it. */
|
|
219
|
+
facts?: ToldFact[];
|
|
220
|
+
}
|
|
227
221
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
222
|
+
/** The RENDER_CHANNEL payload. */
|
|
223
|
+
export interface RenderRequest {
|
|
224
|
+
/** True when the request opens an epoch: the live stack is rendered, so owners add nothing. */
|
|
225
|
+
epoch: boolean;
|
|
226
|
+
/** What the epoch has told the model so far, by fact kind. */
|
|
227
|
+
told: ReadonlyMap<string, ReadonlySet<string>>;
|
|
228
|
+
/** Modes the epoch has told, by key: on or off as last told. */
|
|
229
|
+
modes: ReadonlyMap<string, boolean>;
|
|
230
|
+
/** Whole-text blocks the epoch has told, by key: the content hash of the text last told (`toldBlockFact`). */
|
|
231
|
+
toldBlocks: ReadonlyMap<string, string>;
|
|
232
|
+
/** The request's model. */
|
|
233
|
+
model: { provider?: string; api?: string; id?: string; compat?: unknown } | undefined;
|
|
234
|
+
/** Blocks for the tail of this request. */
|
|
235
|
+
blocks: RenderedBlock[];
|
|
236
|
+
}
|
|
238
237
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
238
|
+
/**
|
|
239
|
+
* The live reminder state: the `first-prepend` stack and the open sticky
|
|
240
|
+
* states, as their owners last emitted them. One-shots are not held here:
|
|
241
|
+
* they are persisted as pending entries when emitted.
|
|
242
|
+
*/
|
|
243
|
+
export class LiveReminders {
|
|
244
|
+
private stack = new Map<string, StackEntry>();
|
|
245
|
+
/** Open states in the order they switched on. */
|
|
246
|
+
private sticky = new Map<string, StickyEntry>();
|
|
247
|
+
private readonly now: () => number;
|
|
248
|
+
|
|
249
|
+
constructor(now: () => number = Date.now) {
|
|
250
|
+
this.now = now;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* queue only through `pin(anchor, "user-prepend")` (it must land on a user
|
|
257
|
-
* message, not a tool result), so an unpinned one stays for the next request. `messages` is the request being built:
|
|
258
|
-
* pins whose anchor is no longer in it (compacted away, or left behind on
|
|
259
|
-
* another branch) are dropped here, as part of draining, so no caller can
|
|
260
|
-
* forget the step. Nothing else is ever evicted: a pin is one small object,
|
|
261
|
-
* and removing a pin whose message is still in context would change that
|
|
262
|
-
* message and re-cache everything after it — the old "oldest past 400" cap
|
|
263
|
-
* did exactly that (CACHE-REVIEW-2026-09-04 M3).
|
|
254
|
+
* Apply one reminder payload. Returns "one-shot" for a `last-append` or
|
|
255
|
+
* `user-prepend` reminder, which the caller persists as a pending entry.
|
|
264
256
|
*/
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
257
|
+
apply(payload: ReminderPayload): "live" | "one-shot" | "ignored" {
|
|
258
|
+
const placement = payload.placement ?? "last-append";
|
|
259
|
+
if (payload.remove) {
|
|
260
|
+
if (!payload.key) return "ignored";
|
|
261
|
+
this.stack.delete(payload.key);
|
|
262
|
+
this.sticky.delete(payload.key);
|
|
263
|
+
return "live";
|
|
269
264
|
}
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
265
|
+
const text = payload.text;
|
|
266
|
+
if (typeof text !== "string" || !text.trim()) return "ignored";
|
|
267
|
+
if (placement === "last-append" || placement === "user-prepend") return "one-shot";
|
|
268
|
+
const key = payload.key ?? text;
|
|
269
|
+
if (placement === "first-prepend") {
|
|
270
|
+
const entry: StackEntry = { key, text, order: payload.order ?? 0 };
|
|
271
|
+
if (payload.raw) entry.raw = true;
|
|
272
|
+
if (payload.systemRoleOnly) entry.systemRoleOnly = true;
|
|
273
|
+
if (payload.facts) entry.facts = payload.facts;
|
|
274
|
+
this.stack.set(key, entry);
|
|
275
|
+
return "live";
|
|
276
|
+
}
|
|
277
|
+
const previous = this.sticky.get(key);
|
|
278
|
+
const same = previous && previous.text === text && !!previous.raw === !!payload.raw && !!previous.skipStackCarrier === !!payload.skipStackCarrier;
|
|
279
|
+
// Re-emitting an unchanged standing state keeps its start and delivery; a
|
|
280
|
+
// changed one is a new state from now.
|
|
281
|
+
if (same && (payload.since === undefined || payload.since === previous.since)) return "live";
|
|
282
|
+
const entry: StickyEntry = { key, text, since: payload.since ?? this.now() };
|
|
283
|
+
if (payload.raw) entry.raw = true;
|
|
284
|
+
if (payload.skipStackCarrier) entry.skipStackCarrier = true;
|
|
285
|
+
if (payload.toolCallId) entry.toolCallId = payload.toolCallId;
|
|
286
|
+
this.sticky.delete(key);
|
|
287
|
+
this.sticky.set(key, entry);
|
|
288
|
+
return "live";
|
|
273
289
|
}
|
|
274
290
|
|
|
275
|
-
|
|
276
|
-
|
|
291
|
+
/** The stack in Claude Code's order (`order`, then emission order). */
|
|
292
|
+
stackEntries(): StackEntry[] {
|
|
293
|
+
return [...this.stack.values()].map((entry, i) => ({ entry, i })).sort((a, b) => a.entry.order - b.entry.order || a.i - b.i).map((x) => x.entry);
|
|
277
294
|
}
|
|
278
295
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return this.nextTurn.some((r) => r.placement === placement);
|
|
296
|
+
stickyEntries(): StickyEntry[] {
|
|
297
|
+
return [...this.sticky.values()];
|
|
282
298
|
}
|
|
299
|
+
}
|
|
283
300
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
301
|
+
/** The told fact of a sticky state's first delivery: it follows the branch, and a failed request's is revoked. */
|
|
302
|
+
export function stickyFact(entry: Pick<StickyEntry, "key" | "text" | "since">): BlockFact {
|
|
303
|
+
return toldBlockFact(`sticky:${entry.key}`, { since: entry.since, text: entry.text });
|
|
288
304
|
}
|
|
289
305
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
306
|
+
export interface RenderInput {
|
|
307
|
+
/** The request opens an epoch: the stack goes on the first user message. */
|
|
308
|
+
epoch: boolean;
|
|
309
|
+
/** The model takes a mid-conversation system message (`systemRoleOnly` blocks render, `moved` is filled). */
|
|
310
|
+
layout: boolean;
|
|
311
|
+
stack: readonly StackEntry[];
|
|
312
|
+
sticky: readonly StickyToPlace[];
|
|
313
|
+
oneShots: readonly OneShot[];
|
|
296
314
|
}
|
|
297
315
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
316
|
+
export interface RenderResult {
|
|
317
|
+
messages: AgentMessage[];
|
|
318
|
+
/** The stack blocks that move to the mid-conversation system message (epoch and layout only). */
|
|
319
|
+
moved: MovedBlock[];
|
|
320
|
+
/** True when the stack was placed. */
|
|
321
|
+
stackPlaced: boolean;
|
|
322
|
+
/** The sticky states this request carried. */
|
|
323
|
+
stickyPlaced: string[];
|
|
324
|
+
/** The one-shots this request carried. */
|
|
325
|
+
placed: OneShot[];
|
|
304
326
|
}
|
|
305
327
|
|
|
328
|
+
type ContentBlock = TextContent | ImageContent;
|
|
329
|
+
|
|
306
330
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* message count on the per-request path.
|
|
331
|
+
* The request's messages (its tail, or all of them on an epoch) with the
|
|
332
|
+
* reminders placed, and what was placed. The input is not mutated. A one-shot
|
|
333
|
+
* that finds no anchor is not placed and stays pending.
|
|
311
334
|
*/
|
|
312
|
-
function
|
|
313
|
-
const
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
335
|
+
export function renderReminders(messages: readonly AgentMessage[], input: RenderInput): RenderResult {
|
|
336
|
+
const before = new Map<number, TextContent[]>();
|
|
337
|
+
const after = new Map<number, TextContent[]>();
|
|
338
|
+
const push = (map: Map<number, TextContent[]>, index: number, blocks: TextContent[]) => {
|
|
339
|
+
if (index < 0 || blocks.length === 0) return;
|
|
340
|
+
map.set(index, [...(map.get(index) ?? []), ...blocks]);
|
|
341
|
+
};
|
|
342
|
+
const firstUser = messages.findIndex(isUserLike);
|
|
343
|
+
const lastUser = messages.findLastIndex(isUserLike);
|
|
344
|
+
const last = messages.length - 1;
|
|
345
|
+
// Mid-turn, the request ends in the tool results the model is about to read:
|
|
346
|
+
// a one-shot belongs there. Otherwise on the last user turn.
|
|
347
|
+
const tail = last >= 0 && messages[last].role === "toolResult" ? last : lastUser;
|
|
348
|
+
const stackAt = firstUser === -1 ? tail : firstUser;
|
|
349
|
+
|
|
350
|
+
let stackPlaced = false;
|
|
351
|
+
let moved: MovedBlock[] = [];
|
|
352
|
+
if (input.epoch && stackAt !== -1) {
|
|
353
|
+
const stack = input.stack.filter((entry) => input.layout || !entry.systemRoleOnly);
|
|
354
|
+
push(before, stackAt, stack.map(block));
|
|
355
|
+
stackPlaced = stack.length > 0;
|
|
356
|
+
if (input.layout) {
|
|
357
|
+
moved = stack.filter((entry) => movesToSystemRole(entry.order)).map((entry) => ({ framed: framedReminderText(entry), inner: entry.text }));
|
|
322
358
|
}
|
|
323
|
-
}
|
|
324
|
-
return (pin) => (pin.kind === "toolResult" ? byToolCall.get(pin.toolCallId) : byTimestamp.get(pin.timestamp)) ?? -1;
|
|
325
|
-
}
|
|
359
|
+
}
|
|
326
360
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
361
|
+
const stickyPlaced: string[] = [];
|
|
362
|
+
for (const state of input.sticky) {
|
|
363
|
+
const carriers: number[] = [];
|
|
364
|
+
messages.forEach((message, index) => {
|
|
365
|
+
if (!isStickyCarrier(message) || stamp(message) < state.since) return;
|
|
366
|
+
if (state.skipStackCarrier && input.epoch && index === firstUser) return;
|
|
367
|
+
carriers.push(index);
|
|
368
|
+
});
|
|
369
|
+
// The pin is for a state switched on mid-turn; one on from the start
|
|
370
|
+
// (`since: 0`, the constant budget line) rides user messages only.
|
|
371
|
+
if (!state.delivered && state.since > 0) {
|
|
372
|
+
const first = firstResultAfter(messages, state);
|
|
373
|
+
if (first !== -1 && !carriers.includes(first)) carriers.unshift(first);
|
|
374
|
+
}
|
|
375
|
+
if (carriers.length === 0) continue;
|
|
376
|
+
stickyPlaced.push(state.key);
|
|
377
|
+
for (const index of carriers.sort((a, b) => a - b)) push(after, index, [block(state)]);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const placed: OneShot[] = [];
|
|
381
|
+
const opening = last >= 0 && isUserLike(messages[last]) ? last : -1;
|
|
382
|
+
const placedOnce = new Set<string>();
|
|
383
|
+
for (const shot of input.oneShots) {
|
|
384
|
+
if (shot.placement === "user-prepend" && (opening !== -1 || !shot.orTail)) {
|
|
385
|
+
if (opening === -1) continue;
|
|
386
|
+
placed.push(shot);
|
|
387
|
+
if (shot.once && placedOnce.has(shot.text)) continue;
|
|
388
|
+
if (shot.once) placedOnce.add(shot.text);
|
|
389
|
+
push(before, opening, [block(shot)]);
|
|
390
|
+
continue;
|
|
335
391
|
}
|
|
392
|
+
const own = shot.toolCallId
|
|
393
|
+
? messages.findIndex((m) => m.role === "toolResult" && (m as { toolCallId?: string }).toolCallId === shot.toolCallId)
|
|
394
|
+
: shot.promptTimestamp !== undefined
|
|
395
|
+
? promptAt(messages, shot.promptTimestamp)
|
|
396
|
+
: -1;
|
|
397
|
+
const at = own !== -1 ? own : tail;
|
|
398
|
+
if (at === -1) continue;
|
|
399
|
+
placed.push(shot);
|
|
400
|
+
push(after, at, [block(shot)]);
|
|
336
401
|
}
|
|
337
|
-
|
|
402
|
+
|
|
403
|
+
const changed = before.size > 0 || after.size > 0;
|
|
404
|
+
return {
|
|
405
|
+
messages: changed ? messages.map((m, index) => withBlocks(m, before.get(index) ?? [], after.get(index) ?? [])) : [...messages],
|
|
406
|
+
moved,
|
|
407
|
+
stackPlaced,
|
|
408
|
+
stickyPlaced,
|
|
409
|
+
placed,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function stamp(message: AgentMessage): number {
|
|
414
|
+
return (message as { timestamp?: number }).timestamp ?? 0;
|
|
338
415
|
}
|
|
339
416
|
|
|
417
|
+
/** How much later pi may stamp a custom message it rebuilt from the session than the one it delivered. */
|
|
418
|
+
const RESTAMP_WINDOW_MS = 1000;
|
|
419
|
+
|
|
340
420
|
/**
|
|
341
|
-
* The
|
|
342
|
-
*
|
|
343
|
-
*
|
|
421
|
+
* The prompt a one-shot's `promptTimestamp` names, or -1 when the request does
|
|
422
|
+
* not carry it. pi stores a custom message (a `/skill:` invocation) with the
|
|
423
|
+
* time it persisted it, at `message_end`, so after `/reload` or resume the
|
|
424
|
+
* rebuilt message is stamped a little after the delivered one the pin took;
|
|
425
|
+
* a user message keeps its own stamp.
|
|
344
426
|
*/
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
if (
|
|
348
|
-
return
|
|
427
|
+
function promptAt(messages: readonly AgentMessage[], timestamp: number): number {
|
|
428
|
+
const exact = messages.findIndex((m) => isStickyCarrier(m) && stamp(m) === timestamp);
|
|
429
|
+
if (exact !== -1) return exact;
|
|
430
|
+
return messages.findIndex((m) => m.role === "custom" && stamp(m) > timestamp && stamp(m) - timestamp < RESTAMP_WINDOW_MS);
|
|
349
431
|
}
|
|
350
432
|
|
|
351
|
-
/**
|
|
352
|
-
|
|
353
|
-
|
|
433
|
+
/**
|
|
434
|
+
* Where a state switched on mid-turn first shows: its switching call's result
|
|
435
|
+
* when the request carries it, else (that result compacted away, or no call
|
|
436
|
+
* named) the first tool result stamped after the switch, but never one after a
|
|
437
|
+
* user message that already carries it. With no carrier at all (an overflow
|
|
438
|
+
* compaction kept only a reply and its results), the request's tail.
|
|
439
|
+
*/
|
|
440
|
+
function firstResultAfter(messages: readonly AgentMessage[], state: StickyEntry): number {
|
|
441
|
+
if (state.toolCallId !== undefined) {
|
|
442
|
+
const own = messages.findIndex((m) => m.role === "toolResult" && (m as { toolCallId?: string }).toolCallId === state.toolCallId);
|
|
443
|
+
if (own !== -1) return own;
|
|
444
|
+
}
|
|
445
|
+
for (let i = 0; i < messages.length; i++) {
|
|
446
|
+
const message = messages[i];
|
|
447
|
+
if (stamp(message) < state.since) continue;
|
|
448
|
+
if (isStickyCarrier(message)) return -1;
|
|
449
|
+
if (message.role === "toolResult") return i;
|
|
450
|
+
}
|
|
451
|
+
if (messages.some(isStickyCarrier)) return -1;
|
|
452
|
+
const last = messages.length - 1;
|
|
453
|
+
return last >= 0 && messages[last].role === "toolResult" ? last : -1;
|
|
354
454
|
}
|
|
355
455
|
|
|
356
456
|
/**
|
|
@@ -381,14 +481,17 @@ export const COMPACTION_SUMMARY_PREFIX =
|
|
|
381
481
|
"The conversation history before this point was compacted into the following summary:\n\n<summary>\n";
|
|
382
482
|
export const COMPACTION_SUMMARY_SUFFIX = "\n</summary>";
|
|
383
483
|
|
|
384
|
-
type ContentBlock = TextContent | ImageContent;
|
|
385
|
-
|
|
386
484
|
function toBlocks(content: string | ContentBlock[]): ContentBlock[] {
|
|
387
485
|
return typeof content === "string" ? [{ type: "text", text: content }] : [...content];
|
|
388
486
|
}
|
|
389
487
|
|
|
390
|
-
|
|
391
|
-
|
|
488
|
+
/** A reminder's text exactly as it rides a message: framed, unless raw. */
|
|
489
|
+
export function framedReminderText(entry: { text: string; raw?: boolean }): string {
|
|
490
|
+
return entry.raw ? entry.text : wrapReminder(entry.text);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function block(entry: { text: string; raw?: boolean }): TextContent {
|
|
494
|
+
return { type: "text", text: framedReminderText(entry) };
|
|
392
495
|
}
|
|
393
496
|
|
|
394
497
|
/**
|
|
@@ -396,9 +499,7 @@ function reminderBlock(entry: ReminderEntry): TextContent {
|
|
|
396
499
|
* (task notification, wakeup, hook context, `<new-diagnostics>`); pi's
|
|
397
500
|
* `convertToLlm` sends it to the model as `role: "user"`, so on the wire it IS
|
|
398
501
|
* a user turn — and a turn opened by one (a `/loop` tick, an agent report
|
|
399
|
-
* arriving while idle) has no `user` message of its own
|
|
400
|
-
* a request got no context stack at all, and its pending one-shots pinned to
|
|
401
|
-
* the previous user message (STEERING-REVIEW-2026-09-05 H1).
|
|
502
|
+
* arriving while idle) has no `user` message of its own (STEERING-REVIEW-2026-09-05 H1).
|
|
402
503
|
*/
|
|
403
504
|
function isUserLike(m: AgentMessage): boolean {
|
|
404
505
|
return isStickyCarrier(m) || m.role === "compactionSummary";
|
|
@@ -427,92 +528,3 @@ function withBlocks(message: AgentMessage, before: TextContent[], after: TextCon
|
|
|
427
528
|
const target = message as AgentMessage & { content: string | ContentBlock[] };
|
|
428
529
|
return { ...target, content: [...before, ...toBlocks(target.content), ...after] } as AgentMessage;
|
|
429
530
|
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Returns a copy of `messages` with reminder blocks injected (Claude Code
|
|
433
|
-
* convention: reminders ride inside message content blocks, never as messages
|
|
434
|
-
* of their own). See the module header for the three placements. A bare string
|
|
435
|
-
* is `last-append` (back-compat). The input array and its messages are not
|
|
436
|
-
* mutated. With nothing to anchor to (no user, tool-result, or compaction
|
|
437
|
-
* summary message), messages are returned unchanged and the caller keeps the
|
|
438
|
-
* reminders queued.
|
|
439
|
-
*/
|
|
440
|
-
export function injectReminders(messages: AgentMessage[], reminders: Array<string | ReminderEntry>): AgentMessage[] {
|
|
441
|
-
if (reminders.length === 0) return messages;
|
|
442
|
-
|
|
443
|
-
const entries: ReminderEntry[] = reminders.map((r) =>
|
|
444
|
-
typeof r === "string" ? { text: r, placement: "last-append", order: 0 } : r,
|
|
445
|
-
);
|
|
446
|
-
const firstPrepend = entries
|
|
447
|
-
.filter((e) => e.placement === "first-prepend")
|
|
448
|
-
.map((e, i) => ({ e, i }))
|
|
449
|
-
.sort((a, b) => a.e.order - b.e.order || a.i - b.i)
|
|
450
|
-
.map((x) => x.e);
|
|
451
|
-
const sticky = entries.filter((e) => e.placement === "sticky-append");
|
|
452
|
-
const pinnedEntries = entries.filter((e) => e.pin !== undefined);
|
|
453
|
-
const lastAppend = entries.filter((e) => e.placement === "last-append" && e.pin === undefined);
|
|
454
|
-
// An unpinned `user-prepend` never reaches here through the queue (drain
|
|
455
|
-
// holds it back); a caller passing one directly gets it on the opening user
|
|
456
|
-
// message, before the text, like a pinned one.
|
|
457
|
-
const looseUserPrepend = entries.filter((e) => e.placement === "user-prepend" && e.pin === undefined);
|
|
458
|
-
|
|
459
|
-
const firstUserIndex = messages.findIndex(isUserLike);
|
|
460
|
-
const lastUserIndex = messages.findLastIndex(isUserLike);
|
|
461
|
-
const lastIndex = messages.length - 1;
|
|
462
|
-
// Mid-turn, the request ends in the tool result(s) the model is about to
|
|
463
|
-
// read — that is where a one-shot reminder belongs. Otherwise the last user turn.
|
|
464
|
-
const tailIndex = lastIndex >= 0 && messages[lastIndex].role === "toolResult" ? lastIndex : lastUserIndex;
|
|
465
|
-
if (firstUserIndex === -1 && tailIndex === -1) return messages;
|
|
466
|
-
|
|
467
|
-
const before = new Map<number, TextContent[]>();
|
|
468
|
-
const after = new Map<number, TextContent[]>();
|
|
469
|
-
const push = (map: Map<number, TextContent[]>, index: number, blocks: TextContent[]) => {
|
|
470
|
-
if (index < 0 || blocks.length === 0) return;
|
|
471
|
-
map.set(index, [...(map.get(index) ?? []), ...blocks]);
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
push(before, firstUserIndex === -1 ? tailIndex : firstUserIndex, firstPrepend.map(reminderBlock));
|
|
475
|
-
|
|
476
|
-
for (const entry of sticky) {
|
|
477
|
-
const since = entry.since ?? 0;
|
|
478
|
-
// The block rides the user message that opened the turn during which the
|
|
479
|
-
// state switched on (the latest user message stamped before `since` — a
|
|
480
|
-
// standing reminder emitted on before_agent_start is always stamped after
|
|
481
|
-
// the turn's user message) and every user message after it. The set only
|
|
482
|
-
// grows while the state holds, so earlier messages never change.
|
|
483
|
-
let opener = -1;
|
|
484
|
-
const carriers: number[] = [];
|
|
485
|
-
messages.forEach((m, index) => {
|
|
486
|
-
if (!isStickyCarrier(m)) return;
|
|
487
|
-
const stamp = (m as { timestamp?: number }).timestamp ?? 0;
|
|
488
|
-
if (stamp >= since) carriers.push(index);
|
|
489
|
-
else opener = index;
|
|
490
|
-
});
|
|
491
|
-
if (opener !== -1) carriers.unshift(opener);
|
|
492
|
-
if (carriers.length === 0) {
|
|
493
|
-
// No user turn at all (overflow compaction mid-turn): ride the tail.
|
|
494
|
-
push(after, tailIndex === -1 ? firstUserIndex : tailIndex, [reminderBlock(entry)]);
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
for (const index of carriers) push(after, index, [reminderBlock(entry)]);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
push(after, tailIndex === -1 ? firstUserIndex : tailIndex, lastAppend.map(reminderBlock));
|
|
501
|
-
if (lastUserIndex !== -1) push(before, lastUserIndex, looseUserPrepend.map(reminderBlock));
|
|
502
|
-
|
|
503
|
-
// Pinned one-shots ride the exact message they first landed on; a message
|
|
504
|
-
// compacted away simply no longer carries its pin. A pinned breadcrumb sits
|
|
505
|
-
// BEFORE that message's text (after the first-prepend stack, which was
|
|
506
|
-
// pushed first); every other pin sits after it.
|
|
507
|
-
if (pinnedEntries.length > 0) {
|
|
508
|
-
const locate = pinLocator(messages);
|
|
509
|
-
for (const entry of pinnedEntries) {
|
|
510
|
-
const index = locate(entry.pin as PinAnchor);
|
|
511
|
-
if (index === -1) continue;
|
|
512
|
-
push(entry.placement === "user-prepend" ? before : after, index, [reminderBlock(entry)]);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
if (before.size === 0 && after.size === 0) return messages;
|
|
517
|
-
return messages.map((m, index) => withBlocks(m, before.get(index) ?? [], after.get(index) ?? []));
|
|
518
|
-
}
|