pi-usereq 0.7.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +2 -0
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/docs/pi.dev/coding-agent-docs/custom-provider.md +42 -1
- package/docs/pi.dev/coding-agent-docs/extensions.md +302 -28
- package/docs/pi.dev/coding-agent-docs/keybindings.md +23 -1
- package/docs/pi.dev/coding-agent-docs/models.md +3 -0
- package/docs/pi.dev/coding-agent-docs/packages.md +2 -2
- package/docs/pi.dev/coding-agent-docs/prompt-templates.md +21 -0
- package/docs/pi.dev/coding-agent-docs/providers.md +1 -0
- package/docs/pi.dev/coding-agent-docs/rpc.md +36 -6
- package/docs/pi.dev/coding-agent-docs/sdk.md +30 -5
- package/docs/pi.dev/coding-agent-docs/session.md +1 -1
- package/docs/pi.dev/coding-agent-docs/settings.md +2 -1
- package/docs/pi.dev/coding-agent-docs/skills.md +12 -12
- package/docs/pi.dev/coding-agent-docs/tree.md +2 -0
- package/docs/pi.dev/coding-agent-docs/tui.md +31 -0
- package/docs/pi.dev/mom-docs/new.md +4 -4
- package/package.json +1 -1
- package/pi-usereq/docs/REFERENCES.md +5184 -24470
- package/pi-usereq/docs/REQUIREMENTS.md +300 -166
- package/pi-usereq/docs/WORKFLOW.md +303 -314
- package/pi.dev-src/pi-mono/agent/src/agent-loop.ts +683 -0
- package/pi.dev-src/pi-mono/agent/src/agent.ts +543 -0
- package/pi.dev-src/pi-mono/agent/src/index.ts +8 -0
- package/pi.dev-src/pi-mono/agent/src/proxy.ts +367 -0
- package/pi.dev-src/pi-mono/agent/src/types.ts +364 -0
- package/pi.dev-src/pi-mono/agent/test/agent-loop.test.ts +1181 -0
- package/pi.dev-src/pi-mono/agent/test/agent.test.ts +463 -0
- package/pi.dev-src/pi-mono/agent/test/e2e.test.ts +404 -0
- package/pi.dev-src/pi-mono/agent/test/utils/calculate.ts +32 -0
- package/pi.dev-src/pi-mono/agent/test/utils/get-current-time.ts +46 -0
- package/pi.dev-src/pi-mono/agent/vitest.config.ts +9 -0
- package/pi.dev-src/pi-mono/ai/bedrock-provider.d.ts +1 -0
- package/pi.dev-src/pi-mono/ai/bedrock-provider.js +1 -0
- package/pi.dev-src/pi-mono/ai/scripts/generate-models.ts +1635 -0
- package/pi.dev-src/pi-mono/ai/scripts/generate-test-image.ts +34 -0
- package/pi.dev-src/pi-mono/ai/src/api-registry.ts +98 -0
- package/pi.dev-src/pi-mono/ai/src/bedrock-provider.ts +6 -0
- package/pi.dev-src/pi-mono/ai/src/cli.ts +133 -0
- package/pi.dev-src/pi-mono/ai/src/env-api-keys.ts +134 -0
- package/pi.dev-src/pi-mono/ai/src/index.ts +35 -0
- package/pi.dev-src/pi-mono/ai/src/models.generated.ts +14928 -0
- package/pi.dev-src/pi-mono/ai/src/models.ts +82 -0
- package/pi.dev-src/pi-mono/ai/src/oauth.ts +1 -0
- package/pi.dev-src/pi-mono/ai/src/providers/amazon-bedrock.ts +929 -0
- package/pi.dev-src/pi-mono/ai/src/providers/anthropic.ts +1125 -0
- package/pi.dev-src/pi-mono/ai/src/providers/azure-openai-responses.ts +253 -0
- package/pi.dev-src/pi-mono/ai/src/providers/faux.ts +499 -0
- package/pi.dev-src/pi-mono/ai/src/providers/github-copilot-headers.ts +37 -0
- package/pi.dev-src/pi-mono/ai/src/providers/google-gemini-cli.ts +996 -0
- package/pi.dev-src/pi-mono/ai/src/providers/google-shared.ts +354 -0
- package/pi.dev-src/pi-mono/ai/src/providers/google-vertex.ts +543 -0
- package/pi.dev-src/pi-mono/ai/src/providers/google.ts +500 -0
- package/pi.dev-src/pi-mono/ai/src/providers/mistral.ts +629 -0
- package/pi.dev-src/pi-mono/ai/src/providers/openai-codex-responses.ts +984 -0
- package/pi.dev-src/pi-mono/ai/src/providers/openai-completions.ts +1063 -0
- package/pi.dev-src/pi-mono/ai/src/providers/openai-responses-shared.ts +539 -0
- package/pi.dev-src/pi-mono/ai/src/providers/openai-responses.ts +264 -0
- package/pi.dev-src/pi-mono/ai/src/providers/register-builtins.ts +433 -0
- package/pi.dev-src/pi-mono/ai/src/providers/simple-options.ts +47 -0
- package/pi.dev-src/pi-mono/ai/src/providers/transform-messages.ts +220 -0
- package/pi.dev-src/pi-mono/ai/src/stream.ts +59 -0
- package/pi.dev-src/pi-mono/ai/src/types.ts +417 -0
- package/pi.dev-src/pi-mono/ai/src/utils/event-stream.ts +87 -0
- package/pi.dev-src/pi-mono/ai/src/utils/hash.ts +13 -0
- package/pi.dev-src/pi-mono/ai/src/utils/headers.ts +7 -0
- package/pi.dev-src/pi-mono/ai/src/utils/json-parse.ts +124 -0
- package/pi.dev-src/pi-mono/ai/src/utils/overflow.ts +138 -0
- package/pi.dev-src/pi-mono/ai/src/utils/sanitize-unicode.ts +25 -0
- package/pi.dev-src/pi-mono/ai/src/utils/typebox-helpers.ts +24 -0
- package/pi.dev-src/pi-mono/ai/src/utils/validation.ts +324 -0
- package/pi.dev-src/pi-mono/ai/test/abort.test.ts +260 -0
- package/pi.dev-src/pi-mono/ai/test/anthropic-oauth.test.ts +99 -0
- package/pi.dev-src/pi-mono/ai/test/anthropic-opus-4-7-smoke.test.ts +72 -0
- package/pi.dev-src/pi-mono/ai/test/anthropic-sse-parsing.test.ts +113 -0
- package/pi.dev-src/pi-mono/ai/test/anthropic-thinking-disable.test.ts +152 -0
- package/pi.dev-src/pi-mono/ai/test/anthropic-tool-name-normalization.test.ts +205 -0
- package/pi.dev-src/pi-mono/ai/test/azure-utils.ts +28 -0
- package/pi.dev-src/pi-mono/ai/test/bedrock-endpoint-resolution.test.ts +131 -0
- package/pi.dev-src/pi-mono/ai/test/bedrock-models.test.ts +66 -0
- package/pi.dev-src/pi-mono/ai/test/bedrock-thinking-payload.test.ts +107 -0
- package/pi.dev-src/pi-mono/ai/test/bedrock-utils.ts +18 -0
- package/pi.dev-src/pi-mono/ai/test/cache-retention.test.ts +335 -0
- package/pi.dev-src/pi-mono/ai/test/context-overflow.test.ts +714 -0
- package/pi.dev-src/pi-mono/ai/test/cross-provider-handoff.test.ts +458 -0
- package/pi.dev-src/pi-mono/ai/test/data/red-circle.png +0 -0
- package/pi.dev-src/pi-mono/ai/test/empty.test.ts +742 -0
- package/pi.dev-src/pi-mono/ai/test/faux-provider.test.ts +597 -0
- package/pi.dev-src/pi-mono/ai/test/fireworks-models.test.ts +49 -0
- package/pi.dev-src/pi-mono/ai/test/github-copilot-anthropic.test.ts +108 -0
- package/pi.dev-src/pi-mono/ai/test/github-copilot-oauth.test.ts +196 -0
- package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-claude-thinking-header.test.ts +103 -0
- package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-empty-stream.test.ts +108 -0
- package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-retry-delay.test.ts +53 -0
- package/pi.dev-src/pi-mono/ai/test/google-shared-convert-tools.test.ts +187 -0
- package/pi.dev-src/pi-mono/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts +172 -0
- package/pi.dev-src/pi-mono/ai/test/google-shared-image-tool-result-routing.test.ts +124 -0
- package/pi.dev-src/pi-mono/ai/test/google-thinking-disable.test.ts +183 -0
- package/pi.dev-src/pi-mono/ai/test/google-thinking-signature.test.ts +38 -0
- package/pi.dev-src/pi-mono/ai/test/google-tool-call-missing-args.test.ts +105 -0
- package/pi.dev-src/pi-mono/ai/test/google-vertex-api-key-resolution.test.ts +144 -0
- package/pi.dev-src/pi-mono/ai/test/image-tool-result.test.ts +481 -0
- package/pi.dev-src/pi-mono/ai/test/interleaved-thinking.test.ts +145 -0
- package/pi.dev-src/pi-mono/ai/test/lazy-module-load.test.ts +102 -0
- package/pi.dev-src/pi-mono/ai/test/mistral-reasoning-mode.test.ts +66 -0
- package/pi.dev-src/pi-mono/ai/test/mistral-tool-schema.test.ts +61 -0
- package/pi.dev-src/pi-mono/ai/test/oauth.ts +89 -0
- package/pi.dev-src/pi-mono/ai/test/openai-codex-cache-affinity-e2e.test.ts +36 -0
- package/pi.dev-src/pi-mono/ai/test/openai-codex-stream.test.ts +686 -0
- package/pi.dev-src/pi-mono/ai/test/openai-completions-cache-control-format.test.ts +189 -0
- package/pi.dev-src/pi-mono/ai/test/openai-completions-prompt-cache.test.ts +198 -0
- package/pi.dev-src/pi-mono/ai/test/openai-completions-thinking-as-text.test.ts +214 -0
- package/pi.dev-src/pi-mono/ai/test/openai-completions-tool-choice.test.ts +560 -0
- package/pi.dev-src/pi-mono/ai/test/openai-completions-tool-result-images.test.ts +101 -0
- package/pi.dev-src/pi-mono/ai/test/openai-responses-cache-affinity-e2e.test.ts +32 -0
- package/pi.dev-src/pi-mono/ai/test/openai-responses-copilot-provider.test.ts +128 -0
- package/pi.dev-src/pi-mono/ai/test/openai-responses-foreign-toolcall-id.test.ts +66 -0
- package/pi.dev-src/pi-mono/ai/test/openai-responses-partial-json-cleanup.test.ts +101 -0
- package/pi.dev-src/pi-mono/ai/test/openai-responses-reasoning-replay-e2e.test.ts +292 -0
- package/pi.dev-src/pi-mono/ai/test/openai-responses-tool-result-images.test.ts +200 -0
- package/pi.dev-src/pi-mono/ai/test/openrouter-cache-write-repro.test.ts +78 -0
- package/pi.dev-src/pi-mono/ai/test/overflow.test.ts +64 -0
- package/pi.dev-src/pi-mono/ai/test/responseid.test.ts +145 -0
- package/pi.dev-src/pi-mono/ai/test/stream.test.ts +1405 -0
- package/pi.dev-src/pi-mono/ai/test/supports-xhigh.test.ts +34 -0
- package/pi.dev-src/pi-mono/ai/test/tokens.test.ts +304 -0
- package/pi.dev-src/pi-mono/ai/test/tool-call-id-normalization.test.ts +291 -0
- package/pi.dev-src/pi-mono/ai/test/tool-call-without-result.test.ts +313 -0
- package/pi.dev-src/pi-mono/ai/test/total-tokens.test.ts +695 -0
- package/pi.dev-src/pi-mono/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts +191 -0
- package/pi.dev-src/pi-mono/ai/test/unicode-surrogate.test.ts +754 -0
- package/pi.dev-src/pi-mono/ai/test/validation.test.ts +116 -0
- package/pi.dev-src/pi-mono/ai/test/xhigh.test.ts +71 -0
- package/pi.dev-src/pi-mono/ai/test/zen.test.ts +25 -0
- package/pi.dev-src/pi-mono/ai/vitest.config.ts +9 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/antigravity-image-gen.ts +418 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/bash-spawn-hook.ts +30 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/bookmark.ts +50 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/built-in-tool-renderer.ts +249 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/claude-rules.ts +86 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/commands.ts +72 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/confirm-destructive.ts +59 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-compaction.ts +127 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-footer.ts +64 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-header.ts +73 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-anthropic/index.ts +604 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-qwen-cli/index.ts +345 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dirty-repo-guard.ts +56 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-component.ts +132 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-keys.ts +104 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/index.ts +74 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/wad-finder.ts +51 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/dynamic.json +79 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/dynamic.md +5 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/index.ts +15 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-tools.ts +74 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/event-bus.ts +43 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/file-trigger.ts +41 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/git-checkpoint.ts +53 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/github-issue-autocomplete.ts +185 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/handoff.ts +153 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/hello.ts +26 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/hidden-thinking-label.ts +53 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/inline-bash.ts +94 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/input-transform.ts +43 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/interactive-shell.ts +196 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/mac-system-theme.ts +47 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/message-renderer.ts +59 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/minimal-mode.ts +426 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/modal-editor.ts +85 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/model-status.ts +31 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/notify.ts +55 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/overlay-qa-tests.ts +1348 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/overlay-test.ts +150 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/permission-gate.ts +34 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/pirate.ts +47 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/plan-mode/index.ts +340 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/plan-mode/utils.ts +168 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/preset.ts +430 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/prompt-customizer.ts +97 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/protected-paths.ts +30 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/provider-payload.ts +18 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/qna.ts +122 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/question.ts +264 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/questionnaire.ts +427 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/rainbow-editor.ts +88 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/reload-runtime.ts +37 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/rpc-demo.ts +118 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/sandbox/index.ts +321 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/sandbox/package-lock.json +92 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/send-user-message.ts +97 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/session-name.ts +27 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/shutdown-command.ts +63 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/snake.ts +343 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/space-invaders.ts +560 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/ssh.ts +220 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/status-line.ts +32 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/structured-output.ts +65 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/planner.md +37 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/scout.md +50 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/worker.md +24 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents.ts +126 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/index.ts +987 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/implement.md +10 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/summarize.ts +206 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/system-prompt-header.ts +17 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tic-tac-toe.ts +1008 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/timed-confirm.ts +70 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/titlebar-spinner.ts +58 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/todo.ts +297 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tool-override.ts +144 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tools.ts +141 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/trigger-compact.ts +50 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/truncated-tool.ts +195 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/widget-placement.ts +9 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/with-deps/index.ts +32 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/with-deps/package-lock.json +31 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/working-indicator.ts +123 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/extensions/working-message-test.ts +25 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/rpc-extension-ui.ts +632 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/01-minimal.ts +22 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/02-custom-model.ts +49 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/03-custom-prompt.ts +62 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/04-skills.ts +55 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/05-tools.ts +44 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/06-extensions.ts +90 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/07-context-files.ts +42 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/08-prompt-templates.ts +51 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/09-api-keys-and-oauth.ts +48 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/10-settings.ts +53 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/11-sessions.ts +48 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/12-full-control.ts +73 -0
- package/pi.dev-src/pi-mono/coding-agent/examples/sdk/13-session-runtime.ts +67 -0
- package/pi.dev-src/pi-mono/coding-agent/scripts/migrate-sessions.sh +93 -0
- package/pi.dev-src/pi-mono/coding-agent/src/bun/cli.ts +6 -0
- package/pi.dev-src/pi-mono/coding-agent/src/bun/register-bedrock.ts +4 -0
- package/pi.dev-src/pi-mono/coding-agent/src/cli/args.ts +335 -0
- package/pi.dev-src/pi-mono/coding-agent/src/cli/config-selector.ts +52 -0
- package/pi.dev-src/pi-mono/coding-agent/src/cli/file-processor.ts +100 -0
- package/pi.dev-src/pi-mono/coding-agent/src/cli/initial-message.ts +43 -0
- package/pi.dev-src/pi-mono/coding-agent/src/cli/list-models.ts +110 -0
- package/pi.dev-src/pi-mono/coding-agent/src/cli/session-picker.ts +52 -0
- package/pi.dev-src/pi-mono/coding-agent/src/cli.ts +17 -0
- package/pi.dev-src/pi-mono/coding-agent/src/config.ts +261 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session-runtime.ts +394 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session-services.ts +196 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session.ts +3096 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/auth-storage.ts +493 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/bash-executor.ts +154 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/branch-summarization.ts +355 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/compaction.ts +839 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/index.ts +7 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/utils.ts +170 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/defaults.ts +3 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/diagnostics.ts +15 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/event-bus.ts +33 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/exec.ts +107 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/ansi-to-html.ts +258 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/index.ts +314 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.css +1017 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.html +55 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.js +1770 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/tool-renderer.ts +158 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/vendor/marked.min.js +6 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/index.ts +171 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/loader.ts +606 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/runner.ts +1019 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/types.ts +1542 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/wrapper.ts +30 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/footer-data-provider.ts +354 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/index.ts +77 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/keybindings.ts +370 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/messages.ts +195 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/model-registry.ts +858 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/model-resolver.ts +629 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/output-guard.ts +74 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/package-manager.ts +2408 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/prompt-templates.ts +296 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/resolve-config-value.ts +142 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/resource-loader.ts +917 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/sdk.ts +380 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/session-cwd.ts +59 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/session-manager.ts +1425 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/settings-manager.ts +999 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/skills.ts +508 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/slash-commands.ts +39 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/source-info.ts +40 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/system-prompt.ts +172 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/telemetry.ts +13 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/timings.ts +31 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/bash.ts +448 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/edit-diff.ts +445 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/edit.ts +487 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/file-mutation-queue.ts +39 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/find.ts +370 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/grep.ts +384 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/index.ts +196 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/ls.ts +229 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/path-utils.ts +94 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/read.ts +273 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/render-utils.ts +64 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/tool-definition-wrapper.ts +45 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/truncate.ts +265 -0
- package/pi.dev-src/pi-mono/coding-agent/src/core/tools/write.ts +281 -0
- package/pi.dev-src/pi-mono/coding-agent/src/index.ts +352 -0
- package/pi.dev-src/pi-mono/coding-agent/src/main.ts +731 -0
- package/pi.dev-src/pi-mono/coding-agent/src/migrations.ts +314 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/index.ts +9 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/assets/clankolas.png +0 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/armin.ts +382 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/assistant-message.ts +147 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/bash-execution.ts +218 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/bordered-loader.ts +68 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/branch-summary-message.ts +58 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/compaction-summary-message.ts +59 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/config-selector.ts +592 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/countdown-timer.ts +38 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/custom-editor.ts +80 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/custom-message.ts +99 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/daxnuts.ts +164 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/diff.ts +147 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/earendil-announcement.ts +53 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-editor.ts +147 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-input.ts +87 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-selector.ts +107 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/footer.ts +220 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/index.ts +32 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/keybinding-hints.ts +24 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/login-dialog.ts +178 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/model-selector.ts +338 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/oauth-selector.ts +121 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/scoped-models-selector.ts +355 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/session-selector-search.ts +194 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/session-selector.ts +1026 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/settings-selector.ts +456 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/show-images-selector.ts +50 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/skill-invocation-message.ts +55 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/theme-selector.ts +67 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/thinking-selector.ts +74 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/tool-execution.ts +352 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/tree-selector.ts +1246 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/user-message-selector.ts +155 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/user-message.ts +35 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/interactive-mode.ts +5155 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/dark.json +85 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/light.json +84 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/theme-schema.json +335 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/theme.ts +1141 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/print-mode.ts +158 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/jsonl.ts +58 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-client.ts +515 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-mode.ts +745 -0
- package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-types.ts +264 -0
- package/pi.dev-src/pi-mono/coding-agent/src/package-manager-cli.ts +270 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/changelog.ts +99 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/child-process.ts +86 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard-image.ts +300 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard-native.ts +22 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard.ts +81 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/exif-orientation.ts +183 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/frontmatter.ts +39 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/fs-watch.ts +30 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/git.ts +192 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/image-convert.ts +41 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/image-resize.ts +176 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/mime.ts +30 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/paths.ts +20 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/photon.ts +139 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/shell.ts +210 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/sleep.ts +18 -0
- package/pi.dev-src/pi-mono/coding-agent/src/utils/tools-manager.ts +287 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-auto-compaction-queue.test.ts +437 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-branching.test.ts +156 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-compaction.test.ts +208 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-concurrent.test.ts +618 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-dynamic-provider.test.ts +117 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-dynamic-tools.test.ts +182 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-retry.test.ts +318 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-runtime-events.test.ts +208 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-stats.test.ts +143 -0
- package/pi.dev-src/pi-mono/coding-agent/test/agent-session-tree-navigation.test.ts +323 -0
- package/pi.dev-src/pi-mono/coding-agent/test/args.test.ts +328 -0
- package/pi.dev-src/pi-mono/coding-agent/test/assistant-message.test.ts +57 -0
- package/pi.dev-src/pi-mono/coding-agent/test/auth-storage.test.ts +462 -0
- package/pi.dev-src/pi-mono/coding-agent/test/bash-close-hang-windows.test.ts +126 -0
- package/pi.dev-src/pi-mono/coding-agent/test/bash-execution-width.test.ts +80 -0
- package/pi.dev-src/pi-mono/coding-agent/test/block-images.test.ts +120 -0
- package/pi.dev-src/pi-mono/coding-agent/test/clipboard-image-bmp-conversion.test.ts +88 -0
- package/pi.dev-src/pi-mono/coding-agent/test/clipboard-image.test.ts +136 -0
- package/pi.dev-src/pi-mono/coding-agent/test/compaction-extensions-example.test.ts +66 -0
- package/pi.dev-src/pi-mono/coding-agent/test/compaction-extensions.test.ts +415 -0
- package/pi.dev-src/pi-mono/coding-agent/test/compaction-serialization.test.ts +79 -0
- package/pi.dev-src/pi-mono/coding-agent/test/compaction-summary-reasoning.test.ts +118 -0
- package/pi.dev-src/pi-mono/coding-agent/test/compaction-thinking-model.test.ts +223 -0
- package/pi.dev-src/pi-mono/coding-agent/test/compaction.test.ts +550 -0
- package/pi.dev-src/pi-mono/coding-agent/test/config.test.ts +30 -0
- package/pi.dev-src/pi-mono/coding-agent/test/edit-tool-legacy-input.test.ts +116 -0
- package/pi.dev-src/pi-mono/coding-agent/test/edit-tool-no-full-redraw.test.ts +235 -0
- package/pi.dev-src/pi-mono/coding-agent/test/export-html-whitespace.test.ts +12 -0
- package/pi.dev-src/pi-mono/coding-agent/test/export-html-xss.test.ts +28 -0
- package/pi.dev-src/pi-mono/coding-agent/test/extensions-discovery.test.ts +463 -0
- package/pi.dev-src/pi-mono/coding-agent/test/extensions-input-event.test.ts +112 -0
- package/pi.dev-src/pi-mono/coding-agent/test/extensions-runner.test.ts +805 -0
- package/pi.dev-src/pi-mono/coding-agent/test/file-mutation-queue.test.ts +163 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/assistant-message-with-thinking-code.json +33 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/before-compaction.jsonl +1003 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/empty-agent/.gitkeep +0 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/empty-cwd/.gitkeep +0 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/large-session.jsonl +1019 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/consecutive-hyphens/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/disable-model-invocation/SKILL.md +9 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/invalid-name-chars/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/invalid-yaml/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/long-name/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/missing-description/SKILL.md +7 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/multiline-description/SKILL.md +11 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/name-mismatch/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/nested/child-skill/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/no-frontmatter/SKILL.md +3 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/root-skill-preferred/SKILL.md +3 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/root-skill-preferred/nested-child/SKILL.md +3 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/unknown-field/SKILL.md +10 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/valid-skill/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills-collision/first/calendar/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills-collision/second/calendar/SKILL.md +8 -0
- package/pi.dev-src/pi-mono/coding-agent/test/footer-data-provider.test.ts +265 -0
- package/pi.dev-src/pi-mono/coding-agent/test/footer-width.test.ts +115 -0
- package/pi.dev-src/pi-mono/coding-agent/test/frontmatter.test.ts +60 -0
- package/pi.dev-src/pi-mono/coding-agent/test/git-ssh-url.test.ts +78 -0
- package/pi.dev-src/pi-mono/coding-agent/test/git-update.test.ts +411 -0
- package/pi.dev-src/pi-mono/coding-agent/test/image-processing.test.ts +145 -0
- package/pi.dev-src/pi-mono/coding-agent/test/image-resize-callers.test.ts +53 -0
- package/pi.dev-src/pi-mono/coding-agent/test/initial-message.test.ts +48 -0
- package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-anthropic-warning.test.ts +73 -0
- package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-clone-command.test.ts +77 -0
- package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-compaction.test.ts +57 -0
- package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-import-command.test.ts +144 -0
- package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-status.test.ts +635 -0
- package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-suspend.test.ts +149 -0
- package/pi.dev-src/pi-mono/coding-agent/test/keybindings-migration.test.ts +88 -0
- package/pi.dev-src/pi-mono/coding-agent/test/model-registry.test.ts +1209 -0
- package/pi.dev-src/pi-mono/coding-agent/test/model-resolver.test.ts +436 -0
- package/pi.dev-src/pi-mono/coding-agent/test/package-command-paths.test.ts +144 -0
- package/pi.dev-src/pi-mono/coding-agent/test/package-manager-ssh.test.ts +97 -0
- package/pi.dev-src/pi-mono/coding-agent/test/package-manager.test.ts +1795 -0
- package/pi.dev-src/pi-mono/coding-agent/test/path-utils.test.ts +162 -0
- package/pi.dev-src/pi-mono/coding-agent/test/plan-mode-utils.test.ts +261 -0
- package/pi.dev-src/pi-mono/coding-agent/test/print-mode.test.ts +140 -0
- package/pi.dev-src/pi-mono/coding-agent/test/prompt-templates.test.ts +515 -0
- package/pi.dev-src/pi-mono/coding-agent/test/resource-loader.test.ts +576 -0
- package/pi.dev-src/pi-mono/coding-agent/test/rpc-client-clone.test.ts +29 -0
- package/pi.dev-src/pi-mono/coding-agent/test/rpc-example.ts +86 -0
- package/pi.dev-src/pi-mono/coding-agent/test/rpc-jsonl.test.ts +65 -0
- package/pi.dev-src/pi-mono/coding-agent/test/rpc-prompt-response-semantics.test.ts +285 -0
- package/pi.dev-src/pi-mono/coding-agent/test/rpc.test.ts +321 -0
- package/pi.dev-src/pi-mono/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts +364 -0
- package/pi.dev-src/pi-mono/coding-agent/test/sdk-openrouter-attribution.test.ts +181 -0
- package/pi.dev-src/pi-mono/coding-agent/test/sdk-session-manager.test.ts +95 -0
- package/pi.dev-src/pi-mono/coding-agent/test/sdk-skills.test.ts +109 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-cwd.test.ts +91 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-info-modified-timestamp.test.ts +83 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-manager/build-context.test.ts +268 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-manager/custom-session-id.test.ts +109 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-manager/file-operations.test.ts +208 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-manager/labels.test.ts +198 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-manager/migration.test.ts +78 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-manager/save-entry.test.ts +55 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-manager/tree-traversal.test.ts +533 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-selector-path-delete.test.ts +315 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-selector-rename.test.ts +111 -0
- package/pi.dev-src/pi-mono/coding-agent/test/session-selector-search.test.ts +195 -0
- package/pi.dev-src/pi-mono/coding-agent/test/settings-manager-bug.test.ts +147 -0
- package/pi.dev-src/pi-mono/coding-agent/test/settings-manager.test.ts +319 -0
- package/pi.dev-src/pi-mono/coding-agent/test/skills.test.ts +432 -0
- package/pi.dev-src/pi-mono/coding-agent/test/stdout-cleanliness.test.ts +103 -0
- package/pi.dev-src/pi-mono/coding-agent/test/streaming-render-debug.ts +97 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-bash-persistence.test.ts +242 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-compaction.test.ts +343 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-model-extension.test.ts +325 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-prompt.test.ts +323 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-queue.test.ts +422 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-retry-events.test.ts +358 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-runtime.test.ts +553 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/harness.ts +208 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2023-queued-slash-command-followup.test.ts +80 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2753-reload-stale-resource-settings.test.ts +100 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2781-skill-collision-precedence.test.ts +120 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2791-fswatch-error-crash.test.ts +106 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2835-tools-allowlist-filters-extension-tools.test.ts +94 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2860-replaced-session-context.test.ts +274 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3217-scoped-model-order.test.ts +104 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3302-find-path-glob.test.ts +72 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3303-find-nested-gitignore.test.ts +83 -0
- package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3317-network-connection-lost-retry.test.ts +33 -0
- package/pi.dev-src/pi-mono/coding-agent/test/system-prompt.test.ts +102 -0
- package/pi.dev-src/pi-mono/coding-agent/test/test-harness.test.ts +321 -0
- package/pi.dev-src/pi-mono/coding-agent/test/test-harness.ts +446 -0
- package/pi.dev-src/pi-mono/coding-agent/test/test-theme-colors.ts +246 -0
- package/pi.dev-src/pi-mono/coding-agent/test/theme-export.test.ts +104 -0
- package/pi.dev-src/pi-mono/coding-agent/test/tool-execution-component.test.ts +331 -0
- package/pi.dev-src/pi-mono/coding-agent/test/tools.test.ts +889 -0
- package/pi.dev-src/pi-mono/coding-agent/test/tree-selector.test.ts +655 -0
- package/pi.dev-src/pi-mono/coding-agent/test/trigger-compact-extension.test.ts +57 -0
- package/pi.dev-src/pi-mono/coding-agent/test/truncate-to-width.test.ts +81 -0
- package/pi.dev-src/pi-mono/coding-agent/test/user-message.test.ts +25 -0
- package/pi.dev-src/pi-mono/coding-agent/test/utilities.ts +316 -0
- package/pi.dev-src/pi-mono/coding-agent/tsconfig.examples.json +16 -0
- package/pi.dev-src/pi-mono/coding-agent/vitest.config.ts +26 -0
- package/pi.dev-src/pi-mono/mom/dev.sh +30 -0
- package/pi.dev-src/pi-mono/mom/docker.sh +95 -0
- package/pi.dev-src/pi-mono/mom/scripts/migrate-timestamps.ts +121 -0
- package/pi.dev-src/pi-mono/mom/src/agent.ts +882 -0
- package/pi.dev-src/pi-mono/mom/src/context.ts +180 -0
- package/pi.dev-src/pi-mono/mom/src/download.ts +117 -0
- package/pi.dev-src/pi-mono/mom/src/events.ts +442 -0
- package/pi.dev-src/pi-mono/mom/src/fs-watch.ts +30 -0
- package/pi.dev-src/pi-mono/mom/src/log.ts +271 -0
- package/pi.dev-src/pi-mono/mom/src/main.ts +367 -0
- package/pi.dev-src/pi-mono/mom/src/sandbox.ts +221 -0
- package/pi.dev-src/pi-mono/mom/src/slack.ts +623 -0
- package/pi.dev-src/pi-mono/mom/src/store.ts +234 -0
- package/pi.dev-src/pi-mono/mom/src/tools/attach.ts +47 -0
- package/pi.dev-src/pi-mono/mom/src/tools/bash.ts +97 -0
- package/pi.dev-src/pi-mono/mom/src/tools/edit.ts +165 -0
- package/pi.dev-src/pi-mono/mom/src/tools/index.ts +19 -0
- package/pi.dev-src/pi-mono/mom/src/tools/read.ts +159 -0
- package/pi.dev-src/pi-mono/mom/src/tools/truncate.ts +236 -0
- package/pi.dev-src/pi-mono/mom/src/tools/write.ts +45 -0
- package/pi.dev-src/pi-mono/mom/test/events.test.ts +50 -0
- package/pi.dev-src/pi-mono/pods/scripts/model_run.sh +83 -0
- package/pi.dev-src/pi-mono/pods/scripts/pod_setup.sh +336 -0
- package/pi.dev-src/pi-mono/pods/src/cli.ts +360 -0
- package/pi.dev-src/pi-mono/pods/src/commands/models.ts +753 -0
- package/pi.dev-src/pi-mono/pods/src/commands/pods.ts +205 -0
- package/pi.dev-src/pi-mono/pods/src/commands/prompt.ts +84 -0
- package/pi.dev-src/pi-mono/pods/src/config.ts +80 -0
- package/pi.dev-src/pi-mono/pods/src/index.ts +2 -0
- package/pi.dev-src/pi-mono/pods/src/model-configs.ts +111 -0
- package/pi.dev-src/pi-mono/pods/src/models.json +295 -0
- package/pi.dev-src/pi-mono/pods/src/ssh.ts +151 -0
- package/pi.dev-src/pi-mono/pods/src/types.ts +27 -0
- package/pi.dev-src/pi-mono/tui/src/autocomplete.ts +783 -0
- package/pi.dev-src/pi-mono/tui/src/components/box.ts +137 -0
- package/pi.dev-src/pi-mono/tui/src/components/cancellable-loader.ts +40 -0
- package/pi.dev-src/pi-mono/tui/src/components/editor.ts +2280 -0
- package/pi.dev-src/pi-mono/tui/src/components/image.ts +104 -0
- package/pi.dev-src/pi-mono/tui/src/components/input.ts +503 -0
- package/pi.dev-src/pi-mono/tui/src/components/loader.ts +86 -0
- package/pi.dev-src/pi-mono/tui/src/components/markdown.ts +852 -0
- package/pi.dev-src/pi-mono/tui/src/components/select-list.ts +229 -0
- package/pi.dev-src/pi-mono/tui/src/components/settings-list.ts +250 -0
- package/pi.dev-src/pi-mono/tui/src/components/spacer.ts +28 -0
- package/pi.dev-src/pi-mono/tui/src/components/text.ts +106 -0
- package/pi.dev-src/pi-mono/tui/src/components/truncated-text.ts +65 -0
- package/pi.dev-src/pi-mono/tui/src/editor-component.ts +74 -0
- package/pi.dev-src/pi-mono/tui/src/fuzzy.ts +133 -0
- package/pi.dev-src/pi-mono/tui/src/index.ts +106 -0
- package/pi.dev-src/pi-mono/tui/src/keybindings.ts +244 -0
- package/pi.dev-src/pi-mono/tui/src/keys.ts +1400 -0
- package/pi.dev-src/pi-mono/tui/src/kill-ring.ts +46 -0
- package/pi.dev-src/pi-mono/tui/src/stdin-buffer.ts +386 -0
- package/pi.dev-src/pi-mono/tui/src/terminal-image.ts +414 -0
- package/pi.dev-src/pi-mono/tui/src/terminal.ts +373 -0
- package/pi.dev-src/pi-mono/tui/src/tui.ts +1243 -0
- package/pi.dev-src/pi-mono/tui/src/undo-stack.ts +28 -0
- package/pi.dev-src/pi-mono/tui/src/utils.ts +1085 -0
- package/pi.dev-src/pi-mono/tui/test/autocomplete.test.ts +542 -0
- package/pi.dev-src/pi-mono/tui/test/bug-regression-isimageline-startswith-bug.test.ts +237 -0
- package/pi.dev-src/pi-mono/tui/test/chat-simple.ts +129 -0
- package/pi.dev-src/pi-mono/tui/test/editor.test.ts +3812 -0
- package/pi.dev-src/pi-mono/tui/test/fuzzy.test.ts +98 -0
- package/pi.dev-src/pi-mono/tui/test/image-test.ts +56 -0
- package/pi.dev-src/pi-mono/tui/test/input.test.ts +580 -0
- package/pi.dev-src/pi-mono/tui/test/key-tester.ts +105 -0
- package/pi.dev-src/pi-mono/tui/test/keybindings.test.ts +38 -0
- package/pi.dev-src/pi-mono/tui/test/keys.test.ts +614 -0
- package/pi.dev-src/pi-mono/tui/test/markdown.test.ts +1236 -0
- package/pi.dev-src/pi-mono/tui/test/overlay-non-capturing.test.ts +609 -0
- package/pi.dev-src/pi-mono/tui/test/overlay-options.test.ts +538 -0
- package/pi.dev-src/pi-mono/tui/test/overlay-short-content.test.ts +56 -0
- package/pi.dev-src/pi-mono/tui/test/regression-regional-indicator-width.test.ts +52 -0
- package/pi.dev-src/pi-mono/tui/test/select-list.test.ts +116 -0
- package/pi.dev-src/pi-mono/tui/test/stdin-buffer.test.ts +422 -0
- package/pi.dev-src/pi-mono/tui/test/terminal-image.test.ts +279 -0
- package/pi.dev-src/pi-mono/tui/test/test-themes.ts +38 -0
- package/pi.dev-src/pi-mono/tui/test/truncate-to-width.test.ts +62 -0
- package/pi.dev-src/pi-mono/tui/test/truncated-text.test.ts +129 -0
- package/pi.dev-src/pi-mono/tui/test/tui-cell-size-input.test.ts +81 -0
- package/pi.dev-src/pi-mono/tui/test/tui-overlay-style-leak.test.ts +72 -0
- package/pi.dev-src/pi-mono/tui/test/tui-render.test.ts +509 -0
- package/pi.dev-src/pi-mono/tui/test/viewport-overwrite-repro.ts +106 -0
- package/pi.dev-src/pi-mono/tui/test/virtual-terminal.ts +218 -0
- package/pi.dev-src/pi-mono/tui/test/wrap-ansi.test.ts +207 -0
- package/pi.dev-src/pi-mono/tui/vitest.config.ts +7 -0
- package/pi.dev-src/pi-mono/web-ui/example/index.html +13 -0
- package/pi.dev-src/pi-mono/web-ui/example/src/app.css +1 -0
- package/pi.dev-src/pi-mono/web-ui/example/src/custom-messages.ts +99 -0
- package/pi.dev-src/pi-mono/web-ui/example/src/main.ts +421 -0
- package/pi.dev-src/pi-mono/web-ui/example/tsconfig.json +23 -0
- package/pi.dev-src/pi-mono/web-ui/example/vite.config.ts +6 -0
- package/pi.dev-src/pi-mono/web-ui/scripts/count-prompt-tokens.ts +88 -0
- package/pi.dev-src/pi-mono/web-ui/src/ChatPanel.ts +209 -0
- package/pi.dev-src/pi-mono/web-ui/src/app.css +68 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/AgentInterface.ts +403 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/AttachmentTile.ts +107 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/ConsoleBlock.ts +72 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/CustomProviderCard.ts +100 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/ExpandableSection.ts +46 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/Input.ts +113 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/MessageEditor.ts +405 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/MessageList.ts +98 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/Messages.ts +383 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/ProviderKeyInput.ts +153 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/SandboxedIframe.ts +626 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/StreamingMessageContainer.ts +103 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/ThinkingBlock.ts +43 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/message-renderer-registry.ts +28 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/ArtifactsRuntimeProvider.ts +220 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/ConsoleRuntimeProvider.ts +186 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
- package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/SandboxRuntimeProvider.ts +52 -0
- package/pi.dev-src/pi-mono/web-ui/src/dialogs/ApiKeyPromptDialog.ts +75 -0
- package/pi.dev-src/pi-mono/web-ui/src/dialogs/AttachmentOverlay.ts +636 -0
- package/pi.dev-src/pi-mono/web-ui/src/dialogs/CustomProviderDialog.ts +274 -0
- package/pi.dev-src/pi-mono/web-ui/src/dialogs/ModelSelector.ts +370 -0
- package/pi.dev-src/pi-mono/web-ui/src/dialogs/PersistentStorageDialog.ts +144 -0
- package/pi.dev-src/pi-mono/web-ui/src/dialogs/ProvidersModelsTab.ts +212 -0
- package/pi.dev-src/pi-mono/web-ui/src/dialogs/SessionListDialog.ts +150 -0
- package/pi.dev-src/pi-mono/web-ui/src/dialogs/SettingsDialog.ts +218 -0
- package/pi.dev-src/pi-mono/web-ui/src/index.ts +120 -0
- package/pi.dev-src/pi-mono/web-ui/src/prompts/prompts.ts +282 -0
- package/pi.dev-src/pi-mono/web-ui/src/storage/app-storage.ts +60 -0
- package/pi.dev-src/pi-mono/web-ui/src/storage/backends/indexeddb-storage-backend.ts +193 -0
- package/pi.dev-src/pi-mono/web-ui/src/storage/store.ts +33 -0
- package/pi.dev-src/pi-mono/web-ui/src/storage/stores/custom-providers-store.ts +62 -0
- package/pi.dev-src/pi-mono/web-ui/src/storage/stores/provider-keys-store.ts +33 -0
- package/pi.dev-src/pi-mono/web-ui/src/storage/stores/sessions-store.ts +136 -0
- package/pi.dev-src/pi-mono/web-ui/src/storage/stores/settings-store.ts +34 -0
- package/pi.dev-src/pi-mono/web-ui/src/storage/types.ts +206 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ArtifactElement.ts +14 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ArtifactPill.ts +26 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/Console.ts +93 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/DocxArtifact.ts +213 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ExcelArtifact.ts +231 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/GenericArtifact.ts +117 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/HtmlArtifact.ts +195 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ImageArtifact.ts +116 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/MarkdownArtifact.ts +82 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/PdfArtifact.ts +201 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/SvgArtifact.ts +119 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/TextArtifact.ts +148 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/artifacts-tool-renderer.ts +310 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/artifacts.ts +713 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/index.ts +7 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/extract-document.ts +275 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/index.ts +46 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/javascript-repl.ts +293 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/renderer-registry.ts +130 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/BashRenderer.ts +52 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/CalculateRenderer.ts +58 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/DefaultRenderer.ts +103 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/GetCurrentTimeRenderer.ts +92 -0
- package/pi.dev-src/pi-mono/web-ui/src/tools/types.ts +15 -0
- package/pi.dev-src/pi-mono/web-ui/src/utils/attachment-utils.ts +472 -0
- package/pi.dev-src/pi-mono/web-ui/src/utils/auth-token.ts +22 -0
- package/pi.dev-src/pi-mono/web-ui/src/utils/format.ts +42 -0
- package/pi.dev-src/pi-mono/web-ui/src/utils/i18n.ts +653 -0
- package/pi.dev-src/pi-mono/web-ui/src/utils/model-discovery.ts +277 -0
- package/pi.dev-src/pi-mono/web-ui/src/utils/proxy-utils.ts +139 -0
- package/pi.dev-src/pi-mono/web-ui/src/utils/test-sessions.ts +2357 -0
- package/pi.dev-src/pi-mono/web-ui/tsconfig.json +7 -0
- package/scripts/lib/extension-debug-harness.ts +3 -10
- package/scripts/lib/recording-extension-api.ts +71 -2
- package/scripts/pi-usereq-debug.sh +1 -1
- package/scripts/tool-args-to-params.ts +4 -15
- package/src/cli.ts +24 -68
- package/src/core/agent-tool-json.ts +53 -239
- package/src/core/compress-payload.ts +4 -74
- package/src/core/compress.ts +26 -15
- package/src/core/config.ts +468 -42
- package/src/core/debug-runtime.ts +542 -0
- package/src/core/extension-status.ts +296 -46
- package/src/core/find-constructs.ts +3 -3
- package/src/core/find-payload.ts +26 -132
- package/src/core/path-context.ts +346 -81
- package/src/core/pi-notify.ts +115 -5
- package/src/core/pi-usereq-tools.ts +57 -19
- package/src/core/prompt-command-catalog.ts +46 -0
- package/src/core/prompt-command-runtime.ts +1650 -0
- package/src/core/prompt-command-state.ts +211 -0
- package/src/core/prompts.ts +173 -41
- package/src/core/reference-payload.ts +5 -63
- package/src/core/resources.ts +61 -5
- package/src/core/settings-menu.ts +12 -6
- package/src/core/source-analyzer.ts +27 -7
- package/src/core/token-counter.ts +6 -76
- package/src/core/tool-runner.ts +38 -317
- package/src/index.ts +2062 -950
- package/src/resources/instructions/git_commit.md +16 -0
- package/src/resources/instructions/git_read-only.md +2 -0
- package/src/resources/prompts/analyze.md +12 -13
- package/src/resources/prompts/change.md +21 -59
- package/src/resources/prompts/check.md +14 -16
- package/src/resources/prompts/cover.md +21 -59
- package/src/resources/prompts/create.md +5 -4
- package/src/resources/prompts/fix.md +20 -58
- package/src/resources/prompts/flowchart.md +14 -49
- package/src/resources/prompts/implement.md +14 -50
- package/src/resources/prompts/new.md +20 -58
- package/src/resources/prompts/readme.md +14 -50
- package/src/resources/prompts/recreate.md +14 -45
- package/src/resources/prompts/refactor.md +20 -58
- package/src/resources/prompts/references.md +7 -43
- package/src/resources/prompts/renumber.md +8 -39
- package/src/resources/prompts/workflow.md +14 -50
- package/src/resources/prompts/write.md +5 -4
- package/tests/attended-results-scenarios.ts +30 -136
- package/tests/cli-command-option-parity.test.ts +148 -192
- package/tests/debug-extension-harness.test.ts +69 -68
- package/tests/extension-registration.test.ts +3118 -657
- package/tests/fixtures_attended_results/project/compress-line-numbers.json +1 -1
- package/tests/fixtures_attended_results/project/compress.json +1 -1
- package/tests/fixtures_attended_results/project/find-line-numbers.json +1 -1
- package/tests/fixtures_attended_results/project/find.json +1 -1
- package/tests/fixtures_attended_results/project/references.json +2 -2
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_go.go.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_go.go.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-find/fixture_go.go.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_go.go.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_c.c.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_cpp.cpp.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_csharp.cs.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_elixir.ex.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_go.go.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_haskell.hs.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_java.java.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_javascript.js.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_kotlin.kt.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_lua.lua.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_perl.pl.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_php.php.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_python.py.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_ruby.rb.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_rust.rs.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_scala.scala.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_shell.sh.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_swift.swift.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_typescript.ts.json +1 -1
- package/tests/fixtures_attended_results/standalone/files-references/fixture_zig.zig.json +1 -1
- package/tests/helpers.ts +24 -15
- package/tests/oracle-project.test.ts +29 -30
- package/tests/oracle-standalone.test.ts +27 -1
- package/tests/prompt-rendering.test.ts +40 -9
- package/tests/python_oracle_stubs/tiktoken.py +76 -0
package/src/index.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* @brief Declares the extension version string.
|
|
9
9
|
* @details The value is exported for external inspection and packaging metadata alignment. Access complexity is O(1).
|
|
10
10
|
*/
|
|
11
|
-
export const VERSION = "0.
|
|
11
|
+
export const VERSION = "0.10.0";
|
|
12
12
|
|
|
13
|
+
import fs from "node:fs";
|
|
13
14
|
import path from "node:path";
|
|
14
15
|
import type {
|
|
15
16
|
AgentEndEvent,
|
|
@@ -18,47 +19,26 @@ import type {
|
|
|
18
19
|
ExtensionContext,
|
|
19
20
|
ToolInfo,
|
|
20
21
|
} from "@mariozechner/pi-coding-agent";
|
|
22
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
21
23
|
import { Type } from "@sinclair/typebox";
|
|
22
24
|
import {
|
|
23
|
-
|
|
24
|
-
TOKEN_COUNTER_ENCODING,
|
|
25
|
-
type TokenToolPayload,
|
|
26
|
-
} from "./core/token-counter.js";
|
|
27
|
-
import {
|
|
28
|
-
buildDocsCheckToolPayload,
|
|
29
|
-
buildGitCheckToolPayload,
|
|
30
|
-
buildPathQueryToolPayload,
|
|
31
|
-
buildStaticCheckToolPayload,
|
|
32
|
-
buildStructuredToolExecuteResult,
|
|
33
|
-
buildToolExecutionSection,
|
|
34
|
-
buildWorktreeMutationToolPayload,
|
|
35
|
-
buildWorktreeNameToolPayload,
|
|
25
|
+
buildMonolithicToolExecuteResult,
|
|
36
26
|
normalizeToolFailure,
|
|
37
27
|
} from "./core/agent-tool-json.js";
|
|
28
|
+
import type { FindToolScope } from "./core/find-payload.js";
|
|
38
29
|
import {
|
|
39
|
-
|
|
40
|
-
buildReferenceToolPayload,
|
|
41
|
-
type ReferenceToolPayload,
|
|
42
|
-
} from "./core/reference-payload.js";
|
|
43
|
-
import {
|
|
44
|
-
buildCompressToolExecutionStderr,
|
|
45
|
-
buildCompressToolPayload,
|
|
46
|
-
type CompressToolPayload,
|
|
47
|
-
} from "./core/compress-payload.js";
|
|
48
|
-
import {
|
|
49
|
-
buildFindToolExecutionStderr,
|
|
50
|
-
buildFindToolPayload,
|
|
51
|
-
type FindToolPayload,
|
|
52
|
-
type FindToolScope,
|
|
53
|
-
} from "./core/find-payload.js";
|
|
54
|
-
import {
|
|
30
|
+
DEFAULT_GIT_WORKTREE_PREFIX,
|
|
55
31
|
DEFAULT_SRC_DIRS,
|
|
32
|
+
createStaticCheckLanguageConfig,
|
|
56
33
|
getDefaultConfig,
|
|
34
|
+
getDefaultStaticCheckConfig,
|
|
57
35
|
getProjectConfigPath,
|
|
58
36
|
loadConfig,
|
|
59
37
|
normalizeConfigPaths,
|
|
38
|
+
resolveEffectiveGitWorktreeEnabled,
|
|
60
39
|
saveConfig,
|
|
61
40
|
type StaticCheckEntry,
|
|
41
|
+
type StaticCheckLanguageConfig,
|
|
62
42
|
type UseReqConfig,
|
|
63
43
|
} from "./core/config.js";
|
|
64
44
|
import {
|
|
@@ -69,47 +49,101 @@ import {
|
|
|
69
49
|
DEFAULT_PI_NOTIFY_SOUND_LOW_CMD,
|
|
70
50
|
DEFAULT_PI_NOTIFY_SOUND_MID_CMD,
|
|
71
51
|
cyclePiNotifySoundLevel,
|
|
52
|
+
formatPiNotifyControlSequenceText,
|
|
72
53
|
formatPiNotifyPushoverStatus,
|
|
73
54
|
formatPiNotifyStatus,
|
|
55
|
+
hasPiNotifyPushoverCredentials,
|
|
74
56
|
normalizePiNotifyCommand,
|
|
75
57
|
normalizePiNotifyPushoverCredential,
|
|
76
58
|
normalizePiNotifyPushoverPriority,
|
|
77
59
|
normalizePiNotifyTemplateValue,
|
|
60
|
+
parsePiNotifyControlSequenceText,
|
|
78
61
|
runPiNotifyEffects,
|
|
79
62
|
type PiNotifyEventRequest,
|
|
80
63
|
type PiNotifyPushoverPriority,
|
|
81
64
|
type PiNotifySoundLevel,
|
|
82
65
|
} from "./core/pi-notify.js";
|
|
83
|
-
import {
|
|
84
|
-
|
|
66
|
+
import {
|
|
67
|
+
bootstrapRuntimePathState,
|
|
68
|
+
formatRuntimePathForDisplay,
|
|
69
|
+
getRuntimeBasePath,
|
|
70
|
+
getRuntimeContextPath,
|
|
71
|
+
setRuntimeGitPath,
|
|
72
|
+
} from "./core/path-context.js";
|
|
85
73
|
import { showPiUsereqSettingsMenu, type PiUsereqSettingsMenuChoice } from "./core/settings-menu.js";
|
|
86
74
|
import {
|
|
75
|
+
comparePiUsereqStartupToolNames,
|
|
76
|
+
PI_USEREQ_CUSTOM_TOOL_NAMES,
|
|
77
|
+
PI_USEREQ_EMBEDDED_TOOL_NAMES,
|
|
87
78
|
PI_USEREQ_STARTUP_TOOL_SET,
|
|
88
79
|
isPiUsereqEmbeddedToolName,
|
|
89
80
|
normalizeEnabledPiUsereqTools,
|
|
81
|
+
type PiUsereqStartupToolName,
|
|
90
82
|
} from "./core/pi-usereq-tools.js";
|
|
91
83
|
import { renderPrompt } from "./core/prompts.js";
|
|
92
|
-
import {
|
|
84
|
+
import {
|
|
85
|
+
abortPromptCommandExecution,
|
|
86
|
+
activatePromptCommandExecution,
|
|
87
|
+
classifyPromptCommandOutcome,
|
|
88
|
+
finalizePromptCommandExecution,
|
|
89
|
+
getPromptCommandErrorContext,
|
|
90
|
+
preparePromptCommandExecution,
|
|
91
|
+
restorePromptCommandExecution,
|
|
92
|
+
type PromptCommandExecutionPlan,
|
|
93
|
+
} from "./core/prompt-command-runtime.js";
|
|
94
|
+
import {
|
|
95
|
+
DEBUG_PROMPT_NAMES,
|
|
96
|
+
DEBUG_WORKFLOW_STATES,
|
|
97
|
+
DEFAULT_DEBUG_LOG_FILE,
|
|
98
|
+
DEFAULT_DEBUG_LOG_ON_STATUS,
|
|
99
|
+
DEFAULT_DEBUG_STATUS_CHANGES,
|
|
100
|
+
DEFAULT_DEBUG_WORKFLOW_EVENTS,
|
|
101
|
+
logDebugPromptEvent,
|
|
102
|
+
logDebugPromptWorkflowEvent,
|
|
103
|
+
logDebugToolExecution,
|
|
104
|
+
normalizeDebugEnabledPrompts,
|
|
105
|
+
normalizeDebugEnabledTools,
|
|
106
|
+
normalizeDebugLogFile,
|
|
107
|
+
normalizeDebugLogOnStatus,
|
|
108
|
+
normalizeDebugStatusChanges,
|
|
109
|
+
normalizeDebugWorkflowEvents,
|
|
110
|
+
shouldLogDebugPromptWorkflowState,
|
|
111
|
+
type DebugLogOnStatus,
|
|
112
|
+
type DebugWorkflowState,
|
|
113
|
+
} from "./core/debug-runtime.js";
|
|
114
|
+
import { PROMPT_COMMAND_NAMES } from "./core/prompt-command-catalog.js";
|
|
115
|
+
import { resolveRuntimeGitPath } from "./core/runtime-project-paths.js";
|
|
116
|
+
import {
|
|
117
|
+
readPersistedPromptCommandRuntimeState,
|
|
118
|
+
writePersistedPromptCommandRuntimeState,
|
|
119
|
+
} from "./core/prompt-command-state.js";
|
|
120
|
+
import { ensureBundledResourcesAccessible, readBundledPromptDescription } from "./core/resources.js";
|
|
121
|
+
import { ReqError } from "./core/errors.js";
|
|
93
122
|
import {
|
|
94
123
|
PI_USEREQ_STATUS_HOOK_NAMES,
|
|
95
124
|
createPiUsereqStatusController,
|
|
96
125
|
disposePiUsereqStatusController,
|
|
126
|
+
isStaleExtensionContextError,
|
|
97
127
|
renderPiUsereqStatus,
|
|
98
128
|
setPiUsereqStatusConfig,
|
|
129
|
+
setPiUsereqWorkflowState,
|
|
130
|
+
shouldPreservePromptCommandStateOnShutdown,
|
|
99
131
|
updateExtensionStatus,
|
|
100
132
|
type PiUsereqStatusController,
|
|
101
133
|
type PiUsereqStatusHookName,
|
|
102
134
|
} from "./core/extension-status.js";
|
|
103
135
|
import {
|
|
104
|
-
|
|
136
|
+
runCompress,
|
|
137
|
+
runFilesCompress,
|
|
138
|
+
runFilesReferences,
|
|
139
|
+
runFilesSearch,
|
|
105
140
|
runFilesStaticCheck,
|
|
106
|
-
|
|
107
|
-
runGitCheck,
|
|
108
|
-
runGitPath,
|
|
109
|
-
runGitWtCreate,
|
|
110
|
-
runGitWtDelete,
|
|
111
|
-
runGitWtName,
|
|
141
|
+
runFilesTokens,
|
|
112
142
|
runProjectStaticCheck,
|
|
143
|
+
runReferences,
|
|
144
|
+
runSearch,
|
|
145
|
+
runTokens,
|
|
146
|
+
type ToolResult,
|
|
113
147
|
} from "./core/tool-runner.js";
|
|
114
148
|
import { LANGUAGE_TAGS } from "./core/find-constructs.js";
|
|
115
149
|
import {
|
|
@@ -117,29 +151,6 @@ import {
|
|
|
117
151
|
} from "./core/static-check.js";
|
|
118
152
|
import { makeRelativeIfContainsProject, shellSplit } from "./core/utils.js";
|
|
119
153
|
|
|
120
|
-
/**
|
|
121
|
-
* @brief Lists bundled prompt commands exposed by the extension.
|
|
122
|
-
* @details Each entry maps to a `req-<name>` command that renders a bundled prompt and sends it to the active session. Access complexity is O(1).
|
|
123
|
-
*/
|
|
124
|
-
const PROMPT_NAMES = [
|
|
125
|
-
"analyze",
|
|
126
|
-
"change",
|
|
127
|
-
"check",
|
|
128
|
-
"cover",
|
|
129
|
-
"create",
|
|
130
|
-
"fix",
|
|
131
|
-
"flowchart",
|
|
132
|
-
"implement",
|
|
133
|
-
"new",
|
|
134
|
-
"readme",
|
|
135
|
-
"recreate",
|
|
136
|
-
"refactor",
|
|
137
|
-
"references",
|
|
138
|
-
"renumber",
|
|
139
|
-
"workflow",
|
|
140
|
-
"write",
|
|
141
|
-
] as const;
|
|
142
|
-
|
|
143
154
|
/**
|
|
144
155
|
* @brief Describes the optional shortcut-registration surface used by pi-usereq.
|
|
145
156
|
* @details Narrows the runtime API to the documented `registerShortcut(...)`
|
|
@@ -164,7 +175,68 @@ interface PiShortcutRegistrar {
|
|
|
164
175
|
* @return {string} Absolute project base path.
|
|
165
176
|
*/
|
|
166
177
|
function getProjectBase(cwd: string): string {
|
|
167
|
-
|
|
178
|
+
const runtimeBasePath = getRuntimeBasePath(cwd);
|
|
179
|
+
if (fs.existsSync(runtimeBasePath)) {
|
|
180
|
+
return runtimeBasePath;
|
|
181
|
+
}
|
|
182
|
+
bootstrapRuntimePathState(cwd, {
|
|
183
|
+
gitPath: resolveRuntimeGitPath(cwd),
|
|
184
|
+
});
|
|
185
|
+
return getRuntimeBasePath(cwd);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @brief Resolves a safe process working directory for extension-load paths.
|
|
190
|
+
* @details Returns `process.cwd()` when available and falls back to absolute `PWD`, `HOME`, or `/` when the current shell directory has been deleted. Runtime is O(1). No external state is mutated.
|
|
191
|
+
* @return {string} Absolute fallback-safe process working directory.
|
|
192
|
+
*/
|
|
193
|
+
function getProcessCwdSafe(): string {
|
|
194
|
+
try {
|
|
195
|
+
return process.cwd();
|
|
196
|
+
} catch {
|
|
197
|
+
const fallbackCwd = process.env.PWD ?? process.env.HOME ?? path.sep;
|
|
198
|
+
return path.isAbsolute(fallbackCwd)
|
|
199
|
+
? fallbackCwd
|
|
200
|
+
: path.resolve(path.sep, fallbackCwd);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @brief Resolves the live working directory used for bootstrap-sensitive flows.
|
|
206
|
+
* @details Prefers the supplied cwd when it still exists. Otherwise reuses the tracked runtime context path when it remains live, then the tracked runtime base path, and finally a process-safe cwd so deleted worktree paths retained by stale contexts cannot poison later prompt preflight or lifecycle bootstrap. Runtime is O(1) plus bounded filesystem probes. No external state is mutated.
|
|
207
|
+
* @param[in] cwd {string} Candidate context cwd.
|
|
208
|
+
* @return {string} Existing absolute cwd used for bootstrap work.
|
|
209
|
+
*/
|
|
210
|
+
function resolveLiveBootstrapCwd(cwd: string): string {
|
|
211
|
+
const normalizedCwd = path.resolve(cwd);
|
|
212
|
+
if (fs.existsSync(normalizedCwd)) {
|
|
213
|
+
return normalizedCwd;
|
|
214
|
+
}
|
|
215
|
+
const processCwd = getProcessCwdSafe();
|
|
216
|
+
const runtimeContextPath = getRuntimeContextPath(processCwd);
|
|
217
|
+
if (fs.existsSync(runtimeContextPath)) {
|
|
218
|
+
return runtimeContextPath;
|
|
219
|
+
}
|
|
220
|
+
const runtimeBasePath = getRuntimeBasePath(processCwd);
|
|
221
|
+
return fs.existsSync(runtimeBasePath) ? runtimeBasePath : processCwd;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @brief Best-effort synchronizes one context `cwd` mirror with bootstrap reality.
|
|
226
|
+
* @details Applies the resolved live cwd to the supplied context when writable and ignores stale or read-only mirrors so command bootstrap can continue using authoritative filesystem probes. Runtime is O(1). Side effects are limited to optional `ctx.cwd` mutation.
|
|
227
|
+
* @param[in,out] ctx {{ cwd?: string }} Mutable context-like object.
|
|
228
|
+
* @param[in] cwd {string} Resolved live cwd.
|
|
229
|
+
* @return {void} No return value.
|
|
230
|
+
*/
|
|
231
|
+
function syncContextCwdMirror(ctx: { cwd?: string }, cwd: string): void {
|
|
232
|
+
if (ctx.cwd === cwd) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
Reflect.set(ctx, "cwd", cwd);
|
|
237
|
+
} catch {
|
|
238
|
+
// Ignore stale or read-only context mirrors.
|
|
239
|
+
}
|
|
168
240
|
}
|
|
169
241
|
|
|
170
242
|
/**
|
|
@@ -194,188 +266,153 @@ function saveProjectConfig(cwd: string, config: UseReqConfig): void {
|
|
|
194
266
|
|
|
195
267
|
/**
|
|
196
268
|
* @brief Formats the current project config path for top-level menu display.
|
|
197
|
-
* @details Resolves `<base-path>/.pi-usereq
|
|
269
|
+
* @details Resolves `<base-path>/.pi-usereq.json` from the cwd-derived project base, reuses the shared runtime-path formatter, and rewrites a leading POSIX `$HOME` token to `~` for the `Show configuration` row only. Runtime is O(p) in path length. No external state is mutated.
|
|
198
270
|
* @param[in] cwd {string} Current working directory.
|
|
199
271
|
* @return {string} `~`-relative or absolute config path display value.
|
|
200
272
|
* @satisfies REQ-162
|
|
201
273
|
*/
|
|
202
274
|
function formatProjectConfigPathForMenu(cwd: string): string {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
return displayPath.startsWith("$HOME/") ? `~/${displayPath.slice("$HOME/".length)}` : displayPath;
|
|
275
|
+
return formatRuntimePathForDisplay(
|
|
276
|
+
getProjectConfigPath(getProjectBase(cwd)),
|
|
277
|
+
);
|
|
208
278
|
}
|
|
209
279
|
|
|
210
280
|
/**
|
|
211
|
-
* @brief
|
|
212
|
-
* @details
|
|
213
|
-
* @param[in]
|
|
214
|
-
* @
|
|
215
|
-
* @
|
|
281
|
+
* @brief Builds the standardized terminal rows appended to every configuration menu.
|
|
282
|
+
* @details Returns the canonical `Reset defaults` row so all configuration menus and descendant selector menus share the same terminal ordering contract without rendering `Save and close`. Runtime is O(1). No external state is mutated.
|
|
283
|
+
* @param[in] options {{ resetDefaultsDescription: string; resetDefaultsValue?: string | undefined }} Menu-specific terminal-row metadata.
|
|
284
|
+
* @return {PiUsereqSettingsMenuChoice[]} Ordered terminal menu rows.
|
|
285
|
+
* @satisfies REQ-193
|
|
216
286
|
*/
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
): {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
.replace(/^\.?\/?/, "")
|
|
230
|
-
.replace(/\/+$/, "");
|
|
231
|
-
const excludedDirectoryPaths = [...new Set([
|
|
232
|
-
"tests/fixtures",
|
|
233
|
-
testsDirRel ? `${testsDirRel}/fixtures` : "fixtures",
|
|
234
|
-
])];
|
|
235
|
-
const selectedPaths = collectSourceFiles(selectionDirectoryPaths, projectBase)
|
|
236
|
-
.filter((filePath) => {
|
|
237
|
-
const relativePath = path.relative(projectBase, filePath).split(path.sep).join("/");
|
|
238
|
-
return !excludedDirectoryPaths.some((excludedDirectoryPath) => {
|
|
239
|
-
return relativePath === excludedDirectoryPath || relativePath.startsWith(`${excludedDirectoryPath}/`);
|
|
240
|
-
});
|
|
241
|
-
})
|
|
242
|
-
.map((filePath) => path.relative(projectBase, filePath).split(path.sep).join("/"));
|
|
243
|
-
return {
|
|
244
|
-
selectionDirectoryPaths,
|
|
245
|
-
excludedDirectoryPaths,
|
|
246
|
-
selectedPaths,
|
|
247
|
-
};
|
|
287
|
+
function buildTerminalSettingsMenuChoices(options: {
|
|
288
|
+
resetDefaultsDescription: string;
|
|
289
|
+
resetDefaultsValue?: string;
|
|
290
|
+
}): PiUsereqSettingsMenuChoice[] {
|
|
291
|
+
return [
|
|
292
|
+
{
|
|
293
|
+
id: "reset-defaults",
|
|
294
|
+
label: "Reset defaults",
|
|
295
|
+
value: options.resetDefaultsValue ?? "",
|
|
296
|
+
description: options.resetDefaultsDescription,
|
|
297
|
+
},
|
|
298
|
+
];
|
|
248
299
|
}
|
|
249
300
|
|
|
250
301
|
/**
|
|
251
|
-
* @brief
|
|
252
|
-
* @details
|
|
253
|
-
* @param[in] payload {TokenToolPayload} Structured token payload.
|
|
254
|
-
* @return {string} Newline-delimited execution diagnostics.
|
|
302
|
+
* @brief Describes one pending reset value change shown in confirmation menus.
|
|
303
|
+
* @details Stores the row label plus its previous and next values so reset-confirmation submenus can expose machine-readable and human-verifiable change previews. The interface is compile-time only and introduces no runtime cost.
|
|
255
304
|
*/
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const errorLines = payload.files
|
|
261
|
-
.filter((entry) => entry.status === "error" && entry.error_message)
|
|
262
|
-
.map((entry) => `error: ${entry.canonical_path}: ${entry.error_message!}`);
|
|
263
|
-
return [...skippedLines, ...errorLines].join("\n");
|
|
305
|
+
interface ResetConfirmationChange {
|
|
306
|
+
label: string;
|
|
307
|
+
previousValue: string;
|
|
308
|
+
nextValue: string;
|
|
264
309
|
}
|
|
265
310
|
|
|
266
311
|
/**
|
|
267
|
-
* @brief
|
|
268
|
-
* @details
|
|
269
|
-
* @param[in]
|
|
270
|
-
* @
|
|
271
|
-
* @
|
|
312
|
+
* @brief Formats one reset-confirmation value pair for menu display.
|
|
313
|
+
* @details Serializes the previous and next values into a deterministic `previous -> next` preview string used by confirmation submenus. Runtime is O(n) in combined value length. No external state is mutated.
|
|
314
|
+
* @param[in] previousValue {string} Current persisted value.
|
|
315
|
+
* @param[in] nextValue {string} Candidate default value.
|
|
316
|
+
* @return {string} Rendered preview string.
|
|
272
317
|
*/
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
): {
|
|
276
|
-
content: Array<{ type: "text"; text: string }>;
|
|
277
|
-
details: TokenToolPayload & { execution: { code: number; stderr: string } };
|
|
278
|
-
} {
|
|
279
|
-
const details = {
|
|
280
|
-
summary: payload.summary,
|
|
281
|
-
files: payload.files,
|
|
282
|
-
execution: {
|
|
283
|
-
code: payload.summary.counted_file_count > 0 ? 0 : 1,
|
|
284
|
-
stderr: buildTokenToolExecutionStderr(payload),
|
|
285
|
-
},
|
|
286
|
-
};
|
|
287
|
-
return buildStructuredToolExecuteResult(details);
|
|
318
|
+
function formatResetConfirmationValue(previousValue: string, nextValue: string): string {
|
|
319
|
+
return `${previousValue} -> ${nextValue}`;
|
|
288
320
|
}
|
|
289
321
|
|
|
290
322
|
/**
|
|
291
|
-
* @brief Builds the
|
|
292
|
-
* @details
|
|
293
|
-
* @param[in]
|
|
294
|
-
* @
|
|
323
|
+
* @brief Builds the shared settings-menu choices for one reset-confirmation submenu.
|
|
324
|
+
* @details Renders each pending changed value as a disabled preview row, appends explicit approve and abort actions, and falls back to one disabled no-op row when no values would change. Runtime is O(n) in changed-value count. No external state is mutated.
|
|
325
|
+
* @param[in] changes {ResetConfirmationChange[]} Changed-value preview rows.
|
|
326
|
+
* @param[in] approveDescription {string} Description for the approve action.
|
|
327
|
+
* @param[in] abortDescription {string} Description for the abort action.
|
|
328
|
+
* @return {PiUsereqSettingsMenuChoice[]} Reset-confirmation submenu choices.
|
|
295
329
|
*/
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* @brief Builds the agent-oriented execute result returned by references tools.
|
|
329
|
-
* @details Mirrors the structured references payload into both the text `content` channel and the machine-readable `details` channel while isolating execution metadata under `execution`. Runtime is O(n) in payload size. No side effects occur.
|
|
330
|
-
* @param[in] payload {ReferenceToolPayload} Structured references payload.
|
|
331
|
-
* @return {{ content: Array<{ type: "text"; text: string }>; details: ReferenceToolPayload & { execution: { code: number; stderr: string } } }} References-tool execute result.
|
|
332
|
-
* @satisfies REQ-076, REQ-077, REQ-078, REQ-079, REQ-099, REQ-102
|
|
333
|
-
*/
|
|
334
|
-
function buildReferenceToolExecuteResult(
|
|
335
|
-
payload: ReferenceToolPayload,
|
|
336
|
-
): {
|
|
337
|
-
content: Array<{ type: "text"; text: string }>;
|
|
338
|
-
details: ReferenceToolPayload & { execution: { code: number; stderr: string } };
|
|
339
|
-
} {
|
|
340
|
-
const details = {
|
|
341
|
-
summary: payload.summary,
|
|
342
|
-
repository: payload.repository,
|
|
343
|
-
files: payload.files,
|
|
344
|
-
execution: {
|
|
345
|
-
code: payload.summary.analyzed_file_count > 0 ? 0 : 1,
|
|
346
|
-
stderr: buildReferenceToolExecutionStderr(payload),
|
|
330
|
+
function buildResetConfirmationChoices(
|
|
331
|
+
changes: ResetConfirmationChange[],
|
|
332
|
+
approveDescription: string,
|
|
333
|
+
abortDescription: string,
|
|
334
|
+
): PiUsereqSettingsMenuChoice[] {
|
|
335
|
+
const previewRows = changes.length > 0
|
|
336
|
+
? changes.map((change, index) => ({
|
|
337
|
+
id: `reset-preview:${index}`,
|
|
338
|
+
label: change.label,
|
|
339
|
+
value: formatResetConfirmationValue(change.previousValue, change.nextValue),
|
|
340
|
+
description: `Reset ${change.label} from ${change.previousValue} to ${change.nextValue}.`,
|
|
341
|
+
disabled: true,
|
|
342
|
+
labelTone: "dim" as const,
|
|
343
|
+
valueTone: "dim" as const,
|
|
344
|
+
}))
|
|
345
|
+
: [{
|
|
346
|
+
id: "reset-preview:none",
|
|
347
|
+
label: "No value changes",
|
|
348
|
+
value: "unchanged",
|
|
349
|
+
description: "Approving the reset leaves the current values unchanged.",
|
|
350
|
+
disabled: true,
|
|
351
|
+
labelTone: "dim" as const,
|
|
352
|
+
valueTone: "dim" as const,
|
|
353
|
+
}];
|
|
354
|
+
return [
|
|
355
|
+
...previewRows,
|
|
356
|
+
{
|
|
357
|
+
id: "reset-approve",
|
|
358
|
+
label: "Approve reset",
|
|
359
|
+
value: `${changes.length} changes`,
|
|
360
|
+
description: approveDescription,
|
|
347
361
|
},
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
* @brief Builds the agent-oriented execute result returned by compression tools.
|
|
354
|
-
* @details Mirrors the structured compression payload into both the text `content` channel and the machine-readable `details` channel while isolating execution metadata under `execution`. Runtime is O(n) in payload size. No side effects occur.
|
|
355
|
-
* @param[in] payload {CompressToolPayload} Structured compression payload.
|
|
356
|
-
* @return {{ content: Array<{ type: "text"; text: string }>; details: CompressToolPayload & { execution: { code: number; stderr: string } } }} Compression-tool execute result.
|
|
357
|
-
* @satisfies REQ-081, REQ-082, REQ-083, REQ-084, REQ-085, REQ-087, REQ-088, REQ-099, REQ-102
|
|
358
|
-
*/
|
|
359
|
-
function buildCompressionToolExecuteResult(
|
|
360
|
-
payload: CompressToolPayload,
|
|
361
|
-
): {
|
|
362
|
-
content: Array<{ type: "text"; text: string }>;
|
|
363
|
-
details: CompressToolPayload & { execution: { code: number; stderr: string } };
|
|
364
|
-
} {
|
|
365
|
-
const details = {
|
|
366
|
-
summary: payload.summary,
|
|
367
|
-
repository: payload.repository,
|
|
368
|
-
files: payload.files,
|
|
369
|
-
execution: {
|
|
370
|
-
code: payload.summary.compressed_file_count > 0 ? 0 : 1,
|
|
371
|
-
stderr: buildCompressToolExecutionStderr(payload),
|
|
362
|
+
{
|
|
363
|
+
id: "reset-abort",
|
|
364
|
+
label: "Abort reset",
|
|
365
|
+
value: "keep current",
|
|
366
|
+
description: abortDescription,
|
|
372
367
|
},
|
|
373
|
-
|
|
374
|
-
return buildStructuredToolExecuteResult(details);
|
|
368
|
+
];
|
|
375
369
|
}
|
|
376
370
|
|
|
377
371
|
/**
|
|
378
|
-
* @brief
|
|
372
|
+
* @brief Opens one explicit reset-confirmation submenu.
|
|
373
|
+
* @details Uses the shared settings-menu renderer to show every changed value before reset application and returns `true` only when the user selects the explicit approval action. Runtime depends on user interaction count. Side effects are limited to transient custom-UI rendering.
|
|
374
|
+
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
375
|
+
* @param[in] title {string} Confirmation submenu title.
|
|
376
|
+
* @param[in] changes {ResetConfirmationChange[]} Changed-value preview rows.
|
|
377
|
+
* @param[in] approveDescription {string} Description for the approve action.
|
|
378
|
+
* @param[in] abortDescription {string} Description for the abort action.
|
|
379
|
+
* @return {Promise<boolean>} `true` when the reset is explicitly approved.
|
|
380
|
+
*/
|
|
381
|
+
async function confirmResetChanges(
|
|
382
|
+
ctx: ExtensionCommandContext,
|
|
383
|
+
title: string,
|
|
384
|
+
changes: ResetConfirmationChange[],
|
|
385
|
+
approveDescription: string,
|
|
386
|
+
abortDescription: string,
|
|
387
|
+
): Promise<boolean> {
|
|
388
|
+
const choice = await showPiUsereqSettingsMenu(
|
|
389
|
+
ctx,
|
|
390
|
+
title,
|
|
391
|
+
buildResetConfirmationChoices(changes, approveDescription, abortDescription),
|
|
392
|
+
);
|
|
393
|
+
return choice === "reset-approve";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* @brief Writes the already-persisted project configuration file text into the editor.
|
|
398
|
+
* @details Reads the current `.pi-usereq.json` file content from disk after the caller has saved any pending configuration changes and forwards that exact persisted text into the editor. Runtime is O(n) in serialized config size. Side effects include filesystem reads and editor-text mutation.
|
|
399
|
+
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
400
|
+
* @param[in] cwd {string} Current working directory.
|
|
401
|
+
* @param[in] _config {UseReqConfig} Unused effective project configuration retained for stable call-site shape.
|
|
402
|
+
* @return {void} No return value.
|
|
403
|
+
* @satisfies REQ-031
|
|
404
|
+
*/
|
|
405
|
+
function writePersistedProjectConfigToEditor(
|
|
406
|
+
ctx: ExtensionCommandContext,
|
|
407
|
+
cwd: string,
|
|
408
|
+
_config: UseReqConfig,
|
|
409
|
+
): void {
|
|
410
|
+
const projectBase = getProjectBase(cwd);
|
|
411
|
+
ctx.ui.setEditorText(fs.readFileSync(getProjectConfigPath(projectBase), "utf8"));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* @brief Maps search-tool language identifiers to stable registration labels.
|
|
379
416
|
* @details Preserves the canonical capitalization used by tool descriptions so supported-tag guidance remains deterministic across inspection snapshots. Access complexity is O(1).
|
|
380
417
|
*/
|
|
381
418
|
const FIND_TOOL_LANGUAGE_LABELS: Record<string, string> = {
|
|
@@ -402,7 +439,7 @@ const FIND_TOOL_LANGUAGE_LABELS: Record<string, string> = {
|
|
|
402
439
|
};
|
|
403
440
|
|
|
404
441
|
/**
|
|
405
|
-
* @brief Defines the stable language order used by
|
|
442
|
+
* @brief Defines the stable language order used by search-tool supported-tag guidance.
|
|
406
443
|
* @details Keeps registration descriptions aligned with the repository-supported language matrix and preserves deterministic inspection snapshots. Access complexity is O(1).
|
|
407
444
|
*/
|
|
408
445
|
const FIND_TOOL_LANGUAGE_ORDER = [
|
|
@@ -429,103 +466,445 @@ const FIND_TOOL_LANGUAGE_ORDER = [
|
|
|
429
466
|
] as const;
|
|
430
467
|
|
|
431
468
|
/**
|
|
432
|
-
* @brief Builds the supported-tag guidance lines embedded in
|
|
469
|
+
* @brief Builds the supported-tag guidance lines embedded in search-tool registrations.
|
|
433
470
|
* @details Emits one deterministic line per supported language containing its canonical registration label and sorted tag list so downstream agents can specialize requests without invoking the tool first. Runtime is O(l * t log t). No side effects occur.
|
|
434
471
|
* @return {string[]} Supported-tag guidance lines.
|
|
435
472
|
*/
|
|
436
|
-
function
|
|
473
|
+
function buildSearchToolSupportedTagGuidelines(): string[] {
|
|
437
474
|
return FIND_TOOL_LANGUAGE_ORDER
|
|
438
475
|
.filter((language) => language in LANGUAGE_TAGS)
|
|
439
476
|
.map((language) => `Supported tags [${FIND_TOOL_LANGUAGE_LABELS[language]}]: ${[...LANGUAGE_TAGS[language]!].sort().join(", ")}`);
|
|
440
477
|
}
|
|
441
478
|
|
|
442
479
|
/**
|
|
443
|
-
* @brief Builds the schema description for one
|
|
444
|
-
* @details Specializes the
|
|
445
|
-
* @param[in] scope {FindToolScope}
|
|
480
|
+
* @brief Builds the schema description for one search-tool registration.
|
|
481
|
+
* @details Specializes the explicit-file and configured-directory input contracts while documenting the monolithic markdown output channel and minimal execution details shape. Runtime is O(1). No side effects occur.
|
|
482
|
+
* @param[in] scope {FindToolScope} Search-tool scope.
|
|
446
483
|
* @return {string} Parameter-schema description.
|
|
447
484
|
*/
|
|
448
|
-
function
|
|
485
|
+
function buildSearchToolSchemaDescription(scope: FindToolScope): string {
|
|
449
486
|
const inputContract = scope === "explicit-files"
|
|
450
487
|
? "Input contract: tag + pattern + files[] + optional enableLineNumbers."
|
|
451
488
|
: "Input contract: tag + pattern + optional enableLineNumbers. Scope is the configured src-dir list resolved from the current project configuration.";
|
|
452
|
-
return `${inputContract} Output contract:
|
|
489
|
+
return `${inputContract} Output contract: monolithic markdown in content[0].text plus details.execution diagnostics. Regex matches construct names only.`;
|
|
453
490
|
}
|
|
454
491
|
|
|
455
492
|
/**
|
|
456
|
-
* @brief Builds the prompt-guideline set for one
|
|
457
|
-
* @details Encodes scope selection, output
|
|
458
|
-
* @param[in] scope {FindToolScope}
|
|
493
|
+
* @brief Builds the prompt-guideline set for one search-tool registration.
|
|
494
|
+
* @details Encodes scope selection, monolithic markdown output semantics, regex semantics, line-number behavior, tag-filter rules, and the full language-to-tag matrix as stable agent-oriented strings. Runtime is O(l * t log t). No side effects occur.
|
|
495
|
+
* @param[in] scope {FindToolScope} Search-tool scope.
|
|
459
496
|
* @return {string[]} Prompt-guideline strings.
|
|
460
497
|
*/
|
|
461
|
-
function
|
|
498
|
+
function buildSearchToolPromptGuidelines(scope: FindToolScope): string[] {
|
|
462
499
|
const scopeLine = scope === "explicit-files"
|
|
463
500
|
? "Scope: explicit source files selected by files[]; caller order is preserved; each item may be project-relative or absolute."
|
|
464
501
|
: "Scope: resolve src-dir from the current project configuration and scan the configured source surface from the current working directory.";
|
|
465
|
-
const outputLine = scope === "explicit-files"
|
|
466
|
-
? "Output contract: summary + repository + files + execution. Repository exposes requested file scope only when it adds dynamic search context; supported tags remain documented in registration metadata. File entries expose status, line ranges, file_doxygen, and matches; match entries expose symbol_kind, signature_text, line ranges, code_lines, stripped_source_text, and structured Doxygen fields."
|
|
467
|
-
: "Output contract: summary + repository + files + execution. Repository exposes source_directory_paths and file_canonical_paths when project-scope search context varies; supported tags remain documented in registration metadata. File entries expose status, line ranges, file_doxygen, and matches; match entries expose symbol_kind, signature_text, line ranges, code_lines, stripped_source_text, and structured Doxygen fields.";
|
|
468
502
|
return [
|
|
469
503
|
scopeLine,
|
|
470
|
-
|
|
504
|
+
"Output contract: monolithic markdown in content[0].text; details.execution preserves only exit code and residual diagnostics.",
|
|
471
505
|
"Regex rule: pattern is applied to construct names only with JavaScript RegExp search semantics; it never matches construct bodies; use ^...$ for exact-name matching.",
|
|
472
|
-
"Tag rule: tag is pipe-separated and case-insensitive; unsupported tags are ignored
|
|
473
|
-
"Line-number behavior: enableLineNumbers
|
|
474
|
-
"Failure contract: invalid tag filters, invalid regex patterns, unsupported extensions, unsupported tag-language combinations, no-match files, and analysis failures
|
|
475
|
-
...
|
|
506
|
+
"Tag rule: tag is pipe-separated and case-insensitive; unsupported tags are ignored.",
|
|
507
|
+
"Line-number behavior: enableLineNumbers toggles original source line prefixes inside fenced code blocks.",
|
|
508
|
+
"Failure contract: invalid tag filters, invalid regex patterns, unsupported extensions, unsupported tag-language combinations, no-match files, and analysis failures surface through details.execution diagnostics.",
|
|
509
|
+
...buildSearchToolSupportedTagGuidelines(),
|
|
476
510
|
];
|
|
477
511
|
}
|
|
478
512
|
|
|
479
513
|
/**
|
|
480
|
-
* @brief
|
|
481
|
-
* @details
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
} {
|
|
492
|
-
const stderr = buildFindToolExecutionStderr(payload);
|
|
493
|
-
const details = {
|
|
494
|
-
summary: payload.summary,
|
|
495
|
-
repository: payload.repository,
|
|
496
|
-
files: payload.files,
|
|
497
|
-
execution: {
|
|
498
|
-
code: payload.summary.search_status === "matched" ? 0 : 1,
|
|
499
|
-
stderr,
|
|
500
|
-
},
|
|
514
|
+
* @brief Describes the monolithic tool-result surface consumed by tool-row renderers.
|
|
515
|
+
* @details Narrows execute-result data to the primary text content block plus the minimal `details.execution` metadata returned by monolithic tool wrappers. The alias is compile-time only and introduces no runtime cost.
|
|
516
|
+
*/
|
|
517
|
+
type MonolithicToolRenderResult = {
|
|
518
|
+
content?: Array<{ type?: string; text?: string }>;
|
|
519
|
+
details?: {
|
|
520
|
+
execution?: {
|
|
521
|
+
code?: number;
|
|
522
|
+
stderr_lines?: string[];
|
|
523
|
+
stderr?: string;
|
|
524
|
+
};
|
|
501
525
|
};
|
|
502
|
-
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* @brief Extracts the primary monolithic text block from one tool result.
|
|
530
|
+
* @details Returns the first text content block when present and falls back to an empty string when the tool emitted no LLM-facing content. Runtime is O(1). No external state is mutated.
|
|
531
|
+
* @param[in] result {MonolithicToolRenderResult} Tool result wrapper.
|
|
532
|
+
* @return {string} Primary monolithic content text.
|
|
533
|
+
*/
|
|
534
|
+
function getMonolithicToolText(result: MonolithicToolRenderResult): string {
|
|
535
|
+
const firstBlock = result.content?.find((entry) => entry?.type === "text" && typeof entry.text === "string");
|
|
536
|
+
return typeof firstBlock?.text === "string" ? firstBlock.text : "";
|
|
503
537
|
}
|
|
504
538
|
|
|
505
539
|
/**
|
|
506
|
-
* @brief
|
|
507
|
-
* @details
|
|
508
|
-
* @param[in]
|
|
540
|
+
* @brief Reads the first residual execution error string from one monolithic tool result.
|
|
541
|
+
* @details Prefers the first `stderr_lines` entry when present and otherwise falls back to the first line of `stderr`. Runtime is O(1) plus first-line split cost. No external state is mutated.
|
|
542
|
+
* @param[in] result {MonolithicToolRenderResult} Tool result wrapper.
|
|
543
|
+
* @return {string | undefined} First residual execution error string.
|
|
544
|
+
*/
|
|
545
|
+
function getMonolithicToolErrorText(result: MonolithicToolRenderResult): string | undefined {
|
|
546
|
+
const stderrLines = result.details?.execution?.stderr_lines;
|
|
547
|
+
if (Array.isArray(stderrLines) && stderrLines.length > 0 && typeof stderrLines[0] === "string") {
|
|
548
|
+
return stderrLines[0];
|
|
549
|
+
}
|
|
550
|
+
const stderrText = result.details?.execution?.stderr;
|
|
551
|
+
if (typeof stderrText !== "string" || stderrText === "") {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
return stderrText.split(/\r?\n/)[0] || undefined;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* @brief Formats one scalar or structural tool argument for compact render summaries.
|
|
559
|
+
* @details Truncates long strings, compresses arrays into short previews, and renders plain object arguments as key indexes so collapsed tool rows stay compact while still exposing the essential invocation shape. Runtime is O(n) in preview size. No external state is mutated.
|
|
560
|
+
* @param[in] value {unknown} Candidate tool argument value.
|
|
561
|
+
* @return {string | undefined} Compact preview string or `undefined` when the value carries no useful summary.
|
|
562
|
+
*/
|
|
563
|
+
function formatCompactToolArgumentValue(value: unknown): string | undefined {
|
|
564
|
+
if (value === undefined || value === null) {
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
if (typeof value === "string") {
|
|
568
|
+
const compactText = value.length > 28 ? `${value.slice(0, 25)}...` : value;
|
|
569
|
+
return JSON.stringify(compactText);
|
|
570
|
+
}
|
|
571
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
572
|
+
return String(value);
|
|
573
|
+
}
|
|
574
|
+
if (typeof value === "boolean") {
|
|
575
|
+
return value ? "true" : "false";
|
|
576
|
+
}
|
|
577
|
+
if (Array.isArray(value)) {
|
|
578
|
+
if (value.length === 0) {
|
|
579
|
+
return undefined;
|
|
580
|
+
}
|
|
581
|
+
const renderedItems = value
|
|
582
|
+
.slice(0, 2)
|
|
583
|
+
.map((item) => formatCompactToolArgumentValue(item))
|
|
584
|
+
.filter((item): item is string => typeof item === "string");
|
|
585
|
+
if (renderedItems.length === 0) {
|
|
586
|
+
return `[${value.length}]`;
|
|
587
|
+
}
|
|
588
|
+
return value.length <= 2
|
|
589
|
+
? `[${renderedItems.join(", ")}]`
|
|
590
|
+
: `[${renderedItems.join(", ")}, ...+${value.length - 2}]`;
|
|
591
|
+
}
|
|
592
|
+
if (typeof value === "object") {
|
|
593
|
+
const keys = Object.keys(value as Record<string, unknown>);
|
|
594
|
+
if (keys.length === 0) {
|
|
595
|
+
return undefined;
|
|
596
|
+
}
|
|
597
|
+
return keys.length <= 2
|
|
598
|
+
? `{${keys.join(",")}}`
|
|
599
|
+
: `{${keys.slice(0, 2).join(",")},...+${keys.length - 2}}`;
|
|
600
|
+
}
|
|
601
|
+
return undefined;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* @brief Builds the compact invocation summary appended to collapsed tool rows.
|
|
606
|
+
* @details Renders only caller-supplied parameters that have stable, non-empty compact previews and joins them in insertion order so agents can infer how the tool was used without expanding the full result. Runtime is O(n) in argument count and preview size. No external state is mutated.
|
|
607
|
+
* @param[in] args {Record<string, unknown> | undefined} Current tool call arguments.
|
|
608
|
+
* @return {string} Compact invocation summary prefixed with one separating space, or the empty string when no useful preview exists.
|
|
609
|
+
*/
|
|
610
|
+
function buildCompactToolInvocationText(args: Record<string, unknown> | undefined): string {
|
|
611
|
+
if (!args) {
|
|
612
|
+
return "";
|
|
613
|
+
}
|
|
614
|
+
const entries = Object.entries(args)
|
|
615
|
+
.map(([key, value]) => {
|
|
616
|
+
const renderedValue = formatCompactToolArgumentValue(value);
|
|
617
|
+
return renderedValue === undefined ? undefined : `${key}=${renderedValue}`;
|
|
618
|
+
})
|
|
619
|
+
.filter((entry): entry is string => typeof entry === "string");
|
|
620
|
+
return entries.length === 0 ? "" : ` ${entries.join(" · ")}`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* @brief Builds the compact default text for one monolithic tool result row.
|
|
625
|
+
* @details Prefers the tool name, compact invocation preview, and success marker for collapsed rows, and falls back to residual execution diagnostics when the tool failed before completing successfully. Runtime is O(n) in compact argument-preview size. No external state is mutated.
|
|
626
|
+
* @param[in] toolName {string} Registered tool name.
|
|
627
|
+
* @param[in] result {MonolithicToolRenderResult} Tool result wrapper.
|
|
628
|
+
* @param[in] args {Record<string, unknown> | undefined} Current tool call arguments.
|
|
629
|
+
* @return {string} Compact single-line summary.
|
|
630
|
+
*/
|
|
631
|
+
function summarizeStructuredToolResult(
|
|
632
|
+
toolName: string,
|
|
633
|
+
result: MonolithicToolRenderResult,
|
|
634
|
+
args?: Record<string, unknown>,
|
|
635
|
+
): string {
|
|
636
|
+
const success = result.details?.execution?.code === undefined
|
|
637
|
+
? true
|
|
638
|
+
: result.details.execution.code === 0;
|
|
639
|
+
const prefix = success ? "✓" : "✗";
|
|
640
|
+
const invocationText = buildCompactToolInvocationText(args);
|
|
641
|
+
const errorText = getMonolithicToolErrorText(result);
|
|
642
|
+
if (!success && errorText) {
|
|
643
|
+
return `${prefix} ${toolName}${invocationText}: ${errorText}`;
|
|
644
|
+
}
|
|
645
|
+
return `${prefix} ${toolName}${invocationText}`;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* @brief Builds a custom `renderResult` implementation for one monolithic tool.
|
|
650
|
+
* @details Reuses a mutable `Text` component when possible, keeps the default collapsed row compact with essential invocation parameters plus result status, and reveals the full monolithic content only when the tool row is expanded. Runtime is O(n) in expanded content length and compact argument-preview size. No external state is mutated.
|
|
651
|
+
* @param[in] toolName {string} Registered tool name.
|
|
652
|
+
* @return {(result: MonolithicToolRenderResult, options: { expanded?: boolean; isPartial?: boolean }, _theme: unknown, context: { args?: Record<string, unknown>; lastComponent?: unknown }) => Text} Custom result renderer.
|
|
653
|
+
* @satisfies REQ-210
|
|
654
|
+
*/
|
|
655
|
+
function buildStructuredToolRenderResult(toolName: string) {
|
|
656
|
+
return (
|
|
657
|
+
result: MonolithicToolRenderResult,
|
|
658
|
+
options: { expanded?: boolean; isPartial?: boolean },
|
|
659
|
+
_theme: unknown,
|
|
660
|
+
context: { args?: Record<string, unknown>; lastComponent?: unknown },
|
|
661
|
+
): Text => {
|
|
662
|
+
const textComponent = context.lastComponent instanceof Text
|
|
663
|
+
? context.lastComponent
|
|
664
|
+
: new Text("", 0, 0);
|
|
665
|
+
if (options.isPartial) {
|
|
666
|
+
textComponent.setText(`… ${toolName}`);
|
|
667
|
+
return textComponent;
|
|
668
|
+
}
|
|
669
|
+
const compactText = summarizeStructuredToolResult(toolName, result, context.args);
|
|
670
|
+
const fullText = getMonolithicToolText(result);
|
|
671
|
+
textComponent.setText(options.expanded && fullText !== "" ? fullText : compactText);
|
|
672
|
+
return textComponent;
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* @brief Executes one CLI-style runner for a monolithic agent tool.
|
|
678
|
+
* @details Reuses the standalone tool-runner contract, normalizes thrown failures into `ToolResult`, and wraps the selected stdout or stderr text into the monolithic content channel. Runtime is dominated by the delegated runner. Side effects depend on the selected tool.
|
|
679
|
+
* @param[in] operation {() => ToolResult} Runner callback.
|
|
680
|
+
* @return {ReturnType<typeof buildMonolithicToolExecuteResult>} Monolithic tool execute result.
|
|
681
|
+
*/
|
|
682
|
+
function executeMonolithicTool(operation: () => ToolResult): ReturnType<typeof buildMonolithicToolExecuteResult> {
|
|
683
|
+
try {
|
|
684
|
+
return buildMonolithicToolExecuteResult(operation());
|
|
685
|
+
} catch (error) {
|
|
686
|
+
return buildMonolithicToolExecuteResult(normalizeToolFailure(error));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* @brief Starts delivery of one rendered prompt into the current active session.
|
|
692
|
+
* @details Prefers the replacement-session `sendUserMessage(...)` helper exposed by `withSession(...)` callbacks after session replacement so post-switch prompt delivery never reuses stale pre-switch session-bound extension objects. Returns the underlying delivery promise without awaiting it so callers can record the `running` workflow transition as soon as prompt handoff is accepted instead of waiting for the full agent turn to complete on runtimes whose async replacement-session helpers resolve only after `agent_end`. When pi later invalidates that replacement-session context during successful prompt-end restoration, the helper suppresses the documented stale-extension-context rejection because the prompt was already accepted and late rethrow would surface a false orchestration failure. Falls back to `pi.sendUserMessage(...)` only for non-replacement flows or runtimes that do not expose replacement-session helpers. Runtime is O(n) in prompt length. Side effects are limited to user-message delivery.
|
|
693
|
+
* @param[in] pi {ExtensionAPI} Handler-scoped extension API instance retained as the fallback dispatcher.
|
|
509
694
|
* @param[in] content {string} Rendered prompt markdown.
|
|
510
|
-
* @
|
|
511
|
-
* @
|
|
695
|
+
* @param[in] context {unknown} Optional replacement-session helper context.
|
|
696
|
+
* @return {Promise<void>} Promise representing eventual prompt-delivery completion.
|
|
697
|
+
* @satisfies REQ-004, REQ-067, REQ-068, REQ-227, REQ-281
|
|
512
698
|
*/
|
|
513
|
-
|
|
699
|
+
function deliverPromptCommand(
|
|
700
|
+
pi: ExtensionAPI,
|
|
701
|
+
content: string,
|
|
702
|
+
context?: unknown,
|
|
703
|
+
): Promise<void> {
|
|
704
|
+
const replacementContext = context as {
|
|
705
|
+
sendUserMessage?: (message: string) => Promise<void> | void;
|
|
706
|
+
} | undefined;
|
|
707
|
+
if (typeof replacementContext?.sendUserMessage === "function") {
|
|
708
|
+
return Promise.resolve(replacementContext.sendUserMessage(content)).catch((error) => {
|
|
709
|
+
if (isStaleExtensionContextError(error)) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
throw error;
|
|
713
|
+
});
|
|
714
|
+
}
|
|
514
715
|
pi.sendUserMessage(content);
|
|
716
|
+
return Promise.resolve();
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* @brief Detects prompt-delivery failures that can be ignored after prompt ownership has moved past the command handler.
|
|
721
|
+
* @details Matches the documented stale-extension-context runtime error once prompt ownership has already moved beyond command-side preflight. The helper treats the failure as ignorable when the persisted prompt runtime state shows the same execution session as the active prompt run or when the persisted workflow state has already advanced beyond `checking|running`, because rethrowing at that point would incorrectly re-enter command-side abort logic after the prompt was already accepted. Runtime is O(n) in error-message length plus path length. No external state is mutated.
|
|
722
|
+
* @param[in] error {unknown} Candidate prompt-delivery failure.
|
|
723
|
+
* @param[in] workflowState {import("./core/extension-status.js").PiUsereqWorkflowState} Current shared prompt workflow state.
|
|
724
|
+
* @param[in] executionPlan {PromptCommandExecutionPlan | undefined} Prepared execution plan for the current prompt command.
|
|
725
|
+
* @return {boolean} `true` when the failure is a late stale-context delivery rejection that MUST be ignored.
|
|
726
|
+
* @satisfies REQ-208, REQ-280, REQ-281, REQ-282
|
|
727
|
+
*/
|
|
728
|
+
function shouldIgnoreLatePromptDeliveryFailure(
|
|
729
|
+
error: unknown,
|
|
730
|
+
workflowState: import("./core/extension-status.js").PiUsereqWorkflowState,
|
|
731
|
+
executionPlan?: PromptCommandExecutionPlan,
|
|
732
|
+
): boolean {
|
|
733
|
+
const persistedRuntimeState = readPersistedPromptCommandRuntimeState();
|
|
734
|
+
const effectiveWorkflowState = persistedRuntimeState.workflowState || workflowState;
|
|
735
|
+
const activeExecutionSessionFile = persistedRuntimeState.activePromptRequest?.executionSessionFile;
|
|
736
|
+
const promptRunAlreadyActive = executionPlan !== undefined
|
|
737
|
+
&& typeof activeExecutionSessionFile === "string"
|
|
738
|
+
&& path.resolve(activeExecutionSessionFile) === path.resolve(executionPlan.executionSessionFile);
|
|
739
|
+
return isStaleExtensionContextError(error)
|
|
740
|
+
&& (
|
|
741
|
+
promptRunAlreadyActive
|
|
742
|
+
|| (effectiveWorkflowState !== "checking" && effectiveWorkflowState !== "running")
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* @brief Appends one workflow-state debug entry for a bundled prompt when selected.
|
|
748
|
+
* @details Reuses the shared debug logger so `req-*` command handlers and prompt-end orchestration can record deterministic workflow transitions without duplicating JSON payload shaping. Runtime is O(n) in serialized payload size only when logging is enabled and O(1) otherwise. Side effects include debug-log file writes for matching enabled prompts.
|
|
749
|
+
* @param[in] projectBase {string} Absolute original project base path.
|
|
750
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
751
|
+
* @param[in] promptName {import("./core/prompt-command-catalog.js").PromptCommandName} Bundled prompt name.
|
|
752
|
+
* @param[in] previousState {string} Prior workflow state.
|
|
753
|
+
* @param[in] nextState {import("./core/debug-runtime.js").DebugWorkflowState} Next workflow state.
|
|
754
|
+
* @return {void} No return value.
|
|
755
|
+
* @satisfies REQ-245, REQ-246, REQ-247
|
|
756
|
+
*/
|
|
757
|
+
function logPromptWorkflowStateChange(
|
|
758
|
+
projectBase: string,
|
|
759
|
+
config: UseReqConfig,
|
|
760
|
+
promptName: import("./core/prompt-command-catalog.js").PromptCommandName,
|
|
761
|
+
previousState: string,
|
|
762
|
+
nextState: import("./core/debug-runtime.js").DebugWorkflowState,
|
|
763
|
+
): void {
|
|
764
|
+
if (!shouldLogDebugPromptWorkflowState(config, nextState, promptName)) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
logDebugPromptEvent(
|
|
768
|
+
projectBase,
|
|
769
|
+
config,
|
|
770
|
+
nextState,
|
|
771
|
+
promptName,
|
|
772
|
+
"workflow_state",
|
|
773
|
+
{ from_state: previousState, to_state: nextState },
|
|
774
|
+
{ success: true, workflow_state: nextState },
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* @brief Appends one dedicated prompt workflow debug entry when selected.
|
|
780
|
+
* @details Reuses the shared workflow-event logger so prompt activation, restoration, closure, and session-shutdown paths can emit higher-granularity orchestration diagnostics without duplicating JSON payload shaping. Runtime is O(n) in serialized payload size only when logging is enabled and O(1) otherwise. Side effects include debug-log file writes for matching enabled prompts.
|
|
781
|
+
* @param[in] projectBase {string} Absolute original project base path.
|
|
782
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
783
|
+
* @param[in] workflowState {DebugWorkflowState} Current workflow state.
|
|
784
|
+
* @param[in] promptName {import("./core/prompt-command-catalog.js").PromptCommandName} Bundled prompt name.
|
|
785
|
+
* @param[in] action {string} Workflow debug action identifier.
|
|
786
|
+
* @param[in] input {unknown} Optional workflow debug input payload.
|
|
787
|
+
* @param[in] result {unknown} Optional workflow debug result payload.
|
|
788
|
+
* @param[in] isError {boolean} Workflow debug error flag.
|
|
789
|
+
* @return {void} No return value.
|
|
790
|
+
* @satisfies REQ-245, REQ-246, REQ-247, REQ-277
|
|
791
|
+
*/
|
|
792
|
+
function logPromptWorkflowEvent(
|
|
793
|
+
projectBase: string,
|
|
794
|
+
config: UseReqConfig,
|
|
795
|
+
workflowState: DebugWorkflowState,
|
|
796
|
+
promptName: import("./core/prompt-command-catalog.js").PromptCommandName,
|
|
797
|
+
action: string,
|
|
798
|
+
input?: unknown,
|
|
799
|
+
result?: unknown,
|
|
800
|
+
isError = false,
|
|
801
|
+
): void {
|
|
802
|
+
logDebugPromptWorkflowEvent(
|
|
803
|
+
projectBase,
|
|
804
|
+
config,
|
|
805
|
+
workflowState,
|
|
806
|
+
promptName,
|
|
807
|
+
action,
|
|
808
|
+
input,
|
|
809
|
+
result,
|
|
810
|
+
isError,
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* @brief Transitions one prompt workflow state and logs the transition immediately after the state update.
|
|
816
|
+
* @details Captures the previous workflow state, applies the new state through the shared status helper, and appends the gated `workflow_state` debug entry only after the transition has completed. Runtime is O(1). Side effects include status mutation, status-bar rendering, and optional debug-log writes.
|
|
817
|
+
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
818
|
+
* @param[in] ctx {ExtensionContext | ExtensionCommandContext} Active extension context.
|
|
819
|
+
* @param[in] projectBase {string} Absolute original project base path.
|
|
820
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
821
|
+
* @param[in] promptName {import("./core/prompt-command-catalog.js").PromptCommandName} Bundled prompt name.
|
|
822
|
+
* @param[in] nextState {DebugWorkflowState} Next workflow state.
|
|
823
|
+
* @return {void} No return value.
|
|
824
|
+
*/
|
|
825
|
+
function transitionPromptWorkflowState(
|
|
826
|
+
statusController: PiUsereqStatusController,
|
|
827
|
+
ctx: ExtensionContext | ExtensionCommandContext,
|
|
828
|
+
projectBase: string,
|
|
829
|
+
config: UseReqConfig,
|
|
830
|
+
promptName: import("./core/prompt-command-catalog.js").PromptCommandName,
|
|
831
|
+
nextState: DebugWorkflowState,
|
|
832
|
+
): void {
|
|
833
|
+
const previousState = statusController.state.workflowState;
|
|
834
|
+
setPiUsereqWorkflowState(statusController, nextState, ctx);
|
|
835
|
+
if (previousState !== nextState) {
|
|
836
|
+
logPromptWorkflowStateChange(projectBase, config, promptName, previousState, nextState);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* @brief Resolves the runtime slash-command description for one bundled prompt.
|
|
842
|
+
* @details Reads the bundled prompt front matter, extracts its normalized `description` field, and falls back to the historical generated label when the prompt metadata omits a description. Runtime is O(n) in prompt length. Side effects are limited to filesystem reads.
|
|
843
|
+
* @param[in] promptName {import("./core/prompt-command-catalog.js").PromptCommandName} Bundled prompt name.
|
|
844
|
+
* @return {string} Runtime command description.
|
|
845
|
+
*/
|
|
846
|
+
function resolvePromptCommandDescription(
|
|
847
|
+
promptName: import("./core/prompt-command-catalog.js").PromptCommandName,
|
|
848
|
+
): string {
|
|
849
|
+
return readBundledPromptDescription(promptName) || `Run pi-usereq prompt ${promptName}`;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* @brief Resolves the original project base used for debug-log file writes.
|
|
854
|
+
* @details Prefers the active or pending prompt execution plan so tool-result logging during worktree-backed prompt runs persists into the original repository path instead of transient worktree directories. Runtime is O(1). No external state is mutated.
|
|
855
|
+
* @param[in] cwd {string} Current extension working directory.
|
|
856
|
+
* @param[in] statusController {PiUsereqStatusController} Mutable status controller.
|
|
857
|
+
* @return {string} Absolute original project base path for debug logging.
|
|
858
|
+
*/
|
|
859
|
+
function resolveDebugProjectBase(cwd: string, statusController: PiUsereqStatusController): string {
|
|
860
|
+
return statusController.state.activePromptRequest?.basePath
|
|
861
|
+
?? statusController.state.pendingPromptRequest?.basePath
|
|
862
|
+
?? getProjectBase(resolveLiveBootstrapCwd(cwd));
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* @brief Delivers one best-effort UI notification without failing on stale replacement contexts.
|
|
867
|
+
* @details Attempts to use the supplied extension context for UI notification delivery and suppresses the documented stale-extension-context runtime error raised after session replacement, because prompt-orchestration closure can outlive the context that initiated the switch. Runtime is O(n) in message length. Side effects are limited to user notification delivery when the context is still active.
|
|
868
|
+
* @param[in] ctx {ExtensionContext | ExtensionCommandContext | undefined} Candidate UI context.
|
|
869
|
+
* @param[in] message {string} Notification message.
|
|
870
|
+
* @param[in] level {"info" | "error"} Notification severity.
|
|
871
|
+
* @return {boolean} `true` when the notification was delivered and `false` when the context was already stale.
|
|
872
|
+
* @satisfies REQ-280
|
|
873
|
+
*/
|
|
874
|
+
function notifyContextSafely(
|
|
875
|
+
ctx: (ExtensionContext | ExtensionCommandContext) | undefined,
|
|
876
|
+
message: string,
|
|
877
|
+
level: "info" | "error",
|
|
878
|
+
): boolean {
|
|
879
|
+
if (ctx === undefined) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
try {
|
|
883
|
+
ctx.ui.notify(message, level);
|
|
884
|
+
return true;
|
|
885
|
+
} catch (error) {
|
|
886
|
+
if (isStaleExtensionContextError(error)) {
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
515
891
|
}
|
|
516
892
|
|
|
517
893
|
/**
|
|
518
894
|
* @brief Returns the configurable active-tool inventory visible to the extension.
|
|
519
|
-
* @details Filters runtime tools against the canonical configurable-tool set,
|
|
895
|
+
* @details Filters runtime tools against the canonical configurable-tool set, keeps only builtin-backed embedded tools, and orders the result by the documented custom/files/embedded/default-disabled grouping. Runtime is O(t log t). No external state is mutated.
|
|
520
896
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
521
897
|
* @return {ToolInfo[]} Sorted configurable tool descriptors.
|
|
522
|
-
* @satisfies REQ-007, REQ-063
|
|
898
|
+
* @satisfies REQ-007, REQ-063, REQ-231, REQ-232
|
|
523
899
|
*/
|
|
524
900
|
function getPiUsereqStartupTools(pi: ExtensionAPI): ToolInfo[] {
|
|
525
901
|
return pi.getAllTools()
|
|
526
902
|
.filter((tool) => PI_USEREQ_STARTUP_TOOL_SET.has(tool.name))
|
|
527
903
|
.filter((tool) => !isPiUsereqEmbeddedToolName(tool.name) || tool.sourceInfo?.source === "builtin")
|
|
528
|
-
.sort((left, right) =>
|
|
904
|
+
.sort((left, right) => comparePiUsereqStartupToolNames(
|
|
905
|
+
left.name as PiUsereqStartupToolName,
|
|
906
|
+
right.name as PiUsereqStartupToolName,
|
|
907
|
+
));
|
|
529
908
|
}
|
|
530
909
|
|
|
531
910
|
/**
|
|
@@ -569,22 +948,14 @@ function applyConfiguredPiUsereqTools(pi: ExtensionAPI, config: UseReqConfig): v
|
|
|
569
948
|
|
|
570
949
|
/**
|
|
571
950
|
* @brief Handles one intercepted pi lifecycle hook for pi-usereq status updates.
|
|
572
|
-
* @details Applies session-start-specific resource validation, project-config
|
|
573
|
-
* refresh, and startup-tool enablement before forwarding the originating hook
|
|
574
|
-
* name and payload into the shared `updateExtensionStatus(...)` pipeline.
|
|
575
|
-
* On `agent_end`, also dispatches configured command-notify, sound, and
|
|
576
|
-
* prompt-specific Pushover effects when the current run originates from a
|
|
577
|
-
* bundled prompt command. Runtime is dominated by configuration loading during
|
|
578
|
-
* `session_start`; all other hooks are O(1). Side effects include resource
|
|
579
|
-
* checks, active-tool mutation, status updates, live-ticker disposal on
|
|
580
|
-
* shutdown, optional child-process spawning, and outbound HTTPS requests.
|
|
951
|
+
* @details Applies session-start-specific resource validation, project-config refresh, startup-tool enablement, and selected debug-tool logging before forwarding the originating hook name and payload into the shared `updateExtensionStatus(...)` pipeline. Before `agent_start`, re-verifies any prepared prompt execution session switch. On `agent_end`, dispatches configured command-notify, sound, and prompt-specific Pushover effects, logs dedicated workflow-closure diagnostics, restores the original session-backed `base-path` for every matched worktree-backed completion by reusing persisted replacement-session command contexts when event contexts omit `switchSession()`, merges and deletes the worktree only for matched successful completions, tolerates stale replacement-session notification contexts after session replacement, retains the worktree plus notifies closure failure for interrupted or failed outcomes, logs selected prompt workflow transitions, and transitions workflow state through `merging`, `error`, and `idle` as required. On `session_shutdown`, captures pre-update prompt snapshots so workflow-shutdown diagnostics and same-runtime command continuation preserve the active prompt workflow state across switch-triggered rebinding, then disposes the shared controller. Runtime is dominated by configuration loading during `session_start` and git finalization during matched successful `agent_end` handling; all other hooks are O(1). Side effects include resource checks, active-tool mutation, active-session replacement, status updates, live-ticker disposal on shutdown, optional child-process spawning, outbound HTTPS requests, branch merges, worktree deletion, and optional debug-log writes.
|
|
581
952
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
582
953
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
583
954
|
* @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
|
|
584
955
|
* @param[in] event {unknown} Hook payload forwarded by pi.
|
|
585
956
|
* @param[in] ctx {ExtensionContext} Active extension context.
|
|
586
957
|
* @return {Promise<void>} Promise resolved when hook processing completes.
|
|
587
|
-
* @satisfies REQ-117, REQ-118, REQ-119, REQ-131, REQ-132, REQ-133, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172, REQ-176, REQ-178, REQ-184, REQ-185, REQ-186, REQ-187
|
|
958
|
+
* @satisfies REQ-117, REQ-118, REQ-119, REQ-131, REQ-132, REQ-133, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172, REQ-176, REQ-178, REQ-184, REQ-185, REQ-186, REQ-187, REQ-208, REQ-209, REQ-221, REQ-228, REQ-229, REQ-230, REQ-244, REQ-245, REQ-246, REQ-247, REQ-276, REQ-277, REQ-278, REQ-279, REQ-280
|
|
588
959
|
*/
|
|
589
960
|
async function handleExtensionStatusEvent(
|
|
590
961
|
pi: ExtensionAPI,
|
|
@@ -593,23 +964,65 @@ async function handleExtensionStatusEvent(
|
|
|
593
964
|
event: unknown,
|
|
594
965
|
ctx: ExtensionContext,
|
|
595
966
|
): Promise<void> {
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
?
|
|
600
|
-
promptName: statusController.state.activePromptRequest.promptName,
|
|
601
|
-
promptArgs: statusController.state.activePromptRequest.promptArgs,
|
|
602
|
-
basePath: path.resolve(ctx.cwd),
|
|
603
|
-
completionTimeMs: Math.max(0, Date.now() - statusController.state.runStartTimeMs),
|
|
604
|
-
}
|
|
967
|
+
const pendingPromptRequest = statusController.state.pendingPromptRequest;
|
|
968
|
+
const activePromptRequest = statusController.state.activePromptRequest;
|
|
969
|
+
const shutdownPromptRequest = hookName === "session_shutdown"
|
|
970
|
+
? activePromptRequest ?? pendingPromptRequest
|
|
605
971
|
: undefined;
|
|
972
|
+
const preservePromptCommandStateOnShutdown = hookName === "session_shutdown"
|
|
973
|
+
? shouldPreservePromptCommandStateOnShutdown(statusController.state)
|
|
974
|
+
: false;
|
|
606
975
|
if (hookName === "session_start") {
|
|
607
976
|
ensureBundledResourcesAccessible();
|
|
608
|
-
const
|
|
977
|
+
const startupCwd = resolveLiveBootstrapCwd(ctx.cwd);
|
|
978
|
+
syncContextCwdMirror(ctx, startupCwd);
|
|
979
|
+
bootstrapRuntimePathState(startupCwd, {
|
|
980
|
+
gitPath: resolveRuntimeGitPath(startupCwd),
|
|
981
|
+
});
|
|
982
|
+
const config = loadProjectConfig(startupCwd);
|
|
609
983
|
applyConfiguredPiUsereqTools(pi, config);
|
|
610
984
|
setPiUsereqStatusConfig(statusController, config);
|
|
611
985
|
}
|
|
986
|
+
if (hookName === "before_agent_start") {
|
|
987
|
+
const requestForActivation = activePromptRequest ?? pendingPromptRequest;
|
|
988
|
+
if (requestForActivation !== undefined) {
|
|
989
|
+
await activatePromptCommandExecution(requestForActivation, ctx);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const notifyRequest: PiNotifyEventRequest | undefined = hookName === "agent_end"
|
|
993
|
+
&& activePromptRequest !== undefined
|
|
994
|
+
&& statusController.state.runStartTimeMs !== undefined
|
|
995
|
+
? {
|
|
996
|
+
promptName: activePromptRequest.promptName,
|
|
997
|
+
promptArgs: activePromptRequest.promptArgs,
|
|
998
|
+
basePath: activePromptRequest.basePath,
|
|
999
|
+
completionTimeMs: Math.max(0, Date.now() - statusController.state.runStartTimeMs),
|
|
1000
|
+
}
|
|
1001
|
+
: undefined;
|
|
612
1002
|
updateExtensionStatus(statusController, hookName, event, ctx);
|
|
1003
|
+
if (hookName === "tool_result" && statusController.config) {
|
|
1004
|
+
const toolEvent = event as {
|
|
1005
|
+
toolName?: string;
|
|
1006
|
+
input?: unknown;
|
|
1007
|
+
content?: unknown;
|
|
1008
|
+
details?: unknown;
|
|
1009
|
+
isError?: boolean;
|
|
1010
|
+
};
|
|
1011
|
+
if (typeof toolEvent.toolName === "string") {
|
|
1012
|
+
logDebugToolExecution(
|
|
1013
|
+
resolveDebugProjectBase(ctx.cwd, statusController),
|
|
1014
|
+
statusController.config,
|
|
1015
|
+
statusController.state.workflowState,
|
|
1016
|
+
toolEvent.toolName,
|
|
1017
|
+
toolEvent.input,
|
|
1018
|
+
{
|
|
1019
|
+
content: toolEvent.content,
|
|
1020
|
+
details: toolEvent.details,
|
|
1021
|
+
},
|
|
1022
|
+
toolEvent.isError === true,
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
613
1026
|
if (hookName === "agent_end") {
|
|
614
1027
|
if (statusController.config) {
|
|
615
1028
|
runPiNotifyEffects(
|
|
@@ -618,9 +1031,190 @@ async function handleExtensionStatusEvent(
|
|
|
618
1031
|
notifyRequest,
|
|
619
1032
|
);
|
|
620
1033
|
}
|
|
621
|
-
|
|
1034
|
+
if (activePromptRequest !== undefined) {
|
|
1035
|
+
const debugConfig = statusController.config;
|
|
1036
|
+
let promptContext = ctx;
|
|
1037
|
+
const outcome = classifyPromptCommandOutcome(
|
|
1038
|
+
event as { messages: AgentEndEvent["messages"] },
|
|
1039
|
+
);
|
|
1040
|
+
const closureFailureMessage = outcome !== "completed"
|
|
1041
|
+
&& activePromptRequest.worktreeDir !== undefined
|
|
1042
|
+
? `ERROR: Prompt closure retained worktree ${activePromptRequest.worktreeDir} after ${outcome} outcome.`
|
|
1043
|
+
: undefined;
|
|
1044
|
+
const shouldFinalizeMatchedSuccess = outcome === "completed"
|
|
1045
|
+
&& statusController.state.workflowState === "running"
|
|
1046
|
+
&& activePromptRequest.worktreeDir !== undefined;
|
|
1047
|
+
if (debugConfig) {
|
|
1048
|
+
logPromptWorkflowEvent(
|
|
1049
|
+
activePromptRequest.basePath,
|
|
1050
|
+
debugConfig,
|
|
1051
|
+
statusController.state.workflowState,
|
|
1052
|
+
activePromptRequest.promptName,
|
|
1053
|
+
"workflow_closure",
|
|
1054
|
+
{
|
|
1055
|
+
hook: "agent_end",
|
|
1056
|
+
outcome,
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
should_finalize_matched_success: shouldFinalizeMatchedSuccess,
|
|
1060
|
+
worktree_dir: activePromptRequest.worktreeDir,
|
|
1061
|
+
event_context_has_switch_session: typeof (ctx as ExtensionCommandContext).switchSession === "function",
|
|
1062
|
+
},
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
if (shouldFinalizeMatchedSuccess) {
|
|
1066
|
+
if (debugConfig) {
|
|
1067
|
+
transitionPromptWorkflowState(
|
|
1068
|
+
statusController,
|
|
1069
|
+
promptContext,
|
|
1070
|
+
activePromptRequest.basePath,
|
|
1071
|
+
debugConfig,
|
|
1072
|
+
activePromptRequest.promptName,
|
|
1073
|
+
"merging",
|
|
1074
|
+
);
|
|
1075
|
+
} else {
|
|
1076
|
+
setPiUsereqWorkflowState(statusController, "merging", promptContext);
|
|
1077
|
+
}
|
|
1078
|
+
let finalization:
|
|
1079
|
+
| {
|
|
1080
|
+
mergeAttempted: boolean;
|
|
1081
|
+
mergeSucceeded: boolean;
|
|
1082
|
+
cleanupSucceeded: boolean;
|
|
1083
|
+
errorMessage?: string;
|
|
1084
|
+
activeContext?: unknown;
|
|
1085
|
+
}
|
|
1086
|
+
| undefined;
|
|
1087
|
+
try {
|
|
1088
|
+
finalization = await finalizePromptCommandExecution(
|
|
1089
|
+
activePromptRequest,
|
|
1090
|
+
promptContext,
|
|
1091
|
+
debugConfig
|
|
1092
|
+
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1093
|
+
: undefined,
|
|
1094
|
+
);
|
|
1095
|
+
promptContext = (finalization.activeContext ?? promptContext) as typeof ctx;
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
|
|
1098
|
+
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
1099
|
+
let cleanupSucceeded = false;
|
|
1100
|
+
try {
|
|
1101
|
+
promptContext = (await restorePromptCommandExecution(
|
|
1102
|
+
activePromptRequest,
|
|
1103
|
+
promptContext,
|
|
1104
|
+
debugConfig
|
|
1105
|
+
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1106
|
+
: undefined,
|
|
1107
|
+
) ?? promptContext) as typeof ctx;
|
|
1108
|
+
cleanupSucceeded = true;
|
|
1109
|
+
} catch (restoreError) {
|
|
1110
|
+
promptContext = (getPromptCommandErrorContext(restoreError) ?? promptContext) as typeof ctx;
|
|
1111
|
+
errorMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
1112
|
+
}
|
|
1113
|
+
finalization = {
|
|
1114
|
+
mergeAttempted: false,
|
|
1115
|
+
mergeSucceeded: false,
|
|
1116
|
+
cleanupSucceeded,
|
|
1117
|
+
errorMessage,
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
if (
|
|
1121
|
+
finalization.errorMessage
|
|
1122
|
+
&& (!finalization.cleanupSucceeded || !finalization.mergeSucceeded)
|
|
1123
|
+
) {
|
|
1124
|
+
if (debugConfig) {
|
|
1125
|
+
transitionPromptWorkflowState(
|
|
1126
|
+
statusController,
|
|
1127
|
+
promptContext,
|
|
1128
|
+
activePromptRequest.basePath,
|
|
1129
|
+
debugConfig,
|
|
1130
|
+
activePromptRequest.promptName,
|
|
1131
|
+
"error",
|
|
1132
|
+
);
|
|
1133
|
+
} else {
|
|
1134
|
+
setPiUsereqWorkflowState(statusController, "error", promptContext);
|
|
1135
|
+
}
|
|
1136
|
+
notifyContextSafely(promptContext, finalization.errorMessage, "error");
|
|
1137
|
+
}
|
|
1138
|
+
} else {
|
|
1139
|
+
try {
|
|
1140
|
+
promptContext = (await restorePromptCommandExecution(
|
|
1141
|
+
activePromptRequest,
|
|
1142
|
+
promptContext,
|
|
1143
|
+
debugConfig
|
|
1144
|
+
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1145
|
+
: undefined,
|
|
1146
|
+
) ?? promptContext) as typeof ctx;
|
|
1147
|
+
if (closureFailureMessage !== undefined) {
|
|
1148
|
+
notifyContextSafely(promptContext, closureFailureMessage, "error");
|
|
1149
|
+
}
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
|
|
1152
|
+
if (debugConfig) {
|
|
1153
|
+
transitionPromptWorkflowState(
|
|
1154
|
+
statusController,
|
|
1155
|
+
promptContext,
|
|
1156
|
+
activePromptRequest.basePath,
|
|
1157
|
+
debugConfig,
|
|
1158
|
+
activePromptRequest.promptName,
|
|
1159
|
+
"error",
|
|
1160
|
+
);
|
|
1161
|
+
} else {
|
|
1162
|
+
setPiUsereqWorkflowState(statusController, "error", promptContext);
|
|
1163
|
+
}
|
|
1164
|
+
notifyContextSafely(promptContext, error instanceof Error ? error.message : String(error), "error");
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
statusController.state.pendingPromptRequest = undefined;
|
|
1168
|
+
statusController.state.activePromptRequest = undefined;
|
|
1169
|
+
if (debugConfig) {
|
|
1170
|
+
transitionPromptWorkflowState(
|
|
1171
|
+
statusController,
|
|
1172
|
+
promptContext,
|
|
1173
|
+
activePromptRequest.basePath,
|
|
1174
|
+
debugConfig,
|
|
1175
|
+
activePromptRequest.promptName,
|
|
1176
|
+
"idle",
|
|
1177
|
+
);
|
|
1178
|
+
} else {
|
|
1179
|
+
setPiUsereqWorkflowState(statusController, "idle", promptContext);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
622
1182
|
}
|
|
623
1183
|
if (hookName === "session_shutdown") {
|
|
1184
|
+
if (shutdownPromptRequest !== undefined && statusController.config) {
|
|
1185
|
+
logPromptWorkflowEvent(
|
|
1186
|
+
shutdownPromptRequest.basePath,
|
|
1187
|
+
statusController.config,
|
|
1188
|
+
preservePromptCommandStateOnShutdown
|
|
1189
|
+
? statusController.state.workflowState
|
|
1190
|
+
: "idle",
|
|
1191
|
+
shutdownPromptRequest.promptName,
|
|
1192
|
+
"workflow_session_shutdown",
|
|
1193
|
+
{
|
|
1194
|
+
reason: (event as { reason?: unknown }).reason,
|
|
1195
|
+
target_session_file: (event as { targetSessionFile?: unknown }).targetSessionFile,
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
preserve_prompt_command_state: preservePromptCommandStateOnShutdown,
|
|
1199
|
+
pending_prompt_request: pendingPromptRequest !== undefined,
|
|
1200
|
+
active_prompt_request: activePromptRequest !== undefined,
|
|
1201
|
+
},
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
if (!preservePromptCommandStateOnShutdown) {
|
|
1205
|
+
if (shutdownPromptRequest !== undefined && statusController.config) {
|
|
1206
|
+
transitionPromptWorkflowState(
|
|
1207
|
+
statusController,
|
|
1208
|
+
ctx,
|
|
1209
|
+
shutdownPromptRequest.basePath,
|
|
1210
|
+
statusController.config,
|
|
1211
|
+
shutdownPromptRequest.promptName,
|
|
1212
|
+
"idle",
|
|
1213
|
+
);
|
|
1214
|
+
} else {
|
|
1215
|
+
setPiUsereqWorkflowState(statusController, "idle", ctx);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
624
1218
|
disposePiUsereqStatusController(statusController);
|
|
625
1219
|
}
|
|
626
1220
|
}
|
|
@@ -629,8 +1223,11 @@ async function handleExtensionStatusEvent(
|
|
|
629
1223
|
* @brief Registers shared wrappers for every supported pi lifecycle hook.
|
|
630
1224
|
* @details Installs one generic wrapper per intercepted hook so every resource,
|
|
631
1225
|
* session, agent, model, tool, bash, and input event is routed through the
|
|
632
|
-
* same extension-status update pipeline.
|
|
633
|
-
*
|
|
1226
|
+
* same extension-status update pipeline. The wrapper suppresses the documented
|
|
1227
|
+
* stale-extension-context error because pi can continue delivering late
|
|
1228
|
+
* lifecycle callbacks against contexts invalidated by session replacement after
|
|
1229
|
+
* prompt orchestration has already completed successfully. Runtime is O(h) in
|
|
1230
|
+
* registered hook count. Side effects include hook registration.
|
|
634
1231
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
635
1232
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
636
1233
|
* @return {void} No return value.
|
|
@@ -646,7 +1243,13 @@ function registerExtensionStatusHooks(
|
|
|
646
1243
|
) => void;
|
|
647
1244
|
for (const hookName of PI_USEREQ_STATUS_HOOK_NAMES) {
|
|
648
1245
|
registerHook(hookName, async (event, ctx) => {
|
|
649
|
-
|
|
1246
|
+
try {
|
|
1247
|
+
await handleExtensionStatusEvent(pi, statusController, hookName, event, ctx);
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
if (!isStaleExtensionContextError(error)) {
|
|
1250
|
+
throw error;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
650
1253
|
});
|
|
651
1254
|
}
|
|
652
1255
|
}
|
|
@@ -664,6 +1267,316 @@ function setConfiguredPiUsereqTools(pi: ExtensionAPI, config: UseReqConfig, enab
|
|
|
664
1267
|
applyConfiguredPiUsereqTools(pi, config);
|
|
665
1268
|
}
|
|
666
1269
|
|
|
1270
|
+
/**
|
|
1271
|
+
* @brief Returns the canonical debug-tool toggle order.
|
|
1272
|
+
* @details Reuses the documented configurable-tool ordering so debug toggles list extension-owned tools before embedded tools and remain deterministic across sessions. Runtime is O(t log t). No external state is mutated.
|
|
1273
|
+
* @return {PiUsereqStartupToolName[]} Ordered debug-tool toggle names.
|
|
1274
|
+
* @satisfies REQ-242
|
|
1275
|
+
*/
|
|
1276
|
+
function getDebugToolToggleNames(): PiUsereqStartupToolName[] {
|
|
1277
|
+
return [...PI_USEREQ_CUSTOM_TOOL_NAMES, ...PI_USEREQ_EMBEDDED_TOOL_NAMES].sort(comparePiUsereqStartupToolNames);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* @brief Restores the debug configuration subtree to its documented defaults.
|
|
1282
|
+
* @details Resets global debug enablement, log path, workflow-state filter, dedicated workflow-event logging, and selected tool plus prompt debug toggles without mutating unrelated settings. Runtime is O(1). Side effect: mutates `config`.
|
|
1283
|
+
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
1284
|
+
* @return {void} No return value.
|
|
1285
|
+
* @satisfies REQ-236, REQ-237, REQ-238, REQ-239, REQ-195, REQ-277
|
|
1286
|
+
*/
|
|
1287
|
+
function resetDebugConfigToDefaults(config: UseReqConfig): void {
|
|
1288
|
+
config.DEBUG_ENABLED = "disable";
|
|
1289
|
+
config.DEBUG_LOG_FILE = DEFAULT_DEBUG_LOG_FILE;
|
|
1290
|
+
config.DEBUG_STATUS_CHANGES = DEFAULT_DEBUG_STATUS_CHANGES;
|
|
1291
|
+
config.DEBUG_WORKFLOW_EVENTS = DEFAULT_DEBUG_WORKFLOW_EVENTS;
|
|
1292
|
+
config.DEBUG_LOG_ON_STATUS = DEFAULT_DEBUG_LOG_ON_STATUS;
|
|
1293
|
+
config.DEBUG_ENABLED_TOOLS = [];
|
|
1294
|
+
config.DEBUG_ENABLED_PROMPTS = [];
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* @brief Formats the top-level Debug summary value.
|
|
1299
|
+
* @details Emits the current global debug mode plus compact selected-tool and selected-prompt counts for right-aligned menu display. Runtime is O(n) in configured selector count. No external state is mutated.
|
|
1300
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
1301
|
+
* @return {string} Compact debug summary string.
|
|
1302
|
+
*/
|
|
1303
|
+
function formatDebugMenuSummary(config: UseReqConfig): string {
|
|
1304
|
+
const toolCount = normalizeDebugEnabledTools(config.DEBUG_ENABLED_TOOLS).length;
|
|
1305
|
+
const promptCount = normalizeDebugEnabledPrompts(config.DEBUG_ENABLED_PROMPTS).length;
|
|
1306
|
+
return config.DEBUG_ENABLED === "enable"
|
|
1307
|
+
? `enable • ${toolCount} tools • ${promptCount} prompts`
|
|
1308
|
+
: "disable";
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* @brief Builds one debug-menu row with optional disabled styling.
|
|
1313
|
+
* @details Applies dim styling and disables selection whenever global debug is off for all rows except the global `Debug` toggle row. Runtime is O(1). No external state is mutated.
|
|
1314
|
+
* @param[in] choice {PiUsereqSettingsMenuChoice} Base debug-menu row.
|
|
1315
|
+
* @param[in] debugEnabled {boolean} Whether global debug is enabled.
|
|
1316
|
+
* @return {PiUsereqSettingsMenuChoice} Styled debug-menu row.
|
|
1317
|
+
* @satisfies REQ-241
|
|
1318
|
+
*/
|
|
1319
|
+
function buildDebugMenuChoice(
|
|
1320
|
+
choice: PiUsereqSettingsMenuChoice,
|
|
1321
|
+
debugEnabled: boolean,
|
|
1322
|
+
): PiUsereqSettingsMenuChoice {
|
|
1323
|
+
if (debugEnabled || choice.id === "debug-enabled") {
|
|
1324
|
+
return choice;
|
|
1325
|
+
}
|
|
1326
|
+
return {
|
|
1327
|
+
...choice,
|
|
1328
|
+
disabled: true,
|
|
1329
|
+
labelTone: "dim",
|
|
1330
|
+
valueTone: "dim",
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* @brief Opens the workflow-state filter selector used by the Debug submenu.
|
|
1336
|
+
* @details Exposes `any` plus each canonical workflow state through the shared settings-menu renderer and returns the selected normalized filter or `undefined` when the user cancels the submenu. Runtime depends on user interaction count. Side effects are limited to transient custom-UI rendering.
|
|
1337
|
+
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
1338
|
+
* @param[in] currentValue {DebugLogOnStatus} Current persisted workflow-state filter.
|
|
1339
|
+
* @return {Promise<DebugLogOnStatus | undefined>} Selected workflow-state filter or `undefined` when cancelled.
|
|
1340
|
+
*/
|
|
1341
|
+
async function selectDebugLogOnStatus(
|
|
1342
|
+
ctx: ExtensionCommandContext,
|
|
1343
|
+
currentValue: DebugLogOnStatus,
|
|
1344
|
+
): Promise<DebugLogOnStatus | undefined> {
|
|
1345
|
+
const choice = await showPiUsereqSettingsMenu(ctx, "Log on status", [
|
|
1346
|
+
{
|
|
1347
|
+
id: "any",
|
|
1348
|
+
label: "any",
|
|
1349
|
+
value: currentValue === "any" ? "selected" : "",
|
|
1350
|
+
description: "Write matching debug entries regardless of the current workflow state.",
|
|
1351
|
+
},
|
|
1352
|
+
...DEBUG_WORKFLOW_STATES.map((workflowState) => ({
|
|
1353
|
+
id: workflowState,
|
|
1354
|
+
label: workflowState,
|
|
1355
|
+
value: currentValue === workflowState ? "selected" : "",
|
|
1356
|
+
description: `Write matching debug entries only while workflow state is ${workflowState}.`,
|
|
1357
|
+
})),
|
|
1358
|
+
...buildTerminalSettingsMenuChoices({
|
|
1359
|
+
resetDefaultsValue: DEFAULT_DEBUG_LOG_ON_STATUS,
|
|
1360
|
+
resetDefaultsDescription: "Restore the documented default workflow-state filter.",
|
|
1361
|
+
}),
|
|
1362
|
+
], { initialSelectedId: currentValue });
|
|
1363
|
+
if (!choice) {
|
|
1364
|
+
return undefined;
|
|
1365
|
+
}
|
|
1366
|
+
if (choice === "reset-defaults") {
|
|
1367
|
+
return DEFAULT_DEBUG_LOG_ON_STATUS;
|
|
1368
|
+
}
|
|
1369
|
+
return normalizeDebugLogOnStatus(choice);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* @brief Builds the shared settings-menu choices for debug logging configuration.
|
|
1374
|
+
* @details Serializes global debug controls plus workflow-state, dedicated workflow-event, per-tool, and per-prompt toggles into one submenu, deriving inventories from the canonical tool and prompt lists and dimming locked rows while debug is disabled. Runtime is O(t + p). No external state is mutated.
|
|
1375
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
1376
|
+
* @return {PiUsereqSettingsMenuChoice[]} Ordered debug-menu choices.
|
|
1377
|
+
* @satisfies REQ-240, REQ-241, REQ-242, REQ-243, REQ-193, REQ-277
|
|
1378
|
+
*/
|
|
1379
|
+
function buildDebugMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
1380
|
+
const debugEnabled = config.DEBUG_ENABLED === "enable";
|
|
1381
|
+
const enabledTools = new Set(normalizeDebugEnabledTools(config.DEBUG_ENABLED_TOOLS));
|
|
1382
|
+
const enabledPrompts = new Set(normalizeDebugEnabledPrompts(config.DEBUG_ENABLED_PROMPTS));
|
|
1383
|
+
return [
|
|
1384
|
+
{
|
|
1385
|
+
id: "debug-enabled",
|
|
1386
|
+
label: "Debug",
|
|
1387
|
+
value: config.DEBUG_ENABLED,
|
|
1388
|
+
description: "Enable or disable all debug logging behavior and unlock the remaining Debug rows.",
|
|
1389
|
+
},
|
|
1390
|
+
buildDebugMenuChoice(
|
|
1391
|
+
{
|
|
1392
|
+
id: "debug-log-file",
|
|
1393
|
+
label: "Log file",
|
|
1394
|
+
value: config.DEBUG_LOG_FILE,
|
|
1395
|
+
description: "Edit the JSON debug log file path. Relative paths resolve against the original project base.",
|
|
1396
|
+
},
|
|
1397
|
+
debugEnabled,
|
|
1398
|
+
),
|
|
1399
|
+
buildDebugMenuChoice(
|
|
1400
|
+
{
|
|
1401
|
+
id: "debug-log-on-status",
|
|
1402
|
+
label: "Log on status",
|
|
1403
|
+
value: config.DEBUG_LOG_ON_STATUS,
|
|
1404
|
+
description: "Select whether matching debug entries are written for one explicit workflow state or for any workflow state.",
|
|
1405
|
+
},
|
|
1406
|
+
debugEnabled,
|
|
1407
|
+
),
|
|
1408
|
+
buildDebugMenuChoice(
|
|
1409
|
+
{
|
|
1410
|
+
id: "debug-status-changes",
|
|
1411
|
+
label: "Status changes",
|
|
1412
|
+
value: normalizeDebugStatusChanges(config.DEBUG_STATUS_CHANGES),
|
|
1413
|
+
description: "Enable or disable `workflow_state` debug entries for prompt-orchestration transitions.",
|
|
1414
|
+
},
|
|
1415
|
+
debugEnabled,
|
|
1416
|
+
),
|
|
1417
|
+
buildDebugMenuChoice(
|
|
1418
|
+
{
|
|
1419
|
+
id: "debug-workflow-events",
|
|
1420
|
+
label: "Workflow events",
|
|
1421
|
+
value: normalizeDebugWorkflowEvents(config.DEBUG_WORKFLOW_EVENTS),
|
|
1422
|
+
description: "Enable or disable dedicated workflow debug entries for activation, restoration, closure, and session-shutdown diagnostics.",
|
|
1423
|
+
},
|
|
1424
|
+
debugEnabled,
|
|
1425
|
+
),
|
|
1426
|
+
...getDebugToolToggleNames().map((toolName) => buildDebugMenuChoice(
|
|
1427
|
+
{
|
|
1428
|
+
id: `debug-tool:${toolName}`,
|
|
1429
|
+
label: toolName,
|
|
1430
|
+
value: enabledTools.has(toolName) ? "enable" : "disable",
|
|
1431
|
+
description: PI_USEREQ_CUSTOM_TOOL_NAMES.includes(toolName as never)
|
|
1432
|
+
? `Toggle debug logging for custom tool ${toolName}.`
|
|
1433
|
+
: `Toggle debug logging for embedded tool ${toolName}.`,
|
|
1434
|
+
},
|
|
1435
|
+
debugEnabled,
|
|
1436
|
+
)),
|
|
1437
|
+
...DEBUG_PROMPT_NAMES.map((promptName) => buildDebugMenuChoice(
|
|
1438
|
+
{
|
|
1439
|
+
id: `debug-prompt:${promptName}`,
|
|
1440
|
+
label: promptName,
|
|
1441
|
+
value: enabledPrompts.has(promptName) ? "enable" : "disable",
|
|
1442
|
+
description: `Toggle prompt-orchestration debug logging for /${promptName}.`,
|
|
1443
|
+
},
|
|
1444
|
+
debugEnabled,
|
|
1445
|
+
)),
|
|
1446
|
+
...buildTerminalSettingsMenuChoices({
|
|
1447
|
+
resetDefaultsValue: DEFAULT_DEBUG_LOG_ON_STATUS,
|
|
1448
|
+
resetDefaultsDescription: "Restore the documented default Debug configuration.",
|
|
1449
|
+
}),
|
|
1450
|
+
];
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* @brief Runs the interactive Debug submenu.
|
|
1455
|
+
* @details Lets the user toggle global debug enablement, edit debug file and workflow filters, toggle dedicated workflow-event logging, mutate per-tool and per-prompt debug selectors, and restore subtree defaults while preserving row focus across re-renders. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
|
|
1456
|
+
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
1457
|
+
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
1458
|
+
* @return {Promise<void>} Promise resolved when the submenu closes.
|
|
1459
|
+
* @satisfies REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243, REQ-192, REQ-193, REQ-195, REQ-277
|
|
1460
|
+
*/
|
|
1461
|
+
async function configureDebugMenu(
|
|
1462
|
+
ctx: ExtensionCommandContext,
|
|
1463
|
+
config: UseReqConfig,
|
|
1464
|
+
onConfigChange: () => void,
|
|
1465
|
+
): Promise<void> {
|
|
1466
|
+
let focusedChoiceId: string | undefined;
|
|
1467
|
+
while (true) {
|
|
1468
|
+
const choice = await showPiUsereqSettingsMenu(ctx, "Debug", buildDebugMenuChoices(config), {
|
|
1469
|
+
initialSelectedId: focusedChoiceId,
|
|
1470
|
+
});
|
|
1471
|
+
if (!choice) {
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
focusedChoiceId = choice;
|
|
1475
|
+
|
|
1476
|
+
if (choice === "debug-enabled") {
|
|
1477
|
+
config.DEBUG_ENABLED = config.DEBUG_ENABLED === "enable" ? "disable" : "enable";
|
|
1478
|
+
onConfigChange();
|
|
1479
|
+
ctx.ui.notify(`Debug ${config.DEBUG_ENABLED}`, "info");
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
if (choice === "reset-defaults") {
|
|
1483
|
+
const resetPreview: ResetConfirmationChange[] = [
|
|
1484
|
+
{ label: "Debug", previousValue: config.DEBUG_ENABLED, nextValue: "disable" },
|
|
1485
|
+
{ label: "Log file", previousValue: config.DEBUG_LOG_FILE, nextValue: DEFAULT_DEBUG_LOG_FILE },
|
|
1486
|
+
{ label: "Status changes", previousValue: normalizeDebugStatusChanges(config.DEBUG_STATUS_CHANGES), nextValue: DEFAULT_DEBUG_STATUS_CHANGES },
|
|
1487
|
+
{ label: "Workflow events", previousValue: normalizeDebugWorkflowEvents(config.DEBUG_WORKFLOW_EVENTS), nextValue: DEFAULT_DEBUG_WORKFLOW_EVENTS },
|
|
1488
|
+
{ label: "Log on status", previousValue: config.DEBUG_LOG_ON_STATUS, nextValue: DEFAULT_DEBUG_LOG_ON_STATUS },
|
|
1489
|
+
{ label: "Enabled debug tools", previousValue: String(normalizeDebugEnabledTools(config.DEBUG_ENABLED_TOOLS).length), nextValue: "0" },
|
|
1490
|
+
{ label: "Enabled debug prompts", previousValue: String(normalizeDebugEnabledPrompts(config.DEBUG_ENABLED_PROMPTS).length), nextValue: "0" },
|
|
1491
|
+
].filter((change) => change.previousValue !== change.nextValue);
|
|
1492
|
+
const approved = await confirmResetChanges(
|
|
1493
|
+
ctx,
|
|
1494
|
+
"Confirm Debug reset",
|
|
1495
|
+
resetPreview,
|
|
1496
|
+
"Approve restoring the documented default Debug configuration.",
|
|
1497
|
+
"Abort the Debug reset and keep the current values.",
|
|
1498
|
+
);
|
|
1499
|
+
if (!approved) {
|
|
1500
|
+
ctx.ui.notify("Aborted Debug reset", "info");
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
resetDebugConfigToDefaults(config);
|
|
1504
|
+
onConfigChange();
|
|
1505
|
+
ctx.ui.notify("Restored default Debug configuration", "info");
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
if (config.DEBUG_ENABLED !== "enable") {
|
|
1509
|
+
ctx.ui.notify("Debug rows are locked while Debug is disabled", "info");
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
if (choice === "debug-log-file") {
|
|
1513
|
+
const value = await ctx.ui.input("Log file", config.DEBUG_LOG_FILE);
|
|
1514
|
+
if (value !== undefined) {
|
|
1515
|
+
config.DEBUG_LOG_FILE = normalizeDebugLogFile(value);
|
|
1516
|
+
onConfigChange();
|
|
1517
|
+
ctx.ui.notify(`Debug log file set to ${config.DEBUG_LOG_FILE}`, "info");
|
|
1518
|
+
}
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if (choice === "debug-status-changes") {
|
|
1522
|
+
config.DEBUG_STATUS_CHANGES = normalizeDebugStatusChanges(
|
|
1523
|
+
config.DEBUG_STATUS_CHANGES === "enable" ? "disable" : "enable",
|
|
1524
|
+
);
|
|
1525
|
+
onConfigChange();
|
|
1526
|
+
ctx.ui.notify(`Debug status-change logging ${config.DEBUG_STATUS_CHANGES}`, "info");
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
if (choice === "debug-workflow-events") {
|
|
1530
|
+
config.DEBUG_WORKFLOW_EVENTS = normalizeDebugWorkflowEvents(
|
|
1531
|
+
config.DEBUG_WORKFLOW_EVENTS === "enable" ? "disable" : "enable",
|
|
1532
|
+
);
|
|
1533
|
+
onConfigChange();
|
|
1534
|
+
ctx.ui.notify(`Debug workflow-event logging ${config.DEBUG_WORKFLOW_EVENTS}`, "info");
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
if (choice === "debug-log-on-status") {
|
|
1538
|
+
const selectedStatus = await selectDebugLogOnStatus(ctx, config.DEBUG_LOG_ON_STATUS);
|
|
1539
|
+
if (selectedStatus !== undefined) {
|
|
1540
|
+
config.DEBUG_LOG_ON_STATUS = selectedStatus;
|
|
1541
|
+
onConfigChange();
|
|
1542
|
+
ctx.ui.notify(`Debug workflow filter set to ${config.DEBUG_LOG_ON_STATUS}`, "info");
|
|
1543
|
+
}
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
if (choice.startsWith("debug-tool:")) {
|
|
1547
|
+
const toolName = choice.slice("debug-tool:".length);
|
|
1548
|
+
const enabledTools = new Set(normalizeDebugEnabledTools(config.DEBUG_ENABLED_TOOLS));
|
|
1549
|
+
if (enabledTools.has(toolName as PiUsereqStartupToolName)) {
|
|
1550
|
+
enabledTools.delete(toolName as PiUsereqStartupToolName);
|
|
1551
|
+
} else {
|
|
1552
|
+
enabledTools.add(toolName as PiUsereqStartupToolName);
|
|
1553
|
+
}
|
|
1554
|
+
config.DEBUG_ENABLED_TOOLS = getDebugToolToggleNames().filter((name) => enabledTools.has(name));
|
|
1555
|
+
onConfigChange();
|
|
1556
|
+
ctx.ui.notify(
|
|
1557
|
+
`${enabledTools.has(toolName as PiUsereqStartupToolName) ? "Enabled" : "Disabled"} debug logging for ${toolName}`,
|
|
1558
|
+
"info",
|
|
1559
|
+
);
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (choice.startsWith("debug-prompt:")) {
|
|
1563
|
+
const promptName = choice.slice("debug-prompt:".length);
|
|
1564
|
+
const enabledPrompts = new Set(normalizeDebugEnabledPrompts(config.DEBUG_ENABLED_PROMPTS));
|
|
1565
|
+
if (enabledPrompts.has(promptName as (typeof DEBUG_PROMPT_NAMES)[number])) {
|
|
1566
|
+
enabledPrompts.delete(promptName as (typeof DEBUG_PROMPT_NAMES)[number]);
|
|
1567
|
+
} else {
|
|
1568
|
+
enabledPrompts.add(promptName as (typeof DEBUG_PROMPT_NAMES)[number]);
|
|
1569
|
+
}
|
|
1570
|
+
config.DEBUG_ENABLED_PROMPTS = DEBUG_PROMPT_NAMES.filter((name) => enabledPrompts.has(name));
|
|
1571
|
+
onConfigChange();
|
|
1572
|
+
ctx.ui.notify(
|
|
1573
|
+
`${enabledPrompts.has(promptName as (typeof DEBUG_PROMPT_NAMES)[number]) ? "Enabled" : "Disabled"} debug logging for ${promptName}`,
|
|
1574
|
+
"info",
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
667
1580
|
/**
|
|
668
1581
|
* @brief Represents one persisted boolean notification-setting key.
|
|
669
1582
|
* @details Restricts menu toggles to the global enable flags and completed/interrupted/failed event toggles used by command-notify, sound, and Pushover configuration. Compile-time only and introduces no runtime cost.
|
|
@@ -898,18 +1811,9 @@ function buildPiNotifyEventMenuChoices(
|
|
|
898
1811
|
value: config[eventMenu.keys[row.eventId]] ? "on" : "off",
|
|
899
1812
|
description: `${eventMenu.systemLabel}: ${row.description}`,
|
|
900
1813
|
})),
|
|
901
|
-
{
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
value: "",
|
|
905
|
-
description: `Restore the documented default ${eventMenu.systemLabel.toLowerCase()} event toggles.`,
|
|
906
|
-
},
|
|
907
|
-
{
|
|
908
|
-
id: "save-and-close",
|
|
909
|
-
label: "Save and close",
|
|
910
|
-
value: "",
|
|
911
|
-
description: "Return to the notifications menu.",
|
|
912
|
-
},
|
|
1814
|
+
...buildTerminalSettingsMenuChoices({
|
|
1815
|
+
resetDefaultsDescription: `Restore the documented default ${eventMenu.systemLabel.toLowerCase()} event toggles.`,
|
|
1816
|
+
}),
|
|
913
1817
|
];
|
|
914
1818
|
}
|
|
915
1819
|
|
|
@@ -961,6 +1865,7 @@ async function configurePiNotifyEventMenu(
|
|
|
961
1865
|
ctx: ExtensionCommandContext,
|
|
962
1866
|
config: UseReqConfig,
|
|
963
1867
|
eventMenu: PiNotifyEventMenuDefinition,
|
|
1868
|
+
onConfigChange: () => void,
|
|
964
1869
|
): Promise<void> {
|
|
965
1870
|
let focusedChoiceId: string | undefined;
|
|
966
1871
|
while (true) {
|
|
@@ -970,12 +1875,32 @@ async function configurePiNotifyEventMenu(
|
|
|
970
1875
|
buildPiNotifyEventMenuChoices(config, eventMenu),
|
|
971
1876
|
{ initialSelectedId: focusedChoiceId },
|
|
972
1877
|
);
|
|
973
|
-
if (!choice
|
|
1878
|
+
if (!choice) {
|
|
974
1879
|
return;
|
|
975
1880
|
}
|
|
976
1881
|
focusedChoiceId = choice;
|
|
977
1882
|
if (choice === "reset-defaults") {
|
|
1883
|
+
const defaults = getDefaultConfig("");
|
|
1884
|
+
const resetPreview = PI_NOTIFY_EVENT_ROW_DEFINITIONS
|
|
1885
|
+
.map((row) => ({
|
|
1886
|
+
label: row.label,
|
|
1887
|
+
previousValue: config[eventMenu.keys[row.eventId]] ? "on" : "off",
|
|
1888
|
+
nextValue: defaults[eventMenu.keys[row.eventId]] ? "on" : "off",
|
|
1889
|
+
}))
|
|
1890
|
+
.filter((change) => change.previousValue !== change.nextValue);
|
|
1891
|
+
const approved = await confirmResetChanges(
|
|
1892
|
+
ctx,
|
|
1893
|
+
`Confirm ${eventMenu.systemLabel} event reset`,
|
|
1894
|
+
resetPreview,
|
|
1895
|
+
`Approve restoring default ${eventMenu.systemLabel.toLowerCase()} event toggles.`,
|
|
1896
|
+
`Abort the ${eventMenu.systemLabel.toLowerCase()} event reset and keep the current values.`,
|
|
1897
|
+
);
|
|
1898
|
+
if (!approved) {
|
|
1899
|
+
ctx.ui.notify(`Aborted ${eventMenu.systemLabel.toLowerCase()} event reset`, "info");
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
978
1902
|
resetPiNotifyEventMenuToDefaults(config, eventMenu);
|
|
1903
|
+
onConfigChange();
|
|
979
1904
|
ctx.ui.notify(
|
|
980
1905
|
`Restored default ${eventMenu.systemLabel.toLowerCase()} events`,
|
|
981
1906
|
"info",
|
|
@@ -986,6 +1911,7 @@ async function configurePiNotifyEventMenu(
|
|
|
986
1911
|
config,
|
|
987
1912
|
choice as PiNotifyEventBooleanConfigKey,
|
|
988
1913
|
);
|
|
1914
|
+
onConfigChange();
|
|
989
1915
|
const eventLabel = resolvePiNotifyEventLabel(
|
|
990
1916
|
choice as PiNotifyEventBooleanConfigKey,
|
|
991
1917
|
eventMenu,
|
|
@@ -999,18 +1925,24 @@ async function configurePiNotifyEventMenu(
|
|
|
999
1925
|
|
|
1000
1926
|
/**
|
|
1001
1927
|
* @brief Builds the direct Pushover rows rendered inside `Notifications`.
|
|
1002
|
-
* @details Serializes the global enable flag, shared-event submenu launcher, priority, title, text, and credential rows into right-valued menu items appended after the sound-command rows. Runtime is O(
|
|
1928
|
+
* @details Serializes the global enable flag, shared-event submenu launcher, priority, title, text, and credential rows into right-valued menu items appended after the sound-command rows, dims and disables the enable row until both credentials are populated, and escapes control characters for the single-line `Pushover text` value. Runtime is O(n) in the rendered text-template length. No external state is mutated.
|
|
1003
1929
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
1004
1930
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered direct Pushover rows.
|
|
1005
|
-
* @satisfies REQ-163, REQ-165, REQ-172, REQ-184, REQ-185, REQ-198
|
|
1931
|
+
* @satisfies REQ-163, REQ-165, REQ-172, REQ-184, REQ-185, REQ-198, REQ-234, REQ-235
|
|
1006
1932
|
*/
|
|
1007
1933
|
function buildPiNotifyPushoverRows(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
1934
|
+
const pushoverCredentialsReady = hasPiNotifyPushoverCredentials(config);
|
|
1008
1935
|
return [
|
|
1009
1936
|
{
|
|
1010
1937
|
id: "notify-pushover-enabled",
|
|
1011
1938
|
label: "Enable pushover",
|
|
1012
|
-
|
|
1013
|
-
|
|
1939
|
+
labelTone: pushoverCredentialsReady ? undefined : "dim",
|
|
1940
|
+
value: pushoverCredentialsReady ? formatPiNotifyPushoverStatus(config) : "off",
|
|
1941
|
+
valueTone: pushoverCredentialsReady ? undefined : "dim",
|
|
1942
|
+
disabled: !pushoverCredentialsReady,
|
|
1943
|
+
description: pushoverCredentialsReady
|
|
1944
|
+
? "Enable or disable all Pushover delivery globally."
|
|
1945
|
+
: "Populate both Pushover credential fields to unlock global Pushover enablement.",
|
|
1014
1946
|
},
|
|
1015
1947
|
buildPiNotifyEventLauncherChoice(
|
|
1016
1948
|
config,
|
|
@@ -1031,7 +1963,7 @@ function buildPiNotifyPushoverRows(config: UseReqConfig): PiUsereqSettingsMenuCh
|
|
|
1031
1963
|
{
|
|
1032
1964
|
id: "notify-pushover-text",
|
|
1033
1965
|
label: "Pushover text",
|
|
1034
|
-
value: config["notify-pushover-text"],
|
|
1966
|
+
value: formatPiNotifyControlSequenceText(config["notify-pushover-text"]),
|
|
1035
1967
|
description: "Edit the text template used for outbound Pushover messages.",
|
|
1036
1968
|
},
|
|
1037
1969
|
{
|
|
@@ -1051,16 +1983,16 @@ function buildPiNotifyPushoverRows(config: UseReqConfig): PiUsereqSettingsMenuCh
|
|
|
1051
1983
|
|
|
1052
1984
|
/**
|
|
1053
1985
|
* @brief Opens the shared settings-menu selector for Pushover priority.
|
|
1054
|
-
* @details Reuses the pi-usereq settings-menu renderer so Pushover priority selection remains stylistically aligned with the notification menus and
|
|
1986
|
+
* @details Reuses the pi-usereq settings-menu renderer so Pushover priority selection remains stylistically aligned with the notification menus and appends subtree-local `Reset defaults` plus `Save and close` rows. Runtime depends on user interaction count. Side effects are limited to transient custom-UI rendering.
|
|
1055
1987
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
1056
1988
|
* @param[in] currentPriority {PiNotifyPushoverPriority} Persisted priority value.
|
|
1057
|
-
* @return {Promise<PiNotifyPushoverPriority | undefined>} Selected priority or `undefined` when cancelled.
|
|
1989
|
+
* @return {Promise<PiNotifyPushoverPriority | "reset-defaults" | undefined>} Selected priority, reset action, or `undefined` when cancelled.
|
|
1058
1990
|
* @satisfies REQ-172, REQ-192
|
|
1059
1991
|
*/
|
|
1060
1992
|
async function selectPiNotifyPushoverPriority(
|
|
1061
1993
|
ctx: ExtensionCommandContext,
|
|
1062
1994
|
currentPriority: PiNotifyPushoverPriority,
|
|
1063
|
-
): Promise<PiNotifyPushoverPriority | undefined> {
|
|
1995
|
+
): Promise<PiNotifyPushoverPriority | "reset-defaults" | undefined> {
|
|
1064
1996
|
const choice = await showPiUsereqSettingsMenu(ctx, "Pushover priority", [
|
|
1065
1997
|
{
|
|
1066
1998
|
id: "0",
|
|
@@ -1074,19 +2006,26 @@ async function selectPiNotifyPushoverPriority(
|
|
|
1074
2006
|
value: currentPriority === 1 ? "selected" : "",
|
|
1075
2007
|
description: "Send outbound Pushover messages with high priority `1`.",
|
|
1076
2008
|
},
|
|
2009
|
+
...buildTerminalSettingsMenuChoices({
|
|
2010
|
+
resetDefaultsValue: formatPiNotifyPushoverPriority(getDefaultConfig("")["notify-pushover-priority"]),
|
|
2011
|
+
resetDefaultsDescription: "Restore the documented default Pushover priority.",
|
|
2012
|
+
}),
|
|
1077
2013
|
], { initialSelectedId: String(currentPriority) });
|
|
1078
2014
|
if (!choice) {
|
|
1079
2015
|
return undefined;
|
|
1080
2016
|
}
|
|
2017
|
+
if (choice === "reset-defaults") {
|
|
2018
|
+
return "reset-defaults";
|
|
2019
|
+
}
|
|
1081
2020
|
return normalizePiNotifyPushoverPriority(choice);
|
|
1082
2021
|
}
|
|
1083
2022
|
|
|
1084
2023
|
/**
|
|
1085
2024
|
* @brief Builds the shared settings-menu choices for notification configuration.
|
|
1086
|
-
* @details Serializes command-notify, sound, and Pushover blocks with dedicated shared-event submenu launchers so the settings-menu renderer can expose one unified but modular configuration surface
|
|
2025
|
+
* @details Serializes command-notify, sound, and Pushover blocks with dedicated shared-event submenu launchers so the settings-menu renderer can expose one unified but modular configuration surface, including locked Pushover enablement and escaped single-line rendering for `Pushover text`. Runtime is O(n) in the longest rendered command or text field. No external state is mutated.
|
|
1087
2026
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
1088
2027
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered notification-menu choice vector.
|
|
1089
|
-
* @satisfies REQ-137, REQ-149, REQ-150, REQ-151, REQ-152, REQ-163, REQ-164, REQ-165, REQ-172, REQ-179, REQ-181, REQ-183, REQ-188, REQ-193, REQ-198
|
|
2028
|
+
* @satisfies REQ-137, REQ-149, REQ-150, REQ-151, REQ-152, REQ-163, REQ-164, REQ-165, REQ-172, REQ-179, REQ-181, REQ-183, REQ-188, REQ-193, REQ-198, REQ-234, REQ-235
|
|
1090
2029
|
*/
|
|
1091
2030
|
function buildPiNotifyMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
1092
2031
|
return [
|
|
@@ -1141,33 +2080,24 @@ function buildPiNotifyMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuCho
|
|
|
1141
2080
|
description: "Edit the shell command used when the selected sound command is `high`.",
|
|
1142
2081
|
},
|
|
1143
2082
|
...buildPiNotifyPushoverRows(config),
|
|
1144
|
-
{
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
value: "",
|
|
1148
|
-
description: "Restore the documented notification defaults for command-notify, sound, and Pushover settings.",
|
|
1149
|
-
},
|
|
1150
|
-
{
|
|
1151
|
-
id: "save-and-close",
|
|
1152
|
-
label: "Save and close",
|
|
1153
|
-
value: "",
|
|
1154
|
-
description: "Return to the parent configuration menu.",
|
|
1155
|
-
},
|
|
2083
|
+
...buildTerminalSettingsMenuChoices({
|
|
2084
|
+
resetDefaultsDescription: "Restore the documented notification defaults for command-notify, sound, and Pushover settings.",
|
|
2085
|
+
}),
|
|
1156
2086
|
];
|
|
1157
2087
|
}
|
|
1158
2088
|
|
|
1159
2089
|
/**
|
|
1160
2090
|
* @brief Opens the shared settings-menu selector for the active sound level.
|
|
1161
|
-
* @details Reuses the pi-usereq settings-menu renderer so sound-level selection remains stylistically aligned with the notification menu and
|
|
2091
|
+
* @details Reuses the pi-usereq settings-menu renderer so sound-level selection remains stylistically aligned with the notification menu and appends subtree-local `Reset defaults` plus `Save and close` rows. Runtime depends on user interaction count. Side effects are limited to transient custom-UI rendering.
|
|
1162
2092
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
1163
2093
|
* @param[in] currentLevel {PiNotifySoundLevel} Currently selected sound level.
|
|
1164
|
-
* @return {Promise<PiNotifySoundLevel | undefined>} Selected sound level or `undefined` when cancelled.
|
|
2094
|
+
* @return {Promise<PiNotifySoundLevel | "reset-defaults" | undefined>} Selected sound level, reset action, or `undefined` when cancelled.
|
|
1165
2095
|
* @satisfies REQ-131, REQ-179, REQ-192
|
|
1166
2096
|
*/
|
|
1167
2097
|
async function selectPiNotifySoundLevel(
|
|
1168
2098
|
ctx: ExtensionCommandContext,
|
|
1169
2099
|
currentLevel: PiNotifySoundLevel,
|
|
1170
|
-
): Promise<PiNotifySoundLevel | undefined> {
|
|
2100
|
+
): Promise<PiNotifySoundLevel | "reset-defaults" | undefined> {
|
|
1171
2101
|
const choice = await showPiUsereqSettingsMenu(ctx, "Enable sound", [
|
|
1172
2102
|
{
|
|
1173
2103
|
id: "none",
|
|
@@ -1193,21 +2123,32 @@ async function selectPiNotifySoundLevel(
|
|
|
1193
2123
|
value: currentLevel === "high" ? "selected" : "",
|
|
1194
2124
|
description: "Use the high-volume sound command when sound delivery is enabled for the current event.",
|
|
1195
2125
|
},
|
|
2126
|
+
...buildTerminalSettingsMenuChoices({
|
|
2127
|
+
resetDefaultsValue: getDefaultConfig("")["notify-sound"],
|
|
2128
|
+
resetDefaultsDescription: "Restore the documented default sound level.",
|
|
2129
|
+
}),
|
|
1196
2130
|
], { initialSelectedId: currentLevel });
|
|
1197
|
-
|
|
2131
|
+
if (!choice) {
|
|
2132
|
+
return undefined;
|
|
2133
|
+
}
|
|
2134
|
+
if (choice === "reset-defaults") {
|
|
2135
|
+
return "reset-defaults";
|
|
2136
|
+
}
|
|
2137
|
+
return choice as PiNotifySoundLevel;
|
|
1198
2138
|
}
|
|
1199
2139
|
|
|
1200
2140
|
/**
|
|
1201
2141
|
* @brief Runs the interactive notification-configuration menu.
|
|
1202
|
-
* @details Exposes command-notify, sound, and Pushover controls through the shared settings-menu renderer, delegates completed/interrupted/failed toggles to dedicated event submenus, and preserves row focus across menu re-renders. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
|
|
2142
|
+
* @details Exposes command-notify, sound, and Pushover controls through the shared settings-menu renderer, delegates completed/interrupted/failed toggles to dedicated event submenus, keeps `Enable pushover` locked until both credentials are populated, decodes escaped control-sequence input for `Pushover text`, and preserves row focus across menu re-renders. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
|
|
1203
2143
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
1204
2144
|
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
1205
2145
|
* @return {Promise<boolean>} `true` when the sound-toggle shortcut changed.
|
|
1206
|
-
* @satisfies REQ-131, REQ-133, REQ-134, REQ-137, REQ-163, REQ-164, REQ-165, REQ-172, REQ-179, REQ-181, REQ-183, REQ-184, REQ-188, REQ-192, REQ-193, REQ-195, REQ-196, REQ-198
|
|
2146
|
+
* @satisfies REQ-131, REQ-133, REQ-134, REQ-137, REQ-163, REQ-164, REQ-165, REQ-172, REQ-179, REQ-181, REQ-183, REQ-184, REQ-188, REQ-192, REQ-193, REQ-195, REQ-196, REQ-198, REQ-234, REQ-235
|
|
1207
2147
|
*/
|
|
1208
2148
|
async function configurePiNotifyMenu(
|
|
1209
2149
|
ctx: ExtensionCommandContext,
|
|
1210
2150
|
config: UseReqConfig,
|
|
2151
|
+
onConfigChange: () => void,
|
|
1211
2152
|
): Promise<boolean> {
|
|
1212
2153
|
const originalShortcut = config["notify-sound-toggle-shortcut"];
|
|
1213
2154
|
let focusedChoiceId: string | undefined;
|
|
@@ -1218,12 +2159,18 @@ async function configurePiNotifyMenu(
|
|
|
1218
2159
|
buildPiNotifyMenuChoices(config),
|
|
1219
2160
|
{ initialSelectedId: focusedChoiceId },
|
|
1220
2161
|
);
|
|
1221
|
-
if (!choice
|
|
2162
|
+
if (!choice) {
|
|
1222
2163
|
return config["notify-sound-toggle-shortcut"] !== originalShortcut;
|
|
1223
2164
|
}
|
|
1224
2165
|
focusedChoiceId = choice;
|
|
1225
2166
|
if (choice === "notify-enabled" || choice === "notify-pushover-enabled") {
|
|
2167
|
+
if (choice === "notify-pushover-enabled" && !hasPiNotifyPushoverCredentials(config)) {
|
|
2168
|
+
config["notify-pushover-enabled"] = false;
|
|
2169
|
+
ctx.ui.notify("Populate both Pushover credential fields before enabling Pushover", "info");
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
1226
2172
|
const enabled = togglePiNotifyFlag(config, choice as PiNotifyBooleanConfigKey);
|
|
2173
|
+
onConfigChange();
|
|
1227
2174
|
const labelMap: Record<string, string> = {
|
|
1228
2175
|
"notify-enabled": "Notification",
|
|
1229
2176
|
"notify-pushover-enabled": "Pushover",
|
|
@@ -1236,6 +2183,7 @@ async function configurePiNotifyMenu(
|
|
|
1236
2183
|
ctx,
|
|
1237
2184
|
config,
|
|
1238
2185
|
PI_NOTIFY_EVENT_MENU_DEFINITIONS.notification,
|
|
2186
|
+
onConfigChange,
|
|
1239
2187
|
);
|
|
1240
2188
|
continue;
|
|
1241
2189
|
}
|
|
@@ -1244,6 +2192,7 @@ async function configurePiNotifyMenu(
|
|
|
1244
2192
|
ctx,
|
|
1245
2193
|
config,
|
|
1246
2194
|
PI_NOTIFY_EVENT_MENU_DEFINITIONS.sound,
|
|
2195
|
+
onConfigChange,
|
|
1247
2196
|
);
|
|
1248
2197
|
continue;
|
|
1249
2198
|
}
|
|
@@ -1252,6 +2201,7 @@ async function configurePiNotifyMenu(
|
|
|
1252
2201
|
ctx,
|
|
1253
2202
|
config,
|
|
1254
2203
|
PI_NOTIFY_EVENT_MENU_DEFINITIONS.pushover,
|
|
2204
|
+
onConfigChange,
|
|
1255
2205
|
);
|
|
1256
2206
|
continue;
|
|
1257
2207
|
}
|
|
@@ -1259,14 +2209,33 @@ async function configurePiNotifyMenu(
|
|
|
1259
2209
|
const value = await ctx.ui.input("Notify command", config.PI_NOTIFY_CMD);
|
|
1260
2210
|
if (value !== undefined) {
|
|
1261
2211
|
config.PI_NOTIFY_CMD = normalizePiNotifyCommand(value, DEFAULT_PI_NOTIFY_CMD);
|
|
2212
|
+
onConfigChange();
|
|
1262
2213
|
ctx.ui.notify("Updated notify command", "info");
|
|
1263
2214
|
}
|
|
1264
2215
|
continue;
|
|
1265
2216
|
}
|
|
1266
2217
|
if (choice === "selected-sound-command") {
|
|
1267
2218
|
const nextLevel = await selectPiNotifySoundLevel(ctx, config["notify-sound"]);
|
|
1268
|
-
if (nextLevel
|
|
2219
|
+
if (nextLevel === "reset-defaults") {
|
|
2220
|
+
const defaultSoundLevel = getDefaultConfig("")["notify-sound"];
|
|
2221
|
+
const approved = await confirmResetChanges(
|
|
2222
|
+
ctx,
|
|
2223
|
+
"Confirm sound reset",
|
|
2224
|
+
[{ label: "Enable sound", previousValue: config["notify-sound"], nextValue: defaultSoundLevel }]
|
|
2225
|
+
.filter((change) => change.previousValue !== change.nextValue),
|
|
2226
|
+
"Approve restoring the documented default sound level.",
|
|
2227
|
+
"Abort the sound reset and keep the current value.",
|
|
2228
|
+
);
|
|
2229
|
+
if (!approved) {
|
|
2230
|
+
ctx.ui.notify("Aborted sound reset", "info");
|
|
2231
|
+
} else {
|
|
2232
|
+
config["notify-sound"] = defaultSoundLevel;
|
|
2233
|
+
onConfigChange();
|
|
2234
|
+
ctx.ui.notify("Restored default sound level", "info");
|
|
2235
|
+
}
|
|
2236
|
+
} else if (nextLevel !== undefined) {
|
|
1269
2237
|
config["notify-sound"] = nextLevel;
|
|
2238
|
+
onConfigChange();
|
|
1270
2239
|
ctx.ui.notify(`Enable sound set to ${nextLevel}`, "info");
|
|
1271
2240
|
}
|
|
1272
2241
|
continue;
|
|
@@ -1278,6 +2247,7 @@ async function configurePiNotifyMenu(
|
|
|
1278
2247
|
);
|
|
1279
2248
|
if (value?.trim()) {
|
|
1280
2249
|
config["notify-sound-toggle-shortcut"] = value.trim();
|
|
2250
|
+
onConfigChange();
|
|
1281
2251
|
ctx.ui.notify(
|
|
1282
2252
|
`Sound toggle hotkey bind set to ${config["notify-sound-toggle-shortcut"]}`,
|
|
1283
2253
|
"info",
|
|
@@ -1292,6 +2262,7 @@ async function configurePiNotifyMenu(
|
|
|
1292
2262
|
value,
|
|
1293
2263
|
DEFAULT_PI_NOTIFY_SOUND_LOW_CMD,
|
|
1294
2264
|
);
|
|
2265
|
+
onConfigChange();
|
|
1295
2266
|
ctx.ui.notify("Updated sound command (low vol.)", "info");
|
|
1296
2267
|
}
|
|
1297
2268
|
continue;
|
|
@@ -1303,6 +2274,7 @@ async function configurePiNotifyMenu(
|
|
|
1303
2274
|
value,
|
|
1304
2275
|
DEFAULT_PI_NOTIFY_SOUND_MID_CMD,
|
|
1305
2276
|
);
|
|
2277
|
+
onConfigChange();
|
|
1306
2278
|
ctx.ui.notify("Updated sound command (mid vol.)", "info");
|
|
1307
2279
|
}
|
|
1308
2280
|
continue;
|
|
@@ -1314,14 +2286,36 @@ async function configurePiNotifyMenu(
|
|
|
1314
2286
|
value,
|
|
1315
2287
|
DEFAULT_PI_NOTIFY_SOUND_HIGH_CMD,
|
|
1316
2288
|
);
|
|
2289
|
+
onConfigChange();
|
|
1317
2290
|
ctx.ui.notify("Updated sound command (high vol.)", "info");
|
|
1318
2291
|
}
|
|
1319
2292
|
continue;
|
|
1320
2293
|
}
|
|
1321
2294
|
if (choice === "notify-pushover-priority") {
|
|
1322
2295
|
const nextPriority = await selectPiNotifyPushoverPriority(ctx, config["notify-pushover-priority"]);
|
|
1323
|
-
if (nextPriority
|
|
2296
|
+
if (nextPriority === "reset-defaults") {
|
|
2297
|
+
const defaultPriority = getDefaultConfig("")["notify-pushover-priority"];
|
|
2298
|
+
const approved = await confirmResetChanges(
|
|
2299
|
+
ctx,
|
|
2300
|
+
"Confirm Pushover priority reset",
|
|
2301
|
+
[{
|
|
2302
|
+
label: "Pushover priority",
|
|
2303
|
+
previousValue: formatPiNotifyPushoverPriority(config["notify-pushover-priority"]),
|
|
2304
|
+
nextValue: formatPiNotifyPushoverPriority(defaultPriority),
|
|
2305
|
+
}].filter((change) => change.previousValue !== change.nextValue),
|
|
2306
|
+
"Approve restoring the documented default Pushover priority.",
|
|
2307
|
+
"Abort the Pushover priority reset and keep the current value.",
|
|
2308
|
+
);
|
|
2309
|
+
if (!approved) {
|
|
2310
|
+
ctx.ui.notify("Aborted Pushover priority reset", "info");
|
|
2311
|
+
} else {
|
|
2312
|
+
config["notify-pushover-priority"] = defaultPriority;
|
|
2313
|
+
onConfigChange();
|
|
2314
|
+
ctx.ui.notify("Restored default Pushover priority", "info");
|
|
2315
|
+
}
|
|
2316
|
+
} else if (nextPriority !== undefined) {
|
|
1324
2317
|
config["notify-pushover-priority"] = nextPriority;
|
|
2318
|
+
onConfigChange();
|
|
1325
2319
|
ctx.ui.notify(`Pushover priority set to ${formatPiNotifyPushoverPriority(nextPriority)}`, "info");
|
|
1326
2320
|
}
|
|
1327
2321
|
continue;
|
|
@@ -1333,17 +2327,22 @@ async function configurePiNotifyMenu(
|
|
|
1333
2327
|
value,
|
|
1334
2328
|
DEFAULT_PI_NOTIFY_PUSHOVER_TITLE,
|
|
1335
2329
|
);
|
|
2330
|
+
onConfigChange();
|
|
1336
2331
|
ctx.ui.notify("Updated Pushover title", "info");
|
|
1337
2332
|
}
|
|
1338
2333
|
continue;
|
|
1339
2334
|
}
|
|
1340
2335
|
if (choice === "notify-pushover-text") {
|
|
1341
|
-
const value = await ctx.ui.input(
|
|
2336
|
+
const value = await ctx.ui.input(
|
|
2337
|
+
"Pushover text",
|
|
2338
|
+
formatPiNotifyControlSequenceText(config["notify-pushover-text"]),
|
|
2339
|
+
);
|
|
1342
2340
|
if (value !== undefined) {
|
|
1343
2341
|
config["notify-pushover-text"] = normalizePiNotifyTemplateValue(
|
|
1344
|
-
value,
|
|
2342
|
+
parsePiNotifyControlSequenceText(value),
|
|
1345
2343
|
DEFAULT_PI_NOTIFY_PUSHOVER_TEXT,
|
|
1346
2344
|
);
|
|
2345
|
+
onConfigChange();
|
|
1347
2346
|
ctx.ui.notify("Updated Pushover text", "info");
|
|
1348
2347
|
}
|
|
1349
2348
|
continue;
|
|
@@ -1355,6 +2354,10 @@ async function configurePiNotifyMenu(
|
|
|
1355
2354
|
);
|
|
1356
2355
|
if (value !== undefined) {
|
|
1357
2356
|
config["notify-pushover-user-key"] = normalizePiNotifyPushoverCredential(value);
|
|
2357
|
+
if (!hasPiNotifyPushoverCredentials(config)) {
|
|
2358
|
+
config["notify-pushover-enabled"] = false;
|
|
2359
|
+
}
|
|
2360
|
+
onConfigChange();
|
|
1358
2361
|
ctx.ui.notify("Updated Pushover user key", "info");
|
|
1359
2362
|
}
|
|
1360
2363
|
continue;
|
|
@@ -1366,12 +2369,44 @@ async function configurePiNotifyMenu(
|
|
|
1366
2369
|
);
|
|
1367
2370
|
if (value !== undefined) {
|
|
1368
2371
|
config["notify-pushover-api-token"] = normalizePiNotifyPushoverCredential(value);
|
|
2372
|
+
if (!hasPiNotifyPushoverCredentials(config)) {
|
|
2373
|
+
config["notify-pushover-enabled"] = false;
|
|
2374
|
+
}
|
|
2375
|
+
onConfigChange();
|
|
1369
2376
|
ctx.ui.notify("Updated Pushover API token", "info");
|
|
1370
2377
|
}
|
|
1371
2378
|
continue;
|
|
1372
2379
|
}
|
|
1373
2380
|
if (choice === "reset-defaults") {
|
|
2381
|
+
const defaults = getDefaultConfig("");
|
|
2382
|
+
const resetPreview: ResetConfirmationChange[] = [
|
|
2383
|
+
{ label: "Enable notification", previousValue: formatPiNotifyStatus(config), nextValue: formatPiNotifyStatus(defaults) },
|
|
2384
|
+
{ label: "Enable sound", previousValue: config["notify-sound"], nextValue: defaults["notify-sound"] },
|
|
2385
|
+
{ label: "Sound toggle hotkey bind", previousValue: config["notify-sound-toggle-shortcut"], nextValue: defaults["notify-sound-toggle-shortcut"] },
|
|
2386
|
+
{ label: "Notify command", previousValue: config.PI_NOTIFY_CMD, nextValue: defaults.PI_NOTIFY_CMD },
|
|
2387
|
+
{ label: "Sound command (low vol.)", previousValue: config.PI_NOTIFY_SOUND_LOW_CMD, nextValue: defaults.PI_NOTIFY_SOUND_LOW_CMD },
|
|
2388
|
+
{ label: "Sound command (mid vol.)", previousValue: config.PI_NOTIFY_SOUND_MID_CMD, nextValue: defaults.PI_NOTIFY_SOUND_MID_CMD },
|
|
2389
|
+
{ label: "Sound command (high vol.)", previousValue: config.PI_NOTIFY_SOUND_HIGH_CMD, nextValue: defaults.PI_NOTIFY_SOUND_HIGH_CMD },
|
|
2390
|
+
{ label: "Enable pushover", previousValue: formatPiNotifyPushoverStatus(config), nextValue: formatPiNotifyPushoverStatus(defaults) },
|
|
2391
|
+
{ label: "Pushover priority", previousValue: formatPiNotifyPushoverPriority(config["notify-pushover-priority"]), nextValue: formatPiNotifyPushoverPriority(defaults["notify-pushover-priority"]) },
|
|
2392
|
+
{ label: "Pushover title", previousValue: config["notify-pushover-title"], nextValue: defaults["notify-pushover-title"] },
|
|
2393
|
+
{ label: "Pushover text", previousValue: formatPiNotifyControlSequenceText(config["notify-pushover-text"]), nextValue: formatPiNotifyControlSequenceText(defaults["notify-pushover-text"]) },
|
|
2394
|
+
{ label: "Pushover User Key/Delivery Group Key", previousValue: config["notify-pushover-user-key"] || "(empty)", nextValue: defaults["notify-pushover-user-key"] || "(empty)" },
|
|
2395
|
+
{ label: "Pushover Token/API Token Key", previousValue: config["notify-pushover-api-token"] || "(empty)", nextValue: defaults["notify-pushover-api-token"] || "(empty)" },
|
|
2396
|
+
].filter((change) => change.previousValue !== change.nextValue);
|
|
2397
|
+
const approved = await confirmResetChanges(
|
|
2398
|
+
ctx,
|
|
2399
|
+
"Confirm Notifications reset",
|
|
2400
|
+
resetPreview,
|
|
2401
|
+
"Approve restoring the documented notification defaults.",
|
|
2402
|
+
"Abort the notification reset and keep the current values.",
|
|
2403
|
+
);
|
|
2404
|
+
if (!approved) {
|
|
2405
|
+
ctx.ui.notify("Aborted notification reset", "info");
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
1374
2408
|
resetPiNotifyConfigToDefaults(config);
|
|
2409
|
+
onConfigChange();
|
|
1375
2410
|
ctx.ui.notify("Restored notification defaults", "info");
|
|
1376
2411
|
continue;
|
|
1377
2412
|
}
|
|
@@ -1399,7 +2434,7 @@ function registerPiNotifyShortcut(
|
|
|
1399
2434
|
if (typeof shortcutRegistrar.registerShortcut !== "function") {
|
|
1400
2435
|
return;
|
|
1401
2436
|
}
|
|
1402
|
-
const config = loadProjectConfig(
|
|
2437
|
+
const config = loadProjectConfig(getProcessCwdSafe());
|
|
1403
2438
|
shortcutRegistrar.registerShortcut(config["notify-sound-toggle-shortcut"], {
|
|
1404
2439
|
description: "Cycle pi-usereq notification sound level",
|
|
1405
2440
|
handler: async (ctx) => {
|
|
@@ -1415,110 +2450,141 @@ function registerPiNotifyShortcut(
|
|
|
1415
2450
|
|
|
1416
2451
|
/**
|
|
1417
2452
|
* @brief Registers bundled prompt commands with the extension.
|
|
1418
|
-
* @details Creates one `req-<prompt>` command per bundled prompt name. Each handler
|
|
2453
|
+
* @details Creates one `req-<prompt>` command per bundled prompt name. Each handler rejects non-`idle` workflow state, transitions the shared workflow state through `checking`, `error`, and `running`, runs dedicated prompt-command git and required-doc preflight checks, optionally prepares a dedicated worktree execution plan using the active session directory, persists the prompt metadata needed for switch-triggered rebinding, switches the active session to the verified execution cwd before prompt handoff, logs dedicated workflow-activation diagnostics, renders the prompt, starts prompt delivery into the forked active session, records `running` immediately after delivery handoff begins, and then awaits the wrapped prompt-delivery promise whose stale post-restore rejections are suppressed. Runtime is O(p) for registration; handler cost depends on prompt preflight, worktree preparation, session switching, prompt rendering, prompt dispatch, and optional debug logging. Side effects include command registration, status-controller mutation, worktree creation, active-session replacement, optional worktree rollback, user-message delivery during execution, and optional debug-log writes.
|
|
1419
2454
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
1420
2455
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
1421
2456
|
* @return {void} No return value.
|
|
1422
|
-
* @satisfies REQ-004, REQ-067, REQ-068, REQ-169
|
|
2457
|
+
* @satisfies REQ-004, REQ-067, REQ-068, REQ-169, REQ-200, REQ-201, REQ-202, REQ-203, REQ-206, REQ-207, REQ-219, REQ-220, REQ-221, REQ-224, REQ-225, REQ-226, REQ-227, REQ-245, REQ-246, REQ-247, REQ-277, REQ-281
|
|
1423
2458
|
*/
|
|
1424
2459
|
function registerPromptCommands(
|
|
1425
2460
|
pi: ExtensionAPI,
|
|
1426
2461
|
statusController: PiUsereqStatusController,
|
|
1427
2462
|
): void {
|
|
1428
|
-
|
|
2463
|
+
PROMPT_COMMAND_NAMES.forEach((promptName) => {
|
|
1429
2464
|
pi.registerCommand(`req-${promptName}`, {
|
|
1430
|
-
description:
|
|
2465
|
+
description: resolvePromptCommandDescription(promptName),
|
|
1431
2466
|
handler: async (args, ctx) => {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
2467
|
+
if (statusController.state.workflowState !== "idle") {
|
|
2468
|
+
const message = `ERROR: Prompt workflow state is ${statusController.state.workflowState}, expected idle.`;
|
|
2469
|
+
ctx.ui.notify(message, "error");
|
|
2470
|
+
throw new ReqError(message, 1);
|
|
2471
|
+
}
|
|
2472
|
+
const commandCwd = resolveLiveBootstrapCwd(ctx.cwd);
|
|
2473
|
+
syncContextCwdMirror(ctx, commandCwd);
|
|
2474
|
+
bootstrapRuntimePathState(commandCwd, {
|
|
2475
|
+
gitPath: resolveRuntimeGitPath(commandCwd),
|
|
2476
|
+
});
|
|
2477
|
+
const projectBase = getProjectBase(commandCwd);
|
|
2478
|
+
const config = loadProjectConfig(commandCwd);
|
|
2479
|
+
transitionPromptWorkflowState(
|
|
2480
|
+
statusController,
|
|
2481
|
+
ctx,
|
|
2482
|
+
projectBase,
|
|
2483
|
+
config,
|
|
1436
2484
|
promptName,
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
2485
|
+
"checking",
|
|
2486
|
+
);
|
|
2487
|
+
ensureBundledResourcesAccessible();
|
|
2488
|
+
let executionPlan: PromptCommandExecutionPlan | undefined;
|
|
2489
|
+
let promptContext = ctx;
|
|
2490
|
+
try {
|
|
2491
|
+
executionPlan = preparePromptCommandExecution(
|
|
2492
|
+
promptName,
|
|
2493
|
+
args,
|
|
2494
|
+
projectBase,
|
|
2495
|
+
config,
|
|
2496
|
+
ctx.sessionManager.getSessionFile(),
|
|
2497
|
+
ctx.sessionManager.getSessionDir?.(),
|
|
2498
|
+
ctx.sessionManager.getBranch?.(),
|
|
2499
|
+
{
|
|
2500
|
+
config,
|
|
2501
|
+
workflowState: statusController.state.workflowState,
|
|
2502
|
+
},
|
|
2503
|
+
);
|
|
2504
|
+
const content = renderPrompt(
|
|
2505
|
+
promptName,
|
|
2506
|
+
args,
|
|
2507
|
+
projectBase,
|
|
2508
|
+
config,
|
|
2509
|
+
executionPlan,
|
|
2510
|
+
);
|
|
2511
|
+
statusController.state.pendingPromptRequest = executionPlan;
|
|
2512
|
+
writePersistedPromptCommandRuntimeState({
|
|
2513
|
+
workflowState: statusController.state.workflowState,
|
|
2514
|
+
pendingPromptRequest: statusController.state.pendingPromptRequest,
|
|
2515
|
+
activePromptRequest: statusController.state.activePromptRequest,
|
|
2516
|
+
});
|
|
2517
|
+
promptContext = (await activatePromptCommandExecution(executionPlan, ctx) ?? ctx) as typeof ctx;
|
|
2518
|
+
logPromptWorkflowEvent(
|
|
2519
|
+
projectBase,
|
|
2520
|
+
config,
|
|
2521
|
+
statusController.state.workflowState,
|
|
2522
|
+
promptName,
|
|
2523
|
+
"workflow_activation",
|
|
2524
|
+
{
|
|
2525
|
+
execution_session_file: executionPlan.executionSessionFile,
|
|
2526
|
+
context_path: executionPlan.contextPath,
|
|
2527
|
+
},
|
|
2528
|
+
{
|
|
2529
|
+
success: true,
|
|
2530
|
+
context_path: promptContext.cwd ?? executionPlan.contextPath,
|
|
2531
|
+
prompt_context_has_switch_session: typeof promptContext.switchSession === "function",
|
|
2532
|
+
},
|
|
2533
|
+
);
|
|
2534
|
+
renderPiUsereqStatus(statusController, promptContext);
|
|
2535
|
+
const promptDelivery = deliverPromptCommand(pi, content, promptContext);
|
|
2536
|
+
transitionPromptWorkflowState(
|
|
2537
|
+
statusController,
|
|
2538
|
+
promptContext,
|
|
2539
|
+
projectBase,
|
|
2540
|
+
config,
|
|
2541
|
+
promptName,
|
|
2542
|
+
"running",
|
|
2543
|
+
);
|
|
2544
|
+
await promptDelivery;
|
|
2545
|
+
} catch (error) {
|
|
2546
|
+
if (shouldIgnoreLatePromptDeliveryFailure(error, statusController.state.workflowState, executionPlan)) {
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
|
|
2550
|
+
statusController.state.pendingPromptRequest = undefined;
|
|
2551
|
+
statusController.state.activePromptRequest = undefined;
|
|
2552
|
+
transitionPromptWorkflowState(
|
|
2553
|
+
statusController,
|
|
2554
|
+
promptContext,
|
|
2555
|
+
projectBase,
|
|
2556
|
+
config,
|
|
2557
|
+
promptName,
|
|
2558
|
+
"error",
|
|
2559
|
+
);
|
|
2560
|
+
if (executionPlan !== undefined) {
|
|
2561
|
+
const abortResult = await abortPromptCommandExecution(executionPlan, promptContext, {
|
|
2562
|
+
config,
|
|
2563
|
+
workflowState: statusController.state.workflowState,
|
|
2564
|
+
});
|
|
2565
|
+
promptContext = (abortResult.activeContext ?? promptContext) as typeof ctx;
|
|
2566
|
+
if (!abortResult.cleanupSucceeded && abortResult.errorMessage) {
|
|
2567
|
+
notifyContextSafely(promptContext, abortResult.errorMessage, "error");
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2571
|
+
notifyContextSafely(promptContext, message, "error");
|
|
2572
|
+
throw error;
|
|
2573
|
+
}
|
|
2574
|
+
},
|
|
2575
|
+
});
|
|
1520
2576
|
});
|
|
2577
|
+
}
|
|
1521
2578
|
|
|
2579
|
+
|
|
2580
|
+
/**
|
|
2581
|
+
* @brief Registers pi-usereq agent tools exposed to the model.
|
|
2582
|
+
* @details Defines the tool schemas, prompt metadata, and execution handlers that bridge extension tool calls into tool-runner operations without registering duplicate custom slash commands for the same capabilities. Runtime is O(t) for registration; execution cost depends on the selected tool. Side effects include tool registration.
|
|
2583
|
+
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
2584
|
+
* @return {void} No return value.
|
|
2585
|
+
* @satisfies REQ-005, REQ-010, REQ-011, REQ-014, REQ-017, REQ-044, REQ-069, REQ-070, REQ-071, REQ-072, REQ-073, REQ-074, REQ-075, REQ-076, REQ-077, REQ-078, REQ-079, REQ-080, REQ-089, REQ-090, REQ-091, REQ-092, REQ-093, REQ-094, REQ-095, REQ-096, REQ-097, REQ-098, REQ-099, REQ-100, REQ-101, REQ-102
|
|
2586
|
+
*/
|
|
2587
|
+
function registerAgentTools(pi: ExtensionAPI): void {
|
|
1522
2588
|
const filesReferencesSchema = Type.Object(
|
|
1523
2589
|
{
|
|
1524
2590
|
files: Type.Array(
|
|
@@ -1527,7 +2593,7 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1527
2593
|
),
|
|
1528
2594
|
},
|
|
1529
2595
|
{
|
|
1530
|
-
description: "Input contract: files[]. Output contract:
|
|
2596
|
+
description: "Input contract: files[]. Output contract: monolithic markdown in content[0].text plus details.execution diagnostics. Missing or unsupported inputs surface through execution diagnostics. The tool fails when no source file can be analyzed.",
|
|
1531
2597
|
},
|
|
1532
2598
|
);
|
|
1533
2599
|
const multiFileSchema = Type.Object(
|
|
@@ -1538,7 +2604,7 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1538
2604
|
),
|
|
1539
2605
|
},
|
|
1540
2606
|
{
|
|
1541
|
-
description: "Input contract: files[]. Output contract:
|
|
2607
|
+
description: "Input contract: files[]. Output contract: monolithic text in content[0].text plus details.execution diagnostics.",
|
|
1542
2608
|
},
|
|
1543
2609
|
);
|
|
1544
2610
|
const filesTokensSchema = Type.Object(
|
|
@@ -1549,58 +2615,43 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1549
2615
|
),
|
|
1550
2616
|
},
|
|
1551
2617
|
{
|
|
1552
|
-
description: "Input contract: files[]. Output contract:
|
|
2618
|
+
description: "Input contract: files[]. Output contract: monolithic pack-summary text in content[0].text plus details.execution diagnostics. The tool fails when no processable files remain.",
|
|
1553
2619
|
},
|
|
1554
2620
|
);
|
|
1555
2621
|
|
|
1556
2622
|
pi.registerTool({
|
|
1557
2623
|
name: "files-tokens",
|
|
1558
2624
|
label: "files-tokens",
|
|
1559
|
-
description: "Scope: explicit files. Return
|
|
1560
|
-
promptSnippet: "Return the
|
|
2625
|
+
description: "Scope: explicit files. Return the monolithic token pack summary in content[0].text and keep only execution metadata in details.execution.",
|
|
2626
|
+
promptSnippet: "Return the monolithic token summary for caller-selected files.",
|
|
1561
2627
|
promptGuidelines: [
|
|
1562
2628
|
"Scope: explicit files selected by files[]; caller order is preserved; each item may be project-relative or absolute.",
|
|
1563
|
-
"Output contract: summary
|
|
1564
|
-
"
|
|
1565
|
-
"Behavior contract: missing or non-file inputs become skipped entries; read failures become error entries; the tool fails only when no processable files remain.",
|
|
2629
|
+
"Output contract: monolithic pack-summary text in content[0].text; details.execution preserves only exit code and residual diagnostics.",
|
|
2630
|
+
"Behavior contract: missing or non-file inputs surface through details.execution diagnostics; the tool fails when no processable files remain.",
|
|
1566
2631
|
],
|
|
2632
|
+
renderResult: buildStructuredToolRenderResult("files-tokens"),
|
|
1567
2633
|
parameters: filesTokensSchema,
|
|
1568
2634
|
async execute(_toolCallId, params) {
|
|
1569
|
-
|
|
1570
|
-
const payload = buildTokenToolPayload({
|
|
1571
|
-
toolName: "files-tokens",
|
|
1572
|
-
scope: "explicit-files",
|
|
1573
|
-
baseDir: process.cwd(),
|
|
1574
|
-
requestedPaths: params.files,
|
|
1575
|
-
encodingName: TOKEN_COUNTER_ENCODING,
|
|
1576
|
-
});
|
|
1577
|
-
return buildTokenToolExecuteResult(payload);
|
|
1578
|
-
} catch (error) {
|
|
1579
|
-
return buildFailedTokenToolExecuteResult(error);
|
|
1580
|
-
}
|
|
2635
|
+
return executeMonolithicTool(() => runFilesTokens(params.files));
|
|
1581
2636
|
},
|
|
1582
2637
|
});
|
|
1583
2638
|
|
|
1584
2639
|
pi.registerTool({
|
|
1585
2640
|
name: "files-references",
|
|
1586
2641
|
label: "files-references",
|
|
1587
|
-
description: "Scope: explicit source files. Return
|
|
1588
|
-
promptSnippet: "Return the
|
|
2642
|
+
description: "Scope: explicit source files. Return the monolithic references markdown report in content[0].text and keep only execution metadata in details.execution.",
|
|
2643
|
+
promptSnippet: "Return the monolithic references markdown report for caller-selected source files.",
|
|
1589
2644
|
promptGuidelines: [
|
|
1590
2645
|
"Scope: explicit source files selected by files[]; caller order is preserved; each item may be project-relative or absolute.",
|
|
1591
|
-
"Output contract:
|
|
1592
|
-
"
|
|
1593
|
-
"Behavior contract: missing inputs, non-file inputs,
|
|
2646
|
+
"Output contract: monolithic markdown in content[0].text; details.execution preserves only exit code and residual diagnostics.",
|
|
2647
|
+
"Formatting contract: content matches the Python reference renderer used by `generate_markdown.py`.",
|
|
2648
|
+
"Behavior contract: missing inputs, non-file inputs, unsupported extensions, and analysis failures surface through details.execution diagnostics.",
|
|
1594
2649
|
],
|
|
2650
|
+
renderResult: buildStructuredToolRenderResult("files-references"),
|
|
1595
2651
|
parameters: filesReferencesSchema,
|
|
1596
2652
|
async execute(_toolCallId, params) {
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
scope: "explicit-files",
|
|
1600
|
-
baseDir: process.cwd(),
|
|
1601
|
-
requestedPaths: params.files,
|
|
1602
|
-
});
|
|
1603
|
-
return buildReferenceToolExecuteResult(payload);
|
|
2653
|
+
const contextPath = getRuntimeContextPath(process.cwd());
|
|
2654
|
+
return executeMonolithicTool(() => runFilesReferences(params.files, contextPath));
|
|
1604
2655
|
},
|
|
1605
2656
|
});
|
|
1606
2657
|
|
|
@@ -1610,38 +2661,37 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1610
2661
|
Type.String({ description: "Project-relative or absolute source file path resolved from the current working directory when not already absolute" }),
|
|
1611
2662
|
{ description: "Explicit source-file list preserved in caller order" },
|
|
1612
2663
|
),
|
|
1613
|
-
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true,
|
|
2664
|
+
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true, fenced code block lines include original source line-number prefixes" })),
|
|
1614
2665
|
},
|
|
1615
2666
|
{
|
|
1616
|
-
description: "Input contract: files[] plus optional enableLineNumbers. Output contract:
|
|
2667
|
+
description: "Input contract: files[] plus optional enableLineNumbers. Output contract: monolithic markdown in content[0].text plus details.execution diagnostics. The tool fails when no file is compressed.",
|
|
1617
2668
|
},
|
|
1618
2669
|
);
|
|
1619
2670
|
|
|
1620
2671
|
pi.registerTool({
|
|
1621
2672
|
name: "files-compress",
|
|
1622
2673
|
label: "files-compress",
|
|
1623
|
-
description: "Scope: explicit files. Return
|
|
1624
|
-
promptSnippet: "Return the
|
|
2674
|
+
description: "Scope: explicit files. Return the monolithic compression markdown report in content[0].text and keep only execution metadata in details.execution.",
|
|
2675
|
+
promptSnippet: "Return the monolithic compression markdown report for caller-selected source files.",
|
|
1625
2676
|
promptGuidelines: [
|
|
1626
2677
|
"Scope: explicit source files selected by files[]; caller order is preserved; each item may be project-relative or absolute.",
|
|
1627
|
-
"Output contract:
|
|
1628
|
-
"
|
|
1629
|
-
"
|
|
2678
|
+
"Output contract: monolithic markdown in content[0].text; details.execution preserves only exit code and residual diagnostics.",
|
|
2679
|
+
"Formatting contract: output uses `@@@ <path> | <language>` headers, `> Lines:` metadata, and fenced code blocks matching `compress_files.py`.",
|
|
2680
|
+
"Line-number behavior: enableLineNumbers toggles original source line prefixes inside fenced code blocks.",
|
|
1630
2681
|
],
|
|
2682
|
+
renderResult: buildStructuredToolRenderResult("files-compress"),
|
|
1631
2683
|
parameters: filesCompressSchema,
|
|
1632
2684
|
async execute(_toolCallId, params) {
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
});
|
|
1640
|
-
return buildCompressionToolExecuteResult(payload);
|
|
2685
|
+
const contextPath = getRuntimeContextPath(process.cwd());
|
|
2686
|
+
return executeMonolithicTool(() => runFilesCompress(
|
|
2687
|
+
params.files,
|
|
2688
|
+
contextPath,
|
|
2689
|
+
params.enableLineNumbers ?? false,
|
|
2690
|
+
));
|
|
1641
2691
|
},
|
|
1642
2692
|
});
|
|
1643
2693
|
|
|
1644
|
-
const
|
|
2694
|
+
const filesSearchSchema = Type.Object(
|
|
1645
2695
|
{
|
|
1646
2696
|
tag: Type.String({ description: "Pipe-separated construct-tag filter applied case-insensitively; unsupported tags are ignored" }),
|
|
1647
2697
|
pattern: Type.String({ description: "JavaScript RegExp applied to construct names only; use ^...$ for exact-name matching" }),
|
|
@@ -1649,434 +2699,198 @@ function registerAgentTools(pi: ExtensionAPI): void {
|
|
|
1649
2699
|
Type.String({ description: "Project-relative or absolute source file path resolved from the current working directory when not already absolute" }),
|
|
1650
2700
|
{ description: "Explicit source-file list preserved in caller order" },
|
|
1651
2701
|
),
|
|
1652
|
-
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true,
|
|
2702
|
+
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true, fenced code block lines include original source line-number prefixes" })),
|
|
1653
2703
|
},
|
|
1654
2704
|
{
|
|
1655
|
-
description:
|
|
2705
|
+
description: buildSearchToolSchemaDescription("explicit-files"),
|
|
1656
2706
|
},
|
|
1657
2707
|
);
|
|
1658
2708
|
|
|
1659
2709
|
pi.registerTool({
|
|
1660
|
-
name: "files-
|
|
1661
|
-
label: "files-
|
|
1662
|
-
description: "Scope: explicit source files. Return
|
|
1663
|
-
promptSnippet: "Return the
|
|
1664
|
-
promptGuidelines:
|
|
1665
|
-
|
|
2710
|
+
name: "files-search",
|
|
2711
|
+
label: "files-search",
|
|
2712
|
+
description: "Scope: explicit source files. Return the monolithic construct-search markdown report in content[0].text and keep only execution metadata in details.execution.",
|
|
2713
|
+
promptSnippet: "Return the monolithic construct-search markdown report for caller-selected source files.",
|
|
2714
|
+
promptGuidelines: buildSearchToolPromptGuidelines("explicit-files"),
|
|
2715
|
+
renderResult: buildStructuredToolRenderResult("files-search"),
|
|
2716
|
+
parameters: filesSearchSchema,
|
|
1666
2717
|
async execute(_toolCallId, params) {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
tagFilter: params.tag,
|
|
1672
|
-
pattern: params.pattern,
|
|
1673
|
-
requestedPaths: params.files,
|
|
1674
|
-
includeLineNumbers: params.enableLineNumbers ?? false,
|
|
1675
|
-
});
|
|
1676
|
-
return buildFindToolExecuteResult(payload);
|
|
2718
|
+
return executeMonolithicTool(() => runFilesSearch(
|
|
2719
|
+
[params.tag, params.pattern, ...params.files],
|
|
2720
|
+
params.enableLineNumbers ?? false,
|
|
2721
|
+
));
|
|
1677
2722
|
},
|
|
1678
2723
|
});
|
|
1679
2724
|
|
|
1680
2725
|
const referencesSchema = Type.Object(
|
|
1681
2726
|
{},
|
|
1682
2727
|
{
|
|
1683
|
-
description: "Input contract: no params. Scope is the configured src-dir list resolved from the current project configuration. Output contract:
|
|
2728
|
+
description: "Input contract: no params. Scope is the configured src-dir list resolved from the current project configuration. Output contract: monolithic markdown in content[0].text plus details.execution diagnostics.",
|
|
1684
2729
|
},
|
|
1685
2730
|
);
|
|
1686
2731
|
const tokensSchema = Type.Object(
|
|
1687
2732
|
{},
|
|
1688
2733
|
{
|
|
1689
|
-
description: "Input contract: no params. Scope is the configured docs-dir plus canonical docs REQUIREMENTS.md, WORKFLOW.md, and REFERENCES.md. Output contract:
|
|
2734
|
+
description: "Input contract: no params. Scope is the configured docs-dir plus canonical docs REQUIREMENTS.md, WORKFLOW.md, and REFERENCES.md. Output contract: monolithic pack-summary text in content[0].text plus details.execution diagnostics.",
|
|
1690
2735
|
},
|
|
1691
2736
|
);
|
|
1692
2737
|
|
|
1693
2738
|
pi.registerTool({
|
|
1694
2739
|
name: "references",
|
|
1695
2740
|
label: "references",
|
|
1696
|
-
description: "Scope: configured project source directories. Return
|
|
1697
|
-
promptSnippet: "Return the
|
|
2741
|
+
description: "Scope: configured project source directories. Return the monolithic references markdown report in content[0].text and keep only execution metadata in details.execution.",
|
|
2742
|
+
promptSnippet: "Return the monolithic project references markdown report from the configured source directories.",
|
|
1698
2743
|
promptGuidelines: [
|
|
1699
2744
|
"Scope: no params; resolve src-dir from the current project configuration and scan the configured source surface from the current working directory.",
|
|
1700
|
-
"Output contract:
|
|
1701
|
-
"
|
|
1702
|
-
"
|
|
2745
|
+
"Output contract: monolithic markdown in content[0].text; details.execution preserves only exit code and residual diagnostics.",
|
|
2746
|
+
"Formatting contract: content prepends the file-structure markdown block before the per-file markdown produced by `generate_markdown.py`.",
|
|
2747
|
+
"Configuration contract: output changes with cwd-derived project config and src-dir values; the tool does not accept explicit file overrides.",
|
|
1703
2748
|
],
|
|
2749
|
+
renderResult: buildStructuredToolRenderResult("references"),
|
|
1704
2750
|
parameters: referencesSchema,
|
|
1705
2751
|
async execute() {
|
|
1706
|
-
const
|
|
1707
|
-
const
|
|
1708
|
-
const
|
|
1709
|
-
|
|
1710
|
-
scope: "configured-source-directories",
|
|
1711
|
-
baseDir: projectBase,
|
|
1712
|
-
requestedPaths: collectSourceFiles(config["src-dir"], projectBase),
|
|
1713
|
-
sourceDirectoryPaths: config["src-dir"],
|
|
1714
|
-
});
|
|
1715
|
-
return buildReferenceToolExecuteResult(payload);
|
|
2752
|
+
const contextPath = getRuntimeContextPath(process.cwd());
|
|
2753
|
+
const projectBase = getProjectBase(contextPath);
|
|
2754
|
+
const config = loadProjectConfig(projectBase);
|
|
2755
|
+
return executeMonolithicTool(() => runReferences(contextPath, config));
|
|
1716
2756
|
},
|
|
1717
2757
|
});
|
|
1718
2758
|
|
|
1719
2759
|
const compressSchema = Type.Object(
|
|
1720
2760
|
{
|
|
1721
|
-
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true,
|
|
2761
|
+
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true, fenced code block lines include original source line-number prefixes" })),
|
|
1722
2762
|
},
|
|
1723
2763
|
{
|
|
1724
|
-
description: "Input contract: optional enableLineNumbers boolean. Scope is the configured src-dir list resolved from the current project configuration. Output contract:
|
|
2764
|
+
description: "Input contract: optional enableLineNumbers boolean. Scope is the configured src-dir list resolved from the current project configuration. Output contract: monolithic markdown in content[0].text plus details.execution diagnostics.",
|
|
1725
2765
|
},
|
|
1726
2766
|
);
|
|
1727
2767
|
|
|
1728
2768
|
pi.registerTool({
|
|
1729
2769
|
name: "compress",
|
|
1730
2770
|
label: "compress",
|
|
1731
|
-
description: "Scope: configured project source directories. Return
|
|
1732
|
-
promptSnippet: "Return the
|
|
2771
|
+
description: "Scope: configured project source directories. Return the monolithic compression markdown report in content[0].text and keep only execution metadata in details.execution.",
|
|
2772
|
+
promptSnippet: "Return the monolithic project compression markdown report from the configured source directories.",
|
|
1733
2773
|
promptGuidelines: [
|
|
1734
2774
|
"Scope: resolve src-dir from the current project configuration and scan the configured source surface from the current working directory.",
|
|
1735
|
-
"Output contract:
|
|
1736
|
-
"
|
|
1737
|
-
"
|
|
2775
|
+
"Output contract: monolithic markdown in content[0].text; details.execution preserves only exit code and residual diagnostics.",
|
|
2776
|
+
"Formatting contract: output uses `@@@ <path> | <language>` headers, `> Lines:` metadata, and fenced code blocks matching `compress_files.py`.",
|
|
2777
|
+
"Line-number behavior: enableLineNumbers toggles original source line prefixes inside fenced code blocks.",
|
|
1738
2778
|
],
|
|
2779
|
+
renderResult: buildStructuredToolRenderResult("compress"),
|
|
1739
2780
|
parameters: compressSchema,
|
|
1740
2781
|
async execute(_toolCallId, params) {
|
|
1741
|
-
const
|
|
1742
|
-
const
|
|
1743
|
-
const
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
includeLineNumbers: params.enableLineNumbers ?? false,
|
|
1750
|
-
sourceDirectoryPaths: config["src-dir"],
|
|
1751
|
-
});
|
|
1752
|
-
return buildCompressionToolExecuteResult(payload);
|
|
2782
|
+
const contextPath = getRuntimeContextPath(process.cwd());
|
|
2783
|
+
const projectBase = getProjectBase(contextPath);
|
|
2784
|
+
const config = loadProjectConfig(projectBase);
|
|
2785
|
+
return executeMonolithicTool(() => runCompress(
|
|
2786
|
+
contextPath,
|
|
2787
|
+
config,
|
|
2788
|
+
params.enableLineNumbers ?? false,
|
|
2789
|
+
));
|
|
1753
2790
|
},
|
|
1754
2791
|
});
|
|
1755
2792
|
|
|
1756
|
-
const
|
|
2793
|
+
const searchSchema = Type.Object(
|
|
1757
2794
|
{
|
|
1758
2795
|
tag: Type.String({ description: "Pipe-separated construct-tag filter applied case-insensitively; unsupported tags are ignored" }),
|
|
1759
2796
|
pattern: Type.String({ description: "JavaScript RegExp applied to construct names only; use ^...$ for exact-name matching" }),
|
|
1760
|
-
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true,
|
|
2797
|
+
enableLineNumbers: Type.Optional(Type.Boolean({ description: "When true, fenced code block lines include original source line-number prefixes" })),
|
|
1761
2798
|
},
|
|
1762
2799
|
{
|
|
1763
|
-
description:
|
|
2800
|
+
description: buildSearchToolSchemaDescription("configured-source-directories"),
|
|
1764
2801
|
},
|
|
1765
2802
|
);
|
|
1766
2803
|
|
|
1767
2804
|
pi.registerTool({
|
|
1768
|
-
name: "
|
|
1769
|
-
label: "
|
|
1770
|
-
description: "Scope: configured project source directories. Return
|
|
1771
|
-
promptSnippet: "Return the
|
|
1772
|
-
promptGuidelines:
|
|
1773
|
-
|
|
2805
|
+
name: "search",
|
|
2806
|
+
label: "search",
|
|
2807
|
+
description: "Scope: configured project source directories. Return the monolithic construct-search markdown report in content[0].text and keep only execution metadata in details.execution.",
|
|
2808
|
+
promptSnippet: "Return the monolithic construct-search markdown report from the configured source directories.",
|
|
2809
|
+
promptGuidelines: buildSearchToolPromptGuidelines("configured-source-directories"),
|
|
2810
|
+
renderResult: buildStructuredToolRenderResult("search"),
|
|
2811
|
+
parameters: searchSchema,
|
|
1774
2812
|
async execute(_toolCallId, params) {
|
|
1775
|
-
const
|
|
1776
|
-
const
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
includeLineNumbers: params.enableLineNumbers ?? false,
|
|
1786
|
-
sourceDirectoryPaths: config["src-dir"],
|
|
1787
|
-
});
|
|
1788
|
-
return buildFindToolExecuteResult(payload);
|
|
2813
|
+
const contextPath = getRuntimeContextPath(process.cwd());
|
|
2814
|
+
const projectBase = getProjectBase(contextPath);
|
|
2815
|
+
const config = loadProjectConfig(projectBase);
|
|
2816
|
+
return executeMonolithicTool(() => runSearch(
|
|
2817
|
+
contextPath,
|
|
2818
|
+
params.tag,
|
|
2819
|
+
params.pattern,
|
|
2820
|
+
config,
|
|
2821
|
+
params.enableLineNumbers ?? false,
|
|
2822
|
+
));
|
|
1789
2823
|
},
|
|
1790
2824
|
});
|
|
1791
2825
|
|
|
1792
2826
|
pi.registerTool({
|
|
1793
2827
|
name: "tokens",
|
|
1794
2828
|
label: "tokens",
|
|
1795
|
-
description: "Scope: canonical docs from the configured docs-dir. Return the
|
|
1796
|
-
promptSnippet: "Return the
|
|
2829
|
+
description: "Scope: canonical docs from the configured docs-dir. Return the monolithic token pack summary in content[0].text and keep only execution metadata in details.execution.",
|
|
2830
|
+
promptSnippet: "Return the monolithic token summary for canonical documentation files.",
|
|
1797
2831
|
promptGuidelines: [
|
|
1798
2832
|
"Scope: no params; resolve docs-dir from project config; target canonical docs REQUIREMENTS.md, WORKFLOW.md, and REFERENCES.md.",
|
|
1799
|
-
"Output contract:
|
|
1800
|
-
"
|
|
1801
|
-
"Behavior contract: missing canonical docs become skipped entries; read failures become error entries; the tool fails only when no processable canonical docs remain.",
|
|
2833
|
+
"Output contract: monolithic pack-summary text in content[0].text; details.execution preserves only exit code and residual diagnostics.",
|
|
2834
|
+
"Behavior contract: missing canonical docs surface through details.execution diagnostics; the tool fails when no processable canonical docs remain.",
|
|
1802
2835
|
],
|
|
2836
|
+
renderResult: buildStructuredToolRenderResult("tokens"),
|
|
1803
2837
|
parameters: tokensSchema,
|
|
1804
2838
|
async execute() {
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
const canonicalDocNames = ["REQUIREMENTS.md", "WORKFLOW.md", "REFERENCES.md"];
|
|
1810
|
-
const payload = buildTokenToolPayload({
|
|
1811
|
-
toolName: "tokens",
|
|
1812
|
-
scope: "canonical-docs",
|
|
1813
|
-
baseDir: projectBase,
|
|
1814
|
-
requestedPaths: canonicalDocNames.map((name) => path.join(docsDir, name)),
|
|
1815
|
-
docsDir,
|
|
1816
|
-
canonicalDocNames,
|
|
1817
|
-
encodingName: TOKEN_COUNTER_ENCODING,
|
|
1818
|
-
});
|
|
1819
|
-
return buildTokenToolExecuteResult(payload);
|
|
1820
|
-
} catch (error) {
|
|
1821
|
-
return buildFailedTokenToolExecuteResult(error);
|
|
1822
|
-
}
|
|
2839
|
+
const contextPath = getRuntimeContextPath(process.cwd());
|
|
2840
|
+
const projectBase = getProjectBase(contextPath);
|
|
2841
|
+
const config = loadProjectConfig(projectBase);
|
|
2842
|
+
return executeMonolithicTool(() => runTokens(contextPath, config));
|
|
1823
2843
|
},
|
|
1824
2844
|
});
|
|
1825
2845
|
|
|
1826
2846
|
pi.registerTool({
|
|
1827
2847
|
name: "files-static-check",
|
|
1828
2848
|
label: "files-static-check",
|
|
1829
|
-
description: "Scope: explicit files. Return
|
|
1830
|
-
promptSnippet: "Return the
|
|
2849
|
+
description: "Scope: explicit files. Return the monolithic static-check report in content[0].text and keep only execution metadata in details.execution.",
|
|
2850
|
+
promptSnippet: "Return the monolithic explicit-file static-check report for the current project configuration.",
|
|
1831
2851
|
promptGuidelines: [
|
|
1832
2852
|
"Input contract: files[]. Scope is explicit caller-selected files resolved from the current working directory.",
|
|
1833
|
-
"Output contract:
|
|
2853
|
+
"Output contract: monolithic text in content[0].text; details.execution preserves only exit code and residual diagnostics.",
|
|
1834
2854
|
"Configuration contract: checker selection is derived from the cwd-resolved static-check configuration and file extensions only.",
|
|
1835
|
-
"Failure contract: execution
|
|
2855
|
+
"Failure contract: execution diagnostics preserve failing checker output and skipped-input warnings.",
|
|
1836
2856
|
],
|
|
2857
|
+
renderResult: buildStructuredToolRenderResult("files-static-check"),
|
|
1837
2858
|
parameters: multiFileSchema,
|
|
1838
2859
|
async execute(_toolCallId, params) {
|
|
1839
|
-
const
|
|
1840
|
-
const
|
|
1841
|
-
const
|
|
1842
|
-
|
|
1843
|
-
const payload = buildStaticCheckToolPayload(
|
|
1844
|
-
"files-static-check",
|
|
1845
|
-
"explicit-files",
|
|
1846
|
-
projectBase,
|
|
1847
|
-
params.files,
|
|
1848
|
-
[],
|
|
1849
|
-
[],
|
|
1850
|
-
staticCheckConfig,
|
|
1851
|
-
buildToolExecutionSection(result),
|
|
1852
|
-
);
|
|
1853
|
-
return buildStructuredToolExecuteResult(payload);
|
|
2860
|
+
const contextPath = getRuntimeContextPath(process.cwd());
|
|
2861
|
+
const projectBase = getProjectBase(contextPath);
|
|
2862
|
+
const config = loadProjectConfig(projectBase);
|
|
2863
|
+
return executeMonolithicTool(() => runFilesStaticCheck(params.files, projectBase, config));
|
|
1854
2864
|
},
|
|
1855
2865
|
});
|
|
1856
2866
|
|
|
1857
2867
|
const staticCheckSchema = Type.Object(
|
|
1858
2868
|
{},
|
|
1859
2869
|
{
|
|
1860
|
-
description: "Input contract: no params. Scope is the configured src-dir plus tests-dir selection after fixture exclusion. Output contract:
|
|
2870
|
+
description: "Input contract: no params. Scope is the configured src-dir plus tests-dir selection after fixture exclusion. Output contract: monolithic text in content[0].text plus details.execution diagnostics.",
|
|
1861
2871
|
},
|
|
1862
2872
|
);
|
|
1863
2873
|
pi.registerTool({
|
|
1864
2874
|
name: "static-check",
|
|
1865
2875
|
label: "static-check",
|
|
1866
|
-
description: "Scope: configured source and test directories. Return
|
|
1867
|
-
promptSnippet: "Return the
|
|
2876
|
+
description: "Scope: configured source and test directories. Return the monolithic static-check report in content[0].text and keep only execution metadata in details.execution.",
|
|
2877
|
+
promptSnippet: "Return the monolithic project static-check report for the current configuration.",
|
|
1868
2878
|
promptGuidelines: [
|
|
1869
2879
|
"Input contract: no params. Scope is src-dir plus tests-dir from the cwd-derived project configuration.",
|
|
1870
|
-
"Output contract:
|
|
2880
|
+
"Output contract: monolithic text in content[0].text; details.execution preserves only exit code and residual diagnostics.",
|
|
1871
2881
|
"Selection contract: tests/fixtures and <tests-dir>/fixtures are excluded before checker dispatch.",
|
|
1872
|
-
"Failure contract: execution
|
|
2882
|
+
"Failure contract: execution diagnostics preserve failing checker output and skipped-selection warnings.",
|
|
1873
2883
|
],
|
|
2884
|
+
renderResult: buildStructuredToolRenderResult("static-check"),
|
|
1874
2885
|
parameters: staticCheckSchema,
|
|
1875
2886
|
async execute() {
|
|
1876
|
-
const
|
|
1877
|
-
const
|
|
1878
|
-
const
|
|
1879
|
-
|
|
1880
|
-
const testsDirRel = makeRelativeIfContainsProject(config["tests-dir"], projectBase)
|
|
1881
|
-
.split(path.sep)
|
|
1882
|
-
.join("/")
|
|
1883
|
-
.replace(/^\.?\/?/, "")
|
|
1884
|
-
.replace(/\/+$/, "");
|
|
1885
|
-
const excludedDirectoryPaths = [...new Set([
|
|
1886
|
-
"tests/fixtures",
|
|
1887
|
-
testsDirRel ? `${testsDirRel}/fixtures` : "fixtures",
|
|
1888
|
-
])];
|
|
1889
|
-
let selectedPaths: string[] = [];
|
|
1890
|
-
let execution;
|
|
1891
|
-
try {
|
|
1892
|
-
selectedPaths = collectProjectStaticCheckSelection(projectBase, config).selectedPaths;
|
|
1893
|
-
execution = buildToolExecutionSection(runProjectStaticCheck(projectBase, config));
|
|
1894
|
-
} catch (error) {
|
|
1895
|
-
execution = buildToolExecutionSection(normalizeToolFailure(error));
|
|
1896
|
-
}
|
|
1897
|
-
const payload = buildStaticCheckToolPayload(
|
|
1898
|
-
"static-check",
|
|
1899
|
-
"configured-source-and-test-directories",
|
|
1900
|
-
projectBase,
|
|
1901
|
-
selectedPaths,
|
|
1902
|
-
selectionDirectoryPaths,
|
|
1903
|
-
excludedDirectoryPaths,
|
|
1904
|
-
staticCheckConfig,
|
|
1905
|
-
execution,
|
|
1906
|
-
);
|
|
1907
|
-
return buildStructuredToolExecuteResult(payload);
|
|
1908
|
-
},
|
|
1909
|
-
});
|
|
1910
|
-
|
|
1911
|
-
const gitCheckSchema = Type.Object(
|
|
1912
|
-
{},
|
|
1913
|
-
{
|
|
1914
|
-
description: "Input contract: no params. Output contract: JSON object with result and execution. Result exposes git-path presence plus aggregate repository status fields.",
|
|
1915
|
-
},
|
|
1916
|
-
);
|
|
1917
|
-
pi.registerTool({
|
|
1918
|
-
name: "git-check",
|
|
1919
|
-
label: "git-check",
|
|
1920
|
-
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes repository validation status through direct fields without request echoes.",
|
|
1921
|
-
promptSnippet: "Return the structured git-validation payload for the runtime repository.",
|
|
1922
|
-
promptGuidelines: [
|
|
1923
|
-
"Input contract: no params. Scope is the cwd-derived runtime path context.",
|
|
1924
|
-
"Output contract: result + execution. Result exposes git_path_present and aggregate status.",
|
|
1925
|
-
"Behavior contract: the tool checks work-tree membership, porcelain cleanliness, and symbolic-or-detached HEAD validity.",
|
|
1926
|
-
"Failure contract: execution.code and execution.stderr_lines surface git-path or repository-state errors.",
|
|
1927
|
-
],
|
|
1928
|
-
parameters: gitCheckSchema,
|
|
1929
|
-
async execute() {
|
|
1930
|
-
const projectBase = getProjectBase(process.cwd());
|
|
1931
|
-
const config = loadProjectConfig(process.cwd());
|
|
1932
|
-
let execution;
|
|
1933
|
-
try {
|
|
1934
|
-
execution = buildToolExecutionSection(runGitCheck(projectBase, config));
|
|
1935
|
-
} catch (error) {
|
|
1936
|
-
execution = buildToolExecutionSection(normalizeToolFailure(error));
|
|
1937
|
-
}
|
|
1938
|
-
const payload = buildGitCheckToolPayload(projectBase, resolveRuntimeGitPath(projectBase), execution);
|
|
1939
|
-
return buildStructuredToolExecuteResult(payload);
|
|
1940
|
-
},
|
|
1941
|
-
});
|
|
1942
|
-
|
|
1943
|
-
const docsCheckSchema = Type.Object(
|
|
1944
|
-
{},
|
|
1945
|
-
{
|
|
1946
|
-
description: "Input contract: no params. Output contract: JSON object with summary, files, and execution. File entries expose canonical paths, prompt_command remediation, and presence status for canonical docs.",
|
|
1947
|
-
},
|
|
1948
|
-
);
|
|
1949
|
-
pi.registerTool({
|
|
1950
|
-
name: "docs-check",
|
|
1951
|
-
label: "docs-check",
|
|
1952
|
-
description: "Scope: canonical docs. Return a token-optimized JSON payload with summary, files, and execution sections. File entries expose remediation prompt commands and direct presence facts for REQUIREMENTS.md, WORKFLOW.md, and REFERENCES.md.",
|
|
1953
|
-
promptSnippet: "Return the structured canonical-document validation payload.",
|
|
1954
|
-
promptGuidelines: [
|
|
1955
|
-
"Input contract: no params. Scope is docs-dir from the cwd-derived project configuration.",
|
|
1956
|
-
"Output contract: summary + files + execution. File entries expose file_name, canonical_path, prompt_command, and status.",
|
|
1957
|
-
"Specialization trigger: remediation differs per missing canonical file through prompt_command.",
|
|
1958
|
-
"Failure contract: execution.code is non-zero when any canonical document is missing; execution.stderr_lines enumerate missing files.",
|
|
1959
|
-
],
|
|
1960
|
-
parameters: docsCheckSchema,
|
|
1961
|
-
async execute() {
|
|
1962
|
-
const projectBase = getProjectBase(process.cwd());
|
|
1963
|
-
const config = loadProjectConfig(process.cwd());
|
|
1964
|
-
const payload = buildDocsCheckToolPayload(projectBase, config["docs-dir"]);
|
|
1965
|
-
return buildStructuredToolExecuteResult(payload);
|
|
1966
|
-
},
|
|
1967
|
-
});
|
|
1968
|
-
|
|
1969
|
-
const gitWtNameSchema = Type.Object(
|
|
1970
|
-
{},
|
|
1971
|
-
{
|
|
1972
|
-
description: "Input contract: no params. Output contract: JSON object with result and execution. Result exposes worktree_name when generation succeeds.",
|
|
1973
|
-
},
|
|
1974
|
-
);
|
|
1975
|
-
pi.registerTool({
|
|
1976
|
-
name: "git-wt-name",
|
|
1977
|
-
label: "git-wt-name",
|
|
1978
|
-
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes the generated worktree name while static naming rules remain in registration metadata.",
|
|
1979
|
-
promptSnippet: "Return the structured worktree-name generation payload.",
|
|
1980
|
-
promptGuidelines: [
|
|
1981
|
-
"Input contract: no params. Scope is the cwd-derived runtime path context.",
|
|
1982
|
-
"Output contract: result + execution. Result exposes worktree_name.",
|
|
1983
|
-
"Behavior contract: generation follows useReq-<project>-<sanitized-branch>-<YYYYMMDDHHMMSS>.",
|
|
1984
|
-
"Failure contract: execution.code and execution.stderr_lines surface git-path or branch-resolution errors.",
|
|
1985
|
-
],
|
|
1986
|
-
parameters: gitWtNameSchema,
|
|
1987
|
-
async execute() {
|
|
1988
|
-
const projectBase = getProjectBase(process.cwd());
|
|
1989
|
-
const config = loadProjectConfig(process.cwd());
|
|
1990
|
-
let execution;
|
|
1991
|
-
try {
|
|
1992
|
-
execution = buildToolExecutionSection(runGitWtName(projectBase, config));
|
|
1993
|
-
} catch (error) {
|
|
1994
|
-
execution = buildToolExecutionSection(normalizeToolFailure(error));
|
|
1995
|
-
}
|
|
1996
|
-
const payload = buildWorktreeNameToolPayload(projectBase, resolveRuntimeGitPath(projectBase), execution);
|
|
1997
|
-
return buildStructuredToolExecuteResult(payload);
|
|
1998
|
-
},
|
|
1999
|
-
});
|
|
2000
|
-
|
|
2001
|
-
const gitWtCreateSchema = Type.Object(
|
|
2002
|
-
{
|
|
2003
|
-
wtName: Type.String({ description: "Exact target worktree name and branch name" }),
|
|
2004
|
-
},
|
|
2005
|
-
{
|
|
2006
|
-
description: "Input contract: wtName. Output contract: JSON object with result and execution. Result exposes worktree_name and derived worktree_path.",
|
|
2007
|
-
},
|
|
2008
|
-
);
|
|
2009
|
-
pi.registerTool({
|
|
2010
|
-
name: "git-wt-create",
|
|
2011
|
-
label: "git-wt-create",
|
|
2012
|
-
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes the exact worktree name and derived path without static operation echoes.",
|
|
2013
|
-
promptSnippet: "Return the structured worktree-creation payload for the requested name.",
|
|
2014
|
-
promptGuidelines: [
|
|
2015
|
-
"Input contract: wtName is required and must match the exact worktree/branch name to create.",
|
|
2016
|
-
"Output contract: result + execution. Result exposes worktree_name and worktree_path.",
|
|
2017
|
-
"Specialization trigger: worktree_path depends on the runtime git root parent directory.",
|
|
2018
|
-
"Failure contract: execution.code and execution.stderr_lines surface invalid-name, git, or finalization errors.",
|
|
2019
|
-
],
|
|
2020
|
-
parameters: gitWtCreateSchema,
|
|
2021
|
-
async execute(_toolCallId, params) {
|
|
2022
|
-
const projectBase = getProjectBase(process.cwd());
|
|
2023
|
-
const config = loadProjectConfig(process.cwd());
|
|
2024
|
-
let execution;
|
|
2025
|
-
try {
|
|
2026
|
-
execution = buildToolExecutionSection(runGitWtCreate(projectBase, params.wtName, config));
|
|
2027
|
-
} catch (error) {
|
|
2028
|
-
execution = buildToolExecutionSection(normalizeToolFailure(error));
|
|
2029
|
-
}
|
|
2030
|
-
const payload = buildWorktreeMutationToolPayload(
|
|
2031
|
-
"git-wt-create",
|
|
2032
|
-
projectBase,
|
|
2033
|
-
resolveRuntimeGitPath(projectBase),
|
|
2034
|
-
params.wtName,
|
|
2035
|
-
execution,
|
|
2036
|
-
);
|
|
2037
|
-
return buildStructuredToolExecuteResult(payload);
|
|
2887
|
+
const contextPath = getRuntimeContextPath(process.cwd());
|
|
2888
|
+
const projectBase = getProjectBase(contextPath);
|
|
2889
|
+
const config = loadProjectConfig(projectBase);
|
|
2890
|
+
return executeMonolithicTool(() => runProjectStaticCheck(contextPath, config));
|
|
2038
2891
|
},
|
|
2039
2892
|
});
|
|
2040
2893
|
|
|
2041
|
-
const gitWtDeleteSchema = Type.Object(
|
|
2042
|
-
{
|
|
2043
|
-
wtName: Type.String({ description: "Exact target worktree name and branch name" }),
|
|
2044
|
-
},
|
|
2045
|
-
{
|
|
2046
|
-
description: "Input contract: wtName. Output contract: JSON object with result and execution. Result exposes worktree_name and derived worktree_path.",
|
|
2047
|
-
},
|
|
2048
|
-
);
|
|
2049
|
-
pi.registerTool({
|
|
2050
|
-
name: "git-wt-delete",
|
|
2051
|
-
label: "git-wt-delete",
|
|
2052
|
-
description: "Scope: current runtime path. Return a token-optimized JSON payload with result and execution sections. Result exposes the exact worktree name and derived path without static operation echoes.",
|
|
2053
|
-
promptSnippet: "Return the structured worktree-deletion payload for the requested name.",
|
|
2054
|
-
promptGuidelines: [
|
|
2055
|
-
"Input contract: wtName is required and must match the exact worktree/branch name to delete.",
|
|
2056
|
-
"Output contract: result + execution. Result exposes worktree_name and worktree_path.",
|
|
2057
|
-
"Specialization trigger: worktree_path depends on the runtime git root parent directory.",
|
|
2058
|
-
"Failure contract: execution.code and execution.stderr_lines surface missing-target or deletion errors.",
|
|
2059
|
-
],
|
|
2060
|
-
parameters: gitWtDeleteSchema,
|
|
2061
|
-
async execute(_toolCallId, params) {
|
|
2062
|
-
const projectBase = getProjectBase(process.cwd());
|
|
2063
|
-
const config = loadProjectConfig(process.cwd());
|
|
2064
|
-
let execution;
|
|
2065
|
-
try {
|
|
2066
|
-
execution = buildToolExecutionSection(runGitWtDelete(projectBase, params.wtName, config));
|
|
2067
|
-
} catch (error) {
|
|
2068
|
-
execution = buildToolExecutionSection(normalizeToolFailure(error));
|
|
2069
|
-
}
|
|
2070
|
-
const payload = buildWorktreeMutationToolPayload(
|
|
2071
|
-
"git-wt-delete",
|
|
2072
|
-
projectBase,
|
|
2073
|
-
resolveRuntimeGitPath(projectBase),
|
|
2074
|
-
params.wtName,
|
|
2075
|
-
execution,
|
|
2076
|
-
);
|
|
2077
|
-
return buildStructuredToolExecuteResult(payload);
|
|
2078
|
-
},
|
|
2079
|
-
});
|
|
2080
2894
|
}
|
|
2081
2895
|
|
|
2082
2896
|
/**
|
|
@@ -2108,28 +2922,20 @@ function buildPiUsereqToolsMenuChoices(pi: ExtensionAPI, config: UseReqConfig):
|
|
|
2108
2922
|
value: `${tools.length} targets`,
|
|
2109
2923
|
description: "Disable every configurable startup tool for future session starts.",
|
|
2110
2924
|
},
|
|
2111
|
-
{
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
description: "Restore the documented default startup-tool selection.",
|
|
2116
|
-
},
|
|
2117
|
-
{
|
|
2118
|
-
id: "save-and-close",
|
|
2119
|
-
label: "Save and close",
|
|
2120
|
-
value: "",
|
|
2121
|
-
description: "Return to the parent configuration menu.",
|
|
2122
|
-
},
|
|
2925
|
+
...buildTerminalSettingsMenuChoices({
|
|
2926
|
+
resetDefaultsValue: `${normalizeEnabledPiUsereqTools(undefined).length} defaults`,
|
|
2927
|
+
resetDefaultsDescription: "Restore the documented default startup-tool selection.",
|
|
2928
|
+
}),
|
|
2123
2929
|
];
|
|
2124
2930
|
}
|
|
2125
2931
|
|
|
2126
2932
|
/**
|
|
2127
2933
|
* @brief Builds the shared settings-menu choices for per-tool startup toggles.
|
|
2128
|
-
* @details Exposes every configurable startup tool as one row whose right-side value reports the current enabled state. Runtime is O(t) in configurable-tool count. No external state is mutated.
|
|
2934
|
+
* @details Exposes every configurable startup tool as one row whose right-side value reports the current enabled state, preserves the documented custom/files/embedded/default-disabled ordering, and appends subtree-local `Reset defaults` plus `Save and close` rows. Runtime is O(t) in configurable-tool count. No external state is mutated.
|
|
2129
2935
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
2130
2936
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
2131
2937
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered per-tool toggle choices.
|
|
2132
|
-
* @satisfies REQ-007, REQ-151, REQ-152, REQ-153, REQ-154
|
|
2938
|
+
* @satisfies REQ-007, REQ-151, REQ-152, REQ-153, REQ-154, REQ-231, REQ-232
|
|
2133
2939
|
*/
|
|
2134
2940
|
function buildPiUsereqToolToggleChoices(pi: ExtensionAPI, config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
2135
2941
|
const enabledTools = new Set(getConfiguredEnabledPiUsereqTools(config));
|
|
@@ -2140,25 +2946,28 @@ function buildPiUsereqToolToggleChoices(pi: ExtensionAPI, config: UseReqConfig):
|
|
|
2140
2946
|
value: enabledTools.has(tool.name) ? "on" : "off",
|
|
2141
2947
|
description: tool.description ?? `Toggle startup activation for ${tool.name}.`,
|
|
2142
2948
|
})),
|
|
2143
|
-
{
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
description: "Return to the Enable tools menu.",
|
|
2148
|
-
},
|
|
2949
|
+
...buildTerminalSettingsMenuChoices({
|
|
2950
|
+
resetDefaultsValue: `${normalizeEnabledPiUsereqTools(undefined).length} defaults`,
|
|
2951
|
+
resetDefaultsDescription: "Restore the documented default startup-tool selection.",
|
|
2952
|
+
}),
|
|
2149
2953
|
];
|
|
2150
2954
|
}
|
|
2151
2955
|
|
|
2152
2956
|
/**
|
|
2153
2957
|
* @brief Runs the interactive active-tool configuration menu.
|
|
2154
|
-
* @details Synchronizes runtime active tools with persisted config, renders startup-tool actions through the shared settings-menu UI, and updates configuration state in response to selections until the user exits. Runtime depends on user interaction count. Side effects include UI updates, active-tool changes, and config mutation.
|
|
2958
|
+
* @details Synchronizes runtime active tools with persisted config, renders startup-tool actions through the shared settings-menu UI, preserves the documented per-tool ordering, and updates configuration state in response to selections until the user exits. Runtime depends on user interaction count. Side effects include UI updates, active-tool changes, and config mutation.
|
|
2155
2959
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
2156
2960
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
2157
2961
|
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
2158
2962
|
* @return {Promise<void>} Promise resolved when the menu closes.
|
|
2159
|
-
* @satisfies REQ-007, REQ-063, REQ-064, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-193
|
|
2963
|
+
* @satisfies REQ-007, REQ-063, REQ-064, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-193, REQ-231, REQ-232
|
|
2160
2964
|
*/
|
|
2161
|
-
async function configurePiUsereqToolsMenu(
|
|
2965
|
+
async function configurePiUsereqToolsMenu(
|
|
2966
|
+
pi: ExtensionAPI,
|
|
2967
|
+
ctx: ExtensionCommandContext,
|
|
2968
|
+
config: UseReqConfig,
|
|
2969
|
+
onConfigChange: () => void,
|
|
2970
|
+
): Promise<void> {
|
|
2162
2971
|
applyConfiguredPiUsereqTools(pi, config);
|
|
2163
2972
|
let focusedChoiceId: string | undefined;
|
|
2164
2973
|
while (true) {
|
|
@@ -2168,32 +2977,71 @@ async function configurePiUsereqToolsMenu(pi: ExtensionAPI, ctx: ExtensionComman
|
|
|
2168
2977
|
initialSelectedId: focusedChoiceId,
|
|
2169
2978
|
});
|
|
2170
2979
|
|
|
2171
|
-
if (!choice
|
|
2980
|
+
if (!choice) {
|
|
2172
2981
|
return;
|
|
2173
2982
|
}
|
|
2174
2983
|
focusedChoiceId = choice;
|
|
2175
2984
|
|
|
2176
2985
|
if (choice === "enable-all-tools") {
|
|
2177
2986
|
setConfiguredPiUsereqTools(pi, config, tools.map((tool) => tool.name));
|
|
2987
|
+
onConfigChange();
|
|
2178
2988
|
ctx.ui.notify("Enabled all configurable active tools", "info");
|
|
2179
2989
|
continue;
|
|
2180
2990
|
}
|
|
2181
2991
|
|
|
2182
2992
|
if (choice === "disable-all-tools") {
|
|
2183
2993
|
setConfiguredPiUsereqTools(pi, config, []);
|
|
2994
|
+
onConfigChange();
|
|
2184
2995
|
ctx.ui.notify("Disabled all configurable active tools", "info");
|
|
2185
2996
|
continue;
|
|
2186
2997
|
}
|
|
2187
2998
|
|
|
2188
2999
|
if (choice === "reset-defaults") {
|
|
3000
|
+
const approved = await confirmResetChanges(
|
|
3001
|
+
ctx,
|
|
3002
|
+
"Confirm Enable tools reset",
|
|
3003
|
+
[{
|
|
3004
|
+
label: "Enable tools",
|
|
3005
|
+
previousValue: String(getConfiguredEnabledPiUsereqTools(config).length),
|
|
3006
|
+
nextValue: String(normalizeEnabledPiUsereqTools(undefined).length),
|
|
3007
|
+
}].filter((change) => change.previousValue !== change.nextValue),
|
|
3008
|
+
"Approve restoring the documented default startup-tool selection.",
|
|
3009
|
+
"Abort the startup-tool reset and keep the current values.",
|
|
3010
|
+
);
|
|
3011
|
+
if (!approved) {
|
|
3012
|
+
ctx.ui.notify("Aborted startup-tool reset", "info");
|
|
3013
|
+
continue;
|
|
3014
|
+
}
|
|
2189
3015
|
setConfiguredPiUsereqTools(pi, config, normalizeEnabledPiUsereqTools(undefined));
|
|
3016
|
+
onConfigChange();
|
|
2190
3017
|
ctx.ui.notify("Restored default configurable active tools", "info");
|
|
2191
3018
|
continue;
|
|
2192
3019
|
}
|
|
2193
3020
|
|
|
2194
3021
|
if (choice === "enable-tools") {
|
|
2195
3022
|
const selectedToolName = await showPiUsereqSettingsMenu(ctx, "Enable tools", buildPiUsereqToolToggleChoices(pi, config));
|
|
2196
|
-
if (!selectedToolName
|
|
3023
|
+
if (!selectedToolName) {
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
3026
|
+
if (selectedToolName === "reset-defaults") {
|
|
3027
|
+
const approved = await confirmResetChanges(
|
|
3028
|
+
ctx,
|
|
3029
|
+
"Confirm Enable tools reset",
|
|
3030
|
+
[{
|
|
3031
|
+
label: "Enable tools",
|
|
3032
|
+
previousValue: String(getConfiguredEnabledPiUsereqTools(config).length),
|
|
3033
|
+
nextValue: String(normalizeEnabledPiUsereqTools(undefined).length),
|
|
3034
|
+
}].filter((change) => change.previousValue !== change.nextValue),
|
|
3035
|
+
"Approve restoring the documented default startup-tool selection.",
|
|
3036
|
+
"Abort the startup-tool reset and keep the current values.",
|
|
3037
|
+
);
|
|
3038
|
+
if (!approved) {
|
|
3039
|
+
ctx.ui.notify("Aborted startup-tool reset", "info");
|
|
3040
|
+
continue;
|
|
3041
|
+
}
|
|
3042
|
+
setConfiguredPiUsereqTools(pi, config, normalizeEnabledPiUsereqTools(undefined));
|
|
3043
|
+
onConfigChange();
|
|
3044
|
+
ctx.ui.notify("Restored default configurable active tools", "info");
|
|
2197
3045
|
continue;
|
|
2198
3046
|
}
|
|
2199
3047
|
if (enabledTools.has(selectedToolName)) {
|
|
@@ -2202,6 +3050,7 @@ async function configurePiUsereqToolsMenu(pi: ExtensionAPI, ctx: ExtensionComman
|
|
|
2202
3050
|
enabledTools.add(selectedToolName);
|
|
2203
3051
|
}
|
|
2204
3052
|
setConfiguredPiUsereqTools(pi, config, tools.map((tool) => tool.name).filter((toolName) => enabledTools.has(toolName)));
|
|
3053
|
+
onConfigChange();
|
|
2205
3054
|
ctx.ui.notify(
|
|
2206
3055
|
`${enabledTools.has(selectedToolName) ? "Enabled" : "Disabled"} ${selectedToolName}`,
|
|
2207
3056
|
"info",
|
|
@@ -2211,34 +3060,75 @@ async function configurePiUsereqToolsMenu(pi: ExtensionAPI, ctx: ExtensionComman
|
|
|
2211
3060
|
}
|
|
2212
3061
|
|
|
2213
3062
|
/**
|
|
2214
|
-
* @brief
|
|
2215
|
-
* @details
|
|
3063
|
+
* @brief Resolves one static-check language config for menu rendering.
|
|
3064
|
+
* @details Returns the configured per-language static-check object when present and otherwise synthesizes a disabled empty-language object so menu code can render all supported languages deterministically. Runtime is O(1). No external state is mutated.
|
|
3065
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
3066
|
+
* @param[in] language {string} Canonical language name.
|
|
3067
|
+
* @return {StaticCheckLanguageConfig} Resolved per-language config object.
|
|
3068
|
+
*/
|
|
3069
|
+
function getStaticCheckLanguageConfigForMenu(
|
|
3070
|
+
config: UseReqConfig,
|
|
3071
|
+
language: string,
|
|
3072
|
+
): StaticCheckLanguageConfig {
|
|
3073
|
+
return config["static-check"][language] ?? createStaticCheckLanguageConfig([]);
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
/**
|
|
3077
|
+
* @brief Counts languages that currently expose at least one configured checker.
|
|
3078
|
+
* @details Treats configured-but-disabled languages as configured when their checker list is non-empty so removal actions remain deterministic. Runtime is O(l). No external state is mutated.
|
|
3079
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
3080
|
+
* @return {number} Number of languages with at least one configured checker.
|
|
3081
|
+
*/
|
|
3082
|
+
function countConfiguredStaticCheckLanguages(config: UseReqConfig): number {
|
|
3083
|
+
return Object.values(config["static-check"]).filter((languageConfig) => languageConfig.checkers.length > 0).length;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
/**
|
|
3087
|
+
* @brief Counts languages whose static-check enable flag is on.
|
|
3088
|
+
* @details Counts only languages whose persisted per-language config explicitly sets `enabled=enable`, regardless of checker count. Runtime is O(l). No external state is mutated.
|
|
3089
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
3090
|
+
* @return {number} Number of enabled languages.
|
|
3091
|
+
*/
|
|
3092
|
+
function countEnabledStaticCheckLanguages(config: UseReqConfig): number {
|
|
3093
|
+
return Object.values(config["static-check"]).filter((languageConfig) => languageConfig.enabled === "enable").length;
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
/**
|
|
3097
|
+
* @brief Restores the documented static-check default configuration.
|
|
3098
|
+
* @details Replaces the mutable config subtree with a fresh clone of the documented per-language defaults so menu reset actions restore both enable flags and checker lists in one step. Runtime is O(l + c). Side effect: mutates `config`.
|
|
3099
|
+
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
3100
|
+
* @return {void} No return value.
|
|
3101
|
+
* @satisfies REQ-250, REQ-251, REQ-252
|
|
3102
|
+
*/
|
|
3103
|
+
function resetStaticCheckConfig(config: UseReqConfig): void {
|
|
3104
|
+
config["static-check"] = getDefaultStaticCheckConfig();
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
/**
|
|
3108
|
+
* @brief Summarizes enabled and configured static-check languages.
|
|
3109
|
+
* @details Counts enabled languages and languages with at least one checker, then emits one compact summary string suitable for the top-level configuration menu. Runtime is O(l). No external state is mutated.
|
|
2216
3110
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
2217
|
-
* @return {string} Compact summary string
|
|
3111
|
+
* @return {string} Compact summary string.
|
|
2218
3112
|
*/
|
|
2219
3113
|
function formatStaticCheckLanguagesSummary(config: UseReqConfig): string {
|
|
2220
|
-
|
|
2221
|
-
.filter(([, entries]) => Array.isArray(entries) && entries.length > 0)
|
|
2222
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
2223
|
-
.map(([language, entries]) => `${language} (${entries.length})`);
|
|
2224
|
-
return languages.join(", ") || "(none)";
|
|
3114
|
+
return `${countEnabledStaticCheckLanguages(config)} enabled • ${countConfiguredStaticCheckLanguages(config)} configured`;
|
|
2225
3115
|
}
|
|
2226
3116
|
|
|
2227
3117
|
/**
|
|
2228
3118
|
* @brief Builds the shared settings-menu choices for static-check management.
|
|
2229
|
-
* @details Serializes guided Command-oriented
|
|
3119
|
+
* @details Serializes guided Command-oriented add and remove actions, renders one direct on/off toggle row for every supported language, and appends canonical terminal rows while omitting raw-spec and reference-only actions. Runtime is O(l). No external state is mutated.
|
|
2230
3120
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
2231
3121
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered static-check menu choices.
|
|
2232
|
-
* @satisfies REQ-008, REQ-150, REQ-
|
|
3122
|
+
* @satisfies REQ-008, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-160, REQ-161, REQ-193, REQ-248
|
|
2233
3123
|
*/
|
|
2234
3124
|
function buildStaticCheckMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
2235
|
-
const
|
|
2236
|
-
const configuredLanguageCount =
|
|
3125
|
+
const supportedLanguages = getSupportedStaticCheckLanguageSupport();
|
|
3126
|
+
const configuredLanguageCount = countConfiguredStaticCheckLanguages(config);
|
|
2237
3127
|
return [
|
|
2238
3128
|
{
|
|
2239
3129
|
id: "add-static-check-entry",
|
|
2240
3130
|
label: "Add static code checker",
|
|
2241
|
-
value: `${
|
|
3131
|
+
value: `${supportedLanguages.length} languages`,
|
|
2242
3132
|
description: "Select a supported language, then configure one Command static-check executable.",
|
|
2243
3133
|
},
|
|
2244
3134
|
{
|
|
@@ -2247,96 +3137,137 @@ function buildStaticCheckMenuChoices(config: UseReqConfig): PiUsereqSettingsMenu
|
|
|
2247
3137
|
value: configuredLanguageCount > 0 ? `${configuredLanguageCount} configured` : "(none)",
|
|
2248
3138
|
description: "Remove every configured static-check entry for one language.",
|
|
2249
3139
|
},
|
|
2250
|
-
{
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
3140
|
+
...supportedLanguages.map(({ language, extensions }) => {
|
|
3141
|
+
const languageConfig = getStaticCheckLanguageConfigForMenu(config, language);
|
|
3142
|
+
const configuredCount = languageConfig.checkers.length;
|
|
3143
|
+
const suffix = configuredCount === 1 ? "checker" : "checkers";
|
|
3144
|
+
return {
|
|
3145
|
+
id: `toggle-static-check-language:${language}`,
|
|
3146
|
+
label: language,
|
|
3147
|
+
value: languageConfig.enabled === "enable" ? "on" : "off",
|
|
3148
|
+
description: `Toggle static-check execution for ${language}. Configured ${configuredCount} ${suffix}. Supported extensions: ${extensions.join(", ")}.`,
|
|
3149
|
+
};
|
|
3150
|
+
}),
|
|
3151
|
+
...buildTerminalSettingsMenuChoices({
|
|
3152
|
+
resetDefaultsValue: formatStaticCheckLanguagesSummary(config),
|
|
3153
|
+
resetDefaultsDescription: "Restore the documented per-language static-check defaults.",
|
|
3154
|
+
}),
|
|
2262
3155
|
];
|
|
2263
3156
|
}
|
|
2264
3157
|
|
|
2265
3158
|
/**
|
|
2266
3159
|
* @brief Builds the shared settings-menu choices for supported static-check languages.
|
|
2267
|
-
* @details Exposes every supported language as one row whose right-side value reports extensions
|
|
3160
|
+
* @details Exposes every supported language as one row whose right-side value reports extensions, enablement, and configured checker count for guided Command configuration flows, then appends subtree-local terminal rows. Runtime is O(l). No external state is mutated.
|
|
2268
3161
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
2269
3162
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered language-choice vector.
|
|
2270
3163
|
*/
|
|
2271
3164
|
function buildSupportedStaticCheckLanguageChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
2272
3165
|
return [
|
|
2273
3166
|
...getSupportedStaticCheckLanguageSupport().map(({ language, extensions }) => {
|
|
2274
|
-
const
|
|
3167
|
+
const languageConfig = getStaticCheckLanguageConfigForMenu(config, language);
|
|
3168
|
+
const configuredCount = languageConfig.checkers.length;
|
|
2275
3169
|
const suffix = configuredCount === 1 ? "checker" : "checkers";
|
|
2276
3170
|
return {
|
|
2277
3171
|
id: language,
|
|
2278
3172
|
label: language,
|
|
2279
|
-
value: `${extensions.join(", ")} • ${configuredCount} ${suffix}`,
|
|
2280
|
-
description: `Configure the Command static-check
|
|
3173
|
+
value: `${extensions.join(", ")} • ${languageConfig.enabled === "enable" ? "on" : "off"} • ${configuredCount} ${suffix}`,
|
|
3174
|
+
description: `Configure the Command static-check entries for ${language}. Supported extensions: ${extensions.join(", ")}.`,
|
|
2281
3175
|
};
|
|
2282
3176
|
}),
|
|
2283
|
-
{
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
description: "Return to the Language static code checkers menu.",
|
|
2288
|
-
},
|
|
3177
|
+
...buildTerminalSettingsMenuChoices({
|
|
3178
|
+
resetDefaultsValue: formatStaticCheckLanguagesSummary(config),
|
|
3179
|
+
resetDefaultsDescription: "Restore the documented per-language static-check defaults.",
|
|
3180
|
+
}),
|
|
2289
3181
|
];
|
|
2290
3182
|
}
|
|
2291
3183
|
|
|
2292
3184
|
/**
|
|
2293
3185
|
* @brief Builds the shared settings-menu choices for configured static-check languages.
|
|
2294
|
-
* @details Exposes only languages
|
|
3186
|
+
* @details Exposes only languages whose checker lists are non-empty so removal remains deterministic, then appends subtree-local terminal rows. Runtime is O(l). No external state is mutated.
|
|
2295
3187
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
2296
3188
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered configured-language vector.
|
|
2297
3189
|
*/
|
|
2298
3190
|
function buildConfiguredStaticCheckLanguageChoices(config: UseReqConfig): PiUsereqSettingsMenuChoice[] {
|
|
2299
3191
|
return [
|
|
2300
3192
|
...getSupportedStaticCheckLanguageSupport()
|
|
2301
|
-
.filter(({ language }) => (config
|
|
2302
|
-
.map(({ language, extensions }) =>
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
3193
|
+
.filter(({ language }) => getStaticCheckLanguageConfigForMenu(config, language).checkers.length > 0)
|
|
3194
|
+
.map(({ language, extensions }) => {
|
|
3195
|
+
const languageConfig = getStaticCheckLanguageConfigForMenu(config, language);
|
|
3196
|
+
return {
|
|
3197
|
+
id: language,
|
|
3198
|
+
label: language,
|
|
3199
|
+
value: `${extensions.join(", ")} • ${languageConfig.checkers.length} configured`,
|
|
3200
|
+
description: `Remove every configured static-check entry for ${language}.`,
|
|
3201
|
+
};
|
|
3202
|
+
}),
|
|
3203
|
+
...buildTerminalSettingsMenuChoices({
|
|
3204
|
+
resetDefaultsValue: formatStaticCheckLanguagesSummary(config),
|
|
3205
|
+
resetDefaultsDescription: "Restore the documented per-language static-check defaults.",
|
|
3206
|
+
}),
|
|
2314
3207
|
];
|
|
2315
3208
|
}
|
|
2316
3209
|
|
|
2317
3210
|
/**
|
|
2318
3211
|
* @brief Runs the interactive static-check configuration menu.
|
|
2319
|
-
* @details Lets the user add Command entries by guided prompts, remove configured language entries, and reset the
|
|
3212
|
+
* @details Lets the user add Command entries by guided prompts, remove configured language entries, toggle direct per-language enable flags, and reset the subtree to documented defaults through the shared settings-menu renderer until the user exits. Runtime depends on user interaction count. Side effects include UI updates and config mutation.
|
|
2320
3213
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
2321
3214
|
* @param[in,out] config {UseReqConfig} Mutable configuration object.
|
|
2322
3215
|
* @return {Promise<void>} Promise resolved when the menu closes.
|
|
2323
|
-
* @satisfies REQ-008, REQ-
|
|
3216
|
+
* @satisfies REQ-008, REQ-151, REQ-152, REQ-153, REQ-154, REQ-160, REQ-161, REQ-193, REQ-195, REQ-248, REQ-253
|
|
2324
3217
|
*/
|
|
2325
|
-
async function configureStaticCheckMenu(
|
|
3218
|
+
async function configureStaticCheckMenu(
|
|
3219
|
+
ctx: ExtensionCommandContext,
|
|
3220
|
+
config: UseReqConfig,
|
|
3221
|
+
onConfigChange: () => void,
|
|
3222
|
+
): Promise<void> {
|
|
2326
3223
|
let focusedChoiceId: string | undefined;
|
|
2327
3224
|
while (true) {
|
|
2328
3225
|
const staticChoice = await showPiUsereqSettingsMenu(ctx, "Language static code checkers", buildStaticCheckMenuChoices(config), {
|
|
2329
3226
|
initialSelectedId: focusedChoiceId,
|
|
2330
3227
|
});
|
|
2331
3228
|
|
|
2332
|
-
if (!staticChoice
|
|
3229
|
+
if (!staticChoice) {
|
|
2333
3230
|
return;
|
|
2334
3231
|
}
|
|
2335
3232
|
focusedChoiceId = staticChoice;
|
|
2336
3233
|
|
|
3234
|
+
if (staticChoice.startsWith("toggle-static-check-language:")) {
|
|
3235
|
+
const language = staticChoice.slice("toggle-static-check-language:".length);
|
|
3236
|
+
config["static-check"][language] ??= createStaticCheckLanguageConfig([]);
|
|
3237
|
+
const languageConfig = config["static-check"][language]!;
|
|
3238
|
+
languageConfig.enabled = languageConfig.enabled === "enable" ? "disable" : "enable";
|
|
3239
|
+
onConfigChange();
|
|
3240
|
+
ctx.ui.notify(
|
|
3241
|
+
`${languageConfig.enabled === "enable" ? "Enabled" : "Disabled"} static-check for ${language}`,
|
|
3242
|
+
"info",
|
|
3243
|
+
);
|
|
3244
|
+
continue;
|
|
3245
|
+
}
|
|
3246
|
+
|
|
2337
3247
|
if (staticChoice === "add-static-check-entry") {
|
|
2338
3248
|
const selectedLanguage = await showPiUsereqSettingsMenu(ctx, "static-check language", buildSupportedStaticCheckLanguageChoices(config));
|
|
2339
|
-
if (!selectedLanguage
|
|
3249
|
+
if (!selectedLanguage) {
|
|
3250
|
+
continue;
|
|
3251
|
+
}
|
|
3252
|
+
if (selectedLanguage === "reset-defaults") {
|
|
3253
|
+
const approved = await confirmResetChanges(
|
|
3254
|
+
ctx,
|
|
3255
|
+
"Confirm static-check reset",
|
|
3256
|
+
[{
|
|
3257
|
+
label: "Language static code checkers",
|
|
3258
|
+
previousValue: formatStaticCheckLanguagesSummary(config),
|
|
3259
|
+
nextValue: formatStaticCheckLanguagesSummary({ ...config, "static-check": getDefaultStaticCheckConfig() }),
|
|
3260
|
+
}].filter((change) => change.previousValue !== change.nextValue),
|
|
3261
|
+
"Approve restoring the documented per-language static-check defaults.",
|
|
3262
|
+
"Abort the static-check reset and keep the current values.",
|
|
3263
|
+
);
|
|
3264
|
+
if (!approved) {
|
|
3265
|
+
ctx.ui.notify("Aborted static-check reset", "info");
|
|
3266
|
+
continue;
|
|
3267
|
+
}
|
|
3268
|
+
resetStaticCheckConfig(config);
|
|
3269
|
+
onConfigChange();
|
|
3270
|
+
ctx.ui.notify("Restored default static code checker configuration", "info");
|
|
2340
3271
|
continue;
|
|
2341
3272
|
}
|
|
2342
3273
|
|
|
@@ -2356,23 +3287,64 @@ async function configureStaticCheckMenu(ctx: ExtensionCommandContext, config: Us
|
|
|
2356
3287
|
entry.params = params;
|
|
2357
3288
|
}
|
|
2358
3289
|
|
|
2359
|
-
config["static-check"][selectedLanguage] ??= [];
|
|
2360
|
-
config["static-check"][selectedLanguage]!.
|
|
3290
|
+
config["static-check"][selectedLanguage] ??= createStaticCheckLanguageConfig([]);
|
|
3291
|
+
config["static-check"][selectedLanguage]!.enabled = "enable";
|
|
3292
|
+
config["static-check"][selectedLanguage]!.checkers.push(entry);
|
|
3293
|
+
onConfigChange();
|
|
2361
3294
|
ctx.ui.notify(`Added ${entry.module} checker for ${selectedLanguage}`, "info");
|
|
2362
3295
|
continue;
|
|
2363
3296
|
}
|
|
2364
3297
|
|
|
2365
3298
|
if (staticChoice === "remove-static-check-entry") {
|
|
2366
3299
|
const configuredLanguage = await showPiUsereqSettingsMenu(ctx, "Remove static code checker", buildConfiguredStaticCheckLanguageChoices(config));
|
|
2367
|
-
if (!configuredLanguage
|
|
3300
|
+
if (!configuredLanguage) {
|
|
2368
3301
|
continue;
|
|
2369
3302
|
}
|
|
2370
|
-
|
|
3303
|
+
if (configuredLanguage === "reset-defaults") {
|
|
3304
|
+
const approved = await confirmResetChanges(
|
|
3305
|
+
ctx,
|
|
3306
|
+
"Confirm static-check reset",
|
|
3307
|
+
[{
|
|
3308
|
+
label: "Language static code checkers",
|
|
3309
|
+
previousValue: formatStaticCheckLanguagesSummary(config),
|
|
3310
|
+
nextValue: formatStaticCheckLanguagesSummary({ ...config, "static-check": getDefaultStaticCheckConfig() }),
|
|
3311
|
+
}].filter((change) => change.previousValue !== change.nextValue),
|
|
3312
|
+
"Approve restoring the documented per-language static-check defaults.",
|
|
3313
|
+
"Abort the static-check reset and keep the current values.",
|
|
3314
|
+
);
|
|
3315
|
+
if (!approved) {
|
|
3316
|
+
ctx.ui.notify("Aborted static-check reset", "info");
|
|
3317
|
+
continue;
|
|
3318
|
+
}
|
|
3319
|
+
resetStaticCheckConfig(config);
|
|
3320
|
+
onConfigChange();
|
|
3321
|
+
ctx.ui.notify("Restored default static code checker configuration", "info");
|
|
3322
|
+
continue;
|
|
3323
|
+
}
|
|
3324
|
+
config["static-check"][configuredLanguage] = createStaticCheckLanguageConfig([]);
|
|
3325
|
+
onConfigChange();
|
|
2371
3326
|
ctx.ui.notify(`Removed static-check entries for ${configuredLanguage}`, "info");
|
|
2372
3327
|
continue;
|
|
2373
3328
|
}
|
|
3329
|
+
|
|
2374
3330
|
if (staticChoice === "reset-defaults") {
|
|
2375
|
-
|
|
3331
|
+
const approved = await confirmResetChanges(
|
|
3332
|
+
ctx,
|
|
3333
|
+
"Confirm static-check reset",
|
|
3334
|
+
[{
|
|
3335
|
+
label: "Language static code checkers",
|
|
3336
|
+
previousValue: formatStaticCheckLanguagesSummary(config),
|
|
3337
|
+
nextValue: formatStaticCheckLanguagesSummary({ ...config, "static-check": getDefaultStaticCheckConfig() }),
|
|
3338
|
+
}].filter((change) => change.previousValue !== change.nextValue),
|
|
3339
|
+
"Approve restoring the documented per-language static-check defaults.",
|
|
3340
|
+
"Abort the static-check reset and keep the current values.",
|
|
3341
|
+
);
|
|
3342
|
+
if (!approved) {
|
|
3343
|
+
ctx.ui.notify("Aborted static-check reset", "info");
|
|
3344
|
+
continue;
|
|
3345
|
+
}
|
|
3346
|
+
resetStaticCheckConfig(config);
|
|
3347
|
+
onConfigChange();
|
|
2376
3348
|
ctx.ui.notify("Restored default static code checker configuration", "info");
|
|
2377
3349
|
}
|
|
2378
3350
|
}
|
|
@@ -2380,16 +3352,21 @@ async function configureStaticCheckMenu(ctx: ExtensionCommandContext, config: Us
|
|
|
2380
3352
|
|
|
2381
3353
|
/**
|
|
2382
3354
|
* @brief Builds the shared settings-menu choices for the top-level pi-usereq configuration UI.
|
|
2383
|
-
* @details Serializes primary configuration actions into right-valued menu rows consumed by the shared settings-menu renderer, including the display-only config path beside `show-config`. Runtime is O(s) in source-directory count. No external state is mutated.
|
|
3355
|
+
* @details Serializes primary configuration actions into right-valued menu rows consumed by the shared settings-menu renderer, including automatic git-commit mode, effective prompt-command worktree state, notification summary, debug summary, locked worktree rows when automatic git commit is disabled, and the display-only config path beside `show-config`. Runtime is O(s) in source-directory count. No external state is mutated.
|
|
2384
3356
|
* @param[in] cwd {string} Current working directory.
|
|
2385
3357
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
2386
3358
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered top-level menu choices.
|
|
2387
|
-
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162, REQ-190, REQ-191, REQ-197
|
|
3359
|
+
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162, REQ-190, REQ-191, REQ-197, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240
|
|
2388
3360
|
*/
|
|
2389
3361
|
function buildPiUsereqMenuChoices(
|
|
2390
3362
|
cwd: string,
|
|
2391
3363
|
config: UseReqConfig,
|
|
2392
3364
|
): PiUsereqSettingsMenuChoice[] {
|
|
3365
|
+
const autoGitCommitDisabled = config.AUTO_GIT_COMMIT === "disable";
|
|
3366
|
+
const effectiveGitWorktreeEnabled = resolveEffectiveGitWorktreeEnabled(
|
|
3367
|
+
config.AUTO_GIT_COMMIT,
|
|
3368
|
+
config.GIT_WORKTREE_ENABLED,
|
|
3369
|
+
);
|
|
2393
3370
|
return [
|
|
2394
3371
|
{
|
|
2395
3372
|
id: "docs-dir",
|
|
@@ -2409,11 +3386,37 @@ function buildPiUsereqMenuChoices(
|
|
|
2409
3386
|
value: config["tests-dir"],
|
|
2410
3387
|
description: "Edit the repository-relative directory used for project test assets and static-check selection.",
|
|
2411
3388
|
},
|
|
3389
|
+
{
|
|
3390
|
+
id: "auto-git-commit",
|
|
3391
|
+
label: "Auto git commit",
|
|
3392
|
+
value: config.AUTO_GIT_COMMIT,
|
|
3393
|
+
description: "Select bundled `git_commit.md` or `git_read-only.md` for `%%COMMIT%%`; disabling also forces prompt-command worktrees off.",
|
|
3394
|
+
},
|
|
3395
|
+
{
|
|
3396
|
+
id: "git-worktree-enabled",
|
|
3397
|
+
label: "Git worktree",
|
|
3398
|
+
labelTone: autoGitCommitDisabled ? "dim" : undefined,
|
|
3399
|
+
value: effectiveGitWorktreeEnabled,
|
|
3400
|
+
valueTone: autoGitCommitDisabled ? "dim" : undefined,
|
|
3401
|
+
description: autoGitCommitDisabled
|
|
3402
|
+
? "Forced to `disable` while `Auto git commit` is disabled."
|
|
3403
|
+
: "Enable or disable prompt-command worktree orchestration.",
|
|
3404
|
+
},
|
|
3405
|
+
{
|
|
3406
|
+
id: "git-worktree-prefix",
|
|
3407
|
+
label: "Worktree prefix",
|
|
3408
|
+
labelTone: autoGitCommitDisabled ? "dim" : undefined,
|
|
3409
|
+
value: config.GIT_WORKTREE_PREFIX,
|
|
3410
|
+
valueTone: autoGitCommitDisabled ? "dim" : undefined,
|
|
3411
|
+
description: autoGitCommitDisabled
|
|
3412
|
+
? "Stored prefix is locked while `Auto git commit` is disabled."
|
|
3413
|
+
: "Edit the static prefix used by generated worktree names.",
|
|
3414
|
+
},
|
|
2412
3415
|
{
|
|
2413
3416
|
id: "static-check",
|
|
2414
3417
|
label: "Language static code checkers",
|
|
2415
3418
|
value: formatStaticCheckLanguagesSummary(config),
|
|
2416
|
-
description: "Manage guided Command static-check entries
|
|
3419
|
+
description: "Manage guided Command static-check entries and per-language enable flags.",
|
|
2417
3420
|
},
|
|
2418
3421
|
{
|
|
2419
3422
|
id: "startup-tools",
|
|
@@ -2427,25 +3430,22 @@ function buildPiUsereqMenuChoices(
|
|
|
2427
3430
|
value: `notification:${formatPiNotifyStatus(config)} • sound:${config["notify-sound"]} • pushover:${formatPiNotifyPushoverStatus(config)}`,
|
|
2428
3431
|
description: "Manage command-notify, sound, and Pushover settings with dedicated event submenus.",
|
|
2429
3432
|
},
|
|
3433
|
+
{
|
|
3434
|
+
id: "debug",
|
|
3435
|
+
label: "Debug",
|
|
3436
|
+
value: formatDebugMenuSummary(config),
|
|
3437
|
+
description: "Manage debug logging for tools and `req-*` prompt orchestration.",
|
|
3438
|
+
},
|
|
2430
3439
|
{
|
|
2431
3440
|
id: "show-config",
|
|
2432
3441
|
label: "Show configuration",
|
|
2433
3442
|
value: formatProjectConfigPathForMenu(cwd),
|
|
2434
3443
|
valueTone: "dim",
|
|
2435
|
-
description: "
|
|
2436
|
-
},
|
|
2437
|
-
{
|
|
2438
|
-
id: "reset-defaults",
|
|
2439
|
-
label: "Reset defaults",
|
|
2440
|
-
value: "",
|
|
2441
|
-
description: "Restore the default pi-usereq configuration for the current project base.",
|
|
2442
|
-
},
|
|
2443
|
-
{
|
|
2444
|
-
id: "save-and-close",
|
|
2445
|
-
label: "Save and close",
|
|
2446
|
-
value: "",
|
|
2447
|
-
description: "Persist the current configuration and return to the normal pi session UI.",
|
|
3444
|
+
description: "Persist the current project configuration file and write its exact text into the editor.",
|
|
2448
3445
|
},
|
|
3446
|
+
...buildTerminalSettingsMenuChoices({
|
|
3447
|
+
resetDefaultsDescription: "Restore the default pi-usereq configuration for the current project base.",
|
|
3448
|
+
}),
|
|
2449
3449
|
];
|
|
2450
3450
|
}
|
|
2451
3451
|
|
|
@@ -2470,24 +3470,16 @@ function buildSrcDirMenuChoices(config: UseReqConfig): PiUsereqSettingsMenuChoic
|
|
|
2470
3470
|
value: config["src-dir"].join(", "),
|
|
2471
3471
|
description: "Select one configured source directory to remove from the current configuration.",
|
|
2472
3472
|
},
|
|
2473
|
-
{
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
description: "Restore the documented default source-directory configuration.",
|
|
2478
|
-
},
|
|
2479
|
-
{
|
|
2480
|
-
id: "save-and-close",
|
|
2481
|
-
label: "Save and close",
|
|
2482
|
-
value: "",
|
|
2483
|
-
description: "Return to the parent configuration menu.",
|
|
2484
|
-
},
|
|
3473
|
+
...buildTerminalSettingsMenuChoices({
|
|
3474
|
+
resetDefaultsValue: `${DEFAULT_SRC_DIRS.join(", ")}`,
|
|
3475
|
+
resetDefaultsDescription: "Restore the documented default source-directory configuration.",
|
|
3476
|
+
}),
|
|
2485
3477
|
];
|
|
2486
3478
|
}
|
|
2487
3479
|
|
|
2488
3480
|
/**
|
|
2489
3481
|
* @brief Builds the shared settings-menu choices for removing one source-directory entry.
|
|
2490
|
-
* @details Exposes every configured `src-dir` entry as one removable row and appends
|
|
3482
|
+
* @details Exposes every configured `src-dir` entry as one removable row and appends subtree-local `Reset defaults` plus `Save and close` rows. Runtime is O(s) in source-directory count. No external state is mutated.
|
|
2491
3483
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
2492
3484
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered removable source-directory choices.
|
|
2493
3485
|
* @satisfies REQ-006, REQ-151, REQ-152, REQ-153, REQ-154
|
|
@@ -2500,34 +3492,36 @@ function buildSrcDirRemovalChoices(config: UseReqConfig): PiUsereqSettingsMenuCh
|
|
|
2500
3492
|
value: "remove",
|
|
2501
3493
|
description: `Remove the source-directory entry ${entry} from the current configuration.`,
|
|
2502
3494
|
})),
|
|
2503
|
-
{
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
description: "Return to the source-directory menu.",
|
|
2508
|
-
},
|
|
3495
|
+
...buildTerminalSettingsMenuChoices({
|
|
3496
|
+
resetDefaultsValue: `${DEFAULT_SRC_DIRS.join(", ")}`,
|
|
3497
|
+
resetDefaultsDescription: "Restore the documented default source-directory configuration.",
|
|
3498
|
+
}),
|
|
2509
3499
|
];
|
|
2510
3500
|
}
|
|
2511
3501
|
|
|
2512
3502
|
/**
|
|
2513
3503
|
* @brief Runs the top-level pi-usereq configuration menu.
|
|
2514
|
-
* @details Loads project config, exposes docs/test/source/static-check/startup-tool/notification actions through the shared settings-menu renderer, persists changes on exit, and refreshes the single-line status bar. Runtime depends on user interaction count. Side effects include UI updates, config writes, active-tool changes, and editor text updates.
|
|
3504
|
+
* @details Loads project config, exposes docs/test/source/automatic-commit/worktree/static-check/startup-tool/notification/debug actions through the shared settings-menu renderer, forces worktree disablement when automatic git commit is disabled, prevents locked row edits, persists changes on exit, closes immediately after `Show configuration`, and refreshes the single-line status bar. Runtime depends on user interaction count. Side effects include UI updates, config writes, active-tool changes, and editor text updates.
|
|
2515
3505
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
2516
3506
|
* @param[in] ctx {ExtensionCommandContext} Active command context.
|
|
2517
3507
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
2518
3508
|
* @return {Promise<void>} Promise resolved when configuration is saved and the menu closes.
|
|
2519
|
-
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-162, REQ-190, REQ-191, REQ-192, REQ-194, REQ-195
|
|
3509
|
+
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154, REQ-162, REQ-190, REQ-191, REQ-192, REQ-194, REQ-195, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243
|
|
2520
3510
|
*/
|
|
2521
3511
|
async function configurePiUsereq(
|
|
2522
3512
|
pi: ExtensionAPI,
|
|
2523
3513
|
ctx: ExtensionCommandContext,
|
|
2524
3514
|
statusController: PiUsereqStatusController,
|
|
2525
3515
|
): Promise<void> {
|
|
3516
|
+
bootstrapRuntimePathState(ctx.cwd, {
|
|
3517
|
+
gitPath: resolveRuntimeGitPath(ctx.cwd),
|
|
3518
|
+
});
|
|
2526
3519
|
let config = loadProjectConfig(ctx.cwd);
|
|
2527
3520
|
const projectBase = getProjectBase(ctx.cwd);
|
|
2528
3521
|
const initialShortcut = config["notify-sound-toggle-shortcut"];
|
|
2529
|
-
const
|
|
2530
|
-
|
|
3522
|
+
const persistConfigChange = () => {
|
|
3523
|
+
Object.assign(config, normalizeConfigPaths(projectBase, config));
|
|
3524
|
+
saveProjectConfig(ctx.cwd, config);
|
|
2531
3525
|
setPiUsereqStatusConfig(statusController, config);
|
|
2532
3526
|
renderPiUsereqStatus(statusController, ctx);
|
|
2533
3527
|
};
|
|
@@ -2540,9 +3534,7 @@ async function configurePiUsereq(
|
|
|
2540
3534
|
buildPiUsereqMenuChoices(ctx.cwd, config),
|
|
2541
3535
|
{ initialSelectedId: focusedChoiceId },
|
|
2542
3536
|
);
|
|
2543
|
-
if (!choice
|
|
2544
|
-
ensureSaved();
|
|
2545
|
-
refreshStatus();
|
|
3537
|
+
if (!choice) {
|
|
2546
3538
|
if (config["notify-sound-toggle-shortcut"] !== initialShortcut) {
|
|
2547
3539
|
ctx.ui.notify("Sound toggle hotkey bind updated; run /reload to apply the new binding", "info");
|
|
2548
3540
|
}
|
|
@@ -2551,12 +3543,61 @@ async function configurePiUsereq(
|
|
|
2551
3543
|
focusedChoiceId = choice;
|
|
2552
3544
|
if (choice === "docs-dir") {
|
|
2553
3545
|
const value = await ctx.ui.input("Document directory", config["docs-dir"]);
|
|
2554
|
-
if (value?.trim())
|
|
3546
|
+
if (value?.trim()) {
|
|
3547
|
+
config["docs-dir"] = value.trim();
|
|
3548
|
+
persistConfigChange();
|
|
3549
|
+
}
|
|
2555
3550
|
continue;
|
|
2556
3551
|
}
|
|
2557
3552
|
if (choice === "tests-dir") {
|
|
2558
3553
|
const value = await ctx.ui.input("Unit tests directory", config["tests-dir"]);
|
|
2559
|
-
if (value?.trim())
|
|
3554
|
+
if (value?.trim()) {
|
|
3555
|
+
config["tests-dir"] = value.trim();
|
|
3556
|
+
persistConfigChange();
|
|
3557
|
+
}
|
|
3558
|
+
continue;
|
|
3559
|
+
}
|
|
3560
|
+
if (choice === "auto-git-commit") {
|
|
3561
|
+
const nextAutoGitCommit = config.AUTO_GIT_COMMIT === "enable"
|
|
3562
|
+
? "disable"
|
|
3563
|
+
: "enable";
|
|
3564
|
+
config.AUTO_GIT_COMMIT = nextAutoGitCommit;
|
|
3565
|
+
if (nextAutoGitCommit === "disable") {
|
|
3566
|
+
config.GIT_WORKTREE_ENABLED = "disable";
|
|
3567
|
+
persistConfigChange();
|
|
3568
|
+
ctx.ui.notify("Auto git commit disabled; Git worktree forced off", "info");
|
|
3569
|
+
} else {
|
|
3570
|
+
persistConfigChange();
|
|
3571
|
+
ctx.ui.notify("Auto git commit enabled", "info");
|
|
3572
|
+
}
|
|
3573
|
+
continue;
|
|
3574
|
+
}
|
|
3575
|
+
if (choice === "git-worktree-enabled") {
|
|
3576
|
+
if (config.AUTO_GIT_COMMIT === "disable") {
|
|
3577
|
+
ctx.ui.notify("Git worktree is locked while Auto git commit is disabled", "info");
|
|
3578
|
+
continue;
|
|
3579
|
+
}
|
|
3580
|
+
config.GIT_WORKTREE_ENABLED = config.GIT_WORKTREE_ENABLED === "enable"
|
|
3581
|
+
? "disable"
|
|
3582
|
+
: "enable";
|
|
3583
|
+
persistConfigChange();
|
|
3584
|
+
ctx.ui.notify(
|
|
3585
|
+
`Git worktree ${resolveEffectiveGitWorktreeEnabled(config.AUTO_GIT_COMMIT, config.GIT_WORKTREE_ENABLED) === "enable" ? "enabled" : "disabled"}`,
|
|
3586
|
+
"info",
|
|
3587
|
+
);
|
|
3588
|
+
continue;
|
|
3589
|
+
}
|
|
3590
|
+
if (choice === "git-worktree-prefix") {
|
|
3591
|
+
if (config.AUTO_GIT_COMMIT === "disable") {
|
|
3592
|
+
ctx.ui.notify("Worktree prefix is locked while Auto git commit is disabled", "info");
|
|
3593
|
+
continue;
|
|
3594
|
+
}
|
|
3595
|
+
const value = await ctx.ui.input("Worktree prefix", config.GIT_WORKTREE_PREFIX);
|
|
3596
|
+
if (value !== undefined) {
|
|
3597
|
+
config.GIT_WORKTREE_PREFIX = value.trim() || DEFAULT_GIT_WORKTREE_PREFIX;
|
|
3598
|
+
persistConfigChange();
|
|
3599
|
+
ctx.ui.notify(`Worktree prefix set to ${config.GIT_WORKTREE_PREFIX}`, "info");
|
|
3600
|
+
}
|
|
2560
3601
|
continue;
|
|
2561
3602
|
}
|
|
2562
3603
|
if (choice === "src-dir") {
|
|
@@ -2565,7 +3606,7 @@ async function configurePiUsereq(
|
|
|
2565
3606
|
const srcAction = await showPiUsereqSettingsMenu(ctx, "Source-code directories", buildSrcDirMenuChoices(config), {
|
|
2566
3607
|
initialSelectedId: srcFocusedChoiceId,
|
|
2567
3608
|
});
|
|
2568
|
-
if (!srcAction
|
|
3609
|
+
if (!srcAction) {
|
|
2569
3610
|
break;
|
|
2570
3611
|
}
|
|
2571
3612
|
srcFocusedChoiceId = srcAction;
|
|
@@ -2573,47 +3614,118 @@ async function configurePiUsereq(
|
|
|
2573
3614
|
const value = await ctx.ui.input("New source-code directory", "src");
|
|
2574
3615
|
if (value?.trim()) {
|
|
2575
3616
|
config["src-dir"] = [...config["src-dir"], value.trim()];
|
|
3617
|
+
persistConfigChange();
|
|
2576
3618
|
}
|
|
2577
3619
|
continue;
|
|
2578
3620
|
}
|
|
2579
3621
|
if (srcAction === "remove-src-dir-entry") {
|
|
2580
3622
|
const toRemove = await showPiUsereqSettingsMenu(ctx, "Remove source-code directory", buildSrcDirRemovalChoices(config));
|
|
2581
|
-
if (toRemove
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
3623
|
+
if (!toRemove) {
|
|
3624
|
+
continue;
|
|
3625
|
+
}
|
|
3626
|
+
if (toRemove === "reset-defaults") {
|
|
3627
|
+
const approved = await confirmResetChanges(
|
|
3628
|
+
ctx,
|
|
3629
|
+
"Confirm source-directory reset",
|
|
3630
|
+
[{
|
|
3631
|
+
label: "Source-code directories",
|
|
3632
|
+
previousValue: config["src-dir"].join(", "),
|
|
3633
|
+
nextValue: DEFAULT_SRC_DIRS.join(", "),
|
|
3634
|
+
}].filter((change) => change.previousValue !== change.nextValue),
|
|
3635
|
+
"Approve restoring the documented default source-directory configuration.",
|
|
3636
|
+
"Abort the source-directory reset and keep the current values.",
|
|
3637
|
+
);
|
|
3638
|
+
if (!approved) {
|
|
3639
|
+
ctx.ui.notify("Aborted source-directory reset", "info");
|
|
3640
|
+
continue;
|
|
2585
3641
|
}
|
|
3642
|
+
config["src-dir"] = [...DEFAULT_SRC_DIRS];
|
|
3643
|
+
persistConfigChange();
|
|
3644
|
+
ctx.ui.notify("Restored default source-code directories", "info");
|
|
3645
|
+
continue;
|
|
3646
|
+
}
|
|
3647
|
+
config["src-dir"] = config["src-dir"].filter((entry) => entry !== toRemove);
|
|
3648
|
+
if (config["src-dir"].length === 0) {
|
|
3649
|
+
config["src-dir"] = ["src"];
|
|
2586
3650
|
}
|
|
3651
|
+
persistConfigChange();
|
|
2587
3652
|
continue;
|
|
2588
3653
|
}
|
|
2589
3654
|
if (srcAction === "reset-defaults") {
|
|
3655
|
+
const approved = await confirmResetChanges(
|
|
3656
|
+
ctx,
|
|
3657
|
+
"Confirm source-directory reset",
|
|
3658
|
+
[{
|
|
3659
|
+
label: "Source-code directories",
|
|
3660
|
+
previousValue: config["src-dir"].join(", "),
|
|
3661
|
+
nextValue: DEFAULT_SRC_DIRS.join(", "),
|
|
3662
|
+
}].filter((change) => change.previousValue !== change.nextValue),
|
|
3663
|
+
"Approve restoring the documented default source-directory configuration.",
|
|
3664
|
+
"Abort the source-directory reset and keep the current values.",
|
|
3665
|
+
);
|
|
3666
|
+
if (!approved) {
|
|
3667
|
+
ctx.ui.notify("Aborted source-directory reset", "info");
|
|
3668
|
+
continue;
|
|
3669
|
+
}
|
|
2590
3670
|
config["src-dir"] = [...DEFAULT_SRC_DIRS];
|
|
3671
|
+
persistConfigChange();
|
|
2591
3672
|
ctx.ui.notify("Restored default source-code directories", "info");
|
|
2592
3673
|
}
|
|
2593
3674
|
}
|
|
2594
3675
|
continue;
|
|
2595
3676
|
}
|
|
2596
3677
|
if (choice === "static-check") {
|
|
2597
|
-
await configureStaticCheckMenu(ctx, config);
|
|
3678
|
+
await configureStaticCheckMenu(ctx, config, persistConfigChange);
|
|
2598
3679
|
continue;
|
|
2599
3680
|
}
|
|
2600
3681
|
if (choice === "startup-tools") {
|
|
2601
|
-
await configurePiUsereqToolsMenu(pi, ctx, config);
|
|
3682
|
+
await configurePiUsereqToolsMenu(pi, ctx, config, persistConfigChange);
|
|
2602
3683
|
continue;
|
|
2603
3684
|
}
|
|
2604
3685
|
if (choice === "notifications") {
|
|
2605
|
-
await configurePiNotifyMenu(ctx, config);
|
|
3686
|
+
await configurePiNotifyMenu(ctx, config, persistConfigChange);
|
|
3687
|
+
continue;
|
|
3688
|
+
}
|
|
3689
|
+
if (choice === "debug") {
|
|
3690
|
+
await configureDebugMenu(ctx, config, persistConfigChange);
|
|
2606
3691
|
continue;
|
|
2607
3692
|
}
|
|
2608
3693
|
if (choice === "reset-defaults") {
|
|
2609
|
-
|
|
3694
|
+
const defaultConfig = getDefaultConfig(projectBase);
|
|
3695
|
+
const approved = await confirmResetChanges(
|
|
3696
|
+
ctx,
|
|
3697
|
+
"Confirm pi-usereq reset",
|
|
3698
|
+
[
|
|
3699
|
+
{ label: "Document directory", previousValue: config["docs-dir"], nextValue: defaultConfig["docs-dir"] },
|
|
3700
|
+
{ label: "Source-code directories", previousValue: config["src-dir"].join(", "), nextValue: defaultConfig["src-dir"].join(", ") },
|
|
3701
|
+
{ label: "Unit tests directory", previousValue: config["tests-dir"], nextValue: defaultConfig["tests-dir"] },
|
|
3702
|
+
{ label: "Auto git commit", previousValue: config.AUTO_GIT_COMMIT, nextValue: defaultConfig.AUTO_GIT_COMMIT },
|
|
3703
|
+
{ label: "Git worktree", previousValue: config.GIT_WORKTREE_ENABLED, nextValue: defaultConfig.GIT_WORKTREE_ENABLED },
|
|
3704
|
+
{ label: "Worktree prefix", previousValue: config.GIT_WORKTREE_PREFIX, nextValue: defaultConfig.GIT_WORKTREE_PREFIX },
|
|
3705
|
+
{ label: "Enable tools", previousValue: String(getConfiguredEnabledPiUsereqTools(config).length), nextValue: String(getConfiguredEnabledPiUsereqTools(defaultConfig).length) },
|
|
3706
|
+
{ label: "Notifications", previousValue: buildPiNotifyMenuChoices(config).length.toString(), nextValue: buildPiNotifyMenuChoices(defaultConfig).length.toString() },
|
|
3707
|
+
{ label: "Debug", previousValue: formatDebugMenuSummary(config), nextValue: formatDebugMenuSummary(defaultConfig) },
|
|
3708
|
+
].filter((change) => change.previousValue !== change.nextValue),
|
|
3709
|
+
"Approve restoring the default pi-usereq configuration.",
|
|
3710
|
+
"Abort the pi-usereq reset and keep the current values.",
|
|
3711
|
+
);
|
|
3712
|
+
if (!approved) {
|
|
3713
|
+
ctx.ui.notify("Aborted pi-usereq reset", "info");
|
|
3714
|
+
continue;
|
|
3715
|
+
}
|
|
3716
|
+
config = defaultConfig;
|
|
2610
3717
|
applyConfiguredPiUsereqTools(pi, config);
|
|
3718
|
+
persistConfigChange();
|
|
2611
3719
|
ctx.ui.notify("Restored all default configuration values", "info");
|
|
2612
3720
|
continue;
|
|
2613
3721
|
}
|
|
2614
3722
|
if (choice === "show-config") {
|
|
2615
|
-
|
|
2616
|
-
|
|
3723
|
+
persistConfigChange();
|
|
3724
|
+
if (config["notify-sound-toggle-shortcut"] !== initialShortcut) {
|
|
3725
|
+
ctx.ui.notify("Sound toggle hotkey bind updated; run /reload to apply the new binding", "info");
|
|
3726
|
+
}
|
|
3727
|
+
writePersistedProjectConfigToEditor(ctx, ctx.cwd, config);
|
|
3728
|
+
return;
|
|
2617
3729
|
}
|
|
2618
3730
|
}
|
|
2619
3731
|
}
|
|
@@ -2645,14 +3757,14 @@ function registerConfigCommands(
|
|
|
2645
3757
|
* notification-sound shortcut when the runtime supports shortcuts, and
|
|
2646
3758
|
* installs shared wrappers for all supported pi lifecycle hooks so status
|
|
2647
3759
|
* telemetry, context usage, prompt timing, cumulative runtime, prompt-specific
|
|
2648
|
-
* Pushover metadata,
|
|
2649
|
-
* events. Runtime is O(h) in hook
|
|
3760
|
+
* Pushover metadata, tool-result debug logging, and prompt-orchestration debug
|
|
3761
|
+
* effects remain synchronized with runtime events. Runtime is O(h) in hook
|
|
2650
3762
|
* count during registration. Side effects include filesystem reads,
|
|
2651
|
-
* command/tool/shortcut registration, UI updates, active-tool changes,
|
|
2652
|
-
* timer scheduling.
|
|
3763
|
+
* command/tool/shortcut registration, UI updates, active-tool changes,
|
|
3764
|
+
* optional debug-log writes, and timer scheduling.
|
|
2653
3765
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
2654
3766
|
* @return {void} No return value.
|
|
2655
|
-
* @satisfies DES-002, REQ-004, REQ-005, REQ-009, REQ-044, REQ-
|
|
3767
|
+
* @satisfies DES-002, REQ-004, REQ-005, REQ-009, REQ-044, REQ-067, REQ-068, REQ-109, REQ-111, REQ-112, REQ-113, REQ-114, REQ-115, REQ-116, REQ-117, REQ-118, REQ-119, REQ-120, REQ-121, REQ-122, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-131, REQ-132, REQ-133, REQ-134, REQ-137, REQ-148, REQ-159, REQ-163, REQ-164, REQ-165, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172, REQ-174, REQ-179, REQ-180, REQ-184, REQ-188, REQ-190, REQ-191, REQ-192, REQ-193, REQ-194, REQ-195, REQ-196, REQ-197, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243, REQ-244, REQ-245, REQ-246, REQ-247
|
|
2656
3768
|
*/
|
|
2657
3769
|
export default function piUsereqExtension(pi: ExtensionAPI): void {
|
|
2658
3770
|
const statusController = createPiUsereqStatusController();
|