@bastani/atomic 0.9.8 → 0.9.9-alpha.1
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/CHANGELOG.md +28 -0
- package/dist/builtin/cursor/CHANGELOG.md +6 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/runs/background/async-execution-chain.ts +2 -0
- package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +7 -1
- package/dist/builtin/subagents/src/runs/background/async-execution-single.ts +2 -0
- package/dist/builtin/subagents/src/runs/background/async-execution-types.ts +8 -2
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-runner.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-sequential-step.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +2 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-resume.ts +2 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +2 -0
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +6 -0
- package/dist/builtin/subagents/src/shared/types-config.ts +4 -0
- package/dist/builtin/subagents/src/shared/types-depth.ts +29 -2
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +22 -0
- package/dist/builtin/workflows/README.md +17 -8
- package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -2
- package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +26 -9
- package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +1 -1
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +8 -3
- package/dist/builtin/workflows/builtin/ralph-runner.ts +2 -0
- package/dist/builtin/workflows/builtin/shared-prompts.ts +24 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/durable/backend.ts +53 -15
- package/dist/builtin/workflows/src/durable/completed-catalog.ts +245 -0
- package/dist/builtin/workflows/src/durable/completed-inspection.ts +213 -0
- package/dist/builtin/workflows/src/durable/dbos-backend.ts +120 -170
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +84 -8
- package/dist/builtin/workflows/src/durable/dbos-metadata.ts +98 -0
- package/dist/builtin/workflows/src/durable/dbos-tombstone.ts +27 -0
- package/dist/builtin/workflows/src/durable/factory.ts +1 -1
- package/dist/builtin/workflows/src/durable/file-backend.ts +247 -241
- package/dist/builtin/workflows/src/durable/file-lock.ts +153 -0
- package/dist/builtin/workflows/src/durable/file-state.ts +104 -0
- package/dist/builtin/workflows/src/durable/format-version.ts +12 -0
- package/dist/builtin/workflows/src/durable/index.ts +12 -0
- package/dist/builtin/workflows/src/durable/resume-catalog.ts +16 -27
- package/dist/builtin/workflows/src/durable/resume-eligibility.ts +20 -0
- package/dist/builtin/workflows/src/durable/resume-runtime.ts +29 -29
- package/dist/builtin/workflows/src/durable/scoped-backend.ts +20 -3
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +29 -8
- package/dist/builtin/workflows/src/durable/types.ts +3 -0
- package/dist/builtin/workflows/src/engine/primitives/task.ts +1 -0
- package/dist/builtin/workflows/src/engine/run.ts +14 -14
- package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +39 -9
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +10 -0
- package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +133 -0
- package/dist/builtin/workflows/src/extension/runtime.ts +15 -40
- package/dist/builtin/workflows/src/extension/wiring.ts +9 -6
- package/dist/builtin/workflows/src/extension/workflow-durable-resume-command.ts +226 -0
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +1 -0
- package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +66 -112
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +5 -5
- package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +16 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +50 -43
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-output.ts +91 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-direct.ts +95 -46
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +3 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-task-prompts.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +3 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +36 -7
- package/dist/builtin/workflows/src/runs/shared/worktree-cache-lifecycle.ts +25 -0
- package/dist/builtin/workflows/src/runs/shared/worktree-cwd.ts +103 -0
- package/dist/builtin/workflows/src/runs/shared/worktree-generation.ts +57 -0
- package/dist/builtin/workflows/src/runs/shared/worktree-git.ts +134 -8
- package/dist/builtin/workflows/src/runs/shared/worktree.ts +2 -0
- package/dist/builtin/workflows/src/shared/resumable-workflow-notices.ts +43 -0
- package/dist/builtin/workflows/src/shared/timing.ts +4 -0
- package/dist/builtin/workflows/src/shared/types.ts +5 -3
- package/dist/builtin/workflows/src/tui/graph-view-constants.ts +1 -0
- package/dist/builtin/workflows/src/tui/graph-view-input.ts +24 -14
- package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +43 -13
- package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-auto-compaction.js +5 -7
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts +6 -34
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +95 -233
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-message-queue.d.ts +0 -4
- package/dist/core/agent-session-message-queue.d.ts.map +1 -1
- package/dist/core/agent-session-message-queue.js +1 -5
- package/dist/core/agent-session-message-queue.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +5 -6
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +2 -12
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +1 -2
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction-boundary.d.ts +9 -0
- package/dist/core/compaction/compaction-boundary.d.ts.map +1 -0
- package/dist/core/compaction/compaction-boundary.js +115 -0
- package/dist/core/compaction/compaction-boundary.js.map +1 -0
- package/dist/core/compaction/compaction-parameters.d.ts +5 -0
- package/dist/core/compaction/compaction-parameters.d.ts.map +1 -0
- package/dist/core/compaction/compaction-parameters.js +27 -0
- package/dist/core/compaction/compaction-parameters.js.map +1 -0
- package/dist/core/compaction/compaction-runner.d.ts +14 -0
- package/dist/core/compaction/compaction-runner.d.ts.map +1 -0
- package/dist/core/compaction/compaction-runner.js +29 -0
- package/dist/core/compaction/compaction-runner.js.map +1 -0
- package/dist/core/compaction/compaction-types.d.ts +79 -0
- package/dist/core/compaction/compaction-types.d.ts.map +1 -0
- package/dist/core/compaction/compaction-types.js +6 -0
- package/dist/core/compaction/compaction-types.js.map +1 -0
- package/dist/core/compaction/deleted-ranges.d.ts +6 -0
- package/dist/core/compaction/deleted-ranges.d.ts.map +1 -0
- package/dist/core/compaction/deleted-ranges.js +134 -0
- package/dist/core/compaction/deleted-ranges.js.map +1 -0
- package/dist/core/compaction/index.d.ts +7 -1
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +7 -1
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/range-planner.d.ts +21 -0
- package/dist/core/compaction/range-planner.d.ts.map +1 -0
- package/dist/core/compaction/range-planner.js +145 -0
- package/dist/core/compaction/range-planner.js.map +1 -0
- package/dist/core/compaction/transcript-serialization.d.ts +11 -0
- package/dist/core/compaction/transcript-serialization.d.ts.map +1 -0
- package/dist/core/compaction/transcript-serialization.js +104 -0
- package/dist/core/compaction/transcript-serialization.js.map +1 -0
- package/dist/core/extensions/context-types.d.ts +2 -2
- package/dist/core/extensions/context-types.d.ts.map +1 -1
- package/dist/core/extensions/context-types.js.map +1 -1
- package/dist/core/extensions/event-results.d.ts +2 -2
- package/dist/core/extensions/event-results.d.ts.map +1 -1
- package/dist/core/extensions/event-results.js.map +1 -1
- package/dist/core/extensions/session-events.d.ts +7 -7
- package/dist/core/extensions/session-events.d.ts.map +1 -1
- package/dist/core/extensions/session-events.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/messages.d.ts +4 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +11 -0
- package/dist/core/messages.js.map +1 -1
- package/dist/core/provider-context-usage.d.ts.map +1 -1
- package/dist/core/provider-context-usage.js +1 -2
- package/dist/core/provider-context-usage.js.map +1 -1
- package/dist/core/session-manager-archive.d.ts +2 -1
- package/dist/core/session-manager-archive.d.ts.map +1 -1
- package/dist/core/session-manager-archive.js +2 -2
- package/dist/core/session-manager-archive.js.map +1 -1
- package/dist/core/session-manager-classification.d.ts +9 -0
- package/dist/core/session-manager-classification.d.ts.map +1 -0
- package/dist/core/session-manager-classification.js +37 -0
- package/dist/core/session-manager-classification.js.map +1 -0
- package/dist/core/session-manager-core.d.ts +4 -4
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +13 -8
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/core/session-manager-entries.d.ts +4 -3
- package/dist/core/session-manager-entries.d.ts.map +1 -1
- package/dist/core/session-manager-entries.js +12 -10
- package/dist/core/session-manager-entries.js.map +1 -1
- package/dist/core/session-manager-history.d.ts +6 -23
- package/dist/core/session-manager-history.d.ts.map +1 -1
- package/dist/core/session-manager-history.js +23 -256
- package/dist/core/session-manager-history.js.map +1 -1
- package/dist/core/session-manager-list.d.ts.map +1 -1
- package/dist/core/session-manager-list.js +3 -3
- package/dist/core/session-manager-list.js.map +1 -1
- package/dist/core/session-manager-storage.d.ts +1 -1
- package/dist/core/session-manager-storage.d.ts.map +1 -1
- package/dist/core/session-manager-storage.js +3 -2
- package/dist/core/session-manager-storage.js.map +1 -1
- package/dist/core/session-manager-types.d.ts +14 -15
- package/dist/core/session-manager-types.d.ts.map +1 -1
- package/dist/core/session-manager-types.js.map +1 -1
- package/dist/core/session-manager.d.ts +3 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main-session.d.ts +1 -0
- package/dist/main-session.d.ts.map +1 -1
- package/dist/main-session.js +7 -0
- package/dist/main-session.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +4 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js +12 -22
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.js +5 -11
- package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.js +3 -0
- package/dist/modes/interactive/components/chat-session-host-rendering.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.d.ts +1 -0
- package/dist/modes/interactive/components/chat-session-host-state.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.js +1 -0
- package/dist/modes/interactive/components/chat-session-host-state.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-utils.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-utils.js +1 -1
- package/dist/modes/interactive/components/chat-session-host-utils.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +1 -0
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/components/compaction-boundary-message.d.ts +20 -0
- package/dist/modes/interactive/components/compaction-boundary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/compaction-boundary-message.js +48 -0
- package/dist/modes/interactive/components/compaction-boundary-message.js.map +1 -0
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-list.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-list.js +3 -0
- package/dist/modes/interactive/components/session-selector-list.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +3 -47
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +2 -2
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +5 -3
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +21 -12
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +3 -5
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +1 -6
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.js +0 -4
- package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +2 -11
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/compaction.md +97 -772
- package/docs/extensions.md +25 -29
- package/docs/json.md +2 -2
- package/docs/rpc.md +32 -24
- package/docs/sdk.md +3 -3
- package/docs/session-format.md +49 -30
- package/docs/sessions.md +5 -3
- package/docs/settings.md +14 -4
- package/docs/usage.md +1 -1
- package/docs/workflows.md +30 -15
- package/examples/extensions/custom-compaction.ts +14 -58
- package/examples/extensions/handoff.ts +2 -3
- package/npm-shrinkwrap.json +23 -23
- package/package.json +2 -2
- package/dist/core/compaction/context-assistant-turns.d.ts +0 -42
- package/dist/core/compaction/context-assistant-turns.d.ts.map +0 -1
- package/dist/core/compaction/context-assistant-turns.js +0 -87
- package/dist/core/compaction/context-assistant-turns.js.map +0 -1
- package/dist/core/compaction/context-compaction-critical.d.ts +0 -15
- package/dist/core/compaction/context-compaction-critical.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-critical.js +0 -57
- package/dist/core/compaction/context-compaction-critical.js.map +0 -1
- package/dist/core/compaction/context-compaction-eviction-alternates.d.ts +0 -18
- package/dist/core/compaction/context-compaction-eviction-alternates.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-eviction-alternates.js +0 -186
- package/dist/core/compaction/context-compaction-eviction-alternates.js.map +0 -1
- package/dist/core/compaction/context-compaction-eviction.d.ts +0 -12
- package/dist/core/compaction/context-compaction-eviction.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-eviction.js +0 -222
- package/dist/core/compaction/context-compaction-eviction.js.map +0 -1
- package/dist/core/compaction/context-compaction-metrics.d.ts +0 -28
- package/dist/core/compaction/context-compaction-metrics.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-metrics.js +0 -107
- package/dist/core/compaction/context-compaction-metrics.js.map +0 -1
- package/dist/core/compaction/context-compaction-prompt.d.ts +0 -9
- package/dist/core/compaction/context-compaction-prompt.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-prompt.js +0 -163
- package/dist/core/compaction/context-compaction-prompt.js.map +0 -1
- package/dist/core/compaction/context-compaction-runner.d.ts +0 -11
- package/dist/core/compaction/context-compaction-runner.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-runner.js +0 -273
- package/dist/core/compaction/context-compaction-runner.js.map +0 -1
- package/dist/core/compaction/context-compaction-strategy.d.ts +0 -5
- package/dist/core/compaction/context-compaction-strategy.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-strategy.js +0 -27
- package/dist/core/compaction/context-compaction-strategy.js.map +0 -1
- package/dist/core/compaction/context-compaction-types.d.ts +0 -75
- package/dist/core/compaction/context-compaction-types.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction-types.js +0 -6
- package/dist/core/compaction/context-compaction-types.js.map +0 -1
- package/dist/core/compaction/context-compaction.d.ts +0 -10
- package/dist/core/compaction/context-compaction.d.ts.map +0 -1
- package/dist/core/compaction/context-compaction.js +0 -8
- package/dist/core/compaction/context-compaction.js.map +0 -1
- package/dist/core/compaction/context-deletion-application.d.ts +0 -14
- package/dist/core/compaction/context-deletion-application.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-application.js +0 -261
- package/dist/core/compaction/context-deletion-application.js.map +0 -1
- package/dist/core/compaction/context-deletion-store.d.ts +0 -78
- package/dist/core/compaction/context-deletion-store.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-store.js +0 -162
- package/dist/core/compaction/context-deletion-store.js.map +0 -1
- package/dist/core/compaction/context-deletion-targets.d.ts +0 -43
- package/dist/core/compaction/context-deletion-targets.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-targets.js +0 -292
- package/dist/core/compaction/context-deletion-targets.js.map +0 -1
- package/dist/core/compaction/context-deletion-tool-definitions.d.ts +0 -193
- package/dist/core/compaction/context-deletion-tool-definitions.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-tool-definitions.js +0 -98
- package/dist/core/compaction/context-deletion-tool-definitions.js.map +0 -1
- package/dist/core/compaction/context-deletion-tool-helpers.d.ts +0 -19
- package/dist/core/compaction/context-deletion-tool-helpers.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-tool-helpers.js +0 -210
- package/dist/core/compaction/context-deletion-tool-helpers.js.map +0 -1
- package/dist/core/compaction/context-deletion-tools.d.ts +0 -4
- package/dist/core/compaction/context-deletion-tools.d.ts.map +0 -1
- package/dist/core/compaction/context-deletion-tools.js +0 -403
- package/dist/core/compaction/context-deletion-tools.js.map +0 -1
- package/dist/core/compaction/context-transcript-analysis.d.ts +0 -11
- package/dist/core/compaction/context-transcript-analysis.d.ts.map +0 -1
- package/dist/core/compaction/context-transcript-analysis.js +0 -228
- package/dist/core/compaction/context-transcript-analysis.js.map +0 -1
- package/dist/core/session-manager-tool-dependencies.d.ts +0 -10
- package/dist/core/session-manager-tool-dependencies.d.ts.map +0 -1
- package/dist/core/session-manager-tool-dependencies.js +0 -133
- package/dist/core/session-manager-tool-dependencies.js.map +0 -1
- package/dist/modes/interactive/components/context-compaction-summary-message.d.ts +0 -17
- package/dist/modes/interactive/components/context-compaction-summary-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/context-compaction-summary-message.js +0 -83
- package/dist/modes/interactive/components/context-compaction-summary-message.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-summarization.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/branch-summarization.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAuB,MAAM,8BAA8B,CAAC;AAIpF,OAAO,EAEN,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAIN,KAAK,cAAc,EAInB,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,mBAAmB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACpC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IACjC,mEAAmE;IACnE,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,gDAAgD;IAChD,OAAO,EAAE,cAAc,CAAC;IACxB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACpC,mDAAmD;IACnD,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,4BAA4B;IAC5C,qCAAqC;IACrC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oCAAoC;IACpC,MAAM,EAAE,WAAW,CAAC;IACpB,qDAAqD;IACrD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wFAAwF;IACxF,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4GAA4G;IAC5G,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACpB;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,sBAAsB,EAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,QAAQ,EAAE,MAAM,GACd,oBAAoB,CAkCtB;AA6CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,iBAAiB,CAqDxG;AAwCD;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,4BAA4B,GACnC,OAAO,CAAC,mBAAmB,CAAC,CA4F9B","sourcesContent":["/**\n * Branch summarization for tree navigation.\n *\n * When navigating to a different point in the session tree, this generates\n * a summary of the branch being left so context isn't lost.\n */\n\nimport type { AgentMessage, StreamFn } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model, SimpleStreamOptions } from \"@earendil-works/pi-ai/compat\";\nimport { completeSimple } from \"@earendil-works/pi-ai/compat\";\nimport { formatCopilotProviderError } from \"../copilot-errors.ts\";\nimport { convertToLlm, createBranchSummaryMessage, createCustomMessage } from \"../messages.ts\";\nimport {\n\tbuildContextDeletionFilteredPath,\n\ttype ReadonlySessionManager,\n\ttype SessionEntry,\n} from \"../session-manager.ts\";\nimport { estimateTokens } from \"./compaction.ts\";\nimport {\n\tcomputeFileLists,\n\tcreateFileOps,\n\textractFileOpsFromMessage,\n\ttype FileOperations,\n\tformatFileOperations,\n\tSUMMARIZATION_SYSTEM_PROMPT,\n\tserializeConversation,\n} from \"./utils.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BranchSummaryResult {\n\tsummary?: string;\n\treadFiles?: string[];\n\tmodifiedFiles?: string[];\n\taborted?: boolean;\n\terror?: string;\n}\n\n/** Details stored in BranchSummaryEntry.details for file tracking */\nexport interface BranchSummaryDetails {\n\treadFiles: string[];\n\tmodifiedFiles: string[];\n}\n\nexport type { FileOperations } from \"./utils.ts\";\n\nexport interface BranchPreparation {\n\t/** Messages extracted for summarization, in chronological order */\n\tmessages: AgentMessage[];\n\t/** File operations extracted from tool calls */\n\tfileOps: FileOperations;\n\t/** Total estimated tokens in messages */\n\ttotalTokens: number;\n}\n\nexport interface CollectEntriesResult {\n\t/** Entries to summarize, in chronological order */\n\tentries: SessionEntry[];\n\t/** Common ancestor between old and new position, if any */\n\tcommonAncestorId: string | null;\n}\n\nexport interface GenerateBranchSummaryOptions {\n\t/** Model to use for summarization */\n\tmodel: Model<Api>;\n\t/** API key for the model */\n\tapiKey: string;\n\t/** Request headers for the model */\n\theaders?: Record<string, string>;\n\t/** Abort signal for cancellation */\n\tsignal: AbortSignal;\n\t/** Optional custom instructions for summarization */\n\tcustomInstructions?: string;\n\t/** If true, customInstructions replaces the default prompt instead of being appended */\n\treplaceInstructions?: boolean;\n\t/** Tokens reserved for prompt + LLM response (default 16384) */\n\treserveTokens?: number;\n\t/** Optional session stream function. Used to preserve SDK request behavior without mutating agent state. */\n\tstreamFn?: StreamFn;\n}\n\n// ============================================================================\n// Entry Collection\n// ============================================================================\n\n/**\n * Collect entries that should be summarized when navigating from one position to another.\n *\n * Walks from oldLeafId back to the common ancestor with targetId, collecting entries\n * along the way. Does NOT stop at legacy compaction entries, but those entries are\n * inert and are not fed into branch summarization prompts.\n *\n * @param session - Session manager (read-only access)\n * @param oldLeafId - Current position (where we're navigating from)\n * @param targetId - Target position (where we're navigating to)\n * @returns Entries to summarize and the common ancestor\n */\nexport function collectEntriesForBranchSummary(\n\tsession: ReadonlySessionManager,\n\toldLeafId: string | null,\n\ttargetId: string,\n): CollectEntriesResult {\n\t// If no old position, nothing to summarize\n\tif (!oldLeafId) {\n\t\treturn { entries: [], commonAncestorId: null };\n\t}\n\n\t// Find common ancestor (deepest node that's on both paths)\n\tconst oldPath = new Set(session.getBranch(oldLeafId).map((e) => e.id));\n\tconst targetPath = session.getBranch(targetId);\n\n\t// targetPath is root-first, so iterate backwards to find deepest common ancestor\n\tlet commonAncestorId: string | null = null;\n\tfor (let i = targetPath.length - 1; i >= 0; i--) {\n\t\tif (oldPath.has(targetPath[i].id)) {\n\t\t\tcommonAncestorId = targetPath[i].id;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Collect entries from old leaf back to common ancestor\n\tconst entries: SessionEntry[] = [];\n\tlet current: string | null = oldLeafId;\n\n\twhile (current && current !== commonAncestorId) {\n\t\tconst entry = session.getEntry(current);\n\t\tif (!entry) break;\n\t\tentries.push(entry);\n\t\tcurrent = entry.parentId;\n\t}\n\n\t// Reverse to get chronological order\n\tentries.reverse();\n\n\treturn { entries, commonAncestorId };\n}\n\n// ============================================================================\n// Entry to Message Conversion\n// ============================================================================\n\n/**\n * Extract AgentMessage from a session entry.\n * Similar to getMessageFromEntry in compaction.ts, with legacy compaction entries kept inert.\n */\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tswitch (entry.type) {\n\t\tcase \"message\":\n\t\t\t// Skip tool results - context is in assistant's tool call\n\t\t\tif (entry.message.role === \"toolResult\") return undefined;\n\t\t\treturn entry.message;\n\n\t\tcase \"custom_message\":\n\t\t\treturn createCustomMessage(\n\t\t\t\tentry.customType,\n\t\t\t\tentry.content,\n\t\t\t\tentry.display,\n\t\t\t\tentry.details,\n\t\t\t\tentry.timestamp,\n\t\t\t\tentry.excludeFromContext,\n\t\t\t);\n\n\t\tcase \"branch_summary\":\n\t\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\n\t\tcase \"compaction\":\n\t\t\treturn undefined;\n\n\t\t// These don't contribute to conversation content\n\t\tcase \"thinking_level_change\":\n\t\tcase \"context_window_change\":\n\t\tcase \"model_change\":\n\t\tcase \"custom\":\n\t\tcase \"label\":\n\t\tcase \"session_info\":\n\t\tcase \"context_compaction\":\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Prepare entries for summarization with token budget.\n *\n * Walks entries from NEWEST to OLDEST, adding messages until we hit the token budget.\n * This ensures we keep the most recent context when the branch is too long.\n *\n * Also collects file operations from:\n * - Tool calls in assistant messages\n * - Existing branch_summary entries' details (for cumulative tracking)\n *\n * @param entries - Entries in chronological order\n * @param tokenBudget - Maximum tokens to include (0 = no limit)\n */\nexport function prepareBranchEntries(entries: SessionEntry[], tokenBudget: number = 0): BranchPreparation {\n\tconst messages: AgentMessage[] = [];\n\tconst fileOps = createFileOps();\n\tconst filteredEntries = buildContextDeletionFilteredPath(entries);\n\tlet totalTokens = 0;\n\n\t// First pass: collect file ops from ALL entries (even if they don't fit in token budget)\n\t// This ensures we capture cumulative file tracking from nested branch summaries\n\t// Only extract from pi-generated summaries (fromHook !== true), not extension-generated ones\n\tfor (const entry of filteredEntries) {\n\t\tif (entry.type === \"branch_summary\" && !entry.fromHook && entry.details) {\n\t\t\tconst details = entry.details as BranchSummaryDetails;\n\t\t\tif (Array.isArray(details.readFiles)) {\n\t\t\t\tfor (const f of details.readFiles) fileOps.read.add(f);\n\t\t\t}\n\t\t\tif (Array.isArray(details.modifiedFiles)) {\n\t\t\t\t// Modified files go into both edited and written for proper deduplication\n\t\t\t\tfor (const f of details.modifiedFiles) {\n\t\t\t\t\tfileOps.edited.add(f);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: walk from newest to oldest, adding messages until token budget\n\tfor (let i = filteredEntries.length - 1; i >= 0; i--) {\n\t\tconst entry = filteredEntries[i];\n\t\tconst message = getMessageFromEntry(entry);\n\t\tif (!message) continue;\n\n\t\t// Extract file ops from assistant messages (tool calls)\n\t\textractFileOpsFromMessage(message, fileOps);\n\n\t\tconst tokens = estimateTokens(message);\n\n\t\t// Check budget before adding\n\t\tif (tokenBudget > 0 && totalTokens + tokens > tokenBudget) {\n\t\t\t// If this is a branch summary entry, try to fit it anyway as it's important context\n\t\t\tif (entry.type === \"branch_summary\") {\n\t\t\t\tif (totalTokens < tokenBudget * 0.9) {\n\t\t\t\t\tmessages.unshift(message);\n\t\t\t\t\ttotalTokens += tokens;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Stop - we've hit the budget\n\t\t\tbreak;\n\t\t}\n\n\t\tmessages.unshift(message);\n\t\ttotalTokens += tokens;\n\t}\n\n\treturn { messages, fileOps, totalTokens };\n}\n\n// ============================================================================\n// Summary Generation\n// ============================================================================\n\nconst BRANCH_SUMMARY_PREAMBLE = `The user explored a different conversation branch before returning here.\nSummary of that exploration:\n\n`;\n\nconst BRANCH_SUMMARY_PROMPT = `Create a structured summary of this conversation branch for context when returning later.\n\nUse this EXACT format:\n\n## Goal\n[What was the user trying to accomplish in this branch?]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Work that was started but not finished]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [What should happen next to continue this work]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\n/**\n * Generate a summary of abandoned branch entries.\n *\n * @param entries - Session entries to summarize (chronological order)\n * @param options - Generation options\n */\nexport async function generateBranchSummary(\n\tentries: SessionEntry[],\n\toptions: GenerateBranchSummaryOptions,\n): Promise<BranchSummaryResult> {\n\tconst {\n\t\tmodel,\n\t\tapiKey,\n\t\theaders,\n\t\tsignal,\n\t\tcustomInstructions,\n\t\treplaceInstructions,\n\t\treserveTokens = 16384,\n\t\tstreamFn,\n\t} = options;\n\n\t// Token budget = context window minus reserved space for prompt + response\n\tconst contextWindow = model.contextWindow || 128000;\n\tconst tokenBudget = contextWindow - reserveTokens;\n\n\tconst { messages, fileOps } = prepareBranchEntries(entries, tokenBudget);\n\n\tif (messages.length === 0) {\n\t\treturn { summary: \"No content to summarize\" };\n\t}\n\n\t// Transform to LLM-compatible messages, then serialize to text\n\t// Serialization prevents the model from treating it as a conversation to continue\n\tconst llmMessages = convertToLlm(messages);\n\tconst conversationText = serializeConversation(llmMessages);\n\n\t// Build prompt\n\tlet instructions: string;\n\tif (replaceInstructions && customInstructions) {\n\t\tinstructions = customInstructions;\n\t} else if (customInstructions) {\n\t\tinstructions = `${BRANCH_SUMMARY_PROMPT}\\n\\nAdditional focus: ${customInstructions}`;\n\t} else {\n\t\tinstructions = BRANCH_SUMMARY_PROMPT;\n\t}\n\tconst promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n${instructions}`;\n\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\t// Call LLM for summarization. Prefer the session stream function so SDK\n\t// request behavior (timeouts, retries, attribution headers) stays consistent\n\t// without running through agent state/events.\n\tconst context = { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages };\n\tconst requestOptions: SimpleStreamOptions = { apiKey, headers, signal, maxTokens: 2048 };\n\tconst response = await (async () => {\n\t\ttry {\n\t\t\treturn streamFn\n\t\t\t\t? await (await streamFn(model, context, requestOptions)).result()\n\t\t\t\t: await completeSimple(model, context, requestOptions);\n\t\t} catch (error) {\n\t\t\tif (signal.aborted) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\" as const,\n\t\t\t\terrorMessage: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t})();\n\n\t// Check if aborted or errored\n\tif (!response || response.stopReason === \"aborted\") {\n\t\treturn { aborted: true };\n\t}\n\tif (response.stopReason === \"error\") {\n\t\treturn { error: formatCopilotProviderError(model.provider, response.errorMessage || \"Summarization failed\") };\n\t}\n\n\tlet summary = response.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\"\\n\");\n\n\t// Prepend preamble to provide context about the branch summary\n\tsummary = BRANCH_SUMMARY_PREAMBLE + summary;\n\n\t// Compute file lists and append to summary\n\tconst { readFiles, modifiedFiles } = computeFileLists(fileOps);\n\tsummary += formatFileOperations(readFiles, modifiedFiles);\n\n\treturn {\n\t\tsummary: summary || \"No summary generated\",\n\t\treadFiles,\n\t\tmodifiedFiles,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"branch-summarization.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/branch-summarization.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAuB,MAAM,8BAA8B,CAAC;AAIpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAElF,OAAO,EAIN,KAAK,cAAc,EAInB,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,mBAAmB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACpC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IACjC,mEAAmE;IACnE,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,gDAAgD;IAChD,OAAO,EAAE,cAAc,CAAC;IACxB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACpC,mDAAmD;IACnD,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,4BAA4B;IAC5C,qCAAqC;IACrC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oCAAoC;IACpC,MAAM,EAAE,WAAW,CAAC;IACpB,qDAAqD;IACrD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wFAAwF;IACxF,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4GAA4G;IAC5G,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACpB;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,sBAAsB,EAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,QAAQ,EAAE,MAAM,GACd,oBAAoB,CAkCtB;AA6CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,iBAAiB,CAqDxG;AAwCD;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,4BAA4B,GACnC,OAAO,CAAC,mBAAmB,CAAC,CA4F9B","sourcesContent":["/**\n * Branch summarization for tree navigation.\n *\n * When navigating to a different point in the session tree, this generates\n * a summary of the branch being left so context isn't lost.\n */\n\nimport type { AgentMessage, StreamFn } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model, SimpleStreamOptions } from \"@earendil-works/pi-ai/compat\";\nimport { completeSimple } from \"@earendil-works/pi-ai/compat\";\nimport { formatCopilotProviderError } from \"../copilot-errors.ts\";\nimport { convertToLlm, createBranchSummaryMessage, createCustomMessage } from \"../messages.ts\";\nimport type { ReadonlySessionManager, SessionEntry } from \"../session-manager.ts\";\nimport { estimateTokens } from \"./compaction.ts\";\nimport {\n\tcomputeFileLists,\n\tcreateFileOps,\n\textractFileOpsFromMessage,\n\ttype FileOperations,\n\tformatFileOperations,\n\tSUMMARIZATION_SYSTEM_PROMPT,\n\tserializeConversation,\n} from \"./utils.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BranchSummaryResult {\n\tsummary?: string;\n\treadFiles?: string[];\n\tmodifiedFiles?: string[];\n\taborted?: boolean;\n\terror?: string;\n}\n\n/** Details stored in BranchSummaryEntry.details for file tracking */\nexport interface BranchSummaryDetails {\n\treadFiles: string[];\n\tmodifiedFiles: string[];\n}\n\nexport type { FileOperations } from \"./utils.ts\";\n\nexport interface BranchPreparation {\n\t/** Messages extracted for summarization, in chronological order */\n\tmessages: AgentMessage[];\n\t/** File operations extracted from tool calls */\n\tfileOps: FileOperations;\n\t/** Total estimated tokens in messages */\n\ttotalTokens: number;\n}\n\nexport interface CollectEntriesResult {\n\t/** Entries to summarize, in chronological order */\n\tentries: SessionEntry[];\n\t/** Common ancestor between old and new position, if any */\n\tcommonAncestorId: string | null;\n}\n\nexport interface GenerateBranchSummaryOptions {\n\t/** Model to use for summarization */\n\tmodel: Model<Api>;\n\t/** API key for the model */\n\tapiKey: string;\n\t/** Request headers for the model */\n\theaders?: Record<string, string>;\n\t/** Abort signal for cancellation */\n\tsignal: AbortSignal;\n\t/** Optional custom instructions for summarization */\n\tcustomInstructions?: string;\n\t/** If true, customInstructions replaces the default prompt instead of being appended */\n\treplaceInstructions?: boolean;\n\t/** Tokens reserved for prompt + LLM response (default 16384) */\n\treserveTokens?: number;\n\t/** Optional session stream function. Used to preserve SDK request behavior without mutating agent state. */\n\tstreamFn?: StreamFn;\n}\n\n// ============================================================================\n// Entry Collection\n// ============================================================================\n\n/**\n * Collect entries that should be summarized when navigating from one position to another.\n *\n * Walks from oldLeafId back to the common ancestor with targetId, collecting entries\n * along the way. Does NOT stop at legacy compaction entries, but those entries are\n * inert and are not fed into branch summarization prompts.\n *\n * @param session - Session manager (read-only access)\n * @param oldLeafId - Current position (where we're navigating from)\n * @param targetId - Target position (where we're navigating to)\n * @returns Entries to summarize and the common ancestor\n */\nexport function collectEntriesForBranchSummary(\n\tsession: ReadonlySessionManager,\n\toldLeafId: string | null,\n\ttargetId: string,\n): CollectEntriesResult {\n\t// If no old position, nothing to summarize\n\tif (!oldLeafId) {\n\t\treturn { entries: [], commonAncestorId: null };\n\t}\n\n\t// Find common ancestor (deepest node that's on both paths)\n\tconst oldPath = new Set(session.getBranch(oldLeafId).map((e) => e.id));\n\tconst targetPath = session.getBranch(targetId);\n\n\t// targetPath is root-first, so iterate backwards to find deepest common ancestor\n\tlet commonAncestorId: string | null = null;\n\tfor (let i = targetPath.length - 1; i >= 0; i--) {\n\t\tif (oldPath.has(targetPath[i].id)) {\n\t\t\tcommonAncestorId = targetPath[i].id;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Collect entries from old leaf back to common ancestor\n\tconst entries: SessionEntry[] = [];\n\tlet current: string | null = oldLeafId;\n\n\twhile (current && current !== commonAncestorId) {\n\t\tconst entry = session.getEntry(current);\n\t\tif (!entry) break;\n\t\tentries.push(entry);\n\t\tcurrent = entry.parentId;\n\t}\n\n\t// Reverse to get chronological order\n\tentries.reverse();\n\n\treturn { entries, commonAncestorId };\n}\n\n// ============================================================================\n// Entry to Message Conversion\n// ============================================================================\n\n/**\n * Extract AgentMessage from a session entry.\n * Similar to getMessageFromEntry in compaction.ts, with legacy compaction entries kept inert.\n */\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tswitch (entry.type) {\n\t\tcase \"message\":\n\t\t\t// Skip tool results - context is in assistant's tool call\n\t\t\tif (entry.message.role === \"toolResult\") return undefined;\n\t\t\treturn entry.message;\n\n\t\tcase \"custom_message\":\n\t\t\treturn createCustomMessage(\n\t\t\t\tentry.customType,\n\t\t\t\tentry.content,\n\t\t\t\tentry.display,\n\t\t\t\tentry.details,\n\t\t\t\tentry.timestamp,\n\t\t\t\tentry.excludeFromContext,\n\t\t\t);\n\n\t\tcase \"branch_summary\":\n\t\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\n\t\tcase \"compaction\":\n\t\t\treturn undefined;\n\n\t\t// These don't contribute to conversation content\n\t\tcase \"thinking_level_change\":\n\t\tcase \"context_window_change\":\n\t\tcase \"model_change\":\n\t\tcase \"custom\":\n\t\tcase \"label\":\n\t\tcase \"session_info\":\n\t\tcase \"context_compaction\":\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Prepare entries for summarization with token budget.\n *\n * Walks entries from NEWEST to OLDEST, adding messages until we hit the token budget.\n * This ensures we keep the most recent context when the branch is too long.\n *\n * Also collects file operations from:\n * - Tool calls in assistant messages\n * - Existing branch_summary entries' details (for cumulative tracking)\n *\n * @param entries - Entries in chronological order\n * @param tokenBudget - Maximum tokens to include (0 = no limit)\n */\nexport function prepareBranchEntries(entries: SessionEntry[], tokenBudget: number = 0): BranchPreparation {\n\tconst messages: AgentMessage[] = [];\n\tconst fileOps = createFileOps();\n\tconst filteredEntries = entries;\n\tlet totalTokens = 0;\n\n\t// First pass: collect file ops from ALL entries (even if they don't fit in token budget)\n\t// This ensures we capture cumulative file tracking from nested branch summaries\n\t// Only extract from pi-generated summaries (fromHook !== true), not extension-generated ones\n\tfor (const entry of filteredEntries) {\n\t\tif (entry.type === \"branch_summary\" && !entry.fromHook && entry.details) {\n\t\t\tconst details = entry.details as BranchSummaryDetails;\n\t\t\tif (Array.isArray(details.readFiles)) {\n\t\t\t\tfor (const f of details.readFiles) fileOps.read.add(f);\n\t\t\t}\n\t\t\tif (Array.isArray(details.modifiedFiles)) {\n\t\t\t\t// Modified files go into both edited and written for proper deduplication\n\t\t\t\tfor (const f of details.modifiedFiles) {\n\t\t\t\t\tfileOps.edited.add(f);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: walk from newest to oldest, adding messages until token budget\n\tfor (let i = filteredEntries.length - 1; i >= 0; i--) {\n\t\tconst entry = filteredEntries[i];\n\t\tconst message = getMessageFromEntry(entry);\n\t\tif (!message) continue;\n\n\t\t// Extract file ops from assistant messages (tool calls)\n\t\textractFileOpsFromMessage(message, fileOps);\n\n\t\tconst tokens = estimateTokens(message);\n\n\t\t// Check budget before adding\n\t\tif (tokenBudget > 0 && totalTokens + tokens > tokenBudget) {\n\t\t\t// If this is a branch summary entry, try to fit it anyway as it's important context\n\t\t\tif (entry.type === \"branch_summary\") {\n\t\t\t\tif (totalTokens < tokenBudget * 0.9) {\n\t\t\t\t\tmessages.unshift(message);\n\t\t\t\t\ttotalTokens += tokens;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Stop - we've hit the budget\n\t\t\tbreak;\n\t\t}\n\n\t\tmessages.unshift(message);\n\t\ttotalTokens += tokens;\n\t}\n\n\treturn { messages, fileOps, totalTokens };\n}\n\n// ============================================================================\n// Summary Generation\n// ============================================================================\n\nconst BRANCH_SUMMARY_PREAMBLE = `The user explored a different conversation branch before returning here.\nSummary of that exploration:\n\n`;\n\nconst BRANCH_SUMMARY_PROMPT = `Create a structured summary of this conversation branch for context when returning later.\n\nUse this EXACT format:\n\n## Goal\n[What was the user trying to accomplish in this branch?]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Work that was started but not finished]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [What should happen next to continue this work]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\n/**\n * Generate a summary of abandoned branch entries.\n *\n * @param entries - Session entries to summarize (chronological order)\n * @param options - Generation options\n */\nexport async function generateBranchSummary(\n\tentries: SessionEntry[],\n\toptions: GenerateBranchSummaryOptions,\n): Promise<BranchSummaryResult> {\n\tconst {\n\t\tmodel,\n\t\tapiKey,\n\t\theaders,\n\t\tsignal,\n\t\tcustomInstructions,\n\t\treplaceInstructions,\n\t\treserveTokens = 16384,\n\t\tstreamFn,\n\t} = options;\n\n\t// Token budget = context window minus reserved space for prompt + response\n\tconst contextWindow = model.contextWindow || 128000;\n\tconst tokenBudget = contextWindow - reserveTokens;\n\n\tconst { messages, fileOps } = prepareBranchEntries(entries, tokenBudget);\n\n\tif (messages.length === 0) {\n\t\treturn { summary: \"No content to summarize\" };\n\t}\n\n\t// Transform to LLM-compatible messages, then serialize to text\n\t// Serialization prevents the model from treating it as a conversation to continue\n\tconst llmMessages = convertToLlm(messages);\n\tconst conversationText = serializeConversation(llmMessages);\n\n\t// Build prompt\n\tlet instructions: string;\n\tif (replaceInstructions && customInstructions) {\n\t\tinstructions = customInstructions;\n\t} else if (customInstructions) {\n\t\tinstructions = `${BRANCH_SUMMARY_PROMPT}\\n\\nAdditional focus: ${customInstructions}`;\n\t} else {\n\t\tinstructions = BRANCH_SUMMARY_PROMPT;\n\t}\n\tconst promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n${instructions}`;\n\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\t// Call LLM for summarization. Prefer the session stream function so SDK\n\t// request behavior (timeouts, retries, attribution headers) stays consistent\n\t// without running through agent state/events.\n\tconst context = { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages };\n\tconst requestOptions: SimpleStreamOptions = { apiKey, headers, signal, maxTokens: 2048 };\n\tconst response = await (async () => {\n\t\ttry {\n\t\t\treturn streamFn\n\t\t\t\t? await (await streamFn(model, context, requestOptions)).result()\n\t\t\t\t: await completeSimple(model, context, requestOptions);\n\t\t} catch (error) {\n\t\t\tif (signal.aborted) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\" as const,\n\t\t\t\terrorMessage: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t})();\n\n\t// Check if aborted or errored\n\tif (!response || response.stopReason === \"aborted\") {\n\t\treturn { aborted: true };\n\t}\n\tif (response.stopReason === \"error\") {\n\t\treturn { error: formatCopilotProviderError(model.provider, response.errorMessage || \"Summarization failed\") };\n\t}\n\n\tlet summary = response.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\"\\n\");\n\n\t// Prepend preamble to provide context about the branch summary\n\tsummary = BRANCH_SUMMARY_PREAMBLE + summary;\n\n\t// Compute file lists and append to summary\n\tconst { readFiles, modifiedFiles } = computeFileLists(fileOps);\n\tsummary += formatFileOperations(readFiles, modifiedFiles);\n\n\treturn {\n\t\tsummary: summary || \"No summary generated\",\n\t\treadFiles,\n\t\tmodifiedFiles,\n\t};\n}\n"]}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
8
8
|
import { formatCopilotProviderError } from "../copilot-errors.js";
|
|
9
9
|
import { convertToLlm, createBranchSummaryMessage, createCustomMessage } from "../messages.js";
|
|
10
|
-
import { buildContextDeletionFilteredPath, } from "../session-manager.js";
|
|
11
10
|
import { estimateTokens } from "./compaction.js";
|
|
12
11
|
import { computeFileLists, createFileOps, extractFileOpsFromMessage, formatFileOperations, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
|
|
13
12
|
// ============================================================================
|
|
@@ -102,7 +101,7 @@ function getMessageFromEntry(entry) {
|
|
|
102
101
|
export function prepareBranchEntries(entries, tokenBudget = 0) {
|
|
103
102
|
const messages = [];
|
|
104
103
|
const fileOps = createFileOps();
|
|
105
|
-
const filteredEntries =
|
|
104
|
+
const filteredEntries = entries;
|
|
106
105
|
let totalTokens = 0;
|
|
107
106
|
// First pass: collect file ops from ALL entries (even if they don't fit in token budget)
|
|
108
107
|
// This ensures we capture cumulative file tracking from nested branch summaries
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-summarization.js","sourceRoot":"","sources":["../../../src/core/compaction/branch-summarization.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Branch summarization for tree navigation.\n *\n * When navigating to a different point in the session tree, this generates\n * a summary of the branch being left so context isn't lost.\n */\n\nimport type { AgentMessage, StreamFn } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model, SimpleStreamOptions } from \"@earendil-works/pi-ai/compat\";\nimport { completeSimple } from \"@earendil-works/pi-ai/compat\";\nimport { formatCopilotProviderError } from \"../copilot-errors.ts\";\nimport { convertToLlm, createBranchSummaryMessage, createCustomMessage } from \"../messages.ts\";\nimport {\n\tbuildContextDeletionFilteredPath,\n\ttype ReadonlySessionManager,\n\ttype SessionEntry,\n} from \"../session-manager.ts\";\nimport { estimateTokens } from \"./compaction.ts\";\nimport {\n\tcomputeFileLists,\n\tcreateFileOps,\n\textractFileOpsFromMessage,\n\ttype FileOperations,\n\tformatFileOperations,\n\tSUMMARIZATION_SYSTEM_PROMPT,\n\tserializeConversation,\n} from \"./utils.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BranchSummaryResult {\n\tsummary?: string;\n\treadFiles?: string[];\n\tmodifiedFiles?: string[];\n\taborted?: boolean;\n\terror?: string;\n}\n\n/** Details stored in BranchSummaryEntry.details for file tracking */\nexport interface BranchSummaryDetails {\n\treadFiles: string[];\n\tmodifiedFiles: string[];\n}\n\nexport type { FileOperations } from \"./utils.ts\";\n\nexport interface BranchPreparation {\n\t/** Messages extracted for summarization, in chronological order */\n\tmessages: AgentMessage[];\n\t/** File operations extracted from tool calls */\n\tfileOps: FileOperations;\n\t/** Total estimated tokens in messages */\n\ttotalTokens: number;\n}\n\nexport interface CollectEntriesResult {\n\t/** Entries to summarize, in chronological order */\n\tentries: SessionEntry[];\n\t/** Common ancestor between old and new position, if any */\n\tcommonAncestorId: string | null;\n}\n\nexport interface GenerateBranchSummaryOptions {\n\t/** Model to use for summarization */\n\tmodel: Model<Api>;\n\t/** API key for the model */\n\tapiKey: string;\n\t/** Request headers for the model */\n\theaders?: Record<string, string>;\n\t/** Abort signal for cancellation */\n\tsignal: AbortSignal;\n\t/** Optional custom instructions for summarization */\n\tcustomInstructions?: string;\n\t/** If true, customInstructions replaces the default prompt instead of being appended */\n\treplaceInstructions?: boolean;\n\t/** Tokens reserved for prompt + LLM response (default 16384) */\n\treserveTokens?: number;\n\t/** Optional session stream function. Used to preserve SDK request behavior without mutating agent state. */\n\tstreamFn?: StreamFn;\n}\n\n// ============================================================================\n// Entry Collection\n// ============================================================================\n\n/**\n * Collect entries that should be summarized when navigating from one position to another.\n *\n * Walks from oldLeafId back to the common ancestor with targetId, collecting entries\n * along the way. Does NOT stop at legacy compaction entries, but those entries are\n * inert and are not fed into branch summarization prompts.\n *\n * @param session - Session manager (read-only access)\n * @param oldLeafId - Current position (where we're navigating from)\n * @param targetId - Target position (where we're navigating to)\n * @returns Entries to summarize and the common ancestor\n */\nexport function collectEntriesForBranchSummary(\n\tsession: ReadonlySessionManager,\n\toldLeafId: string | null,\n\ttargetId: string,\n): CollectEntriesResult {\n\t// If no old position, nothing to summarize\n\tif (!oldLeafId) {\n\t\treturn { entries: [], commonAncestorId: null };\n\t}\n\n\t// Find common ancestor (deepest node that's on both paths)\n\tconst oldPath = new Set(session.getBranch(oldLeafId).map((e) => e.id));\n\tconst targetPath = session.getBranch(targetId);\n\n\t// targetPath is root-first, so iterate backwards to find deepest common ancestor\n\tlet commonAncestorId: string | null = null;\n\tfor (let i = targetPath.length - 1; i >= 0; i--) {\n\t\tif (oldPath.has(targetPath[i].id)) {\n\t\t\tcommonAncestorId = targetPath[i].id;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Collect entries from old leaf back to common ancestor\n\tconst entries: SessionEntry[] = [];\n\tlet current: string | null = oldLeafId;\n\n\twhile (current && current !== commonAncestorId) {\n\t\tconst entry = session.getEntry(current);\n\t\tif (!entry) break;\n\t\tentries.push(entry);\n\t\tcurrent = entry.parentId;\n\t}\n\n\t// Reverse to get chronological order\n\tentries.reverse();\n\n\treturn { entries, commonAncestorId };\n}\n\n// ============================================================================\n// Entry to Message Conversion\n// ============================================================================\n\n/**\n * Extract AgentMessage from a session entry.\n * Similar to getMessageFromEntry in compaction.ts, with legacy compaction entries kept inert.\n */\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tswitch (entry.type) {\n\t\tcase \"message\":\n\t\t\t// Skip tool results - context is in assistant's tool call\n\t\t\tif (entry.message.role === \"toolResult\") return undefined;\n\t\t\treturn entry.message;\n\n\t\tcase \"custom_message\":\n\t\t\treturn createCustomMessage(\n\t\t\t\tentry.customType,\n\t\t\t\tentry.content,\n\t\t\t\tentry.display,\n\t\t\t\tentry.details,\n\t\t\t\tentry.timestamp,\n\t\t\t\tentry.excludeFromContext,\n\t\t\t);\n\n\t\tcase \"branch_summary\":\n\t\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\n\t\tcase \"compaction\":\n\t\t\treturn undefined;\n\n\t\t// These don't contribute to conversation content\n\t\tcase \"thinking_level_change\":\n\t\tcase \"context_window_change\":\n\t\tcase \"model_change\":\n\t\tcase \"custom\":\n\t\tcase \"label\":\n\t\tcase \"session_info\":\n\t\tcase \"context_compaction\":\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Prepare entries for summarization with token budget.\n *\n * Walks entries from NEWEST to OLDEST, adding messages until we hit the token budget.\n * This ensures we keep the most recent context when the branch is too long.\n *\n * Also collects file operations from:\n * - Tool calls in assistant messages\n * - Existing branch_summary entries' details (for cumulative tracking)\n *\n * @param entries - Entries in chronological order\n * @param tokenBudget - Maximum tokens to include (0 = no limit)\n */\nexport function prepareBranchEntries(entries: SessionEntry[], tokenBudget: number = 0): BranchPreparation {\n\tconst messages: AgentMessage[] = [];\n\tconst fileOps = createFileOps();\n\tconst filteredEntries = buildContextDeletionFilteredPath(entries);\n\tlet totalTokens = 0;\n\n\t// First pass: collect file ops from ALL entries (even if they don't fit in token budget)\n\t// This ensures we capture cumulative file tracking from nested branch summaries\n\t// Only extract from pi-generated summaries (fromHook !== true), not extension-generated ones\n\tfor (const entry of filteredEntries) {\n\t\tif (entry.type === \"branch_summary\" && !entry.fromHook && entry.details) {\n\t\t\tconst details = entry.details as BranchSummaryDetails;\n\t\t\tif (Array.isArray(details.readFiles)) {\n\t\t\t\tfor (const f of details.readFiles) fileOps.read.add(f);\n\t\t\t}\n\t\t\tif (Array.isArray(details.modifiedFiles)) {\n\t\t\t\t// Modified files go into both edited and written for proper deduplication\n\t\t\t\tfor (const f of details.modifiedFiles) {\n\t\t\t\t\tfileOps.edited.add(f);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: walk from newest to oldest, adding messages until token budget\n\tfor (let i = filteredEntries.length - 1; i >= 0; i--) {\n\t\tconst entry = filteredEntries[i];\n\t\tconst message = getMessageFromEntry(entry);\n\t\tif (!message) continue;\n\n\t\t// Extract file ops from assistant messages (tool calls)\n\t\textractFileOpsFromMessage(message, fileOps);\n\n\t\tconst tokens = estimateTokens(message);\n\n\t\t// Check budget before adding\n\t\tif (tokenBudget > 0 && totalTokens + tokens > tokenBudget) {\n\t\t\t// If this is a branch summary entry, try to fit it anyway as it's important context\n\t\t\tif (entry.type === \"branch_summary\") {\n\t\t\t\tif (totalTokens < tokenBudget * 0.9) {\n\t\t\t\t\tmessages.unshift(message);\n\t\t\t\t\ttotalTokens += tokens;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Stop - we've hit the budget\n\t\t\tbreak;\n\t\t}\n\n\t\tmessages.unshift(message);\n\t\ttotalTokens += tokens;\n\t}\n\n\treturn { messages, fileOps, totalTokens };\n}\n\n// ============================================================================\n// Summary Generation\n// ============================================================================\n\nconst BRANCH_SUMMARY_PREAMBLE = `The user explored a different conversation branch before returning here.\nSummary of that exploration:\n\n`;\n\nconst BRANCH_SUMMARY_PROMPT = `Create a structured summary of this conversation branch for context when returning later.\n\nUse this EXACT format:\n\n## Goal\n[What was the user trying to accomplish in this branch?]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Work that was started but not finished]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [What should happen next to continue this work]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\n/**\n * Generate a summary of abandoned branch entries.\n *\n * @param entries - Session entries to summarize (chronological order)\n * @param options - Generation options\n */\nexport async function generateBranchSummary(\n\tentries: SessionEntry[],\n\toptions: GenerateBranchSummaryOptions,\n): Promise<BranchSummaryResult> {\n\tconst {\n\t\tmodel,\n\t\tapiKey,\n\t\theaders,\n\t\tsignal,\n\t\tcustomInstructions,\n\t\treplaceInstructions,\n\t\treserveTokens = 16384,\n\t\tstreamFn,\n\t} = options;\n\n\t// Token budget = context window minus reserved space for prompt + response\n\tconst contextWindow = model.contextWindow || 128000;\n\tconst tokenBudget = contextWindow - reserveTokens;\n\n\tconst { messages, fileOps } = prepareBranchEntries(entries, tokenBudget);\n\n\tif (messages.length === 0) {\n\t\treturn { summary: \"No content to summarize\" };\n\t}\n\n\t// Transform to LLM-compatible messages, then serialize to text\n\t// Serialization prevents the model from treating it as a conversation to continue\n\tconst llmMessages = convertToLlm(messages);\n\tconst conversationText = serializeConversation(llmMessages);\n\n\t// Build prompt\n\tlet instructions: string;\n\tif (replaceInstructions && customInstructions) {\n\t\tinstructions = customInstructions;\n\t} else if (customInstructions) {\n\t\tinstructions = `${BRANCH_SUMMARY_PROMPT}\\n\\nAdditional focus: ${customInstructions}`;\n\t} else {\n\t\tinstructions = BRANCH_SUMMARY_PROMPT;\n\t}\n\tconst promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n${instructions}`;\n\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\t// Call LLM for summarization. Prefer the session stream function so SDK\n\t// request behavior (timeouts, retries, attribution headers) stays consistent\n\t// without running through agent state/events.\n\tconst context = { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages };\n\tconst requestOptions: SimpleStreamOptions = { apiKey, headers, signal, maxTokens: 2048 };\n\tconst response = await (async () => {\n\t\ttry {\n\t\t\treturn streamFn\n\t\t\t\t? await (await streamFn(model, context, requestOptions)).result()\n\t\t\t\t: await completeSimple(model, context, requestOptions);\n\t\t} catch (error) {\n\t\t\tif (signal.aborted) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\" as const,\n\t\t\t\terrorMessage: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t})();\n\n\t// Check if aborted or errored\n\tif (!response || response.stopReason === \"aborted\") {\n\t\treturn { aborted: true };\n\t}\n\tif (response.stopReason === \"error\") {\n\t\treturn { error: formatCopilotProviderError(model.provider, response.errorMessage || \"Summarization failed\") };\n\t}\n\n\tlet summary = response.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\"\\n\");\n\n\t// Prepend preamble to provide context about the branch summary\n\tsummary = BRANCH_SUMMARY_PREAMBLE + summary;\n\n\t// Compute file lists and append to summary\n\tconst { readFiles, modifiedFiles } = computeFileLists(fileOps);\n\tsummary += formatFileOperations(readFiles, modifiedFiles);\n\n\treturn {\n\t\tsummary: summary || \"No summary generated\",\n\t\treadFiles,\n\t\tmodifiedFiles,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"branch-summarization.js","sourceRoot":"","sources":["../../../src/core/compaction/branch-summarization.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Branch summarization for tree navigation.\n *\n * When navigating to a different point in the session tree, this generates\n * a summary of the branch being left so context isn't lost.\n */\n\nimport type { AgentMessage, StreamFn } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model, SimpleStreamOptions } from \"@earendil-works/pi-ai/compat\";\nimport { completeSimple } from \"@earendil-works/pi-ai/compat\";\nimport { formatCopilotProviderError } from \"../copilot-errors.ts\";\nimport { convertToLlm, createBranchSummaryMessage, createCustomMessage } from \"../messages.ts\";\nimport type { ReadonlySessionManager, SessionEntry } from \"../session-manager.ts\";\nimport { estimateTokens } from \"./compaction.ts\";\nimport {\n\tcomputeFileLists,\n\tcreateFileOps,\n\textractFileOpsFromMessage,\n\ttype FileOperations,\n\tformatFileOperations,\n\tSUMMARIZATION_SYSTEM_PROMPT,\n\tserializeConversation,\n} from \"./utils.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BranchSummaryResult {\n\tsummary?: string;\n\treadFiles?: string[];\n\tmodifiedFiles?: string[];\n\taborted?: boolean;\n\terror?: string;\n}\n\n/** Details stored in BranchSummaryEntry.details for file tracking */\nexport interface BranchSummaryDetails {\n\treadFiles: string[];\n\tmodifiedFiles: string[];\n}\n\nexport type { FileOperations } from \"./utils.ts\";\n\nexport interface BranchPreparation {\n\t/** Messages extracted for summarization, in chronological order */\n\tmessages: AgentMessage[];\n\t/** File operations extracted from tool calls */\n\tfileOps: FileOperations;\n\t/** Total estimated tokens in messages */\n\ttotalTokens: number;\n}\n\nexport interface CollectEntriesResult {\n\t/** Entries to summarize, in chronological order */\n\tentries: SessionEntry[];\n\t/** Common ancestor between old and new position, if any */\n\tcommonAncestorId: string | null;\n}\n\nexport interface GenerateBranchSummaryOptions {\n\t/** Model to use for summarization */\n\tmodel: Model<Api>;\n\t/** API key for the model */\n\tapiKey: string;\n\t/** Request headers for the model */\n\theaders?: Record<string, string>;\n\t/** Abort signal for cancellation */\n\tsignal: AbortSignal;\n\t/** Optional custom instructions for summarization */\n\tcustomInstructions?: string;\n\t/** If true, customInstructions replaces the default prompt instead of being appended */\n\treplaceInstructions?: boolean;\n\t/** Tokens reserved for prompt + LLM response (default 16384) */\n\treserveTokens?: number;\n\t/** Optional session stream function. Used to preserve SDK request behavior without mutating agent state. */\n\tstreamFn?: StreamFn;\n}\n\n// ============================================================================\n// Entry Collection\n// ============================================================================\n\n/**\n * Collect entries that should be summarized when navigating from one position to another.\n *\n * Walks from oldLeafId back to the common ancestor with targetId, collecting entries\n * along the way. Does NOT stop at legacy compaction entries, but those entries are\n * inert and are not fed into branch summarization prompts.\n *\n * @param session - Session manager (read-only access)\n * @param oldLeafId - Current position (where we're navigating from)\n * @param targetId - Target position (where we're navigating to)\n * @returns Entries to summarize and the common ancestor\n */\nexport function collectEntriesForBranchSummary(\n\tsession: ReadonlySessionManager,\n\toldLeafId: string | null,\n\ttargetId: string,\n): CollectEntriesResult {\n\t// If no old position, nothing to summarize\n\tif (!oldLeafId) {\n\t\treturn { entries: [], commonAncestorId: null };\n\t}\n\n\t// Find common ancestor (deepest node that's on both paths)\n\tconst oldPath = new Set(session.getBranch(oldLeafId).map((e) => e.id));\n\tconst targetPath = session.getBranch(targetId);\n\n\t// targetPath is root-first, so iterate backwards to find deepest common ancestor\n\tlet commonAncestorId: string | null = null;\n\tfor (let i = targetPath.length - 1; i >= 0; i--) {\n\t\tif (oldPath.has(targetPath[i].id)) {\n\t\t\tcommonAncestorId = targetPath[i].id;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Collect entries from old leaf back to common ancestor\n\tconst entries: SessionEntry[] = [];\n\tlet current: string | null = oldLeafId;\n\n\twhile (current && current !== commonAncestorId) {\n\t\tconst entry = session.getEntry(current);\n\t\tif (!entry) break;\n\t\tentries.push(entry);\n\t\tcurrent = entry.parentId;\n\t}\n\n\t// Reverse to get chronological order\n\tentries.reverse();\n\n\treturn { entries, commonAncestorId };\n}\n\n// ============================================================================\n// Entry to Message Conversion\n// ============================================================================\n\n/**\n * Extract AgentMessage from a session entry.\n * Similar to getMessageFromEntry in compaction.ts, with legacy compaction entries kept inert.\n */\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tswitch (entry.type) {\n\t\tcase \"message\":\n\t\t\t// Skip tool results - context is in assistant's tool call\n\t\t\tif (entry.message.role === \"toolResult\") return undefined;\n\t\t\treturn entry.message;\n\n\t\tcase \"custom_message\":\n\t\t\treturn createCustomMessage(\n\t\t\t\tentry.customType,\n\t\t\t\tentry.content,\n\t\t\t\tentry.display,\n\t\t\t\tentry.details,\n\t\t\t\tentry.timestamp,\n\t\t\t\tentry.excludeFromContext,\n\t\t\t);\n\n\t\tcase \"branch_summary\":\n\t\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\n\t\tcase \"compaction\":\n\t\t\treturn undefined;\n\n\t\t// These don't contribute to conversation content\n\t\tcase \"thinking_level_change\":\n\t\tcase \"context_window_change\":\n\t\tcase \"model_change\":\n\t\tcase \"custom\":\n\t\tcase \"label\":\n\t\tcase \"session_info\":\n\t\tcase \"context_compaction\":\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Prepare entries for summarization with token budget.\n *\n * Walks entries from NEWEST to OLDEST, adding messages until we hit the token budget.\n * This ensures we keep the most recent context when the branch is too long.\n *\n * Also collects file operations from:\n * - Tool calls in assistant messages\n * - Existing branch_summary entries' details (for cumulative tracking)\n *\n * @param entries - Entries in chronological order\n * @param tokenBudget - Maximum tokens to include (0 = no limit)\n */\nexport function prepareBranchEntries(entries: SessionEntry[], tokenBudget: number = 0): BranchPreparation {\n\tconst messages: AgentMessage[] = [];\n\tconst fileOps = createFileOps();\n\tconst filteredEntries = entries;\n\tlet totalTokens = 0;\n\n\t// First pass: collect file ops from ALL entries (even if they don't fit in token budget)\n\t// This ensures we capture cumulative file tracking from nested branch summaries\n\t// Only extract from pi-generated summaries (fromHook !== true), not extension-generated ones\n\tfor (const entry of filteredEntries) {\n\t\tif (entry.type === \"branch_summary\" && !entry.fromHook && entry.details) {\n\t\t\tconst details = entry.details as BranchSummaryDetails;\n\t\t\tif (Array.isArray(details.readFiles)) {\n\t\t\t\tfor (const f of details.readFiles) fileOps.read.add(f);\n\t\t\t}\n\t\t\tif (Array.isArray(details.modifiedFiles)) {\n\t\t\t\t// Modified files go into both edited and written for proper deduplication\n\t\t\t\tfor (const f of details.modifiedFiles) {\n\t\t\t\t\tfileOps.edited.add(f);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: walk from newest to oldest, adding messages until token budget\n\tfor (let i = filteredEntries.length - 1; i >= 0; i--) {\n\t\tconst entry = filteredEntries[i];\n\t\tconst message = getMessageFromEntry(entry);\n\t\tif (!message) continue;\n\n\t\t// Extract file ops from assistant messages (tool calls)\n\t\textractFileOpsFromMessage(message, fileOps);\n\n\t\tconst tokens = estimateTokens(message);\n\n\t\t// Check budget before adding\n\t\tif (tokenBudget > 0 && totalTokens + tokens > tokenBudget) {\n\t\t\t// If this is a branch summary entry, try to fit it anyway as it's important context\n\t\t\tif (entry.type === \"branch_summary\") {\n\t\t\t\tif (totalTokens < tokenBudget * 0.9) {\n\t\t\t\t\tmessages.unshift(message);\n\t\t\t\t\ttotalTokens += tokens;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Stop - we've hit the budget\n\t\t\tbreak;\n\t\t}\n\n\t\tmessages.unshift(message);\n\t\ttotalTokens += tokens;\n\t}\n\n\treturn { messages, fileOps, totalTokens };\n}\n\n// ============================================================================\n// Summary Generation\n// ============================================================================\n\nconst BRANCH_SUMMARY_PREAMBLE = `The user explored a different conversation branch before returning here.\nSummary of that exploration:\n\n`;\n\nconst BRANCH_SUMMARY_PROMPT = `Create a structured summary of this conversation branch for context when returning later.\n\nUse this EXACT format:\n\n## Goal\n[What was the user trying to accomplish in this branch?]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Work that was started but not finished]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [What should happen next to continue this work]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\n/**\n * Generate a summary of abandoned branch entries.\n *\n * @param entries - Session entries to summarize (chronological order)\n * @param options - Generation options\n */\nexport async function generateBranchSummary(\n\tentries: SessionEntry[],\n\toptions: GenerateBranchSummaryOptions,\n): Promise<BranchSummaryResult> {\n\tconst {\n\t\tmodel,\n\t\tapiKey,\n\t\theaders,\n\t\tsignal,\n\t\tcustomInstructions,\n\t\treplaceInstructions,\n\t\treserveTokens = 16384,\n\t\tstreamFn,\n\t} = options;\n\n\t// Token budget = context window minus reserved space for prompt + response\n\tconst contextWindow = model.contextWindow || 128000;\n\tconst tokenBudget = contextWindow - reserveTokens;\n\n\tconst { messages, fileOps } = prepareBranchEntries(entries, tokenBudget);\n\n\tif (messages.length === 0) {\n\t\treturn { summary: \"No content to summarize\" };\n\t}\n\n\t// Transform to LLM-compatible messages, then serialize to text\n\t// Serialization prevents the model from treating it as a conversation to continue\n\tconst llmMessages = convertToLlm(messages);\n\tconst conversationText = serializeConversation(llmMessages);\n\n\t// Build prompt\n\tlet instructions: string;\n\tif (replaceInstructions && customInstructions) {\n\t\tinstructions = customInstructions;\n\t} else if (customInstructions) {\n\t\tinstructions = `${BRANCH_SUMMARY_PROMPT}\\n\\nAdditional focus: ${customInstructions}`;\n\t} else {\n\t\tinstructions = BRANCH_SUMMARY_PROMPT;\n\t}\n\tconst promptText = `<conversation>\\n${conversationText}\\n</conversation>\\n\\n${instructions}`;\n\n\tconst summarizationMessages = [\n\t\t{\n\t\t\trole: \"user\" as const,\n\t\t\tcontent: [{ type: \"text\" as const, text: promptText }],\n\t\t\ttimestamp: Date.now(),\n\t\t},\n\t];\n\n\t// Call LLM for summarization. Prefer the session stream function so SDK\n\t// request behavior (timeouts, retries, attribution headers) stays consistent\n\t// without running through agent state/events.\n\tconst context = { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages };\n\tconst requestOptions: SimpleStreamOptions = { apiKey, headers, signal, maxTokens: 2048 };\n\tconst response = await (async () => {\n\t\ttry {\n\t\t\treturn streamFn\n\t\t\t\t? await (await streamFn(model, context, requestOptions)).result()\n\t\t\t\t: await completeSimple(model, context, requestOptions);\n\t\t} catch (error) {\n\t\t\tif (signal.aborted) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\" as const,\n\t\t\t\terrorMessage: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t})();\n\n\t// Check if aborted or errored\n\tif (!response || response.stopReason === \"aborted\") {\n\t\treturn { aborted: true };\n\t}\n\tif (response.stopReason === \"error\") {\n\t\treturn { error: formatCopilotProviderError(model.provider, response.errorMessage || \"Summarization failed\") };\n\t}\n\n\tlet summary = response.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\"\\n\");\n\n\t// Prepend preamble to provide context about the branch summary\n\tsummary = BRANCH_SUMMARY_PREAMBLE + summary;\n\n\t// Compute file lists and append to summary\n\tconst { readFiles, modifiedFiles } = computeFileLists(fileOps);\n\tsummary += formatFileOperations(readFiles, modifiedFiles);\n\n\treturn {\n\t\tsummary: summary || \"No summary generated\",\n\t\treadFiles,\n\t\tmodifiedFiles,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SessionEntry } from "../session-manager-types.js";
|
|
2
|
+
import { type CompactionSettings } from "./compaction.js";
|
|
3
|
+
import { type VerbatimCompactionParameters, type VerbatimCompactionPreparation } from "./compaction-types.js";
|
|
4
|
+
/** Return the independently estimated cost of the protected tail. */
|
|
5
|
+
export declare function getKeptTailTokenEstimate(preparation: VerbatimCompactionPreparation): number;
|
|
6
|
+
export declare function autoDetectCompactionQuery(entries: readonly SessionEntry[]): string;
|
|
7
|
+
/** Prepare a compactable region and a structurally valid, user-turn-aligned tail. */
|
|
8
|
+
export declare function prepareCompactionBoundary(pathEntries: SessionEntry[], settings: CompactionSettings, options?: Partial<VerbatimCompactionParameters>): VerbatimCompactionPreparation | undefined;
|
|
9
|
+
//# sourceMappingURL=compaction-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction-boundary.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compaction-boundary.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAmB,YAAY,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAyC,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEjG,OAAO,EAIN,KAAK,4BAA4B,EACjC,KAAK,6BAA6B,EAClC,MAAM,uBAAuB,CAAC;AAW/B,qEAAqE;AACrE,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,6BAA6B,GAAG,MAAM,CAG3F;AAqCD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CASlF;AAoBD,qFAAqF;AACrF,wBAAgB,yBAAyB,CACxC,WAAW,EAAE,YAAY,EAAE,EAC3B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,GAAE,OAAO,CAAC,4BAA4B,CAAM,GACjD,6BAA6B,GAAG,SAAS,CAiC3C","sourcesContent":["import type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport { convertToLlm, createBranchSummaryMessage, createCustomMessage, messageIsLlmVisible, messageStartsLlmUserTurn } from \"../messages.js\";\nimport { normalizeDerivedSessionEntries } from \"../session-entry-normalization.js\";\nimport { buildSessionContext } from \"../session-manager-history.js\";\nimport type { CompactionEntry, SessionEntry } from \"../session-manager-types.js\";\nimport { estimateContextTokens, estimateTokens, type CompactionSettings } from \"./compaction.js\";\nimport { normalizeCompactionParameters, normalizeCompactionQuery, COMPACTION_AUTO_QUERY } from \"./compaction-parameters.js\";\nimport {\n\tMIN_COMPACTABLE_REGION_LINES,\n\tVERBATIM_COMPACTION_STRATEGY,\n\ttype VerbatimCompactionDetails,\n\ttype VerbatimCompactionParameters,\n\ttype VerbatimCompactionPreparation,\n} from \"./compaction-types.js\";\nimport { createNumberedRegion, serializeConversationForCompaction } from \"./transcript-serialization.js\";\n\ninterface VisibleEntry {\n\tentry: SessionEntry;\n\tindex: number;\n\tmessage: AgentMessage;\n}\n\nconst keptTailTokensByPreparation = new WeakMap<VerbatimCompactionPreparation, number>();\n\n/** Return the independently estimated cost of the protected tail. */\nexport function getKeptTailTokenEstimate(preparation: VerbatimCompactionPreparation): number {\n\treturn keptTailTokensByPreparation.get(preparation)\n\t\t?? Math.max(0, preparation.tokensBefore - preparation.region.tokenEstimate);\n}\n\nfunction messageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"message\") return entry.message;\n\tif (entry.type === \"custom_message\") {\n\t\treturn createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp, entry.excludeFromContext);\n\t}\n\tif (entry.type === \"branch_summary\" && typeof entry.summary === \"string\") {\n\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\t}\n\treturn undefined;\n}\n\nfunction activeBoundary(entry: SessionEntry): entry is CompactionEntry<VerbatimCompactionDetails> {\n\tif (entry.type !== \"compaction\") return false;\n\tconst details = (entry as CompactionEntry<{ strategy?: string }>).details;\n\treturn details?.strategy === VERBATIM_COMPACTION_STRATEGY;\n}\n\nfunction visibleEntries(entries: SessionEntry[], start = 0): VisibleEntry[] {\n\tconst visible: VisibleEntry[] = [];\n\tfor (let index = start; index < entries.length; index++) {\n\t\tconst entry = entries[index];\n\t\tconst message = messageFromEntry(entry);\n\t\tif (message && messageIsLlmVisible(message)) visible.push({ entry, index, message });\n\t}\n\treturn visible;\n}\n\nfunction messageText(message: AgentMessage): string {\n\tconst converted = convertToLlm([message]);\n\tif (converted.length === 0) return \"\";\n\treturn converted\n\t\t.flatMap((item) => typeof item.content === \"string\" ? [item.content] : item.content.filter((block) => block.type === \"text\").map((block) => block.text))\n\t\t.join(\"\\n\");\n}\n\nexport function autoDetectCompactionQuery(entries: readonly SessionEntry[]): string {\n\tconst normalized = normalizeDerivedSessionEntries(entries);\n\tfor (let index = normalized.length - 1; index >= 0; index--) {\n\t\tconst message = messageFromEntry(normalized[index]);\n\t\tif (!message || !messageStartsLlmUserTurn(message)) continue;\n\t\tconst text = messageText(message).trim();\n\t\tif (text) return normalizeCompactionQuery(text, COMPACTION_AUTO_QUERY);\n\t}\n\treturn COMPACTION_AUTO_QUERY;\n}\n\nfunction latestActiveBoundary(entries: SessionEntry[]): { entry: CompactionEntry<VerbatimCompactionDetails>; index: number } | undefined {\n\tfor (let index = entries.length - 1; index >= 0; index--) {\n\t\tconst entry = entries[index];\n\t\tif (activeBoundary(entry)) return { entry, index };\n\t}\n\treturn undefined;\n}\n\nfunction selectCut(visible: VisibleEntry[], preserveRecent: number): VisibleEntry | undefined {\n\tif (visible.length === 0) return undefined;\n\tconst candidate = preserveRecent >= visible.length ? 0 : Math.max(0, visible.length - preserveRecent);\n\tconst searchStart = preserveRecent === 0 ? visible.length - 1 : candidate;\n\tfor (let index = searchStart; index >= 0; index--) {\n\t\tif (messageStartsLlmUserTurn(visible[index].message)) return visible[index];\n\t}\n\treturn undefined;\n}\n\n/** Prepare a compactable region and a structurally valid, user-turn-aligned tail. */\nexport function prepareCompactionBoundary(\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\toptions: Partial<VerbatimCompactionParameters> = {},\n): VerbatimCompactionPreparation | undefined {\n\tconst entries = normalizeDerivedSessionEntries(pathEntries);\n\tconst previous = latestActiveBoundary(entries);\n\tlet regionStart = 0;\n\tif (previous) {\n\t\tconst keptIndex = entries.findIndex((entry) => entry.id === previous.entry.firstKeptEntryId);\n\t\tregionStart = keptIndex >= 0 ? keptIndex : previous.index + 1;\n\t}\n\n\tconst visible = visibleEntries(entries, regionStart);\n\tconst parameters = normalizeCompactionParameters({ ...settings, ...options }, autoDetectCompactionQuery(entries));\n\tconst cut = selectCut(visible, parameters.preserve_recent);\n\tif (!cut) return undefined;\n\tconst regionMessages = visible.filter((item) => item.index < cut.index);\n\tif (regionMessages.length < 2) return undefined;\n\n\tconst serialized = serializeConversationForCompaction(convertToLlm(regionMessages.map((item) => item.message)));\n\tconst regionText = previous?.entry.summary ? `${previous.entry.summary}\\n${serialized}` : serialized;\n\tconst region = createNumberedRegion(regionText);\n\tif (region.lines.length < MIN_COMPACTABLE_REGION_LINES) return undefined;\n\n\tconst tailMessages = visible.filter((item) => item.index >= cut.index).map((item) => item.message);\n\tconst preparation: VerbatimCompactionPreparation = {\n\t\tfirstKeptEntryId: cut.entry.id,\n\t\tregion,\n\t\tregionEntryIds: regionMessages.map((item) => item.entry.id),\n\t\tkeptTailMessageCount: tailMessages.length,\n\t\ttokensBefore: estimateContextTokens(buildSessionContext(entries).messages).tokens,\n\t\tparameters,\n\t\tsettings,\n\t};\n\tkeptTailTokensByPreparation.set(preparation, tailMessages.reduce((sum, message) => sum + estimateTokens(message), 0));\n\treturn preparation;\n}\n"]}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { convertToLlm, createBranchSummaryMessage, createCustomMessage, messageIsLlmVisible, messageStartsLlmUserTurn } from "../messages.js";
|
|
2
|
+
import { normalizeDerivedSessionEntries } from "../session-entry-normalization.js";
|
|
3
|
+
import { buildSessionContext } from "../session-manager-history.js";
|
|
4
|
+
import { estimateContextTokens, estimateTokens } from "./compaction.js";
|
|
5
|
+
import { normalizeCompactionParameters, normalizeCompactionQuery, COMPACTION_AUTO_QUERY } from "./compaction-parameters.js";
|
|
6
|
+
import { MIN_COMPACTABLE_REGION_LINES, VERBATIM_COMPACTION_STRATEGY, } from "./compaction-types.js";
|
|
7
|
+
import { createNumberedRegion, serializeConversationForCompaction } from "./transcript-serialization.js";
|
|
8
|
+
const keptTailTokensByPreparation = new WeakMap();
|
|
9
|
+
/** Return the independently estimated cost of the protected tail. */
|
|
10
|
+
export function getKeptTailTokenEstimate(preparation) {
|
|
11
|
+
return keptTailTokensByPreparation.get(preparation)
|
|
12
|
+
?? Math.max(0, preparation.tokensBefore - preparation.region.tokenEstimate);
|
|
13
|
+
}
|
|
14
|
+
function messageFromEntry(entry) {
|
|
15
|
+
if (entry.type === "message")
|
|
16
|
+
return entry.message;
|
|
17
|
+
if (entry.type === "custom_message") {
|
|
18
|
+
return createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp, entry.excludeFromContext);
|
|
19
|
+
}
|
|
20
|
+
if (entry.type === "branch_summary" && typeof entry.summary === "string") {
|
|
21
|
+
return createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
function activeBoundary(entry) {
|
|
26
|
+
if (entry.type !== "compaction")
|
|
27
|
+
return false;
|
|
28
|
+
const details = entry.details;
|
|
29
|
+
return details?.strategy === VERBATIM_COMPACTION_STRATEGY;
|
|
30
|
+
}
|
|
31
|
+
function visibleEntries(entries, start = 0) {
|
|
32
|
+
const visible = [];
|
|
33
|
+
for (let index = start; index < entries.length; index++) {
|
|
34
|
+
const entry = entries[index];
|
|
35
|
+
const message = messageFromEntry(entry);
|
|
36
|
+
if (message && messageIsLlmVisible(message))
|
|
37
|
+
visible.push({ entry, index, message });
|
|
38
|
+
}
|
|
39
|
+
return visible;
|
|
40
|
+
}
|
|
41
|
+
function messageText(message) {
|
|
42
|
+
const converted = convertToLlm([message]);
|
|
43
|
+
if (converted.length === 0)
|
|
44
|
+
return "";
|
|
45
|
+
return converted
|
|
46
|
+
.flatMap((item) => typeof item.content === "string" ? [item.content] : item.content.filter((block) => block.type === "text").map((block) => block.text))
|
|
47
|
+
.join("\n");
|
|
48
|
+
}
|
|
49
|
+
export function autoDetectCompactionQuery(entries) {
|
|
50
|
+
const normalized = normalizeDerivedSessionEntries(entries);
|
|
51
|
+
for (let index = normalized.length - 1; index >= 0; index--) {
|
|
52
|
+
const message = messageFromEntry(normalized[index]);
|
|
53
|
+
if (!message || !messageStartsLlmUserTurn(message))
|
|
54
|
+
continue;
|
|
55
|
+
const text = messageText(message).trim();
|
|
56
|
+
if (text)
|
|
57
|
+
return normalizeCompactionQuery(text, COMPACTION_AUTO_QUERY);
|
|
58
|
+
}
|
|
59
|
+
return COMPACTION_AUTO_QUERY;
|
|
60
|
+
}
|
|
61
|
+
function latestActiveBoundary(entries) {
|
|
62
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
63
|
+
const entry = entries[index];
|
|
64
|
+
if (activeBoundary(entry))
|
|
65
|
+
return { entry, index };
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
function selectCut(visible, preserveRecent) {
|
|
70
|
+
if (visible.length === 0)
|
|
71
|
+
return undefined;
|
|
72
|
+
const candidate = preserveRecent >= visible.length ? 0 : Math.max(0, visible.length - preserveRecent);
|
|
73
|
+
const searchStart = preserveRecent === 0 ? visible.length - 1 : candidate;
|
|
74
|
+
for (let index = searchStart; index >= 0; index--) {
|
|
75
|
+
if (messageStartsLlmUserTurn(visible[index].message))
|
|
76
|
+
return visible[index];
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
/** Prepare a compactable region and a structurally valid, user-turn-aligned tail. */
|
|
81
|
+
export function prepareCompactionBoundary(pathEntries, settings, options = {}) {
|
|
82
|
+
const entries = normalizeDerivedSessionEntries(pathEntries);
|
|
83
|
+
const previous = latestActiveBoundary(entries);
|
|
84
|
+
let regionStart = 0;
|
|
85
|
+
if (previous) {
|
|
86
|
+
const keptIndex = entries.findIndex((entry) => entry.id === previous.entry.firstKeptEntryId);
|
|
87
|
+
regionStart = keptIndex >= 0 ? keptIndex : previous.index + 1;
|
|
88
|
+
}
|
|
89
|
+
const visible = visibleEntries(entries, regionStart);
|
|
90
|
+
const parameters = normalizeCompactionParameters({ ...settings, ...options }, autoDetectCompactionQuery(entries));
|
|
91
|
+
const cut = selectCut(visible, parameters.preserve_recent);
|
|
92
|
+
if (!cut)
|
|
93
|
+
return undefined;
|
|
94
|
+
const regionMessages = visible.filter((item) => item.index < cut.index);
|
|
95
|
+
if (regionMessages.length < 2)
|
|
96
|
+
return undefined;
|
|
97
|
+
const serialized = serializeConversationForCompaction(convertToLlm(regionMessages.map((item) => item.message)));
|
|
98
|
+
const regionText = previous?.entry.summary ? `${previous.entry.summary}\n${serialized}` : serialized;
|
|
99
|
+
const region = createNumberedRegion(regionText);
|
|
100
|
+
if (region.lines.length < MIN_COMPACTABLE_REGION_LINES)
|
|
101
|
+
return undefined;
|
|
102
|
+
const tailMessages = visible.filter((item) => item.index >= cut.index).map((item) => item.message);
|
|
103
|
+
const preparation = {
|
|
104
|
+
firstKeptEntryId: cut.entry.id,
|
|
105
|
+
region,
|
|
106
|
+
regionEntryIds: regionMessages.map((item) => item.entry.id),
|
|
107
|
+
keptTailMessageCount: tailMessages.length,
|
|
108
|
+
tokensBefore: estimateContextTokens(buildSessionContext(entries).messages).tokens,
|
|
109
|
+
parameters,
|
|
110
|
+
settings,
|
|
111
|
+
};
|
|
112
|
+
keptTailTokensByPreparation.set(preparation, tailMessages.reduce((sum, message) => sum + estimateTokens(message), 0));
|
|
113
|
+
return preparation;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=compaction-boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction-boundary.js","sourceRoot":"","sources":["../../../src/core/compaction/compaction-boundary.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport { convertToLlm, createBranchSummaryMessage, createCustomMessage, messageIsLlmVisible, messageStartsLlmUserTurn } from \"../messages.js\";\nimport { normalizeDerivedSessionEntries } from \"../session-entry-normalization.js\";\nimport { buildSessionContext } from \"../session-manager-history.js\";\nimport type { CompactionEntry, SessionEntry } from \"../session-manager-types.js\";\nimport { estimateContextTokens, estimateTokens, type CompactionSettings } from \"./compaction.js\";\nimport { normalizeCompactionParameters, normalizeCompactionQuery, COMPACTION_AUTO_QUERY } from \"./compaction-parameters.js\";\nimport {\n\tMIN_COMPACTABLE_REGION_LINES,\n\tVERBATIM_COMPACTION_STRATEGY,\n\ttype VerbatimCompactionDetails,\n\ttype VerbatimCompactionParameters,\n\ttype VerbatimCompactionPreparation,\n} from \"./compaction-types.js\";\nimport { createNumberedRegion, serializeConversationForCompaction } from \"./transcript-serialization.js\";\n\ninterface VisibleEntry {\n\tentry: SessionEntry;\n\tindex: number;\n\tmessage: AgentMessage;\n}\n\nconst keptTailTokensByPreparation = new WeakMap<VerbatimCompactionPreparation, number>();\n\n/** Return the independently estimated cost of the protected tail. */\nexport function getKeptTailTokenEstimate(preparation: VerbatimCompactionPreparation): number {\n\treturn keptTailTokensByPreparation.get(preparation)\n\t\t?? Math.max(0, preparation.tokensBefore - preparation.region.tokenEstimate);\n}\n\nfunction messageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tif (entry.type === \"message\") return entry.message;\n\tif (entry.type === \"custom_message\") {\n\t\treturn createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp, entry.excludeFromContext);\n\t}\n\tif (entry.type === \"branch_summary\" && typeof entry.summary === \"string\") {\n\t\treturn createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);\n\t}\n\treturn undefined;\n}\n\nfunction activeBoundary(entry: SessionEntry): entry is CompactionEntry<VerbatimCompactionDetails> {\n\tif (entry.type !== \"compaction\") return false;\n\tconst details = (entry as CompactionEntry<{ strategy?: string }>).details;\n\treturn details?.strategy === VERBATIM_COMPACTION_STRATEGY;\n}\n\nfunction visibleEntries(entries: SessionEntry[], start = 0): VisibleEntry[] {\n\tconst visible: VisibleEntry[] = [];\n\tfor (let index = start; index < entries.length; index++) {\n\t\tconst entry = entries[index];\n\t\tconst message = messageFromEntry(entry);\n\t\tif (message && messageIsLlmVisible(message)) visible.push({ entry, index, message });\n\t}\n\treturn visible;\n}\n\nfunction messageText(message: AgentMessage): string {\n\tconst converted = convertToLlm([message]);\n\tif (converted.length === 0) return \"\";\n\treturn converted\n\t\t.flatMap((item) => typeof item.content === \"string\" ? [item.content] : item.content.filter((block) => block.type === \"text\").map((block) => block.text))\n\t\t.join(\"\\n\");\n}\n\nexport function autoDetectCompactionQuery(entries: readonly SessionEntry[]): string {\n\tconst normalized = normalizeDerivedSessionEntries(entries);\n\tfor (let index = normalized.length - 1; index >= 0; index--) {\n\t\tconst message = messageFromEntry(normalized[index]);\n\t\tif (!message || !messageStartsLlmUserTurn(message)) continue;\n\t\tconst text = messageText(message).trim();\n\t\tif (text) return normalizeCompactionQuery(text, COMPACTION_AUTO_QUERY);\n\t}\n\treturn COMPACTION_AUTO_QUERY;\n}\n\nfunction latestActiveBoundary(entries: SessionEntry[]): { entry: CompactionEntry<VerbatimCompactionDetails>; index: number } | undefined {\n\tfor (let index = entries.length - 1; index >= 0; index--) {\n\t\tconst entry = entries[index];\n\t\tif (activeBoundary(entry)) return { entry, index };\n\t}\n\treturn undefined;\n}\n\nfunction selectCut(visible: VisibleEntry[], preserveRecent: number): VisibleEntry | undefined {\n\tif (visible.length === 0) return undefined;\n\tconst candidate = preserveRecent >= visible.length ? 0 : Math.max(0, visible.length - preserveRecent);\n\tconst searchStart = preserveRecent === 0 ? visible.length - 1 : candidate;\n\tfor (let index = searchStart; index >= 0; index--) {\n\t\tif (messageStartsLlmUserTurn(visible[index].message)) return visible[index];\n\t}\n\treturn undefined;\n}\n\n/** Prepare a compactable region and a structurally valid, user-turn-aligned tail. */\nexport function prepareCompactionBoundary(\n\tpathEntries: SessionEntry[],\n\tsettings: CompactionSettings,\n\toptions: Partial<VerbatimCompactionParameters> = {},\n): VerbatimCompactionPreparation | undefined {\n\tconst entries = normalizeDerivedSessionEntries(pathEntries);\n\tconst previous = latestActiveBoundary(entries);\n\tlet regionStart = 0;\n\tif (previous) {\n\t\tconst keptIndex = entries.findIndex((entry) => entry.id === previous.entry.firstKeptEntryId);\n\t\tregionStart = keptIndex >= 0 ? keptIndex : previous.index + 1;\n\t}\n\n\tconst visible = visibleEntries(entries, regionStart);\n\tconst parameters = normalizeCompactionParameters({ ...settings, ...options }, autoDetectCompactionQuery(entries));\n\tconst cut = selectCut(visible, parameters.preserve_recent);\n\tif (!cut) return undefined;\n\tconst regionMessages = visible.filter((item) => item.index < cut.index);\n\tif (regionMessages.length < 2) return undefined;\n\n\tconst serialized = serializeConversationForCompaction(convertToLlm(regionMessages.map((item) => item.message)));\n\tconst regionText = previous?.entry.summary ? `${previous.entry.summary}\\n${serialized}` : serialized;\n\tconst region = createNumberedRegion(regionText);\n\tif (region.lines.length < MIN_COMPACTABLE_REGION_LINES) return undefined;\n\n\tconst tailMessages = visible.filter((item) => item.index >= cut.index).map((item) => item.message);\n\tconst preparation: VerbatimCompactionPreparation = {\n\t\tfirstKeptEntryId: cut.entry.id,\n\t\tregion,\n\t\tregionEntryIds: regionMessages.map((item) => item.entry.id),\n\t\tkeptTailMessageCount: tailMessages.length,\n\t\ttokensBefore: estimateContextTokens(buildSessionContext(entries).messages).tokens,\n\t\tparameters,\n\t\tsettings,\n\t};\n\tkeptTailTokensByPreparation.set(preparation, tailMessages.reduce((sum, message) => sum + estimateTokens(message), 0));\n\treturn preparation;\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type VerbatimCompactionParameters } from "./compaction-types.js";
|
|
2
|
+
export declare const COMPACTION_AUTO_QUERY = "the latest user request";
|
|
3
|
+
export declare function normalizeCompactionQuery(value: string | undefined, fallback: string): string;
|
|
4
|
+
export declare function normalizeCompactionParameters(input?: Partial<VerbatimCompactionParameters>, fallbackQuery?: string): VerbatimCompactionParameters;
|
|
5
|
+
//# sourceMappingURL=compaction-parameters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction-parameters.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compaction-parameters.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,4BAA4B,EACjC,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAe/D,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI5F;AAED,wBAAgB,6BAA6B,CAC5C,KAAK,GAAE,OAAO,CAAC,4BAA4B,CAAM,EACjD,aAAa,SAAwB,GACnC,4BAA4B,CAM9B","sourcesContent":["import {\n\tDEFAULT_COMPRESSION_RATIO,\n\tDEFAULT_PRESERVE_RECENT,\n\ttype VerbatimCompactionParameters,\n} from \"./compaction-types.js\";\n\nexport const COMPACTION_AUTO_QUERY = \"the latest user request\";\nconst QUERY_MAX_CHARS = 1000;\n\nfunction normalizeCompressionRatio(value: number | undefined): number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0 && value < 1\n\t\t? value\n\t\t: DEFAULT_COMPRESSION_RATIO;\n}\n\nfunction normalizePreserveRecent(value: number | undefined): number {\n\treturn typeof value === \"number\" && Number.isFinite(value)\n\t\t? Math.max(0, Math.floor(value))\n\t\t: DEFAULT_PRESERVE_RECENT;\n}\n\nexport function normalizeCompactionQuery(value: string | undefined, fallback: string): string {\n\tconst query = value?.trim() || fallback.trim() || COMPACTION_AUTO_QUERY;\n\tif (query.length <= QUERY_MAX_CHARS) return query;\n\treturn `${query.slice(0, QUERY_MAX_CHARS)}\\n[... ${query.length - QUERY_MAX_CHARS} more characters omitted from compaction query]`;\n}\n\nexport function normalizeCompactionParameters(\n\tinput: Partial<VerbatimCompactionParameters> = {},\n\tfallbackQuery = COMPACTION_AUTO_QUERY,\n): VerbatimCompactionParameters {\n\treturn {\n\t\tcompression_ratio: normalizeCompressionRatio(input.compression_ratio),\n\t\tpreserve_recent: normalizePreserveRecent(input.preserve_recent),\n\t\tquery: normalizeCompactionQuery(input.query, fallbackQuery),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DEFAULT_COMPRESSION_RATIO, DEFAULT_PRESERVE_RECENT, } from "./compaction-types.js";
|
|
2
|
+
export const COMPACTION_AUTO_QUERY = "the latest user request";
|
|
3
|
+
const QUERY_MAX_CHARS = 1000;
|
|
4
|
+
function normalizeCompressionRatio(value) {
|
|
5
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 && value < 1
|
|
6
|
+
? value
|
|
7
|
+
: DEFAULT_COMPRESSION_RATIO;
|
|
8
|
+
}
|
|
9
|
+
function normalizePreserveRecent(value) {
|
|
10
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
11
|
+
? Math.max(0, Math.floor(value))
|
|
12
|
+
: DEFAULT_PRESERVE_RECENT;
|
|
13
|
+
}
|
|
14
|
+
export function normalizeCompactionQuery(value, fallback) {
|
|
15
|
+
const query = value?.trim() || fallback.trim() || COMPACTION_AUTO_QUERY;
|
|
16
|
+
if (query.length <= QUERY_MAX_CHARS)
|
|
17
|
+
return query;
|
|
18
|
+
return `${query.slice(0, QUERY_MAX_CHARS)}\n[... ${query.length - QUERY_MAX_CHARS} more characters omitted from compaction query]`;
|
|
19
|
+
}
|
|
20
|
+
export function normalizeCompactionParameters(input = {}, fallbackQuery = COMPACTION_AUTO_QUERY) {
|
|
21
|
+
return {
|
|
22
|
+
compression_ratio: normalizeCompressionRatio(input.compression_ratio),
|
|
23
|
+
preserve_recent: normalizePreserveRecent(input.preserve_recent),
|
|
24
|
+
query: normalizeCompactionQuery(input.query, fallbackQuery),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=compaction-parameters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction-parameters.js","sourceRoot":"","sources":["../../../src/core/compaction/compaction-parameters.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,yBAAyB,EACzB,uBAAuB,GAEvB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,qBAAqB,GAAG,yBAAyB,CAAC;AAC/D,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B,SAAS,yBAAyB,CAAC,KAAyB;IAC3D,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC;QACnF,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,yBAAyB,CAAC;AAC9B,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAyB;IACzD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACzD,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC,CAAC,uBAAuB,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAAyB,EAAE,QAAgB;IACnF,MAAM,KAAK,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,qBAAqB,CAAC;IACxE,IAAI,KAAK,CAAC,MAAM,IAAI,eAAe;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,UAAU,KAAK,CAAC,MAAM,GAAG,eAAe,iDAAiD,CAAC;AACpI,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC5C,KAAK,GAA0C,EAAE,EACjD,aAAa,GAAG,qBAAqB;IAErC,OAAO;QACN,iBAAiB,EAAE,yBAAyB,CAAC,KAAK,CAAC,iBAAiB,CAAC;QACrE,eAAe,EAAE,uBAAuB,CAAC,KAAK,CAAC,eAAe,CAAC;QAC/D,KAAK,EAAE,wBAAwB,CAAC,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC;KAC3D,CAAC;AACH,CAAC","sourcesContent":["import {\n\tDEFAULT_COMPRESSION_RATIO,\n\tDEFAULT_PRESERVE_RECENT,\n\ttype VerbatimCompactionParameters,\n} from \"./compaction-types.js\";\n\nexport const COMPACTION_AUTO_QUERY = \"the latest user request\";\nconst QUERY_MAX_CHARS = 1000;\n\nfunction normalizeCompressionRatio(value: number | undefined): number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0 && value < 1\n\t\t? value\n\t\t: DEFAULT_COMPRESSION_RATIO;\n}\n\nfunction normalizePreserveRecent(value: number | undefined): number {\n\treturn typeof value === \"number\" && Number.isFinite(value)\n\t\t? Math.max(0, Math.floor(value))\n\t\t: DEFAULT_PRESERVE_RECENT;\n}\n\nexport function normalizeCompactionQuery(value: string | undefined, fallback: string): string {\n\tconst query = value?.trim() || fallback.trim() || COMPACTION_AUTO_QUERY;\n\tif (query.length <= QUERY_MAX_CHARS) return query;\n\treturn `${query.slice(0, QUERY_MAX_CHARS)}\\n[... ${query.length - QUERY_MAX_CHARS} more characters omitted from compaction query]`;\n}\n\nexport function normalizeCompactionParameters(\n\tinput: Partial<VerbatimCompactionParameters> = {},\n\tfallbackQuery = COMPACTION_AUTO_QUERY,\n): VerbatimCompactionParameters {\n\treturn {\n\t\tcompression_ratio: normalizeCompressionRatio(input.compression_ratio),\n\t\tpreserve_recent: normalizePreserveRecent(input.preserve_recent),\n\t\tquery: normalizeCompactionQuery(input.query, fallbackQuery),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StreamFn, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { Api, Model } from "@earendil-works/pi-ai/compat";
|
|
3
|
+
import type { CompactedTranscript, VerbatimCompactionPreparation } from "./compaction-types.js";
|
|
4
|
+
export interface CompactionPlanOptions {
|
|
5
|
+
streamFn: StreamFn;
|
|
6
|
+
}
|
|
7
|
+
export type CompactionRungResult = CompactedTranscript & {
|
|
8
|
+
rung: "planned";
|
|
9
|
+
};
|
|
10
|
+
/** Calculate the single global line threshold directly from the prepared setting. */
|
|
11
|
+
export declare function targetKeepLines(preparation: VerbatimCompactionPreparation): number;
|
|
12
|
+
/** Execute one whole-region model plan and mechanically accept its safe deletions. */
|
|
13
|
+
export declare function runVerbatimCompaction(preparation: VerbatimCompactionPreparation, model: Model<Api>, apiKey: string, headers: Record<string, string> | undefined, signal: AbortSignal | undefined, thinkingLevel: ThinkingLevel | undefined, options: CompactionPlanOptions): Promise<CompactionRungResult>;
|
|
14
|
+
//# sourceMappingURL=compaction-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction-runner.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compaction-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AAI/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AAEhG,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,QAAQ,CAAC;CACnB;AAED,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAE7E,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,WAAW,EAAE,6BAA6B,GAAG,MAAM,CAMlF;AAiBD,sFAAsF;AACtF,wBAAsB,qBAAqB,CAC1C,WAAW,EAAE,6BAA6B,EAC1C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC3C,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,aAAa,EAAE,aAAa,GAAG,SAAS,EACxC,OAAO,EAAE,qBAAqB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CAkB/B","sourcesContent":["import type { StreamFn, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai/compat\";\nimport { getKeptTailTokenEstimate } from \"./compaction-boundary.js\";\nimport { reconstructCompactedTranscript, validateDeletedRanges } from \"./deleted-ranges.js\";\nimport { planDeletedLineRanges } from \"./range-planner.js\";\nimport type { CompactedTranscript, VerbatimCompactionPreparation } from \"./compaction-types.js\";\n\nexport interface CompactionPlanOptions {\n\tstreamFn: StreamFn;\n}\n\nexport type CompactionRungResult = CompactedTranscript & { rung: \"planned\" };\n\n/** Calculate the single global line threshold directly from the prepared setting. */\nexport function targetKeepLines(preparation: VerbatimCompactionPreparation): number {\n\tconst protectedLines = preparation.region.protectedLineNumbers?.size ?? 0;\n\treturn Math.max(\n\t\tprotectedLines,\n\t\tMath.round(preparation.region.lines.length * preparation.parameters.compression_ratio),\n\t);\n}\n\nfunction withWholeContextStats(\n\tresult: CompactedTranscript,\n\tpreparation: VerbatimCompactionPreparation,\n): CompactionRungResult {\n\tconst tokensAfter = result.stats.tokensAfter + getKeptTailTokenEstimate(preparation);\n\tconst percentReduction = preparation.tokensBefore === 0\n\t\t? 0\n\t\t: Math.round((1 - tokensAfter / preparation.tokensBefore) * 1000) / 10;\n\treturn {\n\t\t...result,\n\t\trung: \"planned\",\n\t\tstats: { ...result.stats, tokensBefore: preparation.tokensBefore, tokensAfter, percentReduction },\n\t};\n}\n\n/** Execute one whole-region model plan and mechanically accept its safe deletions. */\nexport async function runVerbatimCompaction(\n\tpreparation: VerbatimCompactionPreparation,\n\tmodel: Model<Api>,\n\tapiKey: string,\n\theaders: Record<string, string> | undefined,\n\tsignal: AbortSignal | undefined,\n\tthinkingLevel: ThinkingLevel | undefined,\n\toptions: CompactionPlanOptions,\n): Promise<CompactionRungResult> {\n\tif (signal?.aborted) throw new Error(\"Compaction cancelled\");\n\tconst raw = await planDeletedLineRanges(\n\t\tpreparation.region,\n\t\tpreparation.parameters,\n\t\tmodel,\n\t\t{ apiKey, headers },\n\t\tsignal,\n\t\tthinkingLevel,\n\t\tpreparation.settings.reserveTokens,\n\t\ttargetKeepLines(preparation),\n\t\t{ streamFn: options.streamFn },\n\t);\n\tif (signal?.aborted) throw new Error(\"Compaction cancelled\");\n\treturn withWholeContextStats(\n\t\treconstructCompactedTranscript(preparation.region, validateDeletedRanges(raw, preparation.region)),\n\t\tpreparation,\n\t);\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getKeptTailTokenEstimate } from "./compaction-boundary.js";
|
|
2
|
+
import { reconstructCompactedTranscript, validateDeletedRanges } from "./deleted-ranges.js";
|
|
3
|
+
import { planDeletedLineRanges } from "./range-planner.js";
|
|
4
|
+
/** Calculate the single global line threshold directly from the prepared setting. */
|
|
5
|
+
export function targetKeepLines(preparation) {
|
|
6
|
+
const protectedLines = preparation.region.protectedLineNumbers?.size ?? 0;
|
|
7
|
+
return Math.max(protectedLines, Math.round(preparation.region.lines.length * preparation.parameters.compression_ratio));
|
|
8
|
+
}
|
|
9
|
+
function withWholeContextStats(result, preparation) {
|
|
10
|
+
const tokensAfter = result.stats.tokensAfter + getKeptTailTokenEstimate(preparation);
|
|
11
|
+
const percentReduction = preparation.tokensBefore === 0
|
|
12
|
+
? 0
|
|
13
|
+
: Math.round((1 - tokensAfter / preparation.tokensBefore) * 1000) / 10;
|
|
14
|
+
return {
|
|
15
|
+
...result,
|
|
16
|
+
rung: "planned",
|
|
17
|
+
stats: { ...result.stats, tokensBefore: preparation.tokensBefore, tokensAfter, percentReduction },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Execute one whole-region model plan and mechanically accept its safe deletions. */
|
|
21
|
+
export async function runVerbatimCompaction(preparation, model, apiKey, headers, signal, thinkingLevel, options) {
|
|
22
|
+
if (signal?.aborted)
|
|
23
|
+
throw new Error("Compaction cancelled");
|
|
24
|
+
const raw = await planDeletedLineRanges(preparation.region, preparation.parameters, model, { apiKey, headers }, signal, thinkingLevel, preparation.settings.reserveTokens, targetKeepLines(preparation), { streamFn: options.streamFn });
|
|
25
|
+
if (signal?.aborted)
|
|
26
|
+
throw new Error("Compaction cancelled");
|
|
27
|
+
return withWholeContextStats(reconstructCompactedTranscript(preparation.region, validateDeletedRanges(raw, preparation.region)), preparation);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=compaction-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction-runner.js","sourceRoot":"","sources":["../../../src/core/compaction/compaction-runner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,8BAA8B,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5F,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAS3D,qFAAqF;AACrF,MAAM,UAAU,eAAe,CAAC,WAA0C;IACzE,MAAM,cAAc,GAAG,WAAW,CAAC,MAAM,CAAC,oBAAoB,EAAE,IAAI,IAAI,CAAC,CAAC;IAC1E,OAAO,IAAI,CAAC,GAAG,CACd,cAAc,EACd,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,iBAAiB,CAAC,CACtF,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAC7B,MAA2B,EAC3B,WAA0C;IAE1C,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,wBAAwB,CAAC,WAAW,CAAC,CAAC;IACrF,MAAM,gBAAgB,GAAG,WAAW,CAAC,YAAY,KAAK,CAAC;QACtD,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,WAAW,GAAG,WAAW,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACxE,OAAO;QACN,GAAG,MAAM;QACT,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,WAAW,CAAC,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE;KACjG,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,WAA0C,EAC1C,KAAiB,EACjB,MAAc,EACd,OAA2C,EAC3C,MAA+B,EAC/B,aAAwC,EACxC,OAA8B;IAE9B,IAAI,MAAM,EAAE,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,MAAM,qBAAqB,CACtC,WAAW,CAAC,MAAM,EAClB,WAAW,CAAC,UAAU,EACtB,KAAK,EACL,EAAE,MAAM,EAAE,OAAO,EAAE,EACnB,MAAM,EACN,aAAa,EACb,WAAW,CAAC,QAAQ,CAAC,aAAa,EAClC,eAAe,CAAC,WAAW,CAAC,EAC5B,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAC9B,CAAC;IACF,IAAI,MAAM,EAAE,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IAC7D,OAAO,qBAAqB,CAC3B,8BAA8B,CAAC,WAAW,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,EAClG,WAAW,CACX,CAAC;AACH,CAAC","sourcesContent":["import type { StreamFn, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai/compat\";\nimport { getKeptTailTokenEstimate } from \"./compaction-boundary.js\";\nimport { reconstructCompactedTranscript, validateDeletedRanges } from \"./deleted-ranges.js\";\nimport { planDeletedLineRanges } from \"./range-planner.js\";\nimport type { CompactedTranscript, VerbatimCompactionPreparation } from \"./compaction-types.js\";\n\nexport interface CompactionPlanOptions {\n\tstreamFn: StreamFn;\n}\n\nexport type CompactionRungResult = CompactedTranscript & { rung: \"planned\" };\n\n/** Calculate the single global line threshold directly from the prepared setting. */\nexport function targetKeepLines(preparation: VerbatimCompactionPreparation): number {\n\tconst protectedLines = preparation.region.protectedLineNumbers?.size ?? 0;\n\treturn Math.max(\n\t\tprotectedLines,\n\t\tMath.round(preparation.region.lines.length * preparation.parameters.compression_ratio),\n\t);\n}\n\nfunction withWholeContextStats(\n\tresult: CompactedTranscript,\n\tpreparation: VerbatimCompactionPreparation,\n): CompactionRungResult {\n\tconst tokensAfter = result.stats.tokensAfter + getKeptTailTokenEstimate(preparation);\n\tconst percentReduction = preparation.tokensBefore === 0\n\t\t? 0\n\t\t: Math.round((1 - tokensAfter / preparation.tokensBefore) * 1000) / 10;\n\treturn {\n\t\t...result,\n\t\trung: \"planned\",\n\t\tstats: { ...result.stats, tokensBefore: preparation.tokensBefore, tokensAfter, percentReduction },\n\t};\n}\n\n/** Execute one whole-region model plan and mechanically accept its safe deletions. */\nexport async function runVerbatimCompaction(\n\tpreparation: VerbatimCompactionPreparation,\n\tmodel: Model<Api>,\n\tapiKey: string,\n\theaders: Record<string, string> | undefined,\n\tsignal: AbortSignal | undefined,\n\tthinkingLevel: ThinkingLevel | undefined,\n\toptions: CompactionPlanOptions,\n): Promise<CompactionRungResult> {\n\tif (signal?.aborted) throw new Error(\"Compaction cancelled\");\n\tconst raw = await planDeletedLineRanges(\n\t\tpreparation.region,\n\t\tpreparation.parameters,\n\t\tmodel,\n\t\t{ apiKey, headers },\n\t\tsignal,\n\t\tthinkingLevel,\n\t\tpreparation.settings.reserveTokens,\n\t\ttargetKeepLines(preparation),\n\t\t{ streamFn: options.streamFn },\n\t);\n\tif (signal?.aborted) throw new Error(\"Compaction cancelled\");\n\treturn withWholeContextStats(\n\t\treconstructCompactedTranscript(preparation.region, validateDeletedRanges(raw, preparation.region)),\n\t\tpreparation,\n\t);\n}\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { CompactionSettings } from "./compaction.js";
|
|
2
|
+
export declare const VERBATIM_COMPACTION_PROMPT_VERSION: 3;
|
|
3
|
+
export declare const VERBATIM_COMPACTION_STRATEGY: "verbatim-lines";
|
|
4
|
+
export declare const DEFAULT_COMPRESSION_RATIO = 0.5;
|
|
5
|
+
export declare const DEFAULT_PRESERVE_RECENT = 2;
|
|
6
|
+
export declare const MIN_COMPACTABLE_REGION_LINES = 20;
|
|
7
|
+
export interface VerbatimCompactionParameters {
|
|
8
|
+
/** Fraction of compactable region lines to keep. */
|
|
9
|
+
compression_ratio: number;
|
|
10
|
+
/** Recent context-eligible messages retained outside the compactable region. */
|
|
11
|
+
preserve_recent: number;
|
|
12
|
+
/** Relevance focus used by the range planner. */
|
|
13
|
+
query: string;
|
|
14
|
+
}
|
|
15
|
+
export interface LineRange {
|
|
16
|
+
/** One-based inclusive first line. */
|
|
17
|
+
start: number;
|
|
18
|
+
/** One-based inclusive last line. */
|
|
19
|
+
end: number;
|
|
20
|
+
}
|
|
21
|
+
export type RawLineEndpoint = number | string | boolean | null;
|
|
22
|
+
export interface RawLineRange {
|
|
23
|
+
start?: RawLineEndpoint;
|
|
24
|
+
end?: RawLineEndpoint;
|
|
25
|
+
}
|
|
26
|
+
export interface NumberedRegion {
|
|
27
|
+
readonly __brand: "NumberedRegion";
|
|
28
|
+
lines: string[];
|
|
29
|
+
headerLineNumbers: ReadonlySet<number>;
|
|
30
|
+
priorMarkerNs: ReadonlyMap<number, number>;
|
|
31
|
+
/** Optional future protected spans, expressed as one-based line numbers. */
|
|
32
|
+
protectedLineNumbers?: ReadonlySet<number>;
|
|
33
|
+
tokenEstimate: number;
|
|
34
|
+
}
|
|
35
|
+
export type ValidatedRanges = readonly LineRange[] & {
|
|
36
|
+
readonly __brand: "ValidatedRanges";
|
|
37
|
+
};
|
|
38
|
+
export interface CompactedTranscript {
|
|
39
|
+
text: string;
|
|
40
|
+
ranges: LineRange[];
|
|
41
|
+
stats: VerbatimCompactionStats;
|
|
42
|
+
}
|
|
43
|
+
export interface VerbatimCompactionStats {
|
|
44
|
+
linesBefore: number;
|
|
45
|
+
linesDeleted: number;
|
|
46
|
+
linesKept: number;
|
|
47
|
+
rangeCount: number;
|
|
48
|
+
tokensBefore: number;
|
|
49
|
+
tokensAfter: number;
|
|
50
|
+
percentReduction: number;
|
|
51
|
+
}
|
|
52
|
+
export interface VerbatimCompactionDetails {
|
|
53
|
+
strategy: typeof VERBATIM_COMPACTION_STRATEGY;
|
|
54
|
+
promptVersion: typeof VERBATIM_COMPACTION_PROMPT_VERSION;
|
|
55
|
+
parameters: VerbatimCompactionParameters;
|
|
56
|
+
stats: VerbatimCompactionStats;
|
|
57
|
+
rung: "planned" | "extension";
|
|
58
|
+
backupPath?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface VerbatimCompactionPreparation {
|
|
61
|
+
firstKeptEntryId: string;
|
|
62
|
+
region: NumberedRegion;
|
|
63
|
+
regionEntryIds: string[];
|
|
64
|
+
keptTailMessageCount: number;
|
|
65
|
+
tokensBefore: number;
|
|
66
|
+
parameters: VerbatimCompactionParameters;
|
|
67
|
+
settings: CompactionSettings;
|
|
68
|
+
}
|
|
69
|
+
export interface VerbatimCompactionResult {
|
|
70
|
+
compactedText: string;
|
|
71
|
+
firstKeptEntryId: string;
|
|
72
|
+
tokensBefore: number;
|
|
73
|
+
stats: VerbatimCompactionStats;
|
|
74
|
+
parameters: VerbatimCompactionParameters;
|
|
75
|
+
promptVersion: typeof VERBATIM_COMPACTION_PROMPT_VERSION;
|
|
76
|
+
rung: VerbatimCompactionDetails["rung"];
|
|
77
|
+
backupPath?: string;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=compaction-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction-types.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compaction-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,eAAO,MAAM,kCAAkC,EAAG,CAAU,CAAC;AAC7D,eAAO,MAAM,4BAA4B,EAAG,gBAAyB,CAAC;AAEtE,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C,MAAM,WAAW,4BAA4B;IAC5C,oDAAoD;IACpD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,eAAe,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,SAAS;IACzB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC5B,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,GAAG,CAAC,EAAE,eAAe,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,4EAA4E;IAC5E,oBAAoB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3C,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,eAAe,GAAG,SAAS,SAAS,EAAE,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE7F,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,KAAK,EAAE,uBAAuB,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,EAAE,OAAO,4BAA4B,CAAC;IAC9C,aAAa,EAAE,OAAO,kCAAkC,CAAC;IACzD,UAAU,EAAE,4BAA4B,CAAC;IACzC,KAAK,EAAE,uBAAuB,CAAC;IAC/B,IAAI,EAAE,SAAS,GAAG,WAAW,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,6BAA6B;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,4BAA4B,CAAC;IACzC,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,uBAAuB,CAAC;IAC/B,UAAU,EAAE,4BAA4B,CAAC;IACzC,aAAa,EAAE,OAAO,kCAAkC,CAAC;IACzD,IAAI,EAAE,yBAAyB,CAAC,MAAM,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB","sourcesContent":["import type { CompactionSettings } from \"./compaction.js\";\n\nexport const VERBATIM_COMPACTION_PROMPT_VERSION = 3 as const;\nexport const VERBATIM_COMPACTION_STRATEGY = \"verbatim-lines\" as const;\n\nexport const DEFAULT_COMPRESSION_RATIO = 0.5;\nexport const DEFAULT_PRESERVE_RECENT = 2;\nexport const MIN_COMPACTABLE_REGION_LINES = 20;\n\nexport interface VerbatimCompactionParameters {\n\t/** Fraction of compactable region lines to keep. */\n\tcompression_ratio: number;\n\t/** Recent context-eligible messages retained outside the compactable region. */\n\tpreserve_recent: number;\n\t/** Relevance focus used by the range planner. */\n\tquery: string;\n}\n\nexport interface LineRange {\n\t/** One-based inclusive first line. */\n\tstart: number;\n\t/** One-based inclusive last line. */\n\tend: number;\n}\n\nexport type RawLineEndpoint = number | string | boolean | null;\n\nexport interface RawLineRange {\n\tstart?: RawLineEndpoint;\n\tend?: RawLineEndpoint;\n}\n\nexport interface NumberedRegion {\n\treadonly __brand: \"NumberedRegion\";\n\tlines: string[];\n\theaderLineNumbers: ReadonlySet<number>;\n\tpriorMarkerNs: ReadonlyMap<number, number>;\n\t/** Optional future protected spans, expressed as one-based line numbers. */\n\tprotectedLineNumbers?: ReadonlySet<number>;\n\ttokenEstimate: number;\n}\n\nexport type ValidatedRanges = readonly LineRange[] & { readonly __brand: \"ValidatedRanges\" };\n\nexport interface CompactedTranscript {\n\ttext: string;\n\tranges: LineRange[];\n\tstats: VerbatimCompactionStats;\n}\n\nexport interface VerbatimCompactionStats {\n\tlinesBefore: number;\n\tlinesDeleted: number;\n\tlinesKept: number;\n\trangeCount: number;\n\ttokensBefore: number;\n\ttokensAfter: number;\n\tpercentReduction: number;\n}\n\nexport interface VerbatimCompactionDetails {\n\tstrategy: typeof VERBATIM_COMPACTION_STRATEGY;\n\tpromptVersion: typeof VERBATIM_COMPACTION_PROMPT_VERSION;\n\tparameters: VerbatimCompactionParameters;\n\tstats: VerbatimCompactionStats;\n\trung: \"planned\" | \"extension\";\n\tbackupPath?: string;\n}\n\nexport interface VerbatimCompactionPreparation {\n\tfirstKeptEntryId: string;\n\tregion: NumberedRegion;\n\tregionEntryIds: string[];\n\tkeptTailMessageCount: number;\n\ttokensBefore: number;\n\tparameters: VerbatimCompactionParameters;\n\tsettings: CompactionSettings;\n}\n\nexport interface VerbatimCompactionResult {\n\tcompactedText: string;\n\tfirstKeptEntryId: string;\n\ttokensBefore: number;\n\tstats: VerbatimCompactionStats;\n\tparameters: VerbatimCompactionParameters;\n\tpromptVersion: typeof VERBATIM_COMPACTION_PROMPT_VERSION;\n\trung: VerbatimCompactionDetails[\"rung\"];\n\tbackupPath?: string;\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const VERBATIM_COMPACTION_PROMPT_VERSION = 3;
|
|
2
|
+
export const VERBATIM_COMPACTION_STRATEGY = "verbatim-lines";
|
|
3
|
+
export const DEFAULT_COMPRESSION_RATIO = 0.5;
|
|
4
|
+
export const DEFAULT_PRESERVE_RECENT = 2;
|
|
5
|
+
export const MIN_COMPACTABLE_REGION_LINES = 20;
|
|
6
|
+
//# sourceMappingURL=compaction-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction-types.js","sourceRoot":"","sources":["../../../src/core/compaction/compaction-types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kCAAkC,GAAG,CAAU,CAAC;AAC7D,MAAM,CAAC,MAAM,4BAA4B,GAAG,gBAAyB,CAAC;AAEtE,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAC7C,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC","sourcesContent":["import type { CompactionSettings } from \"./compaction.js\";\n\nexport const VERBATIM_COMPACTION_PROMPT_VERSION = 3 as const;\nexport const VERBATIM_COMPACTION_STRATEGY = \"verbatim-lines\" as const;\n\nexport const DEFAULT_COMPRESSION_RATIO = 0.5;\nexport const DEFAULT_PRESERVE_RECENT = 2;\nexport const MIN_COMPACTABLE_REGION_LINES = 20;\n\nexport interface VerbatimCompactionParameters {\n\t/** Fraction of compactable region lines to keep. */\n\tcompression_ratio: number;\n\t/** Recent context-eligible messages retained outside the compactable region. */\n\tpreserve_recent: number;\n\t/** Relevance focus used by the range planner. */\n\tquery: string;\n}\n\nexport interface LineRange {\n\t/** One-based inclusive first line. */\n\tstart: number;\n\t/** One-based inclusive last line. */\n\tend: number;\n}\n\nexport type RawLineEndpoint = number | string | boolean | null;\n\nexport interface RawLineRange {\n\tstart?: RawLineEndpoint;\n\tend?: RawLineEndpoint;\n}\n\nexport interface NumberedRegion {\n\treadonly __brand: \"NumberedRegion\";\n\tlines: string[];\n\theaderLineNumbers: ReadonlySet<number>;\n\tpriorMarkerNs: ReadonlyMap<number, number>;\n\t/** Optional future protected spans, expressed as one-based line numbers. */\n\tprotectedLineNumbers?: ReadonlySet<number>;\n\ttokenEstimate: number;\n}\n\nexport type ValidatedRanges = readonly LineRange[] & { readonly __brand: \"ValidatedRanges\" };\n\nexport interface CompactedTranscript {\n\ttext: string;\n\tranges: LineRange[];\n\tstats: VerbatimCompactionStats;\n}\n\nexport interface VerbatimCompactionStats {\n\tlinesBefore: number;\n\tlinesDeleted: number;\n\tlinesKept: number;\n\trangeCount: number;\n\ttokensBefore: number;\n\ttokensAfter: number;\n\tpercentReduction: number;\n}\n\nexport interface VerbatimCompactionDetails {\n\tstrategy: typeof VERBATIM_COMPACTION_STRATEGY;\n\tpromptVersion: typeof VERBATIM_COMPACTION_PROMPT_VERSION;\n\tparameters: VerbatimCompactionParameters;\n\tstats: VerbatimCompactionStats;\n\trung: \"planned\" | \"extension\";\n\tbackupPath?: string;\n}\n\nexport interface VerbatimCompactionPreparation {\n\tfirstKeptEntryId: string;\n\tregion: NumberedRegion;\n\tregionEntryIds: string[];\n\tkeptTailMessageCount: number;\n\ttokensBefore: number;\n\tparameters: VerbatimCompactionParameters;\n\tsettings: CompactionSettings;\n}\n\nexport interface VerbatimCompactionResult {\n\tcompactedText: string;\n\tfirstKeptEntryId: string;\n\ttokensBefore: number;\n\tstats: VerbatimCompactionStats;\n\tparameters: VerbatimCompactionParameters;\n\tpromptVersion: typeof VERBATIM_COMPACTION_PROMPT_VERSION;\n\trung: VerbatimCompactionDetails[\"rung\"];\n\tbackupPath?: string;\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CompactedTranscript, NumberedRegion, RawLineRange, ValidatedRanges } from "./compaction-types.js";
|
|
2
|
+
/** Normalize untrusted model ranges into the sole range type accepted by reconstruction. */
|
|
3
|
+
export declare function validateDeletedRanges(raw: RawLineRange[], region: NumberedRegion): ValidatedRanges;
|
|
4
|
+
/** Rebuild a compacted transcript mechanically from validated ranges. */
|
|
5
|
+
export declare function reconstructCompactedTranscript(region: NumberedRegion, ranges: ValidatedRanges): CompactedTranscript;
|
|
6
|
+
//# sourceMappingURL=deleted-ranges.d.ts.map
|