one-code-extension 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +32 -23
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +27 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +58 -31
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +123 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +515 -0
- package/extensions/background/index.ts +514 -236
- package/extensions/background/loop-fire.ts +136 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +52 -0
- package/extensions/background/wakeup.ts +159 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +88 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +229 -71
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +446 -58
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +216 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/index.ts +24 -5
- package/extensions/context-management/index.ts +25 -0
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +72 -19
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +337 -26
- package/extensions/file-tracker/replay.ts +96 -10
- package/extensions/file-tracker/restore.ts +63 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +32 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +198 -31
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +13 -5
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/lib/agent-cron.ts +130 -0
- package/extensions/lib/agent-loader.ts +15 -1
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +42 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +78 -0
- package/extensions/lib/context-stack.ts +81 -0
- package/extensions/lib/deferred.ts +219 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/local-command.ts +47 -13
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +174 -0
- package/extensions/lib/model-catalog/models-dev.json +18297 -0
- package/extensions/lib/model-catalog/openrouter.json +5252 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +308 -64
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +21 -2
- package/extensions/lib/plan-mode-channels.ts +20 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/process-tree.ts +131 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +364 -58
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +439 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +13 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +113 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +149 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +143 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +70 -4
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +87 -5
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +209 -71
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +151 -67
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1300 -381
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +6 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +156 -69
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +61 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +12 -7
- package/extensions/recap/index.ts +65 -60
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +421 -101
- package/extensions/skill/invoke.ts +128 -18
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +116 -23
- package/extensions/subagents/announce.ts +69 -0
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +76 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1493 -824
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +2 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/runner.ts +108 -80
- package/extensions/subagents/runs.ts +39 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +131 -47
- package/extensions/system-prompt/template.ts +24 -50
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +175 -13
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/announce.ts +16 -0
- package/extensions/tool-search/index.ts +189 -41
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +105 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +145 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +29 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +121 -39
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +18 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* System-reminder queue — the steering mechanism shared by all One Code
|
|
3
3
|
* extensions. Reminders are injected transiently into the outgoing request via
|
|
4
|
-
* pi's `context` event
|
|
4
|
+
* pi's `context` event. Context and sticky snapshots are stored as hidden custom
|
|
5
|
+
* entries by the owner, never as rendered transcript messages.
|
|
5
6
|
*
|
|
6
7
|
* Cross-extension contract: emit on the event bus channel
|
|
7
8
|
* `one-code:system-reminder` with `{ text, scope?, placement? }` to enqueue from
|
|
@@ -16,14 +17,18 @@
|
|
|
16
17
|
* FIRST user message, before the user's text, ordered by `order`, exactly
|
|
17
18
|
* Claude Code's first-message stack. Byte-stable, so the cached prefix holds.
|
|
18
19
|
* - `sticky-append` — SESSION STATE that switches on and off (auto mode, plan
|
|
19
|
-
* mode, a worktree session, ultracode). Appended to
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* mode, a worktree session, ultracode). Appended to EVERY user message
|
|
21
|
+
* stamped after the state switched on (`since`). Mid-turn it first rides the
|
|
22
|
+
* switching call's tool result (`toolCallId` → `tailPin`), or the first
|
|
23
|
+
* carrier after the switch, never the already-cached turn opener. That pin
|
|
24
|
+
* and every later user carrier keep identical bytes. Switching off CLOSES
|
|
25
|
+
* the lifetime (`until`) and freezes its actual user carriers (`userPins`),
|
|
26
|
+
* so queued input cannot acquire past state. It never removes blocks from
|
|
27
|
+
* history. Re-entry opens a separate lifetime. Closed lifetimes
|
|
28
|
+
* are persisted alongside open ones and dropped only when all their carriers
|
|
29
|
+
* leave the context (compaction/fork). They never migrate onto a compaction
|
|
30
|
+
* summary; retained messages still carry their historical blocks. Legacy
|
|
31
|
+
* `opener` anchors are honored on resume, but never created for new states.
|
|
27
32
|
* - `last-append` — ONE-SHOT STEERING about what just happened (a deferred tool
|
|
28
33
|
* miss, a file changed under the model, a mode change). Delivered where the
|
|
29
34
|
* model reads next, and then KEPT there so the message never changes again:
|
|
@@ -33,8 +38,8 @@
|
|
|
33
38
|
* request ever re-caches the result); a one-shot still pending when a request
|
|
34
39
|
* goes out is `pin`ned to the request's tail (the trailing tool result by call
|
|
35
40
|
* id, else the last user message by timestamp) and re-attached to that same
|
|
36
|
-
* message on every later request, byte-identical. Pins are
|
|
37
|
-
*
|
|
41
|
+
* message on every later request, byte-identical. Pins are persisted with
|
|
42
|
+
* sticky lifetimes so a resume also preserves the switch announcements.
|
|
38
43
|
* - `user-prepend` — LOCAL-COMMAND BREADCRUMBS (`/clear`, `/model`, a panel
|
|
39
44
|
* command): what Claude Code shows the model when the user ran a slash
|
|
40
45
|
* command. Bare text blocks placed BEFORE the user's text on the next user
|
|
@@ -82,11 +87,18 @@ export type PinAnchor = { kind: "toolResult"; toolCallId: string } | { kind: "us
|
|
|
82
87
|
|
|
83
88
|
/**
|
|
84
89
|
* `order` values for the `first-prepend` context stack, matching Claude Code's
|
|
85
|
-
* fixed sequence on the first user message:
|
|
86
|
-
*
|
|
87
|
-
*
|
|
90
|
+
* fixed sequence on the first user message: environment → model line → deferred
|
|
91
|
+
* tools → agent catalog → MCP instructions → skills → the instructions block →
|
|
92
|
+
* the context block → the date (just before the user's text). Gaps leave room
|
|
93
|
+
* for One Code-specific reminders (e.g. subagent models). On a model that takes
|
|
94
|
+
* a mid-conversation system message, everything below `claudeMd`, and the date,
|
|
95
|
+
* moves into that message instead (`movesToSystemRole`, lib/system-role.ts).
|
|
88
96
|
*/
|
|
89
97
|
export const CONTEXT_ORDER = {
|
|
98
|
+
environment: 1,
|
|
99
|
+
modelLine: 2,
|
|
100
|
+
/** One Code's note that a `-p`/json run ends with its turn (`lib/notifications.ts oneShotSessionNote`); not Claude Code's. */
|
|
101
|
+
oneShot: 3,
|
|
90
102
|
deferredTools: 10,
|
|
91
103
|
subagentModels: 20,
|
|
92
104
|
agents: 21,
|
|
@@ -94,17 +106,37 @@ export const CONTEXT_ORDER = {
|
|
|
94
106
|
delegation: 22,
|
|
95
107
|
mcp: 30,
|
|
96
108
|
skills: 40,
|
|
109
|
+
/** Claude Code's auto-mode note; only in the system message (`systemRoleOnly`). */
|
|
110
|
+
autoModeNote: 45,
|
|
111
|
+
/** The `<total_tokens>` line the first prompt carries (later prompts: lib/turn-budget-layout.ts). */
|
|
112
|
+
totalTokens: 46,
|
|
97
113
|
// CLAUDE.md-family (with AGENTS.md as a per-directory fallback when a directory
|
|
98
114
|
// has no CLAUDE.md) — CLAUDE.md > AGENTS.md.
|
|
99
115
|
claudeMd: 50,
|
|
100
|
-
// One Code's own instructions ride in their own block AFTER the
|
|
116
|
+
// One Code's own instructions ride in their own block AFTER the instructions
|
|
101
117
|
// block (higher `order` = closer to the user text = higher precedence), so
|
|
102
118
|
// ONECODE.md takes precedence over CLAUDE.md/AGENTS.md. Not part of CC.
|
|
103
|
-
oneCodeMd:
|
|
119
|
+
oneCodeMd: 52,
|
|
120
|
+
/** The user's email and the git snapshot. */
|
|
121
|
+
context: 54,
|
|
122
|
+
date: 56,
|
|
104
123
|
} as const;
|
|
105
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Whether a `first-prepend` block of this order leaves the first user message
|
|
127
|
+
* for the mid-conversation system message on a model that takes one: the
|
|
128
|
+
* session facts below the instructions block, and the date, which Claude Code
|
|
129
|
+
* puts last in that message. The instructions, One Code's own block and the
|
|
130
|
+
* context block stay in the user message.
|
|
131
|
+
*/
|
|
132
|
+
export function movesToSystemRole(order: number): boolean {
|
|
133
|
+
return order < CONTEXT_ORDER.claudeMd || order === CONTEXT_ORDER.date;
|
|
134
|
+
}
|
|
135
|
+
|
|
106
136
|
/** A drained reminder with everything the injector needs to place it. */
|
|
107
137
|
export interface ReminderEntry {
|
|
138
|
+
/** Present in session snapshots; not rendered into the request. */
|
|
139
|
+
key?: string;
|
|
108
140
|
text: string;
|
|
109
141
|
placement: ReminderPlacement;
|
|
110
142
|
order: number;
|
|
@@ -115,11 +147,28 @@ export interface ReminderEntry {
|
|
|
115
147
|
*/
|
|
116
148
|
suffix?: string;
|
|
117
149
|
/**
|
|
118
|
-
* `sticky-append` only: when the state switched on.
|
|
119
|
-
*
|
|
120
|
-
*
|
|
150
|
+
* `sticky-append` only: when the state switched on. Every user message
|
|
151
|
+
* stamped at or after it carries the block while open. Closing freezes the
|
|
152
|
+
* actual carriers in `userPins`; legacy snapshots use `until` as the end.
|
|
121
153
|
*/
|
|
122
154
|
since?: number;
|
|
155
|
+
/** `sticky-append` only: exclusive end of a closed lifetime; absent while active. */
|
|
156
|
+
until?: number;
|
|
157
|
+
/** Actual user carriers of the last request; frozen on close, so queued input cannot acquire old state. */
|
|
158
|
+
userPins?: number[];
|
|
159
|
+
/** Pending sticky activation belongs to this call, not another result in its parallel batch. */
|
|
160
|
+
toolCallId?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Legacy `sticky-append` anchor from older snapshots. Honored to keep their
|
|
163
|
+
* bytes stable on resume; new lifetimes use null, never an earlier opener.
|
|
164
|
+
*/
|
|
165
|
+
opener?: number | null;
|
|
166
|
+
/**
|
|
167
|
+
* `sticky-append` only: its first mid-turn tool result, or the tail of a
|
|
168
|
+
* request without any user carrier (overflow compaction). Once chosen it
|
|
169
|
+
* stays on that message, even after later user turns arrive or state closes.
|
|
170
|
+
*/
|
|
171
|
+
tailPin?: PinAnchor;
|
|
123
172
|
/** Set on a pinned one-shot: the exact message it rides on every request. */
|
|
124
173
|
pin?: PinAnchor;
|
|
125
174
|
/**
|
|
@@ -128,6 +177,17 @@ export interface ReminderEntry {
|
|
|
128
177
|
* result. Defaults to false.
|
|
129
178
|
*/
|
|
130
179
|
raw?: boolean;
|
|
180
|
+
/**
|
|
181
|
+
* `first-prepend` only: sent only inside the mid-conversation system
|
|
182
|
+
* message, never on the user message of a model without one (Claude Code's
|
|
183
|
+
* auto-mode note).
|
|
184
|
+
*/
|
|
185
|
+
systemRoleOnly?: boolean;
|
|
186
|
+
/**
|
|
187
|
+
* `sticky-append` only: never on the message that carries the context
|
|
188
|
+
* stack, which already says the same (the first prompt's `<total_tokens>`).
|
|
189
|
+
*/
|
|
190
|
+
skipStackCarrier?: boolean;
|
|
131
191
|
}
|
|
132
192
|
|
|
133
193
|
export interface ReminderPayload {
|
|
@@ -135,7 +195,7 @@ export interface ReminderPayload {
|
|
|
135
195
|
scope?: ReminderScope;
|
|
136
196
|
/** Key so a reminder can be replaced (next-turn) or replaced/removed (every-turn). */
|
|
137
197
|
key?: string;
|
|
138
|
-
/**
|
|
198
|
+
/** Close a sticky lifetime under `key`; remove other every-turn reminders. */
|
|
139
199
|
remove?: boolean;
|
|
140
200
|
/** Where in the message stack this reminder lands. Defaults to `last-append`. */
|
|
141
201
|
placement?: ReminderPlacement;
|
|
@@ -145,6 +205,10 @@ export interface ReminderPayload {
|
|
|
145
205
|
suffix?: string;
|
|
146
206
|
/** Emit the text bare, with no `<system-reminder>` frame. */
|
|
147
207
|
raw?: boolean;
|
|
208
|
+
/** `first-prepend` only: sent only inside the mid-conversation system message (see ReminderEntry). */
|
|
209
|
+
systemRoleOnly?: boolean;
|
|
210
|
+
/** `sticky-append` only: never on the message that carries the context stack (see ReminderEntry). */
|
|
211
|
+
skipStackCarrier?: boolean;
|
|
148
212
|
/** `user-prepend` only: skip when the same text is already pending (the shared caveat). */
|
|
149
213
|
once?: boolean;
|
|
150
214
|
/**
|
|
@@ -153,6 +217,13 @@ export interface ReminderPayload {
|
|
|
153
217
|
* brought back (the `<total_tokens>` line rides every user message in CC).
|
|
154
218
|
*/
|
|
155
219
|
since?: number;
|
|
220
|
+
/**
|
|
221
|
+
* The tool call whose result this reminder belongs to. For `last-append`,
|
|
222
|
+
* only that result's `tool_result` hook takes the one-shot. For a new sticky
|
|
223
|
+
* lifetime, that result is its first pin. Both avoid attaching a switch or
|
|
224
|
+
* countdown to another call in a parallel batch.
|
|
225
|
+
*/
|
|
226
|
+
toolCallId?: string;
|
|
156
227
|
}
|
|
157
228
|
|
|
158
229
|
interface StoredReminder extends ReminderEntry {
|
|
@@ -166,10 +237,14 @@ type EnqueueOptions = {
|
|
|
166
237
|
order?: number;
|
|
167
238
|
suffix?: string;
|
|
168
239
|
raw?: boolean;
|
|
240
|
+
systemRoleOnly?: boolean;
|
|
241
|
+
skipStackCarrier?: boolean;
|
|
169
242
|
/** Test seam / explicit anchor for `sticky-append`; defaults to now. */
|
|
170
243
|
since?: number;
|
|
171
244
|
/** `user-prepend` only: skip when the same text is already pending. */
|
|
172
245
|
once?: boolean;
|
|
246
|
+
/** The switching tool result, or the hook that takes a one-shot (see ReminderPayload). */
|
|
247
|
+
toolCallId?: string;
|
|
173
248
|
};
|
|
174
249
|
|
|
175
250
|
export class ReminderQueue {
|
|
@@ -177,15 +252,34 @@ export class ReminderQueue {
|
|
|
177
252
|
/** Delivered one-shots, each fixed to the message it first rode (see `pin`). */
|
|
178
253
|
private pinned: StoredReminder[] = [];
|
|
179
254
|
private everyTurn = new Map<string, StoredReminder>();
|
|
255
|
+
/** Open and closed lifetimes in original insertion order, independent of live keys. */
|
|
256
|
+
private sticky: StoredReminder[] = [];
|
|
180
257
|
private readonly now: () => number;
|
|
258
|
+
/** Only restored keys are protected; fresh sessions retain their existing emitter behavior. */
|
|
259
|
+
private restoredKeys = new Set<string>();
|
|
260
|
+
private pendingCapabilities = new Set<string>();
|
|
261
|
+
private changes = 0;
|
|
181
262
|
|
|
182
263
|
constructor(now: () => number = Date.now) {
|
|
183
264
|
this.now = now;
|
|
184
265
|
}
|
|
185
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Moves on every change to what the queue holds (a drain only when it
|
|
269
|
+
* re-anchored, pinned or dropped something), so the owner can skip
|
|
270
|
+
* serializing a snapshot that cannot have changed.
|
|
271
|
+
*/
|
|
272
|
+
get version(): number {
|
|
273
|
+
return this.changes;
|
|
274
|
+
}
|
|
275
|
+
|
|
186
276
|
enqueue(text: string, opts?: EnqueueOptions): void {
|
|
187
277
|
if (!text.trim()) return;
|
|
188
278
|
const placement = opts?.placement ?? "last-append";
|
|
279
|
+
if (placement === "first-prepend" && opts?.key) {
|
|
280
|
+
if (this.restoredKeys.has(opts.key)) return;
|
|
281
|
+
this.pendingCapabilities.delete(opts.key);
|
|
282
|
+
}
|
|
189
283
|
const entry: StoredReminder = {
|
|
190
284
|
text,
|
|
191
285
|
placement,
|
|
@@ -193,20 +287,30 @@ export class ReminderQueue {
|
|
|
193
287
|
suffix: opts?.suffix,
|
|
194
288
|
key: opts?.key,
|
|
195
289
|
raw: opts?.raw,
|
|
290
|
+
toolCallId: opts?.toolCallId,
|
|
196
291
|
};
|
|
292
|
+
if (opts?.systemRoleOnly) entry.systemRoleOnly = true;
|
|
293
|
+
if (opts?.skipStackCarrier) entry.skipStackCarrier = true;
|
|
294
|
+
const key = opts?.key ?? text;
|
|
295
|
+
const previous = this.everyTurn.get(key);
|
|
197
296
|
if (placement === "sticky-append") {
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
entry.since =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
this.now();
|
|
297
|
+
// Re-emitting an unchanged standing fact keeps both its anchors and its
|
|
298
|
+
// position among other blocks. A changed fact opens a new lifetime.
|
|
299
|
+
const same = previous?.placement === placement && previous.text === text &&
|
|
300
|
+
previous.suffix === entry.suffix && previous.raw === entry.raw &&
|
|
301
|
+
previous.skipStackCarrier === entry.skipStackCarrier && previous.order === entry.order ? previous : undefined;
|
|
302
|
+
entry.since = opts?.since ?? same?.since ?? this.now();
|
|
303
|
+
entry.userPins = [];
|
|
304
|
+
if (same && same.since === entry.since && opts?.scope === "every-turn") return;
|
|
207
305
|
}
|
|
306
|
+
this.changes++;
|
|
208
307
|
if (opts?.scope === "every-turn") {
|
|
209
|
-
this.
|
|
308
|
+
if (previous?.placement === "sticky-append") this.remove(key);
|
|
309
|
+
this.everyTurn.set(key, entry);
|
|
310
|
+
if (placement === "sticky-append") {
|
|
311
|
+
entry.key = key;
|
|
312
|
+
this.sticky.push(entry);
|
|
313
|
+
}
|
|
210
314
|
return;
|
|
211
315
|
}
|
|
212
316
|
// A breadcrumb run shares one caveat block (`once`): the same text pending
|
|
@@ -222,17 +326,132 @@ export class ReminderQueue {
|
|
|
222
326
|
}
|
|
223
327
|
|
|
224
328
|
remove(key: string): void {
|
|
225
|
-
this.
|
|
329
|
+
this.changes++;
|
|
330
|
+
if (!this.restoredKeys.has(key)) {
|
|
331
|
+
const entry = this.everyTurn.get(key);
|
|
332
|
+
if (entry?.placement === "sticky-append") entry.until = this.now();
|
|
333
|
+
this.everyTurn.delete(key);
|
|
334
|
+
}
|
|
335
|
+
this.pendingCapabilities.delete(key);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Seed before startup emitters. Earlier hooks (context-budget) may already have emitted. */
|
|
339
|
+
restore(stack: readonly ReminderEntry[], sticky: readonly ReminderEntry[], frozenKeys: ReadonlySet<string>, liveKeys: ReadonlySet<string> = new Set(), pinned: readonly ReminderEntry[] = []): void {
|
|
340
|
+
this.changes++;
|
|
341
|
+
const early = [...this.everyTurn.values()];
|
|
342
|
+
this.everyTurn.clear();
|
|
343
|
+
this.sticky = [];
|
|
344
|
+
this.pinned = structuredClone([...pinned]);
|
|
345
|
+
this.restoredKeys = new Set(frozenKeys);
|
|
346
|
+
this.pendingCapabilities = new Set(stack.flatMap((entry) => entry.key && liveKeys.has(entry.key) ? [entry.key] : []));
|
|
347
|
+
for (const source of [...stack, ...sticky]) {
|
|
348
|
+
const entry = structuredClone(source);
|
|
349
|
+
if (entry.placement === "sticky-append") this.sticky.push(entry);
|
|
350
|
+
if (entry.until === undefined) this.everyTurn.set(entry.key ?? entry.text, entry);
|
|
351
|
+
}
|
|
352
|
+
for (const entry of early) this.enqueue(entry.text, { ...entry, scope: "every-turn" });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* A session start with no snapshot (`/clear` after a resume): the previous
|
|
357
|
+
* session's resume locks must not keep the new session's emitters out.
|
|
358
|
+
*/
|
|
359
|
+
releaseRestore(): void {
|
|
360
|
+
this.restoredKeys.clear();
|
|
361
|
+
this.pendingCapabilities.clear();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** The first request has seen all startup/turn emitters. An absent capability is gone. */
|
|
365
|
+
finishRestore(): void {
|
|
366
|
+
if (this.pendingCapabilities.size > 0) this.changes++;
|
|
367
|
+
for (const key of this.pendingCapabilities) this.everyTurn.delete(key);
|
|
368
|
+
this.pendingCapabilities.clear();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** A compaction replaces these facts atomically, bypassing only their resume locks. */
|
|
372
|
+
replaceFirstPrepend(keys: readonly string[], entries: readonly ReminderEntry[]): void {
|
|
373
|
+
this.changes++;
|
|
374
|
+
const replaced = new Set(keys);
|
|
375
|
+
for (const key of replaced) this.everyTurn.delete(key);
|
|
376
|
+
this.nextTurn = this.nextTurn.filter((entry) => !entry.key || !replaced.has(entry.key));
|
|
377
|
+
for (const entry of entries) {
|
|
378
|
+
if (entry.key && replaced.has(entry.key) && entry.placement === "first-prepend") {
|
|
379
|
+
this.everyTurn.set(entry.key, structuredClone(entry));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* A `/tree` switch to a branch whose last snapshot is `snapshot`: its
|
|
386
|
+
* session facts (`factKeys`, an absent one included), its sticky lifetimes
|
|
387
|
+
* and its pins come back, so its messages keep the bytes they were sent
|
|
388
|
+
* with. Live state stays live: other first-prepend blocks keep their
|
|
389
|
+
* current text, and a lifetime open on the branch stays open only while its
|
|
390
|
+
* owner still holds it, else it closes on its recorded carriers. A live
|
|
391
|
+
* lifetime the branch shows closed goes on as a new one from now. What the
|
|
392
|
+
* branch left behind put here stays until a drain finds its carriers gone.
|
|
393
|
+
*/
|
|
394
|
+
restoreBranch(snapshot: { stack: readonly ReminderEntry[]; sticky: readonly ReminderEntry[]; pinned?: readonly ReminderEntry[] }, factKeys: readonly string[]): void {
|
|
395
|
+
this.replaceFirstPrepend(factKeys, snapshot.stack);
|
|
396
|
+
const same = (a: ReminderEntry, b: ReminderEntry) => a.key === b.key && a.text === b.text && a.since === b.since;
|
|
397
|
+
const restored: StoredReminder[] = structuredClone([...snapshot.sticky]);
|
|
398
|
+
const now = this.now();
|
|
399
|
+
for (const entry of restored) {
|
|
400
|
+
if (entry.until !== undefined) continue;
|
|
401
|
+
const live = this.sticky.find((current) => current.until === undefined && same(current, entry));
|
|
402
|
+
if (live?.key !== undefined && this.everyTurn.get(live.key) === live) this.everyTurn.set(live.key, entry);
|
|
403
|
+
else entry.until = now;
|
|
404
|
+
}
|
|
405
|
+
this.sticky = [...restored, ...this.sticky.filter((current) => !restored.some((entry) => same(entry, current)))];
|
|
406
|
+
// The branch's carriers keep their closed copy of a lifetime still live here.
|
|
407
|
+
for (const [key, live] of this.everyTurn) {
|
|
408
|
+
if (live.placement !== "sticky-append" || live.until !== undefined || this.sticky.includes(live)) continue;
|
|
409
|
+
const { toolCallId: _call, tailPin: _pin, opener: _opener, ...state } = live;
|
|
410
|
+
const reopened: StoredReminder = { ...state, key, since: now, userPins: [] };
|
|
411
|
+
this.everyTurn.set(key, reopened);
|
|
412
|
+
this.sticky.push(reopened);
|
|
413
|
+
}
|
|
414
|
+
const pinned: StoredReminder[] = structuredClone([...(snapshot.pinned ?? [])]);
|
|
415
|
+
const known = new Set(pinned.map((entry) => JSON.stringify(strip(entry))));
|
|
416
|
+
this.pinned = [...pinned, ...this.pinned.filter((entry) => !known.has(JSON.stringify(strip(entry))))];
|
|
417
|
+
this.changes++;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** A new snapshot supersedes undelivered notices about its old facts. Delivered pins stay history. */
|
|
421
|
+
cancelPending(keys: readonly string[]): void {
|
|
422
|
+
const cancelled = new Set(keys);
|
|
423
|
+
const before = this.nextTurn.length;
|
|
424
|
+
this.nextTurn = this.nextTurn.filter((entry) => !entry.key || !cancelled.has(entry.key));
|
|
425
|
+
if (this.nextTurn.length !== before) this.changes++;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Hidden session metadata, preserving keys and insertion order without changing drain's API. */
|
|
429
|
+
persistentEntries(placement: "first-prepend" | "sticky-append"): ReminderEntry[] {
|
|
430
|
+
return [
|
|
431
|
+
...(placement === "sticky-append" ? this.sticky.map((entry) => ({ ...strip(entry), key: entry.key })) :
|
|
432
|
+
[...this.everyTurn.entries()].filter(([, entry]) => entry.placement === placement).map(([key, entry]) => ({ ...strip(entry), key }))),
|
|
433
|
+
...this.nextTurn.filter((entry) => entry.placement === placement).map((entry) => ({ ...strip(entry), key: entry.key })),
|
|
434
|
+
];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Delivered steering is history too, especially the notice that closes a sticky state. */
|
|
438
|
+
persistentPins(): ReminderEntry[] {
|
|
439
|
+
return this.pinned.map(strip);
|
|
226
440
|
}
|
|
227
441
|
|
|
228
442
|
/**
|
|
229
443
|
* Take the pending `last-append` one-shots out of the queue — for the
|
|
230
|
-
* `tool_result` hook, which writes them into the stored result.
|
|
444
|
+
* `tool_result` hook, which writes them into the stored result. A one-shot
|
|
445
|
+
* bound to another call's result stays queued for that result's hook. Other
|
|
231
446
|
* placements stay queued for the next request.
|
|
232
447
|
*/
|
|
233
|
-
takeOneShots(): ReminderEntry[] {
|
|
234
|
-
const { matching, rest } = partition(
|
|
448
|
+
takeOneShots(toolCallId?: string): ReminderEntry[] {
|
|
449
|
+
const { matching, rest } = partition(
|
|
450
|
+
this.nextTurn,
|
|
451
|
+
(r) => r.placement === "last-append" && (r.toolCallId === undefined || r.toolCallId === toolCallId),
|
|
452
|
+
);
|
|
235
453
|
this.nextTurn = rest;
|
|
454
|
+
if (matching.length > 0) this.changes++;
|
|
236
455
|
return matching.map(strip);
|
|
237
456
|
}
|
|
238
457
|
|
|
@@ -244,10 +463,16 @@ export class ReminderQueue {
|
|
|
244
463
|
* on a request that ends in a user-like message (`openingUserAnchor`) —
|
|
245
464
|
* mid-turn, breadcrumbs stay queued.
|
|
246
465
|
*/
|
|
247
|
-
pin(anchor: PinAnchor, placement: "last-append" | "user-prepend" = "last-append"): void {
|
|
466
|
+
pin(anchor: PinAnchor, placement: "last-append" | "user-prepend" = "last-append", messages?: readonly AgentMessage[]): void {
|
|
248
467
|
const { matching, rest } = partition(this.nextTurn, (r) => r.placement === placement);
|
|
249
|
-
for (const entry of matching)
|
|
468
|
+
for (const entry of matching) {
|
|
469
|
+
// A one-shot bound to a call whose result hook already ran (a read's nested
|
|
470
|
+
// instructions) rides that call's result, not whichever result ended the batch.
|
|
471
|
+
const own = entry.toolCallId !== undefined && messages ? ownResultAnchor(messages, entry.toolCallId) : undefined;
|
|
472
|
+
this.pinned.push({ ...entry, pin: own ?? anchor });
|
|
473
|
+
}
|
|
250
474
|
this.nextTurn = rest;
|
|
475
|
+
if (matching.length > 0) this.changes++;
|
|
251
476
|
}
|
|
252
477
|
|
|
253
478
|
/**
|
|
@@ -263,17 +488,51 @@ export class ReminderQueue {
|
|
|
263
488
|
* did exactly that (CACHE-REVIEW-2026-09-04 M3).
|
|
264
489
|
*/
|
|
265
490
|
drain(messages: AgentMessage[]): ReminderEntry[] {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
491
|
+
const locate = pinLocator(messages);
|
|
492
|
+
const stackCarrier = messages.findIndex(isUserLike);
|
|
493
|
+
let changed = false;
|
|
494
|
+
for (const entry of this.sticky) {
|
|
495
|
+
if (entry.until !== undefined) continue;
|
|
496
|
+
if (entry.opener === undefined) {
|
|
497
|
+
entry.opener = null;
|
|
498
|
+
changed = true;
|
|
499
|
+
}
|
|
500
|
+
// Only live states may find a new carrier after compaction. A closed
|
|
501
|
+
// state's missing pin must never move to the summary or a later turn.
|
|
502
|
+
const tailPin = entry.tailPin;
|
|
503
|
+
if (entry.tailPin && locate(entry.tailPin) === -1) entry.tailPin = undefined;
|
|
504
|
+
if (entry.tailPin === undefined) entry.tailPin = firstStickyPin(messages, entry);
|
|
505
|
+
if (!samePin(tailPin, entry.tailPin)) changed = true;
|
|
506
|
+
// Timestamps alone cannot close a lifetime: a steer typed while it was
|
|
507
|
+
// active can remain queued until after the switch. Keep only messages
|
|
508
|
+
// that actually rode the block, including across a startup-time close.
|
|
509
|
+
const userPins = messages.flatMap((m, index) => carriesSticky(m, entry) &&
|
|
510
|
+
(!entry.skipStackCarrier || index !== stackCarrier) ? [m.timestamp] : []);
|
|
511
|
+
// The same array stays while its carriers do, so its lookup set (`carrierSet`) is reused.
|
|
512
|
+
if (!entry.userPins || entry.userPins.length !== userPins.length || entry.userPins.some((stamp, i) => stamp !== userPins[i])) {
|
|
513
|
+
entry.userPins = userPins;
|
|
514
|
+
changed = true;
|
|
515
|
+
}
|
|
516
|
+
if ((entry.tailPin || entry.userPins.length > 0) && entry.toolCallId !== undefined) {
|
|
517
|
+
delete entry.toolCallId;
|
|
518
|
+
changed = true;
|
|
519
|
+
}
|
|
269
520
|
}
|
|
521
|
+
const sticky = this.sticky.length;
|
|
522
|
+
this.sticky = this.sticky.filter((entry) => entry.until === undefined ||
|
|
523
|
+
messages.some((m) => carriesSticky(m, entry)) || (entry.tailPin !== undefined && locate(entry.tailPin) !== -1));
|
|
524
|
+
const pinned = this.pinned.length;
|
|
525
|
+
if (this.pinned.length > 0) this.pinned = this.pinned.filter((entry) => locate(entry.pin as PinAnchor) !== -1);
|
|
270
526
|
const { matching: held, rest: pending } = partition(this.nextTurn, (r) => r.placement === "user-prepend");
|
|
271
527
|
this.nextTurn = held;
|
|
272
|
-
|
|
528
|
+
if (changed || pending.length > 0 || this.sticky.length !== sticky || this.pinned.length !== pinned) this.changes++;
|
|
529
|
+
return [...[...this.everyTurn.values()].filter((entry) => entry.placement !== "sticky-append").map(strip),
|
|
530
|
+
...this.sticky.map(strip), ...this.pinned.map(strip), ...pending.map(strip)];
|
|
273
531
|
}
|
|
274
532
|
|
|
275
533
|
get size(): number {
|
|
276
|
-
return this.everyTurn.
|
|
534
|
+
return [...this.everyTurn.values()].filter((entry) => entry.placement !== "sticky-append").length +
|
|
535
|
+
this.sticky.length + this.nextTurn.length + this.pinned.length;
|
|
277
536
|
}
|
|
278
537
|
|
|
279
538
|
/** True when a one-shot of `placement` is waiting to be delivered (the owner decides where). */
|
|
@@ -298,7 +557,14 @@ function partition<T>(items: T[], test: (item: T) => boolean): { matching: T[];
|
|
|
298
557
|
function strip(r: StoredReminder): ReminderEntry {
|
|
299
558
|
const entry: ReminderEntry = { text: r.text, placement: r.placement, order: r.order, suffix: r.suffix };
|
|
300
559
|
if (r.raw) entry.raw = true;
|
|
560
|
+
if (r.systemRoleOnly) entry.systemRoleOnly = true;
|
|
561
|
+
if (r.skipStackCarrier) entry.skipStackCarrier = true;
|
|
301
562
|
if (r.since !== undefined) entry.since = r.since;
|
|
563
|
+
if (r.until !== undefined) entry.until = r.until;
|
|
564
|
+
if (r.userPins !== undefined) entry.userPins = [...r.userPins];
|
|
565
|
+
if (r.placement === "sticky-append" && r.toolCallId !== undefined) entry.toolCallId = r.toolCallId;
|
|
566
|
+
if (r.opener !== undefined) entry.opener = r.opener;
|
|
567
|
+
if (r.tailPin) entry.tailPin = r.tailPin;
|
|
302
568
|
if (r.pin !== undefined) entry.pin = r.pin;
|
|
303
569
|
return entry;
|
|
304
570
|
}
|
|
@@ -324,6 +590,49 @@ function pinLocator(messages: AgentMessage[]): (pin: PinAnchor) => number {
|
|
|
324
590
|
return (pin) => (pin.kind === "toolResult" ? byToolCall.get(pin.toolCallId) : byTimestamp.get(pin.timestamp)) ?? -1;
|
|
325
591
|
}
|
|
326
592
|
|
|
593
|
+
function samePin(a: PinAnchor | undefined, b: PinAnchor | undefined): boolean {
|
|
594
|
+
if (!a || !b) return a === b;
|
|
595
|
+
return a.kind === "toolResult" ? b.kind === "toolResult" && a.toolCallId === b.toolCallId : b.kind === "user" && a.timestamp === b.timestamp;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** Each carrier list's lookup set, built once per array: a check runs for every message against every lifetime. */
|
|
599
|
+
const carrierSets = new WeakMap<readonly number[], Set<number>>();
|
|
600
|
+
|
|
601
|
+
function carrierSet(userPins: readonly number[]): Set<number> {
|
|
602
|
+
let set = carrierSets.get(userPins);
|
|
603
|
+
if (!set) carrierSets.set(userPins, (set = new Set(userPins)));
|
|
604
|
+
return set;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** Closed lifetimes keep only delivered carriers; legacy snapshots retain their timestamp interval. */
|
|
608
|
+
function carriesSticky(message: AgentMessage, entry: ReminderEntry): boolean {
|
|
609
|
+
if (!isStickyCarrier(message)) return false;
|
|
610
|
+
const stamp = (message as { timestamp?: number }).timestamp ?? 0;
|
|
611
|
+
if (entry.until !== undefined && entry.userPins !== undefined) return carrierSet(entry.userPins).has(stamp);
|
|
612
|
+
return (stamp >= (entry.since ?? 0) && (entry.until === undefined || stamp < entry.until)) ||
|
|
613
|
+
(entry.opener !== null && entry.opener !== undefined && stamp === entry.opener);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/** First carrier after activation; never reach back into an earlier user turn. */
|
|
617
|
+
function firstStickyPin(messages: AgentMessage[], entry: ReminderEntry): PinAnchor | undefined {
|
|
618
|
+
if (entry.until !== undefined) return undefined;
|
|
619
|
+
if (entry.toolCallId !== undefined) return ownResultAnchor(messages, entry.toolCallId);
|
|
620
|
+
for (const message of messages) {
|
|
621
|
+
if (carriesSticky(message, entry)) return undefined;
|
|
622
|
+
if (message.role === "toolResult" && message.timestamp >= (entry.since ?? 0)) {
|
|
623
|
+
return { kind: "toolResult", toolCallId: message.toolCallId };
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// Overflow compaction may retain only an old reply/result. With no real
|
|
627
|
+
// user turn, a live state must still be visible and pinned to that tail.
|
|
628
|
+
return messages.some(isStickyCarrier) ? undefined : tailAnchor(messages);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/** `toolCallId`'s own result as an anchor, when this request holds it. */
|
|
632
|
+
function ownResultAnchor(messages: readonly AgentMessage[], toolCallId: string): PinAnchor | undefined {
|
|
633
|
+
return messages.some((m) => m.role === "toolResult" && m.toolCallId === toolCallId) ? { kind: "toolResult", toolCallId } : undefined;
|
|
634
|
+
}
|
|
635
|
+
|
|
327
636
|
/** The anchor a one-shot lands on for this request: the trailing tool result, else the last user-like message. */
|
|
328
637
|
export function tailAnchor(messages: AgentMessage[]): PinAnchor | undefined {
|
|
329
638
|
const last = messages[messages.length - 1] as (AgentMessage & { toolCallId?: string }) | undefined;
|
|
@@ -387,8 +696,13 @@ function toBlocks(content: string | ContentBlock[]): ContentBlock[] {
|
|
|
387
696
|
return typeof content === "string" ? [{ type: "text", text: content }] : [...content];
|
|
388
697
|
}
|
|
389
698
|
|
|
699
|
+
/** A reminder's text exactly as it rides a message: framed (unless raw), then its suffix. */
|
|
700
|
+
export function framedReminderText(entry: Pick<ReminderEntry, "text" | "raw" | "suffix">): string {
|
|
701
|
+
return (entry.raw ? entry.text : wrapReminder(entry.text)) + (entry.suffix ?? "");
|
|
702
|
+
}
|
|
703
|
+
|
|
390
704
|
function reminderBlock(entry: ReminderEntry): TextContent {
|
|
391
|
-
return { type: "text", text: (entry
|
|
705
|
+
return { type: "text", text: framedReminderText(entry) };
|
|
392
706
|
}
|
|
393
707
|
|
|
394
708
|
/**
|
|
@@ -473,28 +787,20 @@ export function injectReminders(messages: AgentMessage[], reminders: Array<strin
|
|
|
473
787
|
|
|
474
788
|
push(before, firstUserIndex === -1 ? tailIndex : firstUserIndex, firstPrepend.map(reminderBlock));
|
|
475
789
|
|
|
790
|
+
const locate = pinLocator(messages);
|
|
476
791
|
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
792
|
const carriers: number[] = [];
|
|
485
793
|
messages.forEach((m, index) => {
|
|
486
|
-
if (
|
|
487
|
-
const stamp = (m as { timestamp?: number }).timestamp ?? 0;
|
|
488
|
-
if (stamp >= since) carriers.push(index);
|
|
489
|
-
else opener = index;
|
|
794
|
+
if (carriesSticky(m, entry)) carriers.push(index);
|
|
490
795
|
});
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
796
|
+
const pin = entry.tailPin ?? firstStickyPin(messages, entry);
|
|
797
|
+
const pinned = pin ? locate(pin) : -1;
|
|
798
|
+
if (pinned !== -1 && !carriers.includes(pinned)) carriers.unshift(pinned);
|
|
799
|
+
if (entry.skipStackCarrier && carriers.includes(firstUserIndex)) {
|
|
800
|
+
for (const index of carriers.filter((index) => index !== firstUserIndex).sort((a, b) => a - b)) push(after, index, [reminderBlock(entry)]);
|
|
495
801
|
continue;
|
|
496
802
|
}
|
|
497
|
-
for (const index of carriers) push(after, index, [reminderBlock(entry)]);
|
|
803
|
+
for (const index of carriers.sort((a, b) => a - b)) push(after, index, [reminderBlock(entry)]);
|
|
498
804
|
}
|
|
499
805
|
|
|
500
806
|
push(after, tailIndex === -1 ? firstUserIndex : tailIndex, lastAppend.map(reminderBlock));
|