@pugi/cli 0.1.0-beta.7 → 0.1.0-beta.87
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 +96 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +2 -2
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +12 -12
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +293 -7
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +3 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +140 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +134 -16
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1295 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/symbol-tools.js +372 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2157 -214
- package/dist/core/repl/slash-commands.js +533 -40
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +286 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4162 -488
- package/dist/runtime/commands/agents.js +30 -30
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +32 -32
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +244 -13
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +222 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +623 -45
- package/dist/tools/brief.js +224 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +85 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +332 -54
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +124 -44
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +23 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -1,34 +1,41 @@
|
|
|
1
|
+
// PR-CLI-SERVER-VERSION-HANDSHAKE . The interceptor stamps the
|
|
2
|
+
// outbound X-Pugi-Cli-Version header, inspects the inbound recommended/
|
|
3
|
+
// server-version headers, and throws PugiCliUpgradeRequiredError on a
|
|
4
|
+
// 426 server response. The top-level catch in `runtime/cli.ts` /
|
|
5
|
+
// `index.ts` renders the upgrade message and exits 1.
|
|
6
|
+
import { assertNotUpgradeRequired, injectClientVersionHeader, inspectVersionResponse, } from '../transport/version-interceptor.js';
|
|
7
|
+
import { PUGI_CLI_VERSION } from '../../runtime/version.js';
|
|
1
8
|
/**
|
|
2
9
|
* Anvil-backed engine loop client.
|
|
3
10
|
*
|
|
4
11
|
* Wire format: OpenAI-compatible `/v1/chat/completions` shape proxied
|
|
5
12
|
* through the admin-api Pugi runtime endpoint. The CLI POSTs:
|
|
6
13
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* POST {apiUrl}/api/pugi/engine
|
|
15
|
+
* Authorization: Bearer {apiKey}
|
|
16
|
+
* {
|
|
17
|
+
* "personaSlug": "oes-dev",
|
|
18
|
+
* "messages": [...],
|
|
19
|
+
* "tools": [...],
|
|
20
|
+
* "maxTokens": 4096,
|
|
21
|
+
* "temperature": 0.2
|
|
22
|
+
* }
|
|
16
23
|
*
|
|
17
24
|
* and expects:
|
|
18
25
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
26
|
+
* 200 OK
|
|
27
|
+
* {
|
|
28
|
+
* "stop": "tool_use" | "text",
|
|
29
|
+
* "content": "...", // present when stop=text
|
|
30
|
+
* "toolCalls": [{id, name, arguments}], // present when stop=tool_use
|
|
31
|
+
* "tokensUsed": 1234,
|
|
32
|
+
* "model": "deepseek-chat-v3.1"
|
|
33
|
+
* }
|
|
27
34
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
35
|
+
* 401/403 -> auth_missing
|
|
36
|
+
* 404 -> endpoint_missing
|
|
37
|
+
* 429 -> rate_limited
|
|
38
|
+
* other -> failed
|
|
32
39
|
*
|
|
33
40
|
* The endpoint itself ships in Sprint 2 (Track 2A). Until then the CLI
|
|
34
41
|
* surfaces `endpoint_missing` cleanly and the operator runs `pugi code`
|
|
@@ -54,23 +61,81 @@ export class AnvilEngineLoopClient {
|
|
|
54
61
|
options.signal.addEventListener('abort', onAbort);
|
|
55
62
|
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
56
63
|
try {
|
|
64
|
+
// PR-CLI-SERVER-VERSION-HANDSHAKE . Stamp the outbound
|
|
65
|
+
// X-Pugi-Cli-Version header so the admin-api middleware can
|
|
66
|
+
// decide whether to honour, soft-warn, or 426 this request.
|
|
67
|
+
const outboundHeaders = injectClientVersionHeader({
|
|
68
|
+
'content-type': 'application/json',
|
|
69
|
+
authorization: `Bearer ${this.config.apiKey}`,
|
|
70
|
+
'user-agent': 'pugi-cli/0.0.1',
|
|
71
|
+
}, PUGI_CLI_VERSION);
|
|
57
72
|
const res = await fetch(url, {
|
|
58
73
|
method: 'POST',
|
|
59
|
-
headers:
|
|
60
|
-
'content-type': 'application/json',
|
|
61
|
-
authorization: `Bearer ${this.config.apiKey}`,
|
|
62
|
-
'user-agent': 'pugi-cli/0.0.1',
|
|
63
|
-
},
|
|
74
|
+
headers: outboundHeaders,
|
|
64
75
|
body: JSON.stringify({
|
|
65
76
|
personaSlug: options.personaSlug,
|
|
66
77
|
messages,
|
|
67
78
|
tools,
|
|
68
79
|
maxTokens: options.maxTokens,
|
|
69
80
|
temperature: options.temperature,
|
|
81
|
+
// β1 (audit E2): the admin-api `EngineRequestDto` accepts
|
|
82
|
+
// these optional fields (see `pugi-engine.controller.ts:230`
|
|
83
|
+
// EngineRequestDto schema). Before this fix the CLI dropped
|
|
84
|
+
// them, which forced the controller to fall back to legacy
|
|
85
|
+
// per-persona resolution + emit `command="(none)"` in its
|
|
86
|
+
// structured logs. `undefined` keys are stripped by
|
|
87
|
+
// `JSON.stringify` so the payload stays clean for fixture
|
|
88
|
+
// clients that exact-match the body shape.
|
|
89
|
+
command: options.command,
|
|
90
|
+
// β1a r1: `tag` is `EngineDispatchTag` object shape now —
|
|
91
|
+
// `JSON.stringify` serialises it as `{tag, priority?,
|
|
92
|
+
// budget_hint?}` matching `EngineDispatchTagDto`. Previously
|
|
93
|
+
// this was a bare string and the server's `IsIn` validator
|
|
94
|
+
// rejected every payload with HTTP 400.
|
|
95
|
+
tag: options.tag,
|
|
96
|
+
model: options.model,
|
|
97
|
+
// Task: server-side tier gate. Stripped by JSON.stringify
|
|
98
|
+
// when undefined so older runtimes без the contextTier DTO
|
|
99
|
+
// field never see an unknown key.
|
|
100
|
+
contextTier: options.contextTier,
|
|
70
101
|
}),
|
|
71
102
|
signal: controller.signal,
|
|
72
103
|
});
|
|
73
104
|
const text = await res.text();
|
|
105
|
+
// PR-CLI-SERVER-VERSION-HANDSHAKE: cache server-recommended +
|
|
106
|
+
// server-version headers so UpdateBanner / `pugi doctor` can
|
|
107
|
+
// surface them, then short-circuit on 426 by throwing
|
|
108
|
+
// PugiCliUpgradeRequiredError. The throw bubbles to the
|
|
109
|
+
// top-level catch in index.ts which renders the upgrade banner.
|
|
110
|
+
// The getter shim handles both real `Response` (`.headers.get`)
|
|
111
|
+
// and minimal fixture/stub responses (`.headers?.[name]`) so
|
|
112
|
+
// existing transport tests that mock `fetch` with `{status, text}`
|
|
113
|
+
// don't need to grow a Headers polyfill just to keep passing.
|
|
114
|
+
//
|
|
115
|
+
// Cache poison guard: skip the inspect step on 426. A hostile
|
|
116
|
+
// upstream (proxy with a compromised cert pin, or a transient MITM
|
|
117
|
+
// on a coffee-shop network) could otherwise forge an
|
|
118
|
+
// `X-Pugi-Cli-Upgrade-Recommended` header alongside a 426 status
|
|
119
|
+
// and poison `cachedServerRecommendation` for the rest of the REPL
|
|
120
|
+
// session — `UpdateBanner` would then surface attacker-chosen
|
|
121
|
+
// version strings to the operator. The 426 body still carries the
|
|
122
|
+
// legitimate `recommendedVersion` field, which assertNotUpgrade-
|
|
123
|
+
// Required parses + throws with, so the operator-facing banner
|
|
124
|
+
// remains accurate via the error path.
|
|
125
|
+
if (res.status !== 426) {
|
|
126
|
+
inspectVersionResponse((name) => {
|
|
127
|
+
const h = res.headers;
|
|
128
|
+
if (h && typeof h.get === 'function') {
|
|
129
|
+
return h.get(name);
|
|
130
|
+
}
|
|
131
|
+
if (h && typeof h === 'object') {
|
|
132
|
+
const lowered = h[name.toLowerCase()];
|
|
133
|
+
return lowered ?? null;
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
assertNotUpgradeRequired(res.status, text, PUGI_CLI_VERSION);
|
|
74
139
|
if (res.status === 200) {
|
|
75
140
|
try {
|
|
76
141
|
const json = JSON.parse(text);
|
|
@@ -119,6 +184,55 @@ export class AnvilEngineLoopClient {
|
|
|
119
184
|
};
|
|
120
185
|
}
|
|
121
186
|
if (res.status === 401 || res.status === 403) {
|
|
187
|
+
// 403 has two distinct causes:
|
|
188
|
+
// 1. genuinely invalid / expired token (auth_missing) — the
|
|
189
|
+
// old default.
|
|
190
|
+
// 2. tenant authenticated successfully but the privacy mode
|
|
191
|
+
// (strict / balanced policy) refused upstream LLM dispatch.
|
|
192
|
+
// The admin-api returns
|
|
193
|
+
// `{ code: 'privacy_strict_upstream_blocked', mode, model,
|
|
194
|
+
// message: '...switch via pugi config set privacy=...' }`.
|
|
195
|
+
// Reported as `auth_missing` the user runs `pugi login`
|
|
196
|
+
// again, which does nothing — the actual fix is a privacy-
|
|
197
|
+
// mode change. Parse the body and route accordingly.
|
|
198
|
+
// (2026-05-27 P0.3 — dogfood surfaced this on /api/pugi/engine
|
|
199
|
+
// for a strict-mode tenant; see memory feedback_no_fake_dispatch_promises
|
|
200
|
+
// for the broader "misleading error" pattern.)
|
|
201
|
+
try {
|
|
202
|
+
const parsed = text ? JSON.parse(text) : null;
|
|
203
|
+
// dogfood cycle 2: distinct error code for the
|
|
204
|
+
// infra-side "PII scrubber down" case. Previously the engine
|
|
205
|
+
// server returned `privacy_strict_upstream_blocked` here even
|
|
206
|
+
// when the tenant was on BALANCED (the scrubber crash forced
|
|
207
|
+
// a fail-closed). Operators chased the wrong fix ("switch
|
|
208
|
+
// privacy") for hours. Server now emits
|
|
209
|
+
// `pii_scrubber_unavailable` — surface a distinct remediation
|
|
210
|
+
// that points at the infra side, not the operator's privacy
|
|
211
|
+
// posture.
|
|
212
|
+
if (parsed?.code === 'pii_scrubber_unavailable') {
|
|
213
|
+
return {
|
|
214
|
+
stop: 'error',
|
|
215
|
+
code: 'privacy_blocked',
|
|
216
|
+
message: parsed.message ?? 'PII scrubber unavailable; privacy filter refused dispatch.',
|
|
217
|
+
remediation: 'Infra-side issue (not your tenant privacy mode). Wait for ops to restore ' +
|
|
218
|
+
'the PiiScrubberService, OR temporarily switch your tenant to permissive via ' +
|
|
219
|
+
'`pugi config set privacy=permissive`.',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (parsed?.code === 'privacy_strict_upstream_blocked' || parsed?.code === 'privacy_blocked') {
|
|
223
|
+
return {
|
|
224
|
+
stop: 'error',
|
|
225
|
+
code: 'privacy_blocked',
|
|
226
|
+
message: parsed.message ?? 'Tenant privacy mode forbids upstream LLM dispatch.',
|
|
227
|
+
remediation: 'pugi config set privacy=balanced — OR configure a self-hosted Anvil model.',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Body not JSON — fall through to the generic auth_missing
|
|
233
|
+
// branch below; the 200-char text echo on `failed` will at
|
|
234
|
+
// least give the operator the raw response to triage.
|
|
235
|
+
}
|
|
122
236
|
return {
|
|
123
237
|
stop: 'error',
|
|
124
238
|
code: 'auth_missing',
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crude token-count heuristic mirroring `runEngineLoop`'s fallback
|
|
3
|
+
* accounting (transcript char count / 4). The CLI does not have access
|
|
4
|
+
* to a real tokenizer pre-flight — the runtime returns `usage.totalTokens`
|
|
5
|
+
* only on the server response, which is too late for our pre-turn gate.
|
|
6
|
+
* char/4 is in the right order of magnitude for English/TS and matches
|
|
7
|
+
* what the loop's own fallback uses on `tokensUsed === 0` upstream.
|
|
8
|
+
*/
|
|
9
|
+
export function estimateTranscriptTokens(messages) {
|
|
10
|
+
let chars = 0;
|
|
11
|
+
for (const m of messages) {
|
|
12
|
+
chars += m.content.length;
|
|
13
|
+
const calls = m.toolCalls ?? [];
|
|
14
|
+
for (const c of calls) {
|
|
15
|
+
chars += c.name.length + c.arguments.length;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return Math.ceil(chars / 4);
|
|
19
|
+
}
|
|
20
|
+
const FILE_TOOL_NAMES = new Set([
|
|
21
|
+
'read',
|
|
22
|
+
'write',
|
|
23
|
+
'edit',
|
|
24
|
+
'multi_edit',
|
|
25
|
+
'multiEdit',
|
|
26
|
+
]);
|
|
27
|
+
/**
|
|
28
|
+
* Walk the dropped slice and pull out tool-call metadata. We parse the
|
|
29
|
+
* `arguments` JSON best-effort — a bad parse is harmless here because
|
|
30
|
+
* the executor surfaced the canonical error to the model already; the
|
|
31
|
+
* gist just under-counts that one call.
|
|
32
|
+
*/
|
|
33
|
+
export function summarizeDroppedTurns(dropped) {
|
|
34
|
+
let toolCalls = 0;
|
|
35
|
+
let bashCalls = 0;
|
|
36
|
+
const files = new Set();
|
|
37
|
+
for (const m of dropped) {
|
|
38
|
+
if (m.role === 'assistant') {
|
|
39
|
+
const calls = m.toolCalls ?? [];
|
|
40
|
+
toolCalls += calls.length;
|
|
41
|
+
for (const c of calls) {
|
|
42
|
+
if (c.name === 'bash') {
|
|
43
|
+
bashCalls += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (FILE_TOOL_NAMES.has(c.name)) {
|
|
47
|
+
const p = extractPath(c.arguments);
|
|
48
|
+
if (p)
|
|
49
|
+
files.add(p);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
toolCalls,
|
|
56
|
+
fileCount: files.size,
|
|
57
|
+
bashCalls,
|
|
58
|
+
messagesDropped: dropped.length,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function extractPath(rawArgs) {
|
|
62
|
+
if (!rawArgs)
|
|
63
|
+
return null;
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(rawArgs);
|
|
66
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
67
|
+
const obj = parsed;
|
|
68
|
+
const path = obj['path'] ?? obj['filePath'];
|
|
69
|
+
if (typeof path === 'string' && path.length > 0)
|
|
70
|
+
return path;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Format the deterministic gist string spliced into the synthetic
|
|
80
|
+
* system message. Stable shape so spec assertions and operator
|
|
81
|
+
* logs do not drift turn-over-turn.
|
|
82
|
+
*/
|
|
83
|
+
export function renderAutoCompactSentinel(stats) {
|
|
84
|
+
return (`[auto-compact] Earlier turns ` +
|
|
85
|
+
`(${stats.toolCalls} tool calls, ${stats.fileCount} files read, ${stats.bashCalls} bash commands) ` +
|
|
86
|
+
`summarized to free transcript headroom. ` +
|
|
87
|
+
`Recent turns and the original task remain in context; ` +
|
|
88
|
+
`re-read any earlier file by name if you need its contents again.`);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Minimum transcript length (in messages) before compact is allowed.
|
|
92
|
+
* We always retain `system + user` (the first 2) + the last 2 turns,
|
|
93
|
+
* so anything <= 4 messages has nothing in the middle to drop.
|
|
94
|
+
* Compacting на 4-message transcript would either be a no-op or
|
|
95
|
+
* accidentally drop the user's original task.
|
|
96
|
+
*/
|
|
97
|
+
export const MIN_COMPACT_TRANSCRIPT_LENGTH = 5;
|
|
98
|
+
/**
|
|
99
|
+
* Pure gate. Returns `compact` when ALL of:
|
|
100
|
+
* - `config.enabled` is true
|
|
101
|
+
* - estimated transcript tokens >= `thresholdRatio * maxTokens`
|
|
102
|
+
* - transcript length >= 5 (need history to drop)
|
|
103
|
+
*/
|
|
104
|
+
export function evaluateAutoCompactDecision(input) {
|
|
105
|
+
const usedTokens = estimateTranscriptTokens(input.transcript);
|
|
106
|
+
if (!input.config.enabled) {
|
|
107
|
+
return { kind: 'skip', reason: 'disabled', usedTokens };
|
|
108
|
+
}
|
|
109
|
+
if (input.transcript.length < MIN_COMPACT_TRANSCRIPT_LENGTH) {
|
|
110
|
+
return { kind: 'skip', reason: 'transcript-too-short', usedTokens };
|
|
111
|
+
}
|
|
112
|
+
const thresholdTokens = Math.floor(input.config.thresholdRatio * input.maxTokens);
|
|
113
|
+
if (usedTokens < thresholdTokens) {
|
|
114
|
+
return { kind: 'skip', reason: 'below-threshold', usedTokens };
|
|
115
|
+
}
|
|
116
|
+
return { kind: 'compact', usedTokens, thresholdTokens };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Rewrite the transcript: keep the first two messages (system + user
|
|
120
|
+
* task), drop the middle (assistant + tool turns), insert a synthetic
|
|
121
|
+
* system sentinel summarizing what was dropped, then re-append the
|
|
122
|
+
* last 2 messages so the model has the most-recent tool result + its
|
|
123
|
+
* own last reply in full fidelity.
|
|
124
|
+
*
|
|
125
|
+
* Precondition: caller has already checked the decision is `compact`
|
|
126
|
+
* (length >= MIN_COMPACT_TRANSCRIPT_LENGTH). The function still guards
|
|
127
|
+
* with a defensive identity-return on shorter transcripts so a careless
|
|
128
|
+
* caller cannot corrupt the prefix.
|
|
129
|
+
*/
|
|
130
|
+
export function compactTranscript(transcript) {
|
|
131
|
+
const preUsedTokens = estimateTranscriptTokens(transcript);
|
|
132
|
+
if (transcript.length < MIN_COMPACT_TRANSCRIPT_LENGTH) {
|
|
133
|
+
return {
|
|
134
|
+
transcript: transcript.slice(),
|
|
135
|
+
droppedCount: 0,
|
|
136
|
+
gist: '',
|
|
137
|
+
stats: { toolCalls: 0, fileCount: 0, bashCalls: 0, messagesDropped: 0 },
|
|
138
|
+
preUsedTokens,
|
|
139
|
+
postUsedTokens: preUsedTokens,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// Always retain: index 0 (system) + index 1 (original user task) +
|
|
143
|
+
// last 2 messages. The middle slice is what gets summarised.
|
|
144
|
+
const head = transcript.slice(0, 2);
|
|
145
|
+
const tail = transcript.slice(-2);
|
|
146
|
+
const middle = transcript.slice(2, -2);
|
|
147
|
+
const stats = summarizeDroppedTurns(middle);
|
|
148
|
+
const gist = renderAutoCompactSentinel(stats);
|
|
149
|
+
const sentinelMessage = {
|
|
150
|
+
role: 'system',
|
|
151
|
+
content: gist,
|
|
152
|
+
};
|
|
153
|
+
const next = [...head, sentinelMessage, ...tail];
|
|
154
|
+
const postUsedTokens = estimateTranscriptTokens(next);
|
|
155
|
+
return {
|
|
156
|
+
transcript: next,
|
|
157
|
+
droppedCount: middle.length,
|
|
158
|
+
gist,
|
|
159
|
+
stats,
|
|
160
|
+
preUsedTokens,
|
|
161
|
+
postUsedTokens,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Convenience composer used by `runEngineLoop`: evaluate → compact in
|
|
166
|
+
* one shot. Returns `null` when the decision was `skip` so the loop
|
|
167
|
+
* driver can branch cheaply без destructuring two layers of records.
|
|
168
|
+
*/
|
|
169
|
+
export function maybeCompact(transcript, maxTokens, config) {
|
|
170
|
+
const decision = evaluateAutoCompactDecision({
|
|
171
|
+
transcript,
|
|
172
|
+
maxTokens,
|
|
173
|
+
config,
|
|
174
|
+
});
|
|
175
|
+
if (decision.kind === 'skip')
|
|
176
|
+
return null;
|
|
177
|
+
return compactTranscript(transcript);
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=auto-compact.js.map
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-compact (mid-loop transcript summarization) default trip point as
|
|
3
|
+
* a fraction of the per-command `maxTokens` envelope. CEO P1 #14 (CC
|
|
4
|
+
* parity): when transcript char-count tokens cross 75% of the budget,
|
|
5
|
+
* the engine loop drops the middle turns and inserts a deterministic
|
|
6
|
+
* `[auto-compact]` sentinel so the loop can continue без the model
|
|
7
|
+
* tripping the `budget_exhausted` terminal status mid-build.
|
|
8
|
+
*
|
|
9
|
+
* Empirically — `pugi code "big refactor"` hits the 80k cap on turn 4-5
|
|
10
|
+
* and refuses to finish; `pugi fix` does the same at 50k. Auto-compact
|
|
11
|
+
* keeps the recent N turns + a one-line gist of the dropped tool calls
|
|
12
|
+
* so the model retains the most recent state without paying for the
|
|
13
|
+
* full prefix.
|
|
14
|
+
*
|
|
15
|
+
* Operators can opt out / retune via `.pugi/settings.json`:
|
|
16
|
+
*
|
|
17
|
+
* {
|
|
18
|
+
* "autoCompact": { "enabled": true, "thresholdRatio": 0.75 }
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Bad values fall back silently to the default — the engine loop never
|
|
22
|
+
* crashes on a malformed settings field (mirrors `resolveBudget`).
|
|
23
|
+
*/
|
|
24
|
+
export const AUTO_COMPACT_THRESHOLD_RATIO = 0.75;
|
|
25
|
+
export const DEFAULT_AUTO_COMPACT_CONFIG = {
|
|
26
|
+
enabled: true,
|
|
27
|
+
thresholdRatio: AUTO_COMPACT_THRESHOLD_RATIO,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Pull the auto-compact override from `.pugi/settings.json`. Uses the
|
|
31
|
+
* same defensive-cast pattern as `readSettingsBudget` so an unknown
|
|
32
|
+
* field shape silently falls back к defaults (the gate is a comfort
|
|
33
|
+
* feature; a malformed settings line must not break the engine loop).
|
|
34
|
+
*
|
|
35
|
+
* Returns the merged config — caller never sees `undefined`.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveAutoCompactConfig(settings) {
|
|
38
|
+
if (!settings)
|
|
39
|
+
return DEFAULT_AUTO_COMPACT_CONFIG;
|
|
40
|
+
const root = settings.autoCompact;
|
|
41
|
+
if (!root || typeof root !== 'object' || Array.isArray(root)) {
|
|
42
|
+
return DEFAULT_AUTO_COMPACT_CONFIG;
|
|
43
|
+
}
|
|
44
|
+
const r = root;
|
|
45
|
+
const enabledRaw = r['enabled'];
|
|
46
|
+
const thresholdRaw = r['thresholdRatio'];
|
|
47
|
+
const enabled = typeof enabledRaw === 'boolean'
|
|
48
|
+
? enabledRaw
|
|
49
|
+
: DEFAULT_AUTO_COMPACT_CONFIG.enabled;
|
|
50
|
+
let thresholdRatio = DEFAULT_AUTO_COMPACT_CONFIG.thresholdRatio;
|
|
51
|
+
if (typeof thresholdRaw === 'number' && Number.isFinite(thresholdRaw)) {
|
|
52
|
+
if (thresholdRaw > 0 && thresholdRaw <= 1) {
|
|
53
|
+
thresholdRatio = thresholdRaw;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { enabled, thresholdRatio };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* β1 defaults. Source of truth for the per-command budget envelope.
|
|
60
|
+
* The runtime is allowed to look these up directly (no need to round
|
|
61
|
+
* trip through settings.json when no override is in play).
|
|
62
|
+
*
|
|
63
|
+
* bump (post-Wave-7 hooks-v2 + 6-perm-modes + auto-classifier
|
|
64
|
+
* added ~12K tokens of system-prompt + tools-schema overhead per turn):
|
|
65
|
+
* `code` 30k → 80k и `fix` 30k → 50k so a single-file refactor on a
|
|
66
|
+
* 1000-line source file no longer exhausts the budget on turn 2.
|
|
67
|
+
* Empirical: smoke `pugi code "сделай snake.html"` on beta.37 burned
|
|
68
|
+
* 36k/30k after 2 tool calls; beta.36 same task closed in 8k. The Wave-7
|
|
69
|
+
* additions are good (the upstream tool parity), but the budget cap did not move with
|
|
70
|
+
* them. the upstream tool's `code` default is ~80k; matching that restores headroom.
|
|
71
|
+
*/
|
|
72
|
+
export const beta1DefaultBudgets = {
|
|
73
|
+
// bump (CEO #44, post-auto-compact #14 + prompt-cache #15):
|
|
74
|
+
// auto-compact at 75% threshold + cache_control on stable prefix mean
|
|
75
|
+
// real per-call token use is ~30-40% lower than legacy. Bump headroom
|
|
76
|
+
// so multi-file refactors no longer trip the cap. Anvil clamps per-call
|
|
77
|
+
// max_tokens to 128k (PR) so the engine envelope still safe.
|
|
78
|
+
fix: { maxTokens: 80_000, maxToolCalls: 20 },
|
|
79
|
+
code: { maxTokens: 120_000, maxToolCalls: 25 },
|
|
80
|
+
build: { maxTokens: 200_000, maxToolCalls: 30 },
|
|
81
|
+
plan: { maxTokens: 200_000, maxToolCalls: 8 },
|
|
82
|
+
explain: { maxTokens: 60_000, maxToolCalls: 10 },
|
|
83
|
+
review_triple: { maxTokens: 100_000, maxToolCalls: 10 },
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Hard upper bounds. Anything above this is treated as user error
|
|
87
|
+
* (likely a typo or misplaced decimal) and rejected by
|
|
88
|
+
* `assertBudgetWithinTier`. Stops a careless settings.json edit from
|
|
89
|
+
* silently authorising a 100M-token run.
|
|
90
|
+
*/
|
|
91
|
+
export const HARD_MAX_TOKENS = 5_000_000;
|
|
92
|
+
export const HARD_MAX_TOOL_CALLS = 500;
|
|
93
|
+
/**
|
|
94
|
+
* Compute the effective budget for a given command, applying:
|
|
95
|
+
* 1. β1 defaults
|
|
96
|
+
* 2. settings.json `budgets.<command>` partial overrides
|
|
97
|
+
* 3. task-level override (caller-provided, e.g. CLI `--max-tokens`,
|
|
98
|
+
* `--max-turns`, or the resolved intensity profile)
|
|
99
|
+
*
|
|
100
|
+
* Throws `BudgetConfigError` when the resolved budget exceeds the
|
|
101
|
+
* HARD_MAX_* caps so misconfigured settings.json fails fast.
|
|
102
|
+
*
|
|
103
|
+
* Triple-review P1 follow-up : `maxTurns` propagates through this
|
|
104
|
+
* resolver so the SDK's `runEngineLoop` actually receives the per-tier
|
|
105
|
+
* cap that the intensity dial advertises. Override precedence matches
|
|
106
|
+
* the other knobs: explicit task override > settings.json > undefined
|
|
107
|
+
* (legacy behaviour: no turn cap, only tokens + tool-calls).
|
|
108
|
+
*/
|
|
109
|
+
export function resolveBudget(command, settings, override) {
|
|
110
|
+
const base = beta1DefaultBudgets[command];
|
|
111
|
+
const settingsBudget = readSettingsBudget(settings, command);
|
|
112
|
+
const resolvedMaxTurns = override?.maxTurns ?? settingsBudget?.maxTurns ?? undefined;
|
|
113
|
+
const resolved = {
|
|
114
|
+
maxTokens: override?.maxTokens ??
|
|
115
|
+
settingsBudget?.maxTokens ??
|
|
116
|
+
base.maxTokens,
|
|
117
|
+
maxToolCalls: override?.maxToolCalls ??
|
|
118
|
+
settingsBudget?.maxToolCalls ??
|
|
119
|
+
base.maxToolCalls,
|
|
120
|
+
...(resolvedMaxTurns !== undefined ? { maxTurns: resolvedMaxTurns } : {}),
|
|
121
|
+
};
|
|
122
|
+
assertBudgetWithinTier(command, resolved);
|
|
123
|
+
return resolved;
|
|
124
|
+
}
|
|
125
|
+
export class BudgetConfigError extends Error {
|
|
126
|
+
constructor(message) {
|
|
127
|
+
super(message);
|
|
128
|
+
this.name = 'BudgetConfigError';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Hard upper bound on per-task LLM turns. Above this we reject the
|
|
133
|
+
* configuration as a likely typo. The marathon intensity preset is 200,
|
|
134
|
+
* so 1000 buys an order of magnitude of headroom for power-user overrides
|
|
135
|
+
* without authorising runaway loops.
|
|
136
|
+
*/
|
|
137
|
+
export const HARD_MAX_TURNS = 1000;
|
|
138
|
+
export function assertBudgetWithinTier(command, budget) {
|
|
139
|
+
if (!Number.isFinite(budget.maxTokens) || budget.maxTokens <= 0) {
|
|
140
|
+
throw new BudgetConfigError(`budget[${command}].maxTokens must be a positive number, got ${budget.maxTokens}`);
|
|
141
|
+
}
|
|
142
|
+
if (!Number.isFinite(budget.maxToolCalls) || budget.maxToolCalls <= 0) {
|
|
143
|
+
throw new BudgetConfigError(`budget[${command}].maxToolCalls must be a positive number, got ${budget.maxToolCalls}`);
|
|
144
|
+
}
|
|
145
|
+
if (budget.maxTokens > HARD_MAX_TOKENS) {
|
|
146
|
+
throw new BudgetConfigError(`budget[${command}].maxTokens=${budget.maxTokens} exceeds hard cap ${HARD_MAX_TOKENS}`);
|
|
147
|
+
}
|
|
148
|
+
if (budget.maxToolCalls > HARD_MAX_TOOL_CALLS) {
|
|
149
|
+
throw new BudgetConfigError(`budget[${command}].maxToolCalls=${budget.maxToolCalls} exceeds hard cap ${HARD_MAX_TOOL_CALLS}`);
|
|
150
|
+
}
|
|
151
|
+
if (budget.maxTurns !== undefined) {
|
|
152
|
+
if (!Number.isFinite(budget.maxTurns) || budget.maxTurns <= 0) {
|
|
153
|
+
throw new BudgetConfigError(`budget[${command}].maxTurns must be a positive number, got ${budget.maxTurns}`);
|
|
154
|
+
}
|
|
155
|
+
if (budget.maxTurns > HARD_MAX_TURNS) {
|
|
156
|
+
throw new BudgetConfigError(`budget[${command}].maxTurns=${budget.maxTurns} exceeds hard cap ${HARD_MAX_TURNS}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Pull a settings.json budget override for the given command, with
|
|
162
|
+
* defensive typing. `PugiSettings` does not yet declare `budgets`
|
|
163
|
+
* formally (β1 is the first sprint to land it) so we cast via unknown
|
|
164
|
+
* and validate each field at the boundary.
|
|
165
|
+
*/
|
|
166
|
+
function readSettingsBudget(settings, command) {
|
|
167
|
+
if (!settings)
|
|
168
|
+
return undefined;
|
|
169
|
+
const root = settings.budgets;
|
|
170
|
+
if (!root || typeof root !== 'object' || Array.isArray(root))
|
|
171
|
+
return undefined;
|
|
172
|
+
const map = root;
|
|
173
|
+
const entry = map[command];
|
|
174
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
175
|
+
return undefined;
|
|
176
|
+
const e = entry;
|
|
177
|
+
const out = {};
|
|
178
|
+
if (typeof e['maxTokens'] === 'number')
|
|
179
|
+
out.maxTokens = e['maxTokens'];
|
|
180
|
+
if (typeof e['maxToolCalls'] === 'number')
|
|
181
|
+
out.maxToolCalls = e['maxToolCalls'];
|
|
182
|
+
if (typeof e['maxTurns'] === 'number')
|
|
183
|
+
out.maxTurns = e['maxTurns'];
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=budgets.js.map
|