@ashx-j/lunr 0.2.11 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +211 -236
- package/dist/builtin-extensions/ashxj-tui.d.ts +3 -0
- package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.js +28 -11
- package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +0 -2
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-cron.d.ts +1 -1
- package/dist/builtin-extensions/lunr-cron.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-cron.js +1 -1
- package/dist/builtin-extensions/lunr-cron.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.js +3 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/doctor.d.ts +0 -2
- package/dist/builtin-extensions/pi-subagents/src/extension/doctor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/doctor.js +1 -23
- package/dist/builtin-extensions/pi-subagents/src/extension/doctor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js +6 -7
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +7 -14
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +12 -9
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +38 -34
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +3 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +32 -47
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +7 -10
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +90 -109
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts +3 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +37 -50
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.d.ts +4 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.js +5 -5
- package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +31 -13
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts +4 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +48 -20
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +90 -64
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +6 -8
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +262 -161
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js +24 -33
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +25 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +51 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js +20 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +4 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +5 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +9 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js +20 -17
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +70 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +153 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/settings.d.ts +13 -8
- package/dist/builtin-extensions/pi-subagents/src/shared/settings.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/settings.js +20 -51
- package/dist/builtin-extensions/pi-subagents/src/shared/settings.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +80 -20
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js +10 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +20 -32
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +23 -449
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +13 -11
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.js +18 -17
- package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.js.map +1 -1
- package/dist/builtin-extensions/simple-pi-memory.d.ts +2 -2
- package/dist/builtin-extensions/simple-pi-memory.d.ts.map +1 -1
- package/dist/builtin-extensions/simple-pi-memory.js +41 -17
- package/dist/builtin-extensions/simple-pi-memory.js.map +1 -1
- package/dist/catalog/models.json +1 -1
- package/dist/catalog/providers/amazon-bedrock.json +1 -1
- package/dist/catalog/providers/azure-openai-responses.json +1 -1
- package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
- package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
- package/dist/catalog/providers/fireworks.json +1 -1
- package/dist/catalog/providers/github-copilot.json +1 -1
- package/dist/catalog/providers/huggingface.json +1 -1
- package/dist/catalog/providers/minimax-cn.json +1 -1
- package/dist/catalog/providers/minimax.json +1 -1
- package/dist/catalog/providers/mistral.json +1 -1
- package/dist/catalog/providers/nvidia.json +1 -1
- package/dist/catalog/providers/openai.json +1 -1
- package/dist/catalog/providers/opencode-go.json +1 -1
- package/dist/catalog/providers/opencode.json +1 -1
- package/dist/catalog/providers/openrouter.json +1 -1
- package/dist/catalog/providers/qwen-cloud-cn.json +1 -1
- package/dist/catalog/providers/qwen-cloud.json +1 -1
- package/dist/catalog/providers/qwen-token-plan-cn.json +1 -1
- package/dist/catalog/providers/qwen-token-plan.json +1 -1
- package/dist/catalog/providers/together.json +1 -1
- package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
- package/dist/catalog/providers/zai-coding-cn.json +1 -1
- package/dist/catalog/providers/zai.json +1 -1
- package/dist/catalog/publication.json +3 -3
- package/dist/core/agent-session.d.ts +7 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +34 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/customize.d.ts +2 -0
- package/dist/core/customize.d.ts.map +1 -1
- package/dist/core/customize.js +6 -0
- package/dist/core/customize.js.map +1 -1
- package/dist/core/memory-cap.d.ts +4 -8
- package/dist/core/memory-cap.d.ts.map +1 -1
- package/dist/core/memory-cap.js +8 -8
- package/dist/core/memory-cap.js.map +1 -1
- package/dist/core/permissions.d.ts +2 -0
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +30 -14
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +1 -9
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/rollback.d.ts.map +1 -1
- package/dist/core/rollback.js +1 -1
- package/dist/core/rollback.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +24 -10
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -5
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-permission-inherit.d.ts +34 -22
- package/dist/core/subagent-permission-inherit.d.ts.map +1 -1
- package/dist/core/subagent-permission-inherit.js +37 -80
- package/dist/core/subagent-permission-inherit.js.map +1 -1
- package/dist/core/swarm.d.ts.map +1 -1
- package/dist/core/swarm.js +13 -7
- package/dist/core/swarm.js.map +1 -1
- package/dist/core/system-prompt.d.ts +6 -4
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +38 -48
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/usage-adapters/openai-codex.d.ts +1 -1
- package/dist/core/usage-adapters/openai-codex.d.ts.map +1 -1
- package/dist/core/usage-adapters/openai-codex.js +22 -17
- package/dist/core/usage-adapters/openai-codex.js.map +1 -1
- package/dist/core/usage-service.d.ts +10 -1
- package/dist/core/usage-service.d.ts.map +1 -1
- package/dist/core/usage-service.js +17 -6
- package/dist/core/usage-service.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +1 -4
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +1 -4
- package/dist/gateway/cron.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +0 -5
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +2 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +21 -0
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -3
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +21 -77
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/usage-view.d.ts +1 -1
- package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/usage-view.js +7 -4
- package/dist/modes/interactive/components/usage-view.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +39 -93
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/compaction.md +23 -23
- package/docs/containerization.md +24 -24
- package/docs/custom-provider.md +18 -18
- package/docs/development.md +16 -15
- package/docs/docs.json +6 -2
- package/docs/extensions.md +82 -80
- package/docs/features.md +102 -0
- package/docs/index.md +25 -19
- package/docs/json.md +7 -7
- package/docs/keybindings.md +4 -4
- package/docs/models.md +2 -2
- package/docs/packages.md +38 -42
- package/docs/prompt-templates.md +4 -4
- package/docs/providers.md +22 -10
- package/docs/quickstart.md +81 -46
- package/docs/rpc.md +7 -13
- package/docs/sdk.md +43 -41
- package/docs/security.md +18 -18
- package/docs/session-format.md +9 -9
- package/docs/sessions.md +13 -12
- package/docs/settings.md +62 -24
- package/docs/shell-aliases.md +2 -2
- package/docs/skills.md +11 -11
- package/docs/terminal-setup.md +5 -5
- package/docs/termux.md +7 -7
- package/docs/themes.md +12 -14
- package/docs/tmux.md +4 -4
- package/docs/tui.md +23 -23
- package/docs/usage.md +112 -87
- package/docs/windows.md +10 -2
- package/examples/README.md +3 -1
- package/examples/extensions/README.md +6 -6
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/doom-overlay/README.md +2 -2
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/plan-mode/README.md +3 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +13 -11
- package/examples/extensions/todo.ts +2 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/README.md +9 -5
- package/package.json +6 -7
- package/dist/builtin-extensions/lunr-behavior.d.ts +0 -44
- package/dist/builtin-extensions/lunr-behavior.d.ts.map +0 -1
- package/dist/builtin-extensions/lunr-behavior.js +0 -213
- package/dist/builtin-extensions/lunr-behavior.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/agents/context-builder.md +0 -46
- package/dist/builtin-extensions/pi-subagents/agents/deep-researcher.md +0 -36
- package/dist/builtin-extensions/pi-subagents/agents/delegate.md +0 -12
- package/dist/builtin-extensions/pi-subagents/agents/oracle.md +0 -73
- package/dist/builtin-extensions/pi-subagents/agents/planner.md +0 -55
- package/dist/builtin-extensions/pi-subagents/agents/research-writer.md +0 -37
- package/dist/builtin-extensions/pi-subagents/agents/researcher.md +0 -52
- package/dist/builtin-extensions/pi-subagents/agents/reviewer.md +0 -79
- package/dist/builtin-extensions/pi-subagents/agents/scout.md +0 -50
- package/dist/builtin-extensions/pi-subagents/agents/worker.md +0 -55
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts +0 -20
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js +0 -1273
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.d.ts +0 -49
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.js +0 -252
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.d.ts +0 -3
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.js +0 -6
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.d.ts +0 -3
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.js +0 -23
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.d.ts +0 -8
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.js +0 -143
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agents.d.ts +0 -176
- package/dist/builtin-extensions/pi-subagents/src/agents/agents.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agents.js +0 -1326
- package/dist/builtin-extensions/pi-subagents/src/agents/agents.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.d.ts +0 -7
- package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.js +0 -307
- package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/identity.d.ts +0 -8
- package/dist/builtin-extensions/pi-subagents/src/agents/identity.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/identity.js +0 -30
- package/dist/builtin-extensions/pi-subagents/src/agents/identity.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.d.ts +0 -35
- package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.js +0 -151
- package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.d.ts +0 -131
- package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.js +0 -491
- package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.js.map +0 -1
- package/dist/core/behavior-preset.d.ts +0 -60
- package/dist/core/behavior-preset.d.ts.map +0 -1
- package/dist/core/behavior-preset.js +0 -257
- package/dist/core/behavior-preset.js.map +0 -1
package/dist/core/customize.d.ts
CHANGED
|
@@ -11,10 +11,12 @@ export declare const CUSTOMIZE_BRIDGE_SYMBOL: unique symbol;
|
|
|
11
11
|
export interface CustomizeBridge {
|
|
12
12
|
getGutterRail(): boolean;
|
|
13
13
|
getPromptSymbol(): boolean;
|
|
14
|
+
getOpenAIFastMode(): boolean;
|
|
14
15
|
getFooterMcp(): boolean;
|
|
15
16
|
getFooterLsp(): boolean;
|
|
16
17
|
getFooterContext(): boolean;
|
|
17
18
|
getFooterTokens(): boolean;
|
|
19
|
+
getFooterCacheHitRate(): boolean;
|
|
18
20
|
getFooterTps(): boolean;
|
|
19
21
|
getFooterStatuses(): boolean;
|
|
20
22
|
getFooterGit(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"customize.d.ts","sourceRoot":"","sources":["../../src/core/customize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;;;GAOG;AAEH,eAAO,MAAM,uBAAuB,eAAgC,CAAC;AAErE,MAAM,WAAW,eAAe;IAC/B,aAAa,IAAI,OAAO,CAAC;IACzB,eAAe,IAAI,OAAO,CAAC;IAC3B,YAAY,IAAI,OAAO,CAAC;IACxB,YAAY,IAAI,OAAO,CAAC;IACxB,gBAAgB,IAAI,OAAO,CAAC;IAC5B,eAAe,IAAI,OAAO,CAAC;IAC3B,YAAY,IAAI,OAAO,CAAC;IACxB,iBAAiB,IAAI,OAAO,CAAC;IAC7B,YAAY,IAAI,OAAO,CAAC;IACxB,aAAa,IAAI,OAAO,CAAC;IACzB,gBAAgB,IAAI,OAAO,CAAC;IAC5B,oBAAoB,IAAI,OAAO,CAAC;IAChC,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1C;
|
|
1
|
+
{"version":3,"file":"customize.d.ts","sourceRoot":"","sources":["../../src/core/customize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;;;GAOG;AAEH,eAAO,MAAM,uBAAuB,eAAgC,CAAC;AAErE,MAAM,WAAW,eAAe;IAC/B,aAAa,IAAI,OAAO,CAAC;IACzB,eAAe,IAAI,OAAO,CAAC;IAC3B,iBAAiB,IAAI,OAAO,CAAC;IAC7B,YAAY,IAAI,OAAO,CAAC;IACxB,YAAY,IAAI,OAAO,CAAC;IACxB,gBAAgB,IAAI,OAAO,CAAC;IAC5B,eAAe,IAAI,OAAO,CAAC;IAC3B,qBAAqB,IAAI,OAAO,CAAC;IACjC,YAAY,IAAI,OAAO,CAAC;IACxB,iBAAiB,IAAI,OAAO,CAAC;IAC7B,YAAY,IAAI,OAAO,CAAC;IACxB,aAAa,IAAI,OAAO,CAAC;IACzB,gBAAgB,IAAI,OAAO,CAAC;IAC5B,oBAAoB,IAAI,OAAO,CAAC;IAChC,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1C;AAoDD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI,CAG9E;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,SAAS,CAEhE","sourcesContent":["import type { SettingsManager } from \"./settings-manager.ts\";\n\n/**\n * lunR TUI customize bridge.\n *\n * Exposed on `globalThis` under `Symbol.for(\"@lunr/customize\")` so the baked-in\n * ashxj-tui extension can read the customize settings (gutter rail, prompt\n * symbol, footer element toggles) without importing core code — same pattern as\n * the model-tiers and memory-cap bridges.\n */\n\nexport const CUSTOMIZE_BRIDGE_SYMBOL = Symbol.for(\"@lunr/customize\");\n\nexport interface CustomizeBridge {\n\tgetGutterRail(): boolean;\n\tgetPromptSymbol(): boolean;\n\tgetOpenAIFastMode(): boolean;\n\tgetFooterMcp(): boolean;\n\tgetFooterLsp(): boolean;\n\tgetFooterContext(): boolean;\n\tgetFooterTokens(): boolean;\n\tgetFooterCacheHitRate(): boolean;\n\tgetFooterTps(): boolean;\n\tgetFooterStatuses(): boolean;\n\tgetFooterGit(): boolean;\n\tgetFooterPlan(): boolean;\n\tgetFooterPlanBar(): boolean;\n\tgetHideThinkingBlock(): boolean;\n\tsetHideThinkingBlock(hide: boolean): void;\n}\n\nlet activeSettingsManager: SettingsManager | undefined;\n\nconst bridge: CustomizeBridge = {\n\tgetGutterRail(): boolean {\n\t\treturn activeSettingsManager?.getGutterRail() ?? true;\n\t},\n\tgetPromptSymbol(): boolean {\n\t\treturn activeSettingsManager?.getPromptSymbol() ?? true;\n\t},\n\tgetOpenAIFastMode(): boolean {\n\t\treturn activeSettingsManager?.getOpenAIFastMode() ?? false;\n\t},\n\tgetFooterMcp(): boolean {\n\t\treturn activeSettingsManager?.getFooterMcp() ?? true;\n\t},\n\tgetFooterLsp(): boolean {\n\t\treturn activeSettingsManager?.getFooterLsp() ?? false;\n\t},\n\tgetFooterContext(): boolean {\n\t\treturn activeSettingsManager?.getFooterContext() ?? true;\n\t},\n\tgetFooterTokens(): boolean {\n\t\treturn activeSettingsManager?.getFooterTokens() ?? true;\n\t},\n\tgetFooterCacheHitRate(): boolean {\n\t\treturn activeSettingsManager?.getFooterCacheHitRate() ?? true;\n\t},\n\tgetFooterTps(): boolean {\n\t\treturn activeSettingsManager?.getFooterTps() ?? true;\n\t},\n\tgetFooterStatuses(): boolean {\n\t\treturn activeSettingsManager?.getFooterStatuses() ?? true;\n\t},\n\tgetFooterGit(): boolean {\n\t\treturn activeSettingsManager?.getFooterGit() ?? true;\n\t},\n\tgetFooterPlan(): boolean {\n\t\treturn activeSettingsManager?.getFooterPlan() ?? true;\n\t},\n\tgetFooterPlanBar(): boolean {\n\t\treturn activeSettingsManager?.getFooterPlanBar() ?? true;\n\t},\n\tgetHideThinkingBlock(): boolean {\n\t\treturn activeSettingsManager?.getHideThinkingBlock() ?? false;\n\t},\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tactiveSettingsManager?.setHideThinkingBlock(hide);\n\t},\n};\n\n/**\n * Register (or re-point) the global customize bridge.\n * Safe to call multiple times — later calls only swap the settings source, so an\n * early startup registration (before extensions load) can be replaced by the live\n * runtime settings manager.\n */\nexport function registerCustomizeBridge(settingsManager: SettingsManager): void {\n\tactiveSettingsManager = settingsManager;\n\t(globalThis as Record<symbol, unknown>)[CUSTOMIZE_BRIDGE_SYMBOL] = bridge;\n}\n\n/** Read the bridge from `globalThis`, or undefined when not registered (extension-safe). */\nexport function getCustomizeBridge(): CustomizeBridge | undefined {\n\treturn (globalThis as Record<symbol, unknown>)[CUSTOMIZE_BRIDGE_SYMBOL] as CustomizeBridge | undefined;\n}\n"]}
|
package/dist/core/customize.js
CHANGED
|
@@ -15,6 +15,9 @@ const bridge = {
|
|
|
15
15
|
getPromptSymbol() {
|
|
16
16
|
return activeSettingsManager?.getPromptSymbol() ?? true;
|
|
17
17
|
},
|
|
18
|
+
getOpenAIFastMode() {
|
|
19
|
+
return activeSettingsManager?.getOpenAIFastMode() ?? false;
|
|
20
|
+
},
|
|
18
21
|
getFooterMcp() {
|
|
19
22
|
return activeSettingsManager?.getFooterMcp() ?? true;
|
|
20
23
|
},
|
|
@@ -27,6 +30,9 @@ const bridge = {
|
|
|
27
30
|
getFooterTokens() {
|
|
28
31
|
return activeSettingsManager?.getFooterTokens() ?? true;
|
|
29
32
|
},
|
|
33
|
+
getFooterCacheHitRate() {
|
|
34
|
+
return activeSettingsManager?.getFooterCacheHitRate() ?? true;
|
|
35
|
+
},
|
|
30
36
|
getFooterTps() {
|
|
31
37
|
return activeSettingsManager?.getFooterTps() ?? true;
|
|
32
38
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"customize.js","sourceRoot":"","sources":["../../src/core/customize.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"customize.js","sourceRoot":"","sources":["../../src/core/customize.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAoBrE,IAAI,qBAAkD,CAAC;AAEvD,MAAM,MAAM,GAAoB;IAC/B,aAAa,GAAY;QACxB,OAAO,qBAAqB,EAAE,aAAa,EAAE,IAAI,IAAI,CAAC;IAAA,CACtD;IACD,eAAe,GAAY;QAC1B,OAAO,qBAAqB,EAAE,eAAe,EAAE,IAAI,IAAI,CAAC;IAAA,CACxD;IACD,iBAAiB,GAAY;QAC5B,OAAO,qBAAqB,EAAE,iBAAiB,EAAE,IAAI,KAAK,CAAC;IAAA,CAC3D;IACD,YAAY,GAAY;QACvB,OAAO,qBAAqB,EAAE,YAAY,EAAE,IAAI,IAAI,CAAC;IAAA,CACrD;IACD,YAAY,GAAY;QACvB,OAAO,qBAAqB,EAAE,YAAY,EAAE,IAAI,KAAK,CAAC;IAAA,CACtD;IACD,gBAAgB,GAAY;QAC3B,OAAO,qBAAqB,EAAE,gBAAgB,EAAE,IAAI,IAAI,CAAC;IAAA,CACzD;IACD,eAAe,GAAY;QAC1B,OAAO,qBAAqB,EAAE,eAAe,EAAE,IAAI,IAAI,CAAC;IAAA,CACxD;IACD,qBAAqB,GAAY;QAChC,OAAO,qBAAqB,EAAE,qBAAqB,EAAE,IAAI,IAAI,CAAC;IAAA,CAC9D;IACD,YAAY,GAAY;QACvB,OAAO,qBAAqB,EAAE,YAAY,EAAE,IAAI,IAAI,CAAC;IAAA,CACrD;IACD,iBAAiB,GAAY;QAC5B,OAAO,qBAAqB,EAAE,iBAAiB,EAAE,IAAI,IAAI,CAAC;IAAA,CAC1D;IACD,YAAY,GAAY;QACvB,OAAO,qBAAqB,EAAE,YAAY,EAAE,IAAI,IAAI,CAAC;IAAA,CACrD;IACD,aAAa,GAAY;QACxB,OAAO,qBAAqB,EAAE,aAAa,EAAE,IAAI,IAAI,CAAC;IAAA,CACtD;IACD,gBAAgB,GAAY;QAC3B,OAAO,qBAAqB,EAAE,gBAAgB,EAAE,IAAI,IAAI,CAAC;IAAA,CACzD;IACD,oBAAoB,GAAY;QAC/B,OAAO,qBAAqB,EAAE,oBAAoB,EAAE,IAAI,KAAK,CAAC;IAAA,CAC9D;IACD,oBAAoB,CAAC,IAAa,EAAQ;QACzC,qBAAqB,EAAE,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAAA,CAClD;CACD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,eAAgC,EAAQ;IAC/E,qBAAqB,GAAG,eAAe,CAAC;IACvC,UAAsC,CAAC,uBAAuB,CAAC,GAAG,MAAM,CAAC;AAAA,CAC1E;AAED,4FAA4F;AAC5F,MAAM,UAAU,kBAAkB,GAAgC;IACjE,OAAQ,UAAsC,CAAC,uBAAuB,CAAgC,CAAC;AAAA,CACvG","sourcesContent":["import type { SettingsManager } from \"./settings-manager.ts\";\n\n/**\n * lunR TUI customize bridge.\n *\n * Exposed on `globalThis` under `Symbol.for(\"@lunr/customize\")` so the baked-in\n * ashxj-tui extension can read the customize settings (gutter rail, prompt\n * symbol, footer element toggles) without importing core code — same pattern as\n * the model-tiers and memory-cap bridges.\n */\n\nexport const CUSTOMIZE_BRIDGE_SYMBOL = Symbol.for(\"@lunr/customize\");\n\nexport interface CustomizeBridge {\n\tgetGutterRail(): boolean;\n\tgetPromptSymbol(): boolean;\n\tgetOpenAIFastMode(): boolean;\n\tgetFooterMcp(): boolean;\n\tgetFooterLsp(): boolean;\n\tgetFooterContext(): boolean;\n\tgetFooterTokens(): boolean;\n\tgetFooterCacheHitRate(): boolean;\n\tgetFooterTps(): boolean;\n\tgetFooterStatuses(): boolean;\n\tgetFooterGit(): boolean;\n\tgetFooterPlan(): boolean;\n\tgetFooterPlanBar(): boolean;\n\tgetHideThinkingBlock(): boolean;\n\tsetHideThinkingBlock(hide: boolean): void;\n}\n\nlet activeSettingsManager: SettingsManager | undefined;\n\nconst bridge: CustomizeBridge = {\n\tgetGutterRail(): boolean {\n\t\treturn activeSettingsManager?.getGutterRail() ?? true;\n\t},\n\tgetPromptSymbol(): boolean {\n\t\treturn activeSettingsManager?.getPromptSymbol() ?? true;\n\t},\n\tgetOpenAIFastMode(): boolean {\n\t\treturn activeSettingsManager?.getOpenAIFastMode() ?? false;\n\t},\n\tgetFooterMcp(): boolean {\n\t\treturn activeSettingsManager?.getFooterMcp() ?? true;\n\t},\n\tgetFooterLsp(): boolean {\n\t\treturn activeSettingsManager?.getFooterLsp() ?? false;\n\t},\n\tgetFooterContext(): boolean {\n\t\treturn activeSettingsManager?.getFooterContext() ?? true;\n\t},\n\tgetFooterTokens(): boolean {\n\t\treturn activeSettingsManager?.getFooterTokens() ?? true;\n\t},\n\tgetFooterCacheHitRate(): boolean {\n\t\treturn activeSettingsManager?.getFooterCacheHitRate() ?? true;\n\t},\n\tgetFooterTps(): boolean {\n\t\treturn activeSettingsManager?.getFooterTps() ?? true;\n\t},\n\tgetFooterStatuses(): boolean {\n\t\treturn activeSettingsManager?.getFooterStatuses() ?? true;\n\t},\n\tgetFooterGit(): boolean {\n\t\treturn activeSettingsManager?.getFooterGit() ?? true;\n\t},\n\tgetFooterPlan(): boolean {\n\t\treturn activeSettingsManager?.getFooterPlan() ?? true;\n\t},\n\tgetFooterPlanBar(): boolean {\n\t\treturn activeSettingsManager?.getFooterPlanBar() ?? true;\n\t},\n\tgetHideThinkingBlock(): boolean {\n\t\treturn activeSettingsManager?.getHideThinkingBlock() ?? false;\n\t},\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tactiveSettingsManager?.setHideThinkingBlock(hide);\n\t},\n};\n\n/**\n * Register (or re-point) the global customize bridge.\n * Safe to call multiple times — later calls only swap the settings source, so an\n * early startup registration (before extensions load) can be replaced by the live\n * runtime settings manager.\n */\nexport function registerCustomizeBridge(settingsManager: SettingsManager): void {\n\tactiveSettingsManager = settingsManager;\n\t(globalThis as Record<symbol, unknown>)[CUSTOMIZE_BRIDGE_SYMBOL] = bridge;\n}\n\n/** Read the bridge from `globalThis`, or undefined when not registered (extension-safe). */\nexport function getCustomizeBridge(): CustomizeBridge | undefined {\n\treturn (globalThis as Record<symbol, unknown>)[CUSTOMIZE_BRIDGE_SYMBOL] as CustomizeBridge | undefined;\n}\n"]}
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import type { SettingsManager } from "./settings-manager.ts";
|
|
2
|
-
/**
|
|
3
|
-
* simple-pi-memory character-cap bridge.
|
|
4
|
-
*
|
|
5
|
-
* The bridge is exposed on `globalThis` under `Symbol.for("@lunr/memory-cap")` so
|
|
6
|
-
* the baked-in simple-pi-memory extension can read/write the cap from lunR
|
|
7
|
-
* settings instead of its legacy `~/.pi/simple-memory/config.json` — without
|
|
8
|
-
* importing core code (same pattern as the model-tiers bridge).
|
|
9
|
-
*/
|
|
2
|
+
/** Memory settings shared with the baked-in simple-memory extension. */
|
|
10
3
|
export declare const MEMORY_CAP_BRIDGE_SYMBOL: unique symbol;
|
|
11
4
|
export declare const MEMORY_CHAR_CAP_DEFAULT = 5000;
|
|
12
5
|
export declare const MEMORY_CHAR_CAP_MIN = 1;
|
|
13
6
|
export declare const MEMORY_CHAR_CAP_MAX = 30000;
|
|
7
|
+
export declare const MEMORY_TOOL_NAMES: Set<string>;
|
|
14
8
|
export interface MemoryCapBridge {
|
|
9
|
+
isEnabled(): boolean;
|
|
15
10
|
/** Current character cap (clamped to 1..30000). */
|
|
16
11
|
getCharCap(): number;
|
|
17
12
|
/** Persist a new character cap into lunR settings. */
|
|
@@ -24,4 +19,5 @@ export interface MemoryCapBridge {
|
|
|
24
19
|
* runtime settings manager.
|
|
25
20
|
*/
|
|
26
21
|
export declare function registerMemoryCapBridge(settingsManager: SettingsManager): void;
|
|
22
|
+
export declare function getMemoryCapBridge(): MemoryCapBridge | undefined;
|
|
27
23
|
//# sourceMappingURL=memory-cap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-cap.d.ts","sourceRoot":"","sources":["../../src/core/memory-cap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D
|
|
1
|
+
{"version":3,"file":"memory-cap.d.ts","sourceRoot":"","sources":["../../src/core/memory-cap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,wEAAwE;AAExE,eAAO,MAAM,wBAAwB,eAAiC,CAAC;AAEvE,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AACzC,eAAO,MAAM,iBAAiB,aAA0D,CAAC;AAEzF,MAAM,WAAW,eAAe;IAC/B,SAAS,IAAI,OAAO,CAAC;IACrB,mDAAmD;IACnD,UAAU,IAAI,MAAM,CAAC;IACrB,sDAAsD;IACtD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAgBD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI,CAG9E;AAED,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,SAAS,CAEhE","sourcesContent":["import type { SettingsManager } from \"./settings-manager.ts\";\n\n/** Memory settings shared with the baked-in simple-memory extension. */\n\nexport const MEMORY_CAP_BRIDGE_SYMBOL = Symbol.for(\"@lunr/memory-cap\");\n\nexport const MEMORY_CHAR_CAP_DEFAULT = 5000;\nexport const MEMORY_CHAR_CAP_MIN = 1;\nexport const MEMORY_CHAR_CAP_MAX = 30000;\nexport const MEMORY_TOOL_NAMES = new Set([\"memory_add\", \"memory_remove\", \"memory_load\"]);\n\nexport interface MemoryCapBridge {\n\tisEnabled(): boolean;\n\t/** Current character cap (clamped to 1..30000). */\n\tgetCharCap(): number;\n\t/** Persist a new character cap into lunR settings. */\n\tsetCharCap(cap: number): void;\n}\n\nlet activeSettingsManager: SettingsManager | undefined;\n\nconst bridge: MemoryCapBridge = {\n\tisEnabled(): boolean {\n\t\treturn activeSettingsManager?.getMemoryEnabled() ?? true;\n\t},\n\tgetCharCap(): number {\n\t\treturn activeSettingsManager?.getMemoryCharCap() ?? MEMORY_CHAR_CAP_DEFAULT;\n\t},\n\tsetCharCap(cap: number): void {\n\t\tactiveSettingsManager?.setMemoryCharCap(cap);\n\t},\n};\n\n/**\n * Register (or re-point) the global memory-cap bridge.\n * Safe to call multiple times — later calls only swap the settings source, so an\n * early startup registration (before extensions load) can be replaced by the live\n * runtime settings manager.\n */\nexport function registerMemoryCapBridge(settingsManager: SettingsManager): void {\n\tactiveSettingsManager = settingsManager;\n\t(globalThis as Record<symbol, unknown>)[MEMORY_CAP_BRIDGE_SYMBOL] = bridge;\n}\n\nexport function getMemoryCapBridge(): MemoryCapBridge | undefined {\n\treturn (globalThis as Record<symbol, unknown>)[MEMORY_CAP_BRIDGE_SYMBOL] as MemoryCapBridge | undefined;\n}\n"]}
|
package/dist/core/memory-cap.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* simple-pi-memory character-cap bridge.
|
|
3
|
-
*
|
|
4
|
-
* The bridge is exposed on `globalThis` under `Symbol.for("@lunr/memory-cap")` so
|
|
5
|
-
* the baked-in simple-pi-memory extension can read/write the cap from lunR
|
|
6
|
-
* settings instead of its legacy `~/.pi/simple-memory/config.json` — without
|
|
7
|
-
* importing core code (same pattern as the model-tiers bridge).
|
|
8
|
-
*/
|
|
1
|
+
/** Memory settings shared with the baked-in simple-memory extension. */
|
|
9
2
|
export const MEMORY_CAP_BRIDGE_SYMBOL = Symbol.for("@lunr/memory-cap");
|
|
10
3
|
export const MEMORY_CHAR_CAP_DEFAULT = 5000;
|
|
11
4
|
export const MEMORY_CHAR_CAP_MIN = 1;
|
|
12
5
|
export const MEMORY_CHAR_CAP_MAX = 30000;
|
|
6
|
+
export const MEMORY_TOOL_NAMES = new Set(["memory_add", "memory_remove", "memory_load"]);
|
|
13
7
|
let activeSettingsManager;
|
|
14
8
|
const bridge = {
|
|
9
|
+
isEnabled() {
|
|
10
|
+
return activeSettingsManager?.getMemoryEnabled() ?? true;
|
|
11
|
+
},
|
|
15
12
|
getCharCap() {
|
|
16
13
|
return activeSettingsManager?.getMemoryCharCap() ?? MEMORY_CHAR_CAP_DEFAULT;
|
|
17
14
|
},
|
|
@@ -29,4 +26,7 @@ export function registerMemoryCapBridge(settingsManager) {
|
|
|
29
26
|
activeSettingsManager = settingsManager;
|
|
30
27
|
globalThis[MEMORY_CAP_BRIDGE_SYMBOL] = bridge;
|
|
31
28
|
}
|
|
29
|
+
export function getMemoryCapBridge() {
|
|
30
|
+
return globalThis[MEMORY_CAP_BRIDGE_SYMBOL];
|
|
31
|
+
}
|
|
32
32
|
//# sourceMappingURL=memory-cap.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-cap.js","sourceRoot":"","sources":["../../src/core/memory-cap.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"memory-cap.js","sourceRoot":"","sources":["../../src/core/memory-cap.ts"],"names":[],"mappings":"AAEA,wEAAwE;AAExE,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAEvE,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAC5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AACrC,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,CAAC;AACzC,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC,CAAC;AAUzF,IAAI,qBAAkD,CAAC;AAEvD,MAAM,MAAM,GAAoB;IAC/B,SAAS,GAAY;QACpB,OAAO,qBAAqB,EAAE,gBAAgB,EAAE,IAAI,IAAI,CAAC;IAAA,CACzD;IACD,UAAU,GAAW;QACpB,OAAO,qBAAqB,EAAE,gBAAgB,EAAE,IAAI,uBAAuB,CAAC;IAAA,CAC5E;IACD,UAAU,CAAC,GAAW,EAAQ;QAC7B,qBAAqB,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAAA,CAC7C;CACD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,eAAgC,EAAQ;IAC/E,qBAAqB,GAAG,eAAe,CAAC;IACvC,UAAsC,CAAC,wBAAwB,CAAC,GAAG,MAAM,CAAC;AAAA,CAC3E;AAED,MAAM,UAAU,kBAAkB,GAAgC;IACjE,OAAQ,UAAsC,CAAC,wBAAwB,CAAgC,CAAC;AAAA,CACxG","sourcesContent":["import type { SettingsManager } from \"./settings-manager.ts\";\n\n/** Memory settings shared with the baked-in simple-memory extension. */\n\nexport const MEMORY_CAP_BRIDGE_SYMBOL = Symbol.for(\"@lunr/memory-cap\");\n\nexport const MEMORY_CHAR_CAP_DEFAULT = 5000;\nexport const MEMORY_CHAR_CAP_MIN = 1;\nexport const MEMORY_CHAR_CAP_MAX = 30000;\nexport const MEMORY_TOOL_NAMES = new Set([\"memory_add\", \"memory_remove\", \"memory_load\"]);\n\nexport interface MemoryCapBridge {\n\tisEnabled(): boolean;\n\t/** Current character cap (clamped to 1..30000). */\n\tgetCharCap(): number;\n\t/** Persist a new character cap into lunR settings. */\n\tsetCharCap(cap: number): void;\n}\n\nlet activeSettingsManager: SettingsManager | undefined;\n\nconst bridge: MemoryCapBridge = {\n\tisEnabled(): boolean {\n\t\treturn activeSettingsManager?.getMemoryEnabled() ?? true;\n\t},\n\tgetCharCap(): number {\n\t\treturn activeSettingsManager?.getMemoryCharCap() ?? MEMORY_CHAR_CAP_DEFAULT;\n\t},\n\tsetCharCap(cap: number): void {\n\t\tactiveSettingsManager?.setMemoryCharCap(cap);\n\t},\n};\n\n/**\n * Register (or re-point) the global memory-cap bridge.\n * Safe to call multiple times — later calls only swap the settings source, so an\n * early startup registration (before extensions load) can be replaced by the live\n * runtime settings manager.\n */\nexport function registerMemoryCapBridge(settingsManager: SettingsManager): void {\n\tactiveSettingsManager = settingsManager;\n\t(globalThis as Record<symbol, unknown>)[MEMORY_CAP_BRIDGE_SYMBOL] = bridge;\n}\n\nexport function getMemoryCapBridge(): MemoryCapBridge | undefined {\n\treturn (globalThis as Record<symbol, unknown>)[MEMORY_CAP_BRIDGE_SYMBOL] as MemoryCapBridge | undefined;\n}\n"]}
|
|
@@ -80,6 +80,8 @@ export declare function clearSessionApprovals(sessionId?: string): void;
|
|
|
80
80
|
export declare function resetAllPermissionContexts(): void;
|
|
81
81
|
/** Appended to the system prompt while auto mode is active. */
|
|
82
82
|
export declare const AUTO_MODE_ADDENDUM = "You are running fully autonomously. Do not ask the user questions or wait for confirmation; make reasonable decisions and complete the task end-to-end.";
|
|
83
|
+
export declare const GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON = "The global AGENTS.md file is user-managed. The agent cannot change ~/.lunr/agent/AGENTS.md.";
|
|
84
|
+
export declare const MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON = "Memory is model-managed through the memory tools. Do not directly change ~/.lunr/simple-memory/memory.md.";
|
|
83
85
|
export interface GateOptions {
|
|
84
86
|
/** True when the current turn started from an explicit /swarm prompt — the
|
|
85
87
|
* user already asked for the swarm, so the swarm approval gate is skipped. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/core/permissions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: permission modes (Manual / YOLO / Plan / Auto).\n *\n * Per-session only — never persisted per session, but the default mode new\n * sessions start with is configurable in /settings (`defaultPermissionMode`,\n * default \"manual\").\n *\n * - manual: every mutating tool call (bash, edit, write) requires approval.\n * - yolo: auto-approve tools; the agent may still ask questions.\n * - plan: read-only; mutating tools are hard-blocked (planModeBlockReason).\n * - auto: fully autonomous; a system-prompt addendum steers the model to\n * self-decide, and rollback is force-enabled for the session.\n *\n * The gate is wired into `agent-session.ts` `_installAgentToolHooks` before\n * the synchronous `_toolCallGates` loop. beforeToolCall is already async, so\n * awaiting the approval dialog is fine.\n *\n * State (mode + session-scoped approvals) is keyed by session id so\n * concurrent gateway chats do not share permission decisions.\n */\n\nimport { resolve } from \"node:path\";\nimport { isCodeRewriteMutating, planModeBlockReason } from \"./plan-mode.ts\";\nimport { effectiveSwarmCountForTurn, SWARM_APPROVAL_THRESHOLD } from \"./swarm.ts\";\n\n/** Shift+Tab cycle order. */\nexport const PERMISSION_MODES = [\"manual\", \"yolo\", \"plan\", \"auto\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\nexport function nextPermissionMode(current: PermissionMode): PermissionMode {\n\tconst i = PERMISSION_MODES.indexOf(current);\n\treturn PERMISSION_MODES[(i + 1) % PERMISSION_MODES.length];\n}\n\n/** True when the (default or session) permission mode is plan. */\nexport function isPlanModeActive(sessionId?: string): boolean {\n\treturn getPermissionMode(sessionId) === \"plan\";\n}\n\n/**\n * Destination mode when leaving plan via approve / `/plan off` / `/plan <text>`.\n * Explicit Shift+Tab or `/mode` picks do not use this — they already chose a mode.\n */\nexport function restorePermissionModeAfterPlan(\n\tprevious: PermissionMode | undefined,\n\tdefaultMode: PermissionMode,\n): PermissionMode {\n\tif (previous && previous !== \"plan\") return previous;\n\treturn defaultMode === \"plan\" ? \"yolo\" : defaultMode;\n}\n\nexport interface ApprovalRequest {\n\ttoolName: string;\n\t/** \"bash\" (detail = command), \"edit\"/\"write\" (detail = path),\n\t * \"edit-outside\"/\"write-outside\" when path escapes cwd,\n\t * \"swarm\" for auto-activated agent swarms (detail = summary lines). */\n\taction: string;\n\tdetail: string;\n\t/** \"swarm\" when this is the agent-swarm approval prompt, \"plan\" for the\n\t * present_plan plan-approval prompt — the UI renders a dedicated dialog\n\t * for each instead of the generic tool dialog. */\n\tkind?: \"swarm\" | \"plan\";\n}\n\nexport type ApprovalDecision = \"once\" | \"session\" | \"reject\";\n/** Handlers may return a bare decision string, a reject carrying user feedback\n * that becomes the block reason the model sees, or an approve carrying optional\n * feedback (plan approval with comments). */\nexport type ApprovalResponse =\n\t| ApprovalDecision\n\t| { decision: \"reject\"; feedback: string }\n\t| { decision: \"approve\"; feedback?: string };\n\n/** Read-only tools that never need approval. */\nconst READ_ONLY_TOOLS = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"web_search\",\n\t\"web_fetch\",\n\t\"fetch_content\",\n\t\"get_search_content\",\n\t\"sourcegraph\",\n\t\"repo_map\",\n\t\"symbol_outline\",\n\t\"read_block\",\n\t\"code_overview\",\n\t\"ast_search\",\n\t\"lsp_diagnostics\",\n\t\"lsp_hover\",\n\t\"lsp_definition\",\n\t\"lsp_references\",\n\t\"lsp_symbols\",\n\t\"lsp_code_actions\",\n\t\"lsp_completions\",\n\t\"behavior_load\",\n\t\"memory_load\",\n]);\n\n/** Tools that mutate state and must be gated in manual mode. */\nconst MUTATING_TOOLS = new Set([\n\t\"bash\",\n\t\"edit\",\n\t\"write\",\n\t\"behavior_add\",\n\t\"behavior_remove\",\n\t\"memory_add\",\n\t\"memory_remove\",\n\t\"cron\",\n]);\n\nconst REJECT_REASON = \"Rejected by user (permission mode: manual).\";\nexport const NO_HANDLER_REASON = \"Mutating tool blocked in manual mode: no approval channel available.\";\nexport const SWARM_REJECT_REASON = \"Agent swarm rejected by user.\";\nexport const NO_SWARM_HANDLER_REASON = \"Agent swarm blocked: no approval channel available.\";\n/** Session-approval key for auto-activated agent swarms. */\nconst SWARM_ACTION = \"swarm\";\n\ninterface PermissionContext {\n\tmode: PermissionMode;\n\tapprovals: Set<string>;\n}\n\nconst contexts = new Map<string, PermissionContext>();\nlet defaultContext: PermissionContext = { mode: \"manual\", approvals: new Set() };\nlet approvalHandler: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined;\n/** One swarm prompt covers every sibling SINGLE `subagent` on the same assistant message. */\nconst turnSwarmDecisions = new WeakMap<object, { decision: ApprovalDecision; feedback?: string }>();\n\nfunction getContext(sessionId?: string): PermissionContext {\n\tif (!sessionId) return defaultContext;\n\treturn contexts.get(sessionId) ?? defaultContext;\n}\n\nexport function createPermissionContext(sessionId: string, mode: PermissionMode = defaultContext.mode): void {\n\tcontexts.set(sessionId, { mode, approvals: new Set() });\n}\n\nexport function deletePermissionContext(sessionId: string): void {\n\tcontexts.delete(sessionId);\n}\n\nexport function getPermissionMode(sessionId?: string): PermissionMode {\n\treturn getContext(sessionId).mode;\n}\n\nexport function setPermissionMode(mode: PermissionMode, sessionId?: string): void {\n\tgetContext(sessionId).mode = mode;\n}\n\nexport function resetPermissions(defaultMode: PermissionMode = \"manual\", sessionId?: string): void {\n\tif (sessionId) {\n\t\tcontexts.set(sessionId, { mode: defaultMode, approvals: new Set() });\n\t} else {\n\t\tdefaultContext = { mode: defaultMode, approvals: new Set() };\n\t}\n}\n\nexport function registerApprovalHandler(fn: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined): void {\n\tapprovalHandler = fn;\n}\n\n/** Model-facing result texts for the present_plan tool. */\nexport const PLAN_APPROVED_TEXT = \"Plan approved — implement it now.\";\nexport const PLAN_DECLINED_TEXT = \"Plan declined — revise the plan and call present_plan again.\";\nexport const PLAN_PASS_THROUGH_TEXT = \"Plan presented. The user will review and reply.\";\n\n/** lunr: map an approval response to the model-facing present_plan result text. */\nexport function planApprovalResultText(resp: ApprovalResponse): string {\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (decision === \"reject\") {\n\t\treturn feedback\n\t\t\t? `Plan declined: ${feedback} — revise the plan and call present_plan again.`\n\t\t\t: PLAN_DECLINED_TEXT;\n\t}\n\treturn feedback ? `Plan approved with feedback: ${feedback} — implement it now.` : PLAN_APPROVED_TEXT;\n}\n\n/**\n * lunr: present_plan approval request. Unlike the mutating-tool and swarm gates\n * this fails OPEN when no approval handler is registered (or none is reachable,\n * e.g. a gateway turn without a chat context): headless sessions have no one to\n * show the dialog to, so the plan is presented and the user replies in chat\n * instead of deadlocking the turn. Everything else keeps fail-closed.\n */\nexport async function requestPlanApproval(summary: string): Promise<string> {\n\tif (!approvalHandler) return PLAN_PASS_THROUGH_TEXT;\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName: \"present_plan\", action: \"plan\", detail: summary, kind: \"plan\" });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"approval request failed\";\n\t\tif (message === NO_HANDLER_REASON) return PLAN_PASS_THROUGH_TEXT;\n\t\treturn `Plan declined: ${message} — revise the plan and call present_plan again.`;\n\t}\n\treturn planApprovalResultText(resp);\n}\n\nexport function clearSessionApprovals(sessionId?: string): void {\n\tgetContext(sessionId).approvals.clear();\n}\n\n/** Test seam: remove every per-session context and restore the default. */\nexport function resetAllPermissionContexts(): void {\n\tcontexts.clear();\n\tdefaultContext = { mode: \"manual\", approvals: new Set() };\n\tapprovalHandler = undefined;\n}\n\n/** Appended to the system prompt while auto mode is active. */\nexport const AUTO_MODE_ADDENDUM =\n\t\"You are running fully autonomously. Do not ask the user questions or wait for confirmation; make reasonable decisions and complete the task end-to-end.\";\n\nfunction isPathOutsideCwd(absPath: string, cwd: string): boolean {\n\tconst norm = (p: string) => p.replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n\tconst np = norm(absPath);\n\tconst nc = norm(cwd);\n\tif (np === nc) return false;\n\treturn !np.startsWith(`${nc}/`);\n}\n\nfunction resolvePath(cwd: string, p: unknown): string {\n\tif (typeof p !== \"string\") return \"\";\n\tif (!p) return \"\";\n\treturn resolve(cwd, p);\n}\n\nfunction isMutatingTool(toolName: string): boolean {\n\treturn MUTATING_TOOLS.has(toolName);\n}\n\nfunction requiresManualApproval(toolName: string, input: Record<string, unknown>): boolean {\n\tif (isMutatingTool(toolName)) return true;\n\treturn toolName === \"code_rewrite\" && isCodeRewriteMutating(input);\n}\n\nexport interface GateOptions {\n\t/** True when the current turn started from an explicit /swarm prompt — the\n\t * user already asked for the swarm, so the swarm approval gate is skipped. */\n\texplicitSwarmTurn?: boolean;\n\t/** Assistant message that issued this tool call. Same-turn sibling SINGLE\n\t * `subagent` calls count toward the swarm threshold. */\n\tassistantMessage?: {\n\t\tcontent?: ReadonlyArray<{ type?: string; name?: string; arguments?: unknown }>;\n\t};\n}\n\nfunction firstTaskText(value: unknown): string {\n\tif (typeof value === \"string\" && value.trim()) return sanitizeDetail(value);\n\treturn \"\";\n}\n\n/** First task text of a parallel fan-out or sibling SINGLE, used as the dialog summary. */\nfunction swarmTaskSummary(input: Record<string, unknown>, assistantMessage?: GateOptions[\"assistantMessage\"]): string {\n\tif (Array.isArray(input.tasks)) {\n\t\tfor (const task of input.tasks) {\n\t\t\tconst text = task && typeof task === \"object\" ? firstTaskText((task as { task?: unknown }).task) : \"\";\n\t\t\tif (text) return text;\n\t\t}\n\t}\n\tconst own = firstTaskText(input.task);\n\tif (own) return own;\n\tfor (const block of assistantMessage?.content ?? []) {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") continue;\n\t\tconst args =\n\t\t\tblock.arguments && typeof block.arguments === \"object\" && !Array.isArray(block.arguments)\n\t\t\t\t? (block.arguments as Record<string, unknown>)\n\t\t\t\t: undefined;\n\t\tconst text = firstTaskText(args?.task);\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\n/**\n * Agent-swarm gate. An auto-activated swarm (more than SWARM_APPROVAL_THRESHOLD\n * parallel subagents in one `tasks`/`chain.parallel` call, or that many same-turn\n * SINGLE `subagent` calls) requires user approval in manual AND yolo modes; auto\n * mode runs it unconditionally and explicit /swarm turns are pre-approved.\n * Fail-closed without an approval handler, same as the mutating-tool gate.\n */\nasync function gateSwarmCall(\n\tinput: Record<string, unknown>,\n\tctx: PermissionContext,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tif (ctx.mode === \"auto\") return undefined;\n\tif (options?.explicitSwarmTurn) return undefined;\n\tconst count = effectiveSwarmCountForTurn(input, options?.assistantMessage);\n\tif (count <= SWARM_APPROVAL_THRESHOLD) return undefined;\n\tif (ctx.approvals.has(SWARM_ACTION)) return undefined;\n\n\tconst turnKey = options?.assistantMessage;\n\tconst cached = turnKey ? turnSwarmDecisions.get(turnKey) : undefined;\n\tif (cached) {\n\t\tif (cached.decision === \"reject\") {\n\t\t\treturn { block: true, reason: cached.feedback ? `${SWARM_REJECT_REASON} ${cached.feedback}` : SWARM_REJECT_REASON };\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_SWARM_HANDLER_REASON };\n\t}\n\n\tconst summary = swarmTaskSummary(input, options?.assistantMessage);\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({\n\t\t\ttoolName: \"subagent\",\n\t\t\taction: SWARM_ACTION,\n\t\t\tdetail: summary ? `agent swarm (${count} subagents)\\n${summary}` : `agent swarm (${count} subagents)`,\n\t\t\tkind: \"swarm\",\n\t\t});\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_SWARM_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst rawDecision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst decision: ApprovalDecision = rawDecision === \"approve\" ? \"once\" : rawDecision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (turnKey) turnSwarmDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}) });\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(SWARM_ACTION);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\treturn { block: true, reason: feedback ? `${SWARM_REJECT_REASON} ${feedback}` : SWARM_REJECT_REASON };\n\t}\n\treturn undefined;\n}\n\nfunction sanitizeDetail(value: unknown): string {\n\tconst text = String(value ?? \"\").trim();\n\treturn text.length > 200 ? `${text.slice(0, 200)}…` : text;\n}\n\n/**\n * Returns a block result when the tool call should be blocked, else undefined.\n * Read-only tools always pass. YOLO and Auto always pass mutating tools. Plan\n * mode hard-blocks mutating tools (no prompt). Manual mode gates every mutating\n * tool; if an approval handler is registered it is asked, otherwise the call is\n * blocked (fail-closed — no silent allow).\n */\nexport async function gateToolCall(\n\ttoolName: string,\n\tinput: Record<string, unknown>,\n\tcwd: string,\n\tsessionId?: string,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tconst ctx = getContext(sessionId);\n\tif (READ_ONLY_TOOLS.has(toolName)) return undefined;\n\n\t// lunr: agent-swarm gate runs before the mode early-return — it applies in\n\t// yolo mode too (only auto mode bypasses it).\n\tif (toolName === \"subagent\") {\n\t\tconst swarmBlock = await gateSwarmCall(input, ctx, options);\n\t\tif (swarmBlock) return swarmBlock;\n\t}\n\n\tif (ctx.mode === \"plan\") {\n\t\tconst reason = planModeBlockReason(toolName, input);\n\t\treturn reason ? { block: true, reason } : undefined;\n\t}\n\n\tif (ctx.mode !== \"manual\") return undefined;\n\tif (!requiresManualApproval(toolName, input)) return undefined;\n\n\tlet action: string;\n\tlet detail: string;\n\n\tif (toolName === \"bash\") {\n\t\taction = \"bash\";\n\t\tdetail = sanitizeDetail(input.command);\n\t} else if (toolName === \"edit\" || toolName === \"write\") {\n\t\tconst path = resolvePath(cwd, input.path);\n\t\tconst outside = path ? isPathOutsideCwd(path, cwd) : false;\n\t\taction = outside ? `${toolName}-outside` : toolName;\n\t\tdetail = path || String(input.path ?? \"\");\n\t} else if (toolName === \"behavior_add\" || toolName === \"memory_add\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.content);\n\t} else if (toolName === \"behavior_remove\" || toolName === \"memory_remove\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.line);\n\t} else if (toolName === \"cron\") {\n\t\taction = \"cron\";\n\t\tdetail = sanitizeDetail(input.action);\n\t} else if (toolName === \"code_rewrite\") {\n\t\taction = \"code_rewrite\";\n\t\tdetail = sanitizeDetail(input.path ?? input.pattern);\n\t} else {\n\t\t// All other mutating tools fall back to the tool name with no extra detail.\n\t\taction = toolName;\n\t\tdetail = \"\";\n\t}\n\n\tif (ctx.approvals.has(action)) return undefined;\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_HANDLER_REASON };\n\t}\n\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName, action, detail });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(action);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\t\treturn { block: true, reason: feedback ? `${REJECT_REASON} ${feedback}` : REJECT_REASON };\n\t}\n\t// \"once\" — allow this call only\n\treturn undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/core/permissions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: permission modes (Manual / YOLO / Plan / Auto).\n *\n * Per-session only — never persisted per session, but the default mode new\n * sessions start with is configurable in /settings (`defaultPermissionMode`,\n * default \"manual\").\n *\n * - manual: every mutating tool call (bash, edit, write) requires approval.\n * - yolo: auto-approve tools; the agent may still ask questions.\n * - plan: read-only; mutating tools are hard-blocked (planModeBlockReason).\n * - auto: fully autonomous; a system-prompt addendum steers the model to\n * self-decide, and rollback is force-enabled for the session.\n *\n * The gate is wired into `agent-session.ts` `_installAgentToolHooks` before\n * the synchronous `_toolCallGates` loop. beforeToolCall is already async, so\n * awaiting the approval dialog is fine.\n *\n * State (mode + session-scoped approvals) is keyed by session id so\n * concurrent gateway chats do not share permission decisions.\n */\n\nimport { dirname, join, resolve } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { isCodeRewriteMutating, planModeBlockReason } from \"./plan-mode.ts\";\nimport { effectiveSwarmCountForTurn, SWARM_APPROVAL_THRESHOLD } from \"./swarm.ts\";\n\n/** Shift+Tab cycle order. */\nexport const PERMISSION_MODES = [\"manual\", \"yolo\", \"plan\", \"auto\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\nexport function nextPermissionMode(current: PermissionMode): PermissionMode {\n\tconst i = PERMISSION_MODES.indexOf(current);\n\treturn PERMISSION_MODES[(i + 1) % PERMISSION_MODES.length];\n}\n\n/** True when the (default or session) permission mode is plan. */\nexport function isPlanModeActive(sessionId?: string): boolean {\n\treturn getPermissionMode(sessionId) === \"plan\";\n}\n\n/**\n * Destination mode when leaving plan via approve / `/plan off` / `/plan <text>`.\n * Explicit Shift+Tab or `/mode` picks do not use this — they already chose a mode.\n */\nexport function restorePermissionModeAfterPlan(\n\tprevious: PermissionMode | undefined,\n\tdefaultMode: PermissionMode,\n): PermissionMode {\n\tif (previous && previous !== \"plan\") return previous;\n\treturn defaultMode === \"plan\" ? \"yolo\" : defaultMode;\n}\n\nexport interface ApprovalRequest {\n\ttoolName: string;\n\t/** \"bash\" (detail = command), \"edit\"/\"write\" (detail = path),\n\t * \"edit-outside\"/\"write-outside\" when path escapes cwd,\n\t * \"swarm\" for auto-activated agent swarms (detail = summary lines). */\n\taction: string;\n\tdetail: string;\n\t/** \"swarm\" when this is the agent-swarm approval prompt, \"plan\" for the\n\t * present_plan plan-approval prompt — the UI renders a dedicated dialog\n\t * for each instead of the generic tool dialog. */\n\tkind?: \"swarm\" | \"plan\";\n}\n\nexport type ApprovalDecision = \"once\" | \"session\" | \"reject\";\n/** Handlers may return a bare decision string, a reject carrying user feedback\n * that becomes the block reason the model sees, or an approve carrying optional\n * feedback (plan approval with comments). */\nexport type ApprovalResponse =\n\t| ApprovalDecision\n\t| { decision: \"reject\"; feedback: string }\n\t| { decision: \"approve\"; feedback?: string };\n\n/** Read-only tools that never need approval. */\nconst READ_ONLY_TOOLS = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"web_search\",\n\t\"web_fetch\",\n\t\"fetch_content\",\n\t\"get_search_content\",\n\t\"sourcegraph\",\n\t\"repo_map\",\n\t\"symbol_outline\",\n\t\"read_block\",\n\t\"code_overview\",\n\t\"ast_search\",\n\t\"lsp_diagnostics\",\n\t\"lsp_hover\",\n\t\"lsp_definition\",\n\t\"lsp_references\",\n\t\"lsp_symbols\",\n\t\"lsp_code_actions\",\n\t\"lsp_completions\",\n\t\"memory_load\",\n]);\n\n/** Tools that mutate state and must be gated in manual mode. */\nconst MUTATING_TOOLS = new Set([\"bash\", \"edit\", \"write\", \"memory_add\", \"memory_remove\", \"cron\"]);\n\nconst REJECT_REASON = \"Rejected by user (permission mode: manual).\";\nexport const NO_HANDLER_REASON = \"Mutating tool blocked in manual mode: no approval channel available.\";\nexport const SWARM_REJECT_REASON = \"Agent swarm rejected by user.\";\nexport const NO_SWARM_HANDLER_REASON = \"Agent swarm blocked: no approval channel available.\";\n/** Session-approval key for auto-activated agent swarms. */\nconst SWARM_ACTION = \"swarm\";\n\ninterface PermissionContext {\n\tmode: PermissionMode;\n\tapprovals: Set<string>;\n}\n\nconst contexts = new Map<string, PermissionContext>();\nlet defaultContext: PermissionContext = { mode: \"manual\", approvals: new Set() };\nlet approvalHandler: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined;\n/** One swarm prompt covers every sibling SINGLE `subagent` on the same assistant message. */\nconst turnSwarmDecisions = new WeakMap<object, { decision: ApprovalDecision; feedback?: string }>();\n\nfunction getContext(sessionId?: string): PermissionContext {\n\tif (!sessionId) return defaultContext;\n\treturn contexts.get(sessionId) ?? defaultContext;\n}\n\nexport function createPermissionContext(sessionId: string, mode: PermissionMode = defaultContext.mode): void {\n\tcontexts.set(sessionId, { mode, approvals: new Set() });\n}\n\nexport function deletePermissionContext(sessionId: string): void {\n\tcontexts.delete(sessionId);\n}\n\nexport function getPermissionMode(sessionId?: string): PermissionMode {\n\treturn getContext(sessionId).mode;\n}\n\nexport function setPermissionMode(mode: PermissionMode, sessionId?: string): void {\n\tgetContext(sessionId).mode = mode;\n}\n\nexport function resetPermissions(defaultMode: PermissionMode = \"manual\", sessionId?: string): void {\n\tif (sessionId) {\n\t\tcontexts.set(sessionId, { mode: defaultMode, approvals: new Set() });\n\t} else {\n\t\tdefaultContext = { mode: defaultMode, approvals: new Set() };\n\t}\n}\n\nexport function registerApprovalHandler(fn: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined): void {\n\tapprovalHandler = fn;\n}\n\n/** Model-facing result texts for the present_plan tool. */\nexport const PLAN_APPROVED_TEXT = \"Plan approved — implement it now.\";\nexport const PLAN_DECLINED_TEXT = \"Plan declined — revise the plan and call present_plan again.\";\nexport const PLAN_PASS_THROUGH_TEXT = \"Plan presented. The user will review and reply.\";\n\n/** lunr: map an approval response to the model-facing present_plan result text. */\nexport function planApprovalResultText(resp: ApprovalResponse): string {\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (decision === \"reject\") {\n\t\treturn feedback\n\t\t\t? `Plan declined: ${feedback} — revise the plan and call present_plan again.`\n\t\t\t: PLAN_DECLINED_TEXT;\n\t}\n\treturn feedback ? `Plan approved with feedback: ${feedback} — implement it now.` : PLAN_APPROVED_TEXT;\n}\n\n/**\n * lunr: present_plan approval request. Unlike the mutating-tool and swarm gates\n * this fails OPEN when no approval handler is registered (or none is reachable,\n * e.g. a gateway turn without a chat context): headless sessions have no one to\n * show the dialog to, so the plan is presented and the user replies in chat\n * instead of deadlocking the turn. Everything else keeps fail-closed.\n */\nexport async function requestPlanApproval(summary: string): Promise<string> {\n\tif (!approvalHandler) return PLAN_PASS_THROUGH_TEXT;\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName: \"present_plan\", action: \"plan\", detail: summary, kind: \"plan\" });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"approval request failed\";\n\t\tif (message === NO_HANDLER_REASON) return PLAN_PASS_THROUGH_TEXT;\n\t\treturn `Plan declined: ${message} — revise the plan and call present_plan again.`;\n\t}\n\treturn planApprovalResultText(resp);\n}\n\nexport function clearSessionApprovals(sessionId?: string): void {\n\tgetContext(sessionId).approvals.clear();\n}\n\n/** Test seam: remove every per-session context and restore the default. */\nexport function resetAllPermissionContexts(): void {\n\tcontexts.clear();\n\tdefaultContext = { mode: \"manual\", approvals: new Set() };\n\tapprovalHandler = undefined;\n}\n\n/** Appended to the system prompt while auto mode is active. */\nexport const AUTO_MODE_ADDENDUM =\n\t\"You are running fully autonomously. Do not ask the user questions or wait for confirmation; make reasonable decisions and complete the task end-to-end.\";\n\nfunction isPathOutsideCwd(absPath: string, cwd: string): boolean {\n\tconst norm = (p: string) => p.replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n\tconst np = norm(absPath);\n\tconst nc = norm(cwd);\n\tif (np === nc) return false;\n\treturn !np.startsWith(`${nc}/`);\n}\n\nfunction resolvePath(cwd: string, p: unknown): string {\n\tif (typeof p !== \"string\") return \"\";\n\tif (!p) return \"\";\n\treturn resolve(cwd, p);\n}\n\nexport const GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON =\n\t\"The global AGENTS.md file is user-managed. The agent cannot change ~/.lunr/agent/AGENTS.md.\";\nexport const MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON =\n\t\"Memory is model-managed through the memory tools. Do not directly change ~/.lunr/simple-memory/memory.md.\";\n\nfunction protectedFileWriteReason(toolName: string, input: Record<string, unknown>, cwd: string): string | undefined {\n\tif (toolName !== \"edit\" && toolName !== \"write\" && toolName !== \"code_rewrite\") return undefined;\n\tconst target = resolvePath(cwd, input.path);\n\tif (!target) return undefined;\n\tconst normalize = (path: string) => {\n\t\tconst normalized = resolve(path).replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n\t\treturn process.platform === \"win32\" ? normalized.toLowerCase() : normalized;\n\t};\n\tconst agentsPaths = [join(getAgentDir(), \"AGENTS.md\"), join(getAgentDir(), \"AGENTS.MD\")];\n\tif (agentsPaths.some((candidate) => normalize(target) === normalize(candidate))) {\n\t\treturn GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON;\n\t}\n\tif (normalize(target) === normalize(join(dirname(getAgentDir()), \"simple-memory\", \"memory.md\"))) {\n\t\treturn MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON;\n\t}\n\treturn undefined;\n}\n\nfunction isMutatingTool(toolName: string): boolean {\n\treturn MUTATING_TOOLS.has(toolName);\n}\n\nfunction requiresManualApproval(toolName: string, input: Record<string, unknown>): boolean {\n\tif (isMutatingTool(toolName)) return true;\n\treturn toolName === \"code_rewrite\" && isCodeRewriteMutating(input);\n}\n\nexport interface GateOptions {\n\t/** True when the current turn started from an explicit /swarm prompt — the\n\t * user already asked for the swarm, so the swarm approval gate is skipped. */\n\texplicitSwarmTurn?: boolean;\n\t/** Assistant message that issued this tool call. Same-turn sibling SINGLE\n\t * `subagent` calls count toward the swarm threshold. */\n\tassistantMessage?: {\n\t\tcontent?: ReadonlyArray<{ type?: string; name?: string; arguments?: unknown }>;\n\t};\n}\n\nfunction firstTaskText(value: unknown): string {\n\tif (typeof value === \"string\" && value.trim()) return sanitizeDetail(value);\n\treturn \"\";\n}\n\n/** First task text of a parallel fan-out or sibling SINGLE, used as the dialog summary. */\nfunction swarmTaskSummary(input: Record<string, unknown>, assistantMessage?: GateOptions[\"assistantMessage\"]): string {\n\tif (Array.isArray(input.tasks)) {\n\t\tfor (const task of input.tasks) {\n\t\t\tconst text = task && typeof task === \"object\" ? firstTaskText((task as { task?: unknown }).task) : \"\";\n\t\t\tif (text) return text;\n\t\t}\n\t}\n\tconst own = firstTaskText(input.task);\n\tif (own) return own;\n\tfor (const block of assistantMessage?.content ?? []) {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") continue;\n\t\tconst args =\n\t\t\tblock.arguments && typeof block.arguments === \"object\" && !Array.isArray(block.arguments)\n\t\t\t\t? (block.arguments as Record<string, unknown>)\n\t\t\t\t: undefined;\n\t\tconst text = firstTaskText(args?.task);\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\n/**\n * Agent-swarm gate. An auto-activated swarm (more than SWARM_APPROVAL_THRESHOLD\n * parallel subagents in one `tasks`/`chain.parallel` call, or that many same-turn\n * SINGLE `subagent` calls) requires user approval in manual AND yolo modes; auto\n * mode runs it unconditionally and explicit /swarm turns are pre-approved.\n * Fail-closed without an approval handler, same as the mutating-tool gate.\n */\nasync function gateSwarmCall(\n\tinput: Record<string, unknown>,\n\tctx: PermissionContext,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tif (ctx.mode === \"auto\") return undefined;\n\tif (options?.explicitSwarmTurn) return undefined;\n\tconst count = effectiveSwarmCountForTurn(input, options?.assistantMessage);\n\tif (count <= SWARM_APPROVAL_THRESHOLD) return undefined;\n\tif (ctx.approvals.has(SWARM_ACTION)) return undefined;\n\n\tconst turnKey = options?.assistantMessage;\n\tconst cached = turnKey ? turnSwarmDecisions.get(turnKey) : undefined;\n\tif (cached) {\n\t\tif (cached.decision === \"reject\") {\n\t\t\treturn { block: true, reason: cached.feedback ? `${SWARM_REJECT_REASON} ${cached.feedback}` : SWARM_REJECT_REASON };\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_SWARM_HANDLER_REASON };\n\t}\n\n\tconst summary = swarmTaskSummary(input, options?.assistantMessage);\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({\n\t\t\ttoolName: \"subagent\",\n\t\t\taction: SWARM_ACTION,\n\t\t\tdetail: summary ? `agent swarm (${count} subagents)\\n${summary}` : `agent swarm (${count} subagents)`,\n\t\t\tkind: \"swarm\",\n\t\t});\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_SWARM_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst rawDecision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst decision: ApprovalDecision = rawDecision === \"approve\" ? \"once\" : rawDecision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (turnKey) turnSwarmDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}) });\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(SWARM_ACTION);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\treturn { block: true, reason: feedback ? `${SWARM_REJECT_REASON} ${feedback}` : SWARM_REJECT_REASON };\n\t}\n\treturn undefined;\n}\n\nfunction sanitizeDetail(value: unknown): string {\n\tconst text = String(value ?? \"\").trim();\n\treturn text.length > 200 ? `${text.slice(0, 200)}…` : text;\n}\n\n/**\n * Returns a block result when the tool call should be blocked, else undefined.\n * Read-only tools always pass. YOLO and Auto always pass mutating tools. Plan\n * mode hard-blocks mutating tools (no prompt). Manual mode gates every mutating\n * tool; if an approval handler is registered it is asked, otherwise the call is\n * blocked (fail-closed — no silent allow).\n */\nexport async function gateToolCall(\n\ttoolName: string,\n\tinput: Record<string, unknown>,\n\tcwd: string,\n\tsessionId?: string,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tconst ctx = getContext(sessionId);\n\tconst protectedWriteReason = protectedFileWriteReason(toolName, input, cwd);\n\tif (protectedWriteReason) {\n\t\treturn { block: true, reason: protectedWriteReason };\n\t}\n\tif (READ_ONLY_TOOLS.has(toolName)) return undefined;\n\n\t// lunr: agent-swarm gate runs before the mode early-return — it applies in\n\t// yolo mode too (only auto mode bypasses it).\n\tif (toolName === \"subagent\") {\n\t\tconst swarmBlock = await gateSwarmCall(input, ctx, options);\n\t\tif (swarmBlock) return swarmBlock;\n\t}\n\n\tif (ctx.mode === \"plan\") {\n\t\tconst reason = planModeBlockReason(toolName, input);\n\t\treturn reason ? { block: true, reason } : undefined;\n\t}\n\n\tif (ctx.mode !== \"manual\") return undefined;\n\tif (!requiresManualApproval(toolName, input)) return undefined;\n\n\tlet action: string;\n\tlet detail: string;\n\n\tif (toolName === \"bash\") {\n\t\taction = \"bash\";\n\t\tdetail = sanitizeDetail(input.command);\n\t} else if (toolName === \"edit\" || toolName === \"write\") {\n\t\tconst path = resolvePath(cwd, input.path);\n\t\tconst outside = path ? isPathOutsideCwd(path, cwd) : false;\n\t\taction = outside ? `${toolName}-outside` : toolName;\n\t\tdetail = path || String(input.path ?? \"\");\n\t} else if (toolName === \"memory_add\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.content);\n\t} else if (toolName === \"memory_remove\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.line);\n\t} else if (toolName === \"cron\") {\n\t\taction = \"cron\";\n\t\tdetail = sanitizeDetail(input.action);\n\t} else if (toolName === \"code_rewrite\") {\n\t\taction = \"code_rewrite\";\n\t\tdetail = sanitizeDetail(input.path ?? input.pattern);\n\t} else {\n\t\t// All other mutating tools fall back to the tool name with no extra detail.\n\t\taction = toolName;\n\t\tdetail = \"\";\n\t}\n\n\tif (ctx.approvals.has(action)) return undefined;\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_HANDLER_REASON };\n\t}\n\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName, action, detail });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(action);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\t\treturn { block: true, reason: feedback ? `${REJECT_REASON} ${feedback}` : REJECT_REASON };\n\t}\n\t// \"once\" — allow this call only\n\treturn undefined;\n}\n"]}
|
package/dist/core/permissions.js
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* State (mode + session-scoped approvals) is keyed by session id so
|
|
19
19
|
* concurrent gateway chats do not share permission decisions.
|
|
20
20
|
*/
|
|
21
|
-
import { resolve } from "node:path";
|
|
21
|
+
import { dirname, join, resolve } from "node:path";
|
|
22
|
+
import { getAgentDir } from "../config.js";
|
|
22
23
|
import { isCodeRewriteMutating, planModeBlockReason } from "./plan-mode.js";
|
|
23
24
|
import { effectiveSwarmCountForTurn, SWARM_APPROVAL_THRESHOLD } from "./swarm.js";
|
|
24
25
|
/** Shift+Tab cycle order. */
|
|
@@ -63,20 +64,10 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
63
64
|
"lsp_symbols",
|
|
64
65
|
"lsp_code_actions",
|
|
65
66
|
"lsp_completions",
|
|
66
|
-
"behavior_load",
|
|
67
67
|
"memory_load",
|
|
68
68
|
]);
|
|
69
69
|
/** Tools that mutate state and must be gated in manual mode. */
|
|
70
|
-
const MUTATING_TOOLS = new Set([
|
|
71
|
-
"bash",
|
|
72
|
-
"edit",
|
|
73
|
-
"write",
|
|
74
|
-
"behavior_add",
|
|
75
|
-
"behavior_remove",
|
|
76
|
-
"memory_add",
|
|
77
|
-
"memory_remove",
|
|
78
|
-
"cron",
|
|
79
|
-
]);
|
|
70
|
+
const MUTATING_TOOLS = new Set(["bash", "edit", "write", "memory_add", "memory_remove", "cron"]);
|
|
80
71
|
const REJECT_REASON = "Rejected by user (permission mode: manual).";
|
|
81
72
|
export const NO_HANDLER_REASON = "Mutating tool blocked in manual mode: no approval channel available.";
|
|
82
73
|
export const SWARM_REJECT_REASON = "Agent swarm rejected by user.";
|
|
@@ -179,6 +170,27 @@ function resolvePath(cwd, p) {
|
|
|
179
170
|
return "";
|
|
180
171
|
return resolve(cwd, p);
|
|
181
172
|
}
|
|
173
|
+
export const GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON = "The global AGENTS.md file is user-managed. The agent cannot change ~/.lunr/agent/AGENTS.md.";
|
|
174
|
+
export const MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON = "Memory is model-managed through the memory tools. Do not directly change ~/.lunr/simple-memory/memory.md.";
|
|
175
|
+
function protectedFileWriteReason(toolName, input, cwd) {
|
|
176
|
+
if (toolName !== "edit" && toolName !== "write" && toolName !== "code_rewrite")
|
|
177
|
+
return undefined;
|
|
178
|
+
const target = resolvePath(cwd, input.path);
|
|
179
|
+
if (!target)
|
|
180
|
+
return undefined;
|
|
181
|
+
const normalize = (path) => {
|
|
182
|
+
const normalized = resolve(path).replace(/\\/g, "/").replace(/\/$/, "");
|
|
183
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
184
|
+
};
|
|
185
|
+
const agentsPaths = [join(getAgentDir(), "AGENTS.md"), join(getAgentDir(), "AGENTS.MD")];
|
|
186
|
+
if (agentsPaths.some((candidate) => normalize(target) === normalize(candidate))) {
|
|
187
|
+
return GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON;
|
|
188
|
+
}
|
|
189
|
+
if (normalize(target) === normalize(join(dirname(getAgentDir()), "simple-memory", "memory.md"))) {
|
|
190
|
+
return MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON;
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
182
194
|
function isMutatingTool(toolName) {
|
|
183
195
|
return MUTATING_TOOLS.has(toolName);
|
|
184
196
|
}
|
|
@@ -285,6 +297,10 @@ function sanitizeDetail(value) {
|
|
|
285
297
|
*/
|
|
286
298
|
export async function gateToolCall(toolName, input, cwd, sessionId, options) {
|
|
287
299
|
const ctx = getContext(sessionId);
|
|
300
|
+
const protectedWriteReason = protectedFileWriteReason(toolName, input, cwd);
|
|
301
|
+
if (protectedWriteReason) {
|
|
302
|
+
return { block: true, reason: protectedWriteReason };
|
|
303
|
+
}
|
|
288
304
|
if (READ_ONLY_TOOLS.has(toolName))
|
|
289
305
|
return undefined;
|
|
290
306
|
// lunr: agent-swarm gate runs before the mode early-return — it applies in
|
|
@@ -314,11 +330,11 @@ export async function gateToolCall(toolName, input, cwd, sessionId, options) {
|
|
|
314
330
|
action = outside ? `${toolName}-outside` : toolName;
|
|
315
331
|
detail = path || String(input.path ?? "");
|
|
316
332
|
}
|
|
317
|
-
else if (toolName === "
|
|
333
|
+
else if (toolName === "memory_add") {
|
|
318
334
|
action = toolName;
|
|
319
335
|
detail = sanitizeDetail(input.content);
|
|
320
336
|
}
|
|
321
|
-
else if (toolName === "
|
|
337
|
+
else if (toolName === "memory_remove") {
|
|
322
338
|
action = toolName;
|
|
323
339
|
detail = sanitizeDetail(input.line);
|
|
324
340
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/core/permissions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: permission modes (Manual / YOLO / Plan / Auto).\n *\n * Per-session only — never persisted per session, but the default mode new\n * sessions start with is configurable in /settings (`defaultPermissionMode`,\n * default \"manual\").\n *\n * - manual: every mutating tool call (bash, edit, write) requires approval.\n * - yolo: auto-approve tools; the agent may still ask questions.\n * - plan: read-only; mutating tools are hard-blocked (planModeBlockReason).\n * - auto: fully autonomous; a system-prompt addendum steers the model to\n * self-decide, and rollback is force-enabled for the session.\n *\n * The gate is wired into `agent-session.ts` `_installAgentToolHooks` before\n * the synchronous `_toolCallGates` loop. beforeToolCall is already async, so\n * awaiting the approval dialog is fine.\n *\n * State (mode + session-scoped approvals) is keyed by session id so\n * concurrent gateway chats do not share permission decisions.\n */\n\nimport { resolve } from \"node:path\";\nimport { isCodeRewriteMutating, planModeBlockReason } from \"./plan-mode.ts\";\nimport { effectiveSwarmCountForTurn, SWARM_APPROVAL_THRESHOLD } from \"./swarm.ts\";\n\n/** Shift+Tab cycle order. */\nexport const PERMISSION_MODES = [\"manual\", \"yolo\", \"plan\", \"auto\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\nexport function nextPermissionMode(current: PermissionMode): PermissionMode {\n\tconst i = PERMISSION_MODES.indexOf(current);\n\treturn PERMISSION_MODES[(i + 1) % PERMISSION_MODES.length];\n}\n\n/** True when the (default or session) permission mode is plan. */\nexport function isPlanModeActive(sessionId?: string): boolean {\n\treturn getPermissionMode(sessionId) === \"plan\";\n}\n\n/**\n * Destination mode when leaving plan via approve / `/plan off` / `/plan <text>`.\n * Explicit Shift+Tab or `/mode` picks do not use this — they already chose a mode.\n */\nexport function restorePermissionModeAfterPlan(\n\tprevious: PermissionMode | undefined,\n\tdefaultMode: PermissionMode,\n): PermissionMode {\n\tif (previous && previous !== \"plan\") return previous;\n\treturn defaultMode === \"plan\" ? \"yolo\" : defaultMode;\n}\n\nexport interface ApprovalRequest {\n\ttoolName: string;\n\t/** \"bash\" (detail = command), \"edit\"/\"write\" (detail = path),\n\t * \"edit-outside\"/\"write-outside\" when path escapes cwd,\n\t * \"swarm\" for auto-activated agent swarms (detail = summary lines). */\n\taction: string;\n\tdetail: string;\n\t/** \"swarm\" when this is the agent-swarm approval prompt, \"plan\" for the\n\t * present_plan plan-approval prompt — the UI renders a dedicated dialog\n\t * for each instead of the generic tool dialog. */\n\tkind?: \"swarm\" | \"plan\";\n}\n\nexport type ApprovalDecision = \"once\" | \"session\" | \"reject\";\n/** Handlers may return a bare decision string, a reject carrying user feedback\n * that becomes the block reason the model sees, or an approve carrying optional\n * feedback (plan approval with comments). */\nexport type ApprovalResponse =\n\t| ApprovalDecision\n\t| { decision: \"reject\"; feedback: string }\n\t| { decision: \"approve\"; feedback?: string };\n\n/** Read-only tools that never need approval. */\nconst READ_ONLY_TOOLS = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"web_search\",\n\t\"web_fetch\",\n\t\"fetch_content\",\n\t\"get_search_content\",\n\t\"sourcegraph\",\n\t\"repo_map\",\n\t\"symbol_outline\",\n\t\"read_block\",\n\t\"code_overview\",\n\t\"ast_search\",\n\t\"lsp_diagnostics\",\n\t\"lsp_hover\",\n\t\"lsp_definition\",\n\t\"lsp_references\",\n\t\"lsp_symbols\",\n\t\"lsp_code_actions\",\n\t\"lsp_completions\",\n\t\"behavior_load\",\n\t\"memory_load\",\n]);\n\n/** Tools that mutate state and must be gated in manual mode. */\nconst MUTATING_TOOLS = new Set([\n\t\"bash\",\n\t\"edit\",\n\t\"write\",\n\t\"behavior_add\",\n\t\"behavior_remove\",\n\t\"memory_add\",\n\t\"memory_remove\",\n\t\"cron\",\n]);\n\nconst REJECT_REASON = \"Rejected by user (permission mode: manual).\";\nexport const NO_HANDLER_REASON = \"Mutating tool blocked in manual mode: no approval channel available.\";\nexport const SWARM_REJECT_REASON = \"Agent swarm rejected by user.\";\nexport const NO_SWARM_HANDLER_REASON = \"Agent swarm blocked: no approval channel available.\";\n/** Session-approval key for auto-activated agent swarms. */\nconst SWARM_ACTION = \"swarm\";\n\ninterface PermissionContext {\n\tmode: PermissionMode;\n\tapprovals: Set<string>;\n}\n\nconst contexts = new Map<string, PermissionContext>();\nlet defaultContext: PermissionContext = { mode: \"manual\", approvals: new Set() };\nlet approvalHandler: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined;\n/** One swarm prompt covers every sibling SINGLE `subagent` on the same assistant message. */\nconst turnSwarmDecisions = new WeakMap<object, { decision: ApprovalDecision; feedback?: string }>();\n\nfunction getContext(sessionId?: string): PermissionContext {\n\tif (!sessionId) return defaultContext;\n\treturn contexts.get(sessionId) ?? defaultContext;\n}\n\nexport function createPermissionContext(sessionId: string, mode: PermissionMode = defaultContext.mode): void {\n\tcontexts.set(sessionId, { mode, approvals: new Set() });\n}\n\nexport function deletePermissionContext(sessionId: string): void {\n\tcontexts.delete(sessionId);\n}\n\nexport function getPermissionMode(sessionId?: string): PermissionMode {\n\treturn getContext(sessionId).mode;\n}\n\nexport function setPermissionMode(mode: PermissionMode, sessionId?: string): void {\n\tgetContext(sessionId).mode = mode;\n}\n\nexport function resetPermissions(defaultMode: PermissionMode = \"manual\", sessionId?: string): void {\n\tif (sessionId) {\n\t\tcontexts.set(sessionId, { mode: defaultMode, approvals: new Set() });\n\t} else {\n\t\tdefaultContext = { mode: defaultMode, approvals: new Set() };\n\t}\n}\n\nexport function registerApprovalHandler(fn: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined): void {\n\tapprovalHandler = fn;\n}\n\n/** Model-facing result texts for the present_plan tool. */\nexport const PLAN_APPROVED_TEXT = \"Plan approved — implement it now.\";\nexport const PLAN_DECLINED_TEXT = \"Plan declined — revise the plan and call present_plan again.\";\nexport const PLAN_PASS_THROUGH_TEXT = \"Plan presented. The user will review and reply.\";\n\n/** lunr: map an approval response to the model-facing present_plan result text. */\nexport function planApprovalResultText(resp: ApprovalResponse): string {\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (decision === \"reject\") {\n\t\treturn feedback\n\t\t\t? `Plan declined: ${feedback} — revise the plan and call present_plan again.`\n\t\t\t: PLAN_DECLINED_TEXT;\n\t}\n\treturn feedback ? `Plan approved with feedback: ${feedback} — implement it now.` : PLAN_APPROVED_TEXT;\n}\n\n/**\n * lunr: present_plan approval request. Unlike the mutating-tool and swarm gates\n * this fails OPEN when no approval handler is registered (or none is reachable,\n * e.g. a gateway turn without a chat context): headless sessions have no one to\n * show the dialog to, so the plan is presented and the user replies in chat\n * instead of deadlocking the turn. Everything else keeps fail-closed.\n */\nexport async function requestPlanApproval(summary: string): Promise<string> {\n\tif (!approvalHandler) return PLAN_PASS_THROUGH_TEXT;\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName: \"present_plan\", action: \"plan\", detail: summary, kind: \"plan\" });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"approval request failed\";\n\t\tif (message === NO_HANDLER_REASON) return PLAN_PASS_THROUGH_TEXT;\n\t\treturn `Plan declined: ${message} — revise the plan and call present_plan again.`;\n\t}\n\treturn planApprovalResultText(resp);\n}\n\nexport function clearSessionApprovals(sessionId?: string): void {\n\tgetContext(sessionId).approvals.clear();\n}\n\n/** Test seam: remove every per-session context and restore the default. */\nexport function resetAllPermissionContexts(): void {\n\tcontexts.clear();\n\tdefaultContext = { mode: \"manual\", approvals: new Set() };\n\tapprovalHandler = undefined;\n}\n\n/** Appended to the system prompt while auto mode is active. */\nexport const AUTO_MODE_ADDENDUM =\n\t\"You are running fully autonomously. Do not ask the user questions or wait for confirmation; make reasonable decisions and complete the task end-to-end.\";\n\nfunction isPathOutsideCwd(absPath: string, cwd: string): boolean {\n\tconst norm = (p: string) => p.replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n\tconst np = norm(absPath);\n\tconst nc = norm(cwd);\n\tif (np === nc) return false;\n\treturn !np.startsWith(`${nc}/`);\n}\n\nfunction resolvePath(cwd: string, p: unknown): string {\n\tif (typeof p !== \"string\") return \"\";\n\tif (!p) return \"\";\n\treturn resolve(cwd, p);\n}\n\nfunction isMutatingTool(toolName: string): boolean {\n\treturn MUTATING_TOOLS.has(toolName);\n}\n\nfunction requiresManualApproval(toolName: string, input: Record<string, unknown>): boolean {\n\tif (isMutatingTool(toolName)) return true;\n\treturn toolName === \"code_rewrite\" && isCodeRewriteMutating(input);\n}\n\nexport interface GateOptions {\n\t/** True when the current turn started from an explicit /swarm prompt — the\n\t * user already asked for the swarm, so the swarm approval gate is skipped. */\n\texplicitSwarmTurn?: boolean;\n\t/** Assistant message that issued this tool call. Same-turn sibling SINGLE\n\t * `subagent` calls count toward the swarm threshold. */\n\tassistantMessage?: {\n\t\tcontent?: ReadonlyArray<{ type?: string; name?: string; arguments?: unknown }>;\n\t};\n}\n\nfunction firstTaskText(value: unknown): string {\n\tif (typeof value === \"string\" && value.trim()) return sanitizeDetail(value);\n\treturn \"\";\n}\n\n/** First task text of a parallel fan-out or sibling SINGLE, used as the dialog summary. */\nfunction swarmTaskSummary(input: Record<string, unknown>, assistantMessage?: GateOptions[\"assistantMessage\"]): string {\n\tif (Array.isArray(input.tasks)) {\n\t\tfor (const task of input.tasks) {\n\t\t\tconst text = task && typeof task === \"object\" ? firstTaskText((task as { task?: unknown }).task) : \"\";\n\t\t\tif (text) return text;\n\t\t}\n\t}\n\tconst own = firstTaskText(input.task);\n\tif (own) return own;\n\tfor (const block of assistantMessage?.content ?? []) {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") continue;\n\t\tconst args =\n\t\t\tblock.arguments && typeof block.arguments === \"object\" && !Array.isArray(block.arguments)\n\t\t\t\t? (block.arguments as Record<string, unknown>)\n\t\t\t\t: undefined;\n\t\tconst text = firstTaskText(args?.task);\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\n/**\n * Agent-swarm gate. An auto-activated swarm (more than SWARM_APPROVAL_THRESHOLD\n * parallel subagents in one `tasks`/`chain.parallel` call, or that many same-turn\n * SINGLE `subagent` calls) requires user approval in manual AND yolo modes; auto\n * mode runs it unconditionally and explicit /swarm turns are pre-approved.\n * Fail-closed without an approval handler, same as the mutating-tool gate.\n */\nasync function gateSwarmCall(\n\tinput: Record<string, unknown>,\n\tctx: PermissionContext,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tif (ctx.mode === \"auto\") return undefined;\n\tif (options?.explicitSwarmTurn) return undefined;\n\tconst count = effectiveSwarmCountForTurn(input, options?.assistantMessage);\n\tif (count <= SWARM_APPROVAL_THRESHOLD) return undefined;\n\tif (ctx.approvals.has(SWARM_ACTION)) return undefined;\n\n\tconst turnKey = options?.assistantMessage;\n\tconst cached = turnKey ? turnSwarmDecisions.get(turnKey) : undefined;\n\tif (cached) {\n\t\tif (cached.decision === \"reject\") {\n\t\t\treturn { block: true, reason: cached.feedback ? `${SWARM_REJECT_REASON} ${cached.feedback}` : SWARM_REJECT_REASON };\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_SWARM_HANDLER_REASON };\n\t}\n\n\tconst summary = swarmTaskSummary(input, options?.assistantMessage);\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({\n\t\t\ttoolName: \"subagent\",\n\t\t\taction: SWARM_ACTION,\n\t\t\tdetail: summary ? `agent swarm (${count} subagents)\\n${summary}` : `agent swarm (${count} subagents)`,\n\t\t\tkind: \"swarm\",\n\t\t});\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_SWARM_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst rawDecision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst decision: ApprovalDecision = rawDecision === \"approve\" ? \"once\" : rawDecision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (turnKey) turnSwarmDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}) });\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(SWARM_ACTION);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\treturn { block: true, reason: feedback ? `${SWARM_REJECT_REASON} ${feedback}` : SWARM_REJECT_REASON };\n\t}\n\treturn undefined;\n}\n\nfunction sanitizeDetail(value: unknown): string {\n\tconst text = String(value ?? \"\").trim();\n\treturn text.length > 200 ? `${text.slice(0, 200)}…` : text;\n}\n\n/**\n * Returns a block result when the tool call should be blocked, else undefined.\n * Read-only tools always pass. YOLO and Auto always pass mutating tools. Plan\n * mode hard-blocks mutating tools (no prompt). Manual mode gates every mutating\n * tool; if an approval handler is registered it is asked, otherwise the call is\n * blocked (fail-closed — no silent allow).\n */\nexport async function gateToolCall(\n\ttoolName: string,\n\tinput: Record<string, unknown>,\n\tcwd: string,\n\tsessionId?: string,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tconst ctx = getContext(sessionId);\n\tif (READ_ONLY_TOOLS.has(toolName)) return undefined;\n\n\t// lunr: agent-swarm gate runs before the mode early-return — it applies in\n\t// yolo mode too (only auto mode bypasses it).\n\tif (toolName === \"subagent\") {\n\t\tconst swarmBlock = await gateSwarmCall(input, ctx, options);\n\t\tif (swarmBlock) return swarmBlock;\n\t}\n\n\tif (ctx.mode === \"plan\") {\n\t\tconst reason = planModeBlockReason(toolName, input);\n\t\treturn reason ? { block: true, reason } : undefined;\n\t}\n\n\tif (ctx.mode !== \"manual\") return undefined;\n\tif (!requiresManualApproval(toolName, input)) return undefined;\n\n\tlet action: string;\n\tlet detail: string;\n\n\tif (toolName === \"bash\") {\n\t\taction = \"bash\";\n\t\tdetail = sanitizeDetail(input.command);\n\t} else if (toolName === \"edit\" || toolName === \"write\") {\n\t\tconst path = resolvePath(cwd, input.path);\n\t\tconst outside = path ? isPathOutsideCwd(path, cwd) : false;\n\t\taction = outside ? `${toolName}-outside` : toolName;\n\t\tdetail = path || String(input.path ?? \"\");\n\t} else if (toolName === \"behavior_add\" || toolName === \"memory_add\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.content);\n\t} else if (toolName === \"behavior_remove\" || toolName === \"memory_remove\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.line);\n\t} else if (toolName === \"cron\") {\n\t\taction = \"cron\";\n\t\tdetail = sanitizeDetail(input.action);\n\t} else if (toolName === \"code_rewrite\") {\n\t\taction = \"code_rewrite\";\n\t\tdetail = sanitizeDetail(input.path ?? input.pattern);\n\t} else {\n\t\t// All other mutating tools fall back to the tool name with no extra detail.\n\t\taction = toolName;\n\t\tdetail = \"\";\n\t}\n\n\tif (ctx.approvals.has(action)) return undefined;\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_HANDLER_REASON };\n\t}\n\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName, action, detail });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(action);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\t\treturn { block: true, reason: feedback ? `${REJECT_REASON} ${feedback}` : REJECT_REASON };\n\t}\n\t// \"once\" — allow this call only\n\treturn undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/core/permissions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: permission modes (Manual / YOLO / Plan / Auto).\n *\n * Per-session only — never persisted per session, but the default mode new\n * sessions start with is configurable in /settings (`defaultPermissionMode`,\n * default \"manual\").\n *\n * - manual: every mutating tool call (bash, edit, write) requires approval.\n * - yolo: auto-approve tools; the agent may still ask questions.\n * - plan: read-only; mutating tools are hard-blocked (planModeBlockReason).\n * - auto: fully autonomous; a system-prompt addendum steers the model to\n * self-decide, and rollback is force-enabled for the session.\n *\n * The gate is wired into `agent-session.ts` `_installAgentToolHooks` before\n * the synchronous `_toolCallGates` loop. beforeToolCall is already async, so\n * awaiting the approval dialog is fine.\n *\n * State (mode + session-scoped approvals) is keyed by session id so\n * concurrent gateway chats do not share permission decisions.\n */\n\nimport { dirname, join, resolve } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { isCodeRewriteMutating, planModeBlockReason } from \"./plan-mode.ts\";\nimport { effectiveSwarmCountForTurn, SWARM_APPROVAL_THRESHOLD } from \"./swarm.ts\";\n\n/** Shift+Tab cycle order. */\nexport const PERMISSION_MODES = [\"manual\", \"yolo\", \"plan\", \"auto\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\nexport function nextPermissionMode(current: PermissionMode): PermissionMode {\n\tconst i = PERMISSION_MODES.indexOf(current);\n\treturn PERMISSION_MODES[(i + 1) % PERMISSION_MODES.length];\n}\n\n/** True when the (default or session) permission mode is plan. */\nexport function isPlanModeActive(sessionId?: string): boolean {\n\treturn getPermissionMode(sessionId) === \"plan\";\n}\n\n/**\n * Destination mode when leaving plan via approve / `/plan off` / `/plan <text>`.\n * Explicit Shift+Tab or `/mode` picks do not use this — they already chose a mode.\n */\nexport function restorePermissionModeAfterPlan(\n\tprevious: PermissionMode | undefined,\n\tdefaultMode: PermissionMode,\n): PermissionMode {\n\tif (previous && previous !== \"plan\") return previous;\n\treturn defaultMode === \"plan\" ? \"yolo\" : defaultMode;\n}\n\nexport interface ApprovalRequest {\n\ttoolName: string;\n\t/** \"bash\" (detail = command), \"edit\"/\"write\" (detail = path),\n\t * \"edit-outside\"/\"write-outside\" when path escapes cwd,\n\t * \"swarm\" for auto-activated agent swarms (detail = summary lines). */\n\taction: string;\n\tdetail: string;\n\t/** \"swarm\" when this is the agent-swarm approval prompt, \"plan\" for the\n\t * present_plan plan-approval prompt — the UI renders a dedicated dialog\n\t * for each instead of the generic tool dialog. */\n\tkind?: \"swarm\" | \"plan\";\n}\n\nexport type ApprovalDecision = \"once\" | \"session\" | \"reject\";\n/** Handlers may return a bare decision string, a reject carrying user feedback\n * that becomes the block reason the model sees, or an approve carrying optional\n * feedback (plan approval with comments). */\nexport type ApprovalResponse =\n\t| ApprovalDecision\n\t| { decision: \"reject\"; feedback: string }\n\t| { decision: \"approve\"; feedback?: string };\n\n/** Read-only tools that never need approval. */\nconst READ_ONLY_TOOLS = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"web_search\",\n\t\"web_fetch\",\n\t\"fetch_content\",\n\t\"get_search_content\",\n\t\"sourcegraph\",\n\t\"repo_map\",\n\t\"symbol_outline\",\n\t\"read_block\",\n\t\"code_overview\",\n\t\"ast_search\",\n\t\"lsp_diagnostics\",\n\t\"lsp_hover\",\n\t\"lsp_definition\",\n\t\"lsp_references\",\n\t\"lsp_symbols\",\n\t\"lsp_code_actions\",\n\t\"lsp_completions\",\n\t\"memory_load\",\n]);\n\n/** Tools that mutate state and must be gated in manual mode. */\nconst MUTATING_TOOLS = new Set([\"bash\", \"edit\", \"write\", \"memory_add\", \"memory_remove\", \"cron\"]);\n\nconst REJECT_REASON = \"Rejected by user (permission mode: manual).\";\nexport const NO_HANDLER_REASON = \"Mutating tool blocked in manual mode: no approval channel available.\";\nexport const SWARM_REJECT_REASON = \"Agent swarm rejected by user.\";\nexport const NO_SWARM_HANDLER_REASON = \"Agent swarm blocked: no approval channel available.\";\n/** Session-approval key for auto-activated agent swarms. */\nconst SWARM_ACTION = \"swarm\";\n\ninterface PermissionContext {\n\tmode: PermissionMode;\n\tapprovals: Set<string>;\n}\n\nconst contexts = new Map<string, PermissionContext>();\nlet defaultContext: PermissionContext = { mode: \"manual\", approvals: new Set() };\nlet approvalHandler: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined;\n/** One swarm prompt covers every sibling SINGLE `subagent` on the same assistant message. */\nconst turnSwarmDecisions = new WeakMap<object, { decision: ApprovalDecision; feedback?: string }>();\n\nfunction getContext(sessionId?: string): PermissionContext {\n\tif (!sessionId) return defaultContext;\n\treturn contexts.get(sessionId) ?? defaultContext;\n}\n\nexport function createPermissionContext(sessionId: string, mode: PermissionMode = defaultContext.mode): void {\n\tcontexts.set(sessionId, { mode, approvals: new Set() });\n}\n\nexport function deletePermissionContext(sessionId: string): void {\n\tcontexts.delete(sessionId);\n}\n\nexport function getPermissionMode(sessionId?: string): PermissionMode {\n\treturn getContext(sessionId).mode;\n}\n\nexport function setPermissionMode(mode: PermissionMode, sessionId?: string): void {\n\tgetContext(sessionId).mode = mode;\n}\n\nexport function resetPermissions(defaultMode: PermissionMode = \"manual\", sessionId?: string): void {\n\tif (sessionId) {\n\t\tcontexts.set(sessionId, { mode: defaultMode, approvals: new Set() });\n\t} else {\n\t\tdefaultContext = { mode: defaultMode, approvals: new Set() };\n\t}\n}\n\nexport function registerApprovalHandler(fn: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined): void {\n\tapprovalHandler = fn;\n}\n\n/** Model-facing result texts for the present_plan tool. */\nexport const PLAN_APPROVED_TEXT = \"Plan approved — implement it now.\";\nexport const PLAN_DECLINED_TEXT = \"Plan declined — revise the plan and call present_plan again.\";\nexport const PLAN_PASS_THROUGH_TEXT = \"Plan presented. The user will review and reply.\";\n\n/** lunr: map an approval response to the model-facing present_plan result text. */\nexport function planApprovalResultText(resp: ApprovalResponse): string {\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (decision === \"reject\") {\n\t\treturn feedback\n\t\t\t? `Plan declined: ${feedback} — revise the plan and call present_plan again.`\n\t\t\t: PLAN_DECLINED_TEXT;\n\t}\n\treturn feedback ? `Plan approved with feedback: ${feedback} — implement it now.` : PLAN_APPROVED_TEXT;\n}\n\n/**\n * lunr: present_plan approval request. Unlike the mutating-tool and swarm gates\n * this fails OPEN when no approval handler is registered (or none is reachable,\n * e.g. a gateway turn without a chat context): headless sessions have no one to\n * show the dialog to, so the plan is presented and the user replies in chat\n * instead of deadlocking the turn. Everything else keeps fail-closed.\n */\nexport async function requestPlanApproval(summary: string): Promise<string> {\n\tif (!approvalHandler) return PLAN_PASS_THROUGH_TEXT;\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName: \"present_plan\", action: \"plan\", detail: summary, kind: \"plan\" });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"approval request failed\";\n\t\tif (message === NO_HANDLER_REASON) return PLAN_PASS_THROUGH_TEXT;\n\t\treturn `Plan declined: ${message} — revise the plan and call present_plan again.`;\n\t}\n\treturn planApprovalResultText(resp);\n}\n\nexport function clearSessionApprovals(sessionId?: string): void {\n\tgetContext(sessionId).approvals.clear();\n}\n\n/** Test seam: remove every per-session context and restore the default. */\nexport function resetAllPermissionContexts(): void {\n\tcontexts.clear();\n\tdefaultContext = { mode: \"manual\", approvals: new Set() };\n\tapprovalHandler = undefined;\n}\n\n/** Appended to the system prompt while auto mode is active. */\nexport const AUTO_MODE_ADDENDUM =\n\t\"You are running fully autonomously. Do not ask the user questions or wait for confirmation; make reasonable decisions and complete the task end-to-end.\";\n\nfunction isPathOutsideCwd(absPath: string, cwd: string): boolean {\n\tconst norm = (p: string) => p.replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n\tconst np = norm(absPath);\n\tconst nc = norm(cwd);\n\tif (np === nc) return false;\n\treturn !np.startsWith(`${nc}/`);\n}\n\nfunction resolvePath(cwd: string, p: unknown): string {\n\tif (typeof p !== \"string\") return \"\";\n\tif (!p) return \"\";\n\treturn resolve(cwd, p);\n}\n\nexport const GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON =\n\t\"The global AGENTS.md file is user-managed. The agent cannot change ~/.lunr/agent/AGENTS.md.\";\nexport const MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON =\n\t\"Memory is model-managed through the memory tools. Do not directly change ~/.lunr/simple-memory/memory.md.\";\n\nfunction protectedFileWriteReason(toolName: string, input: Record<string, unknown>, cwd: string): string | undefined {\n\tif (toolName !== \"edit\" && toolName !== \"write\" && toolName !== \"code_rewrite\") return undefined;\n\tconst target = resolvePath(cwd, input.path);\n\tif (!target) return undefined;\n\tconst normalize = (path: string) => {\n\t\tconst normalized = resolve(path).replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n\t\treturn process.platform === \"win32\" ? normalized.toLowerCase() : normalized;\n\t};\n\tconst agentsPaths = [join(getAgentDir(), \"AGENTS.md\"), join(getAgentDir(), \"AGENTS.MD\")];\n\tif (agentsPaths.some((candidate) => normalize(target) === normalize(candidate))) {\n\t\treturn GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON;\n\t}\n\tif (normalize(target) === normalize(join(dirname(getAgentDir()), \"simple-memory\", \"memory.md\"))) {\n\t\treturn MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON;\n\t}\n\treturn undefined;\n}\n\nfunction isMutatingTool(toolName: string): boolean {\n\treturn MUTATING_TOOLS.has(toolName);\n}\n\nfunction requiresManualApproval(toolName: string, input: Record<string, unknown>): boolean {\n\tif (isMutatingTool(toolName)) return true;\n\treturn toolName === \"code_rewrite\" && isCodeRewriteMutating(input);\n}\n\nexport interface GateOptions {\n\t/** True when the current turn started from an explicit /swarm prompt — the\n\t * user already asked for the swarm, so the swarm approval gate is skipped. */\n\texplicitSwarmTurn?: boolean;\n\t/** Assistant message that issued this tool call. Same-turn sibling SINGLE\n\t * `subagent` calls count toward the swarm threshold. */\n\tassistantMessage?: {\n\t\tcontent?: ReadonlyArray<{ type?: string; name?: string; arguments?: unknown }>;\n\t};\n}\n\nfunction firstTaskText(value: unknown): string {\n\tif (typeof value === \"string\" && value.trim()) return sanitizeDetail(value);\n\treturn \"\";\n}\n\n/** First task text of a parallel fan-out or sibling SINGLE, used as the dialog summary. */\nfunction swarmTaskSummary(input: Record<string, unknown>, assistantMessage?: GateOptions[\"assistantMessage\"]): string {\n\tif (Array.isArray(input.tasks)) {\n\t\tfor (const task of input.tasks) {\n\t\t\tconst text = task && typeof task === \"object\" ? firstTaskText((task as { task?: unknown }).task) : \"\";\n\t\t\tif (text) return text;\n\t\t}\n\t}\n\tconst own = firstTaskText(input.task);\n\tif (own) return own;\n\tfor (const block of assistantMessage?.content ?? []) {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") continue;\n\t\tconst args =\n\t\t\tblock.arguments && typeof block.arguments === \"object\" && !Array.isArray(block.arguments)\n\t\t\t\t? (block.arguments as Record<string, unknown>)\n\t\t\t\t: undefined;\n\t\tconst text = firstTaskText(args?.task);\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\n/**\n * Agent-swarm gate. An auto-activated swarm (more than SWARM_APPROVAL_THRESHOLD\n * parallel subagents in one `tasks`/`chain.parallel` call, or that many same-turn\n * SINGLE `subagent` calls) requires user approval in manual AND yolo modes; auto\n * mode runs it unconditionally and explicit /swarm turns are pre-approved.\n * Fail-closed without an approval handler, same as the mutating-tool gate.\n */\nasync function gateSwarmCall(\n\tinput: Record<string, unknown>,\n\tctx: PermissionContext,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tif (ctx.mode === \"auto\") return undefined;\n\tif (options?.explicitSwarmTurn) return undefined;\n\tconst count = effectiveSwarmCountForTurn(input, options?.assistantMessage);\n\tif (count <= SWARM_APPROVAL_THRESHOLD) return undefined;\n\tif (ctx.approvals.has(SWARM_ACTION)) return undefined;\n\n\tconst turnKey = options?.assistantMessage;\n\tconst cached = turnKey ? turnSwarmDecisions.get(turnKey) : undefined;\n\tif (cached) {\n\t\tif (cached.decision === \"reject\") {\n\t\t\treturn { block: true, reason: cached.feedback ? `${SWARM_REJECT_REASON} ${cached.feedback}` : SWARM_REJECT_REASON };\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_SWARM_HANDLER_REASON };\n\t}\n\n\tconst summary = swarmTaskSummary(input, options?.assistantMessage);\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({\n\t\t\ttoolName: \"subagent\",\n\t\t\taction: SWARM_ACTION,\n\t\t\tdetail: summary ? `agent swarm (${count} subagents)\\n${summary}` : `agent swarm (${count} subagents)`,\n\t\t\tkind: \"swarm\",\n\t\t});\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_SWARM_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst rawDecision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst decision: ApprovalDecision = rawDecision === \"approve\" ? \"once\" : rawDecision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (turnKey) turnSwarmDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}) });\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(SWARM_ACTION);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\treturn { block: true, reason: feedback ? `${SWARM_REJECT_REASON} ${feedback}` : SWARM_REJECT_REASON };\n\t}\n\treturn undefined;\n}\n\nfunction sanitizeDetail(value: unknown): string {\n\tconst text = String(value ?? \"\").trim();\n\treturn text.length > 200 ? `${text.slice(0, 200)}…` : text;\n}\n\n/**\n * Returns a block result when the tool call should be blocked, else undefined.\n * Read-only tools always pass. YOLO and Auto always pass mutating tools. Plan\n * mode hard-blocks mutating tools (no prompt). Manual mode gates every mutating\n * tool; if an approval handler is registered it is asked, otherwise the call is\n * blocked (fail-closed — no silent allow).\n */\nexport async function gateToolCall(\n\ttoolName: string,\n\tinput: Record<string, unknown>,\n\tcwd: string,\n\tsessionId?: string,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tconst ctx = getContext(sessionId);\n\tconst protectedWriteReason = protectedFileWriteReason(toolName, input, cwd);\n\tif (protectedWriteReason) {\n\t\treturn { block: true, reason: protectedWriteReason };\n\t}\n\tif (READ_ONLY_TOOLS.has(toolName)) return undefined;\n\n\t// lunr: agent-swarm gate runs before the mode early-return — it applies in\n\t// yolo mode too (only auto mode bypasses it).\n\tif (toolName === \"subagent\") {\n\t\tconst swarmBlock = await gateSwarmCall(input, ctx, options);\n\t\tif (swarmBlock) return swarmBlock;\n\t}\n\n\tif (ctx.mode === \"plan\") {\n\t\tconst reason = planModeBlockReason(toolName, input);\n\t\treturn reason ? { block: true, reason } : undefined;\n\t}\n\n\tif (ctx.mode !== \"manual\") return undefined;\n\tif (!requiresManualApproval(toolName, input)) return undefined;\n\n\tlet action: string;\n\tlet detail: string;\n\n\tif (toolName === \"bash\") {\n\t\taction = \"bash\";\n\t\tdetail = sanitizeDetail(input.command);\n\t} else if (toolName === \"edit\" || toolName === \"write\") {\n\t\tconst path = resolvePath(cwd, input.path);\n\t\tconst outside = path ? isPathOutsideCwd(path, cwd) : false;\n\t\taction = outside ? `${toolName}-outside` : toolName;\n\t\tdetail = path || String(input.path ?? \"\");\n\t} else if (toolName === \"memory_add\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.content);\n\t} else if (toolName === \"memory_remove\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.line);\n\t} else if (toolName === \"cron\") {\n\t\taction = \"cron\";\n\t\tdetail = sanitizeDetail(input.action);\n\t} else if (toolName === \"code_rewrite\") {\n\t\taction = \"code_rewrite\";\n\t\tdetail = sanitizeDetail(input.path ?? input.pattern);\n\t} else {\n\t\t// All other mutating tools fall back to the tool name with no extra detail.\n\t\taction = toolName;\n\t\tdetail = \"\";\n\t}\n\n\tif (ctx.approvals.has(action)) return undefined;\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_HANDLER_REASON };\n\t}\n\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName, action, detail });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(action);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\t\treturn { block: true, reason: feedback ? `${REJECT_REASON} ${feedback}` : REJECT_REASON };\n\t}\n\t// \"once\" — allow this call only\n\treturn undefined;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-mode.d.ts","sourceRoot":"","sources":["../../src/core/plan-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,kBAAkB,+RACyP,CAAC;AAEzR,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,gEAA2D,CAAC;AAgJhG,yFAAyF;AACzF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAcxF;AAOD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAe9D","sourcesContent":["/**\n * lunr: plan permission mode — read-only tool-gating heuristics + system-prompt addendum.\n *\n * Plan is a first-class permission mode (`PermissionMode = \"plan\"`). `gateToolCall`\n * in permissions.ts applies `planModeBlockReason` when the session is in plan:\n * `edit`/`write` and mutating `bash` are hard-blocked; read tools stay open.\n * InteractiveMode installs `PLAN_MODE_ADDENDUM` via the shared system-prompt\n * append slot (same slot auto mode uses).\n *\n * Bash heuristic (conservative, blocklist-based — NOT a security boundary):\n * - any `>`/`>>` redirect outside quotes blocks the whole command;\n * - the command is split on `;`, `|`, `&` separators (outside quotes) and every segment's\n * leading command must be read-only:\n * - known mutating commands (rm, mv, cp, mkdir, sed -i, tee, …) are blocked;\n * - `git` is limited to read-only subcommands (status/log/diff/show/… plus flag-only\n * branch/tag/remote); mutating subcommands (add/commit/push/checkout/…) are blocked;\n * - package managers are limited to read-only subcommands (install/add/remove/… blocked);\n * - arbitrary runners (sudo, xargs, npx, sh -c …) are blocked.\n * Unknown-but-not-listed commands are ALLOWED (blocklist, not allowlist) — the model is\n * additionally steered by the system-prompt addendum. False positives are expected; the\n * user can run the command themselves or exit plan mode with /plan off.\n */\n\n/** Appended to the system prompt while plan mode is active. */\nexport const PLAN_MODE_ADDENDUM =\n\t\"You are in plan mode. Investigate read-only, then present your plan by calling the present_plan tool with a concise summary — the user approves or declines it in a dialog. Do not make changes until the plan is approved. The user can also exit plan mode manually with /plan off.\";\n\n/** Error returned to the model when a tool call is blocked by plan mode. */\nexport const PLAN_MODE_BLOCK_MESSAGE = \"Plan mode is active — propose a plan; no file changes.\";\n\nconst BLOCKED_TOOLS = new Set([\n\t\"edit\",\n\t\"write\",\n\t\"behavior_add\",\n\t\"behavior_remove\",\n\t\"memory_add\",\n\t\"memory_remove\",\n\t\"cron\",\n]);\n\n/** Small allowlist of read-only commands permitted in plan mode. Everything else is rejected. */\nconst ALLOWED_COMMANDS = new Set([\n\t\"ls\",\n\t\"ll\",\n\t\"cat\",\n\t\"tac\",\n\t\"grep\",\n\t\"rg\",\n\t\"find\",\n\t\"pwd\",\n\t\"echo\",\n\t\"printf\",\n\t\"head\",\n\t\"tail\",\n\t\"wc\",\n\t\"less\",\n\t\"more\",\n\t\"sort\",\n\t\"uniq\",\n\t\"diff\",\n\t\"cmp\",\n\t\"comm\",\n\t\"test\",\n\t\"[\",\n\t\"true\",\n\t\"false\",\n\t\"which\",\n\t\"whereis\",\n\t\"stat\",\n\t\"file\",\n\t\"id\",\n\t\"whoami\",\n\t\"who\",\n\t\"date\",\n\t\"cal\",\n\t\"env\",\n\t\"printenv\",\n\t\"uname\",\n\t\"hostname\",\n\t\"uptime\",\n\t\"nproc\",\n\t\"tput\",\n\t\"git\",\n\t\"gh\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"yarn\",\n\t\"bun\",\n]);\n\nconst MUTATING_GIT_SUBCOMMANDS = new Set([\n\t\"add\",\n\t\"commit\",\n\t\"push\",\n\t\"pull\",\n\t\"merge\",\n\t\"rebase\",\n\t\"reset\",\n\t\"checkout\",\n\t\"switch\",\n\t\"restore\",\n\t\"rm\",\n\t\"mv\",\n\t\"stash\",\n\t\"cherry-pick\",\n\t\"revert\",\n\t\"clean\",\n\t\"fetch\",\n\t\"config\",\n\t\"apply\",\n\t\"am\",\n\t\"init\",\n\t\"clone\",\n\t\"submodule\",\n\t\"worktree\",\n\t\"gc\",\n\t\"prune\",\n\t\"update-index\",\n]);\n\n// git subcommands that are read-only only when they carry no positional args or\n// mutating flags (`git branch` lists, `git branch foo` / `git branch -d foo` mutate).\nconst CONDITIONAL_GIT_SUBCOMMANDS = new Set([\"branch\", \"tag\", \"remote\"]);\nconst CONDITIONAL_GIT_MUTATING_FLAGS = new Set([\"-d\", \"-D\", \"-m\", \"-M\", \"-c\", \"-C\"]);\n\nconst PACKAGE_MANAGER_MUTATIONS: Record<string, Set<string>> = {\n\tnpm: new Set([\n\t\t\"install\",\n\t\t\"add\",\n\t\t\"remove\",\n\t\t\"uninstall\",\n\t\t\"update\",\n\t\t\"upgrade\",\n\t\t\"publish\",\n\t\t\"link\",\n\t\t\"unlink\",\n\t\t\"ci\",\n\t\t\"init\",\n\t]),\n\tpnpm: new Set([\"install\", \"add\", \"remove\", \"uninstall\", \"update\", \"upgrade\", \"publish\", \"link\", \"unlink\", \"init\"]),\n\tyarn: new Set([\"install\", \"add\", \"remove\", \"uninstall\", \"upgrade\", \"publish\", \"link\", \"unlink\", \"init\"]),\n\tbun: new Set([\"install\", \"add\", \"remove\", \"uninstall\", \"update\", \"upgrade\", \"publish\", \"link\", \"unlink\", \"init\"]),\n};\n\n// Almost every subcommand mutates the system.\nconst ALWAYS_MUTATING_MANAGERS = new Set([\n\t\"apt\",\n\t\"apt-get\",\n\t\"brew\",\n\t\"dnf\",\n\t\"yum\",\n\t\"pacman\",\n\t\"choco\",\n\t\"winget\",\n\t\"scoop\",\n]);\n\n/** Flags that cause an interpreter to execute code and are never allowed in plan mode. */\nconst EXECUTING_NODE_FLAGS = new Set([\n\t\"-e\",\n\t\"--eval\",\n\t\"-p\",\n\t\"--print\",\n\t\"-r\",\n\t\"--require\",\n\t\"--import\",\n\t\"-i\",\n\t\"--interactive\",\n]);\nconst EXECUTING_PYTHON_FLAGS = new Set([\"-c\", \"-m\", \"-i\", \"--interactive\"]);\n\n/** Apply-mode rewrite only. Default / omitted `dry_run` is preview and stays allowed. */\nexport function isCodeRewriteMutating(input: unknown): boolean {\n\treturn (input as { dry_run?: unknown } | undefined)?.dry_run === false;\n}\n\n/**\n * Returns the block reason when plan mode should block this tool call, else undefined.\n */\nexport function planModeBlockReason(toolName: string, input: unknown): string | undefined {\n\tif (BLOCKED_TOOLS.has(toolName)) {\n\t\treturn PLAN_MODE_BLOCK_MESSAGE;\n\t}\n\tif (toolName === \"code_rewrite\" && isCodeRewriteMutating(input)) {\n\t\treturn PLAN_MODE_BLOCK_MESSAGE;\n\t}\n\tif (toolName === \"bash\") {\n\t\tconst command = readBashCommand(input);\n\t\tif (command && isMutatingBashCommand(command)) {\n\t\t\treturn `${PLAN_MODE_BLOCK_MESSAGE} Blocked command: ${command}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction readBashCommand(input: unknown): string {\n\tconst command = (input as { command?: unknown } | undefined)?.command;\n\treturn typeof command === \"string\" ? command : \"\";\n}\n\n/**\n * Plan-mode bash allowlist. A command is mutating unless every segment is a\n * known read-only command used safely (no redirects, no command substitution,\n * no process substitution, no executing-interpreter flags).\n */\nexport function isMutatingBashCommand(command: string): boolean {\n\tif (!command.trim()) return false;\n\n\t// Any output redirect outside quotes can write a file.\n\tif (hasUnquotedRedirect(command)) return true;\n\n\t// Command substitution / process substitution / grouped redirect syntaxes\n\t// bypass a simple command-name check.\n\tif (hasShellSubstitution(command)) return true;\n\n\t// Every segment separated by ; | & && || must individually be read-only.\n\tfor (const segment of splitShellSegments(command)) {\n\t\tif (isMutatingSegment(segment)) return true;\n\t}\n\treturn false;\n}\n\nfunction hasUnquotedRedirect(command: string): boolean {\n\tlet quote: \"'\" | '\"' | undefined;\n\tlet escaped = false;\n\tfor (let i = 0; i < command.length; i++) {\n\t\tconst ch = command[i];\n\t\tif (escaped) {\n\t\t\tescaped = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\\\" && quote !== \"'\") {\n\t\t\tescaped = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tif (ch === quote) quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"'\" || ch === '\"') {\n\t\t\tquote = ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \">\") return true;\n\t}\n\treturn false;\n}\n\nfunction splitShellSegments(command: string): string[] {\n\tconst segments: string[] = [];\n\tlet quote: \"'\" | '\"' | undefined;\n\tlet escaped = false;\n\tlet start = 0;\n\tfor (let i = 0; i < command.length; i++) {\n\t\tconst ch = command[i];\n\t\tconst next = command[i + 1];\n\t\tif (escaped) {\n\t\t\tescaped = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\\\" && quote !== \"'\") {\n\t\t\tescaped = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tif (ch === quote) quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"'\" || ch === '\"') {\n\t\t\tquote = ch;\n\t\t\tcontinue;\n\t\t}\n\t\t// Combined operators && and || split as one boundary.\n\t\tif ((ch === \"&\" && next === \"&\") || (ch === \"|\" && next === \"|\")) {\n\t\t\tsegments.push(command.slice(start, i));\n\t\t\ti++;\n\t\t\tstart = i + 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \";\" || ch === \"|\" || ch === \"&\" || ch === \"\\n\") {\n\t\t\tsegments.push(command.slice(start, i));\n\t\t\tstart = i + 1;\n\t\t}\n\t}\n\tsegments.push(command.slice(start));\n\treturn segments.map((s) => s.trim()).filter((s) => s.length > 0);\n}\n\n/** True when the command contains command/process substitution outside quotes. */\nfunction hasShellSubstitution(command: string): boolean {\n\tlet quote: \"'\" | '\"' | undefined;\n\tlet escaped = false;\n\tfor (let i = 0; i < command.length; i++) {\n\t\tconst ch = command[i];\n\t\tif (escaped) {\n\t\t\tescaped = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\\\" && quote !== \"'\") {\n\t\t\tescaped = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tif (ch === quote) quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"'\" || ch === '\"') {\n\t\t\tquote = ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"`\") return true;\n\t\tif (ch === \"$\" && command[i + 1] === \"(\") return true;\n\t\tif (ch === \"<\" && command[i + 1] === \"(\") return true;\n\t\tif (ch === \">\" && command[i + 1] === \"(\") return true;\n\t\tif (ch === \"&\" && command[i + 1] === \">\") return true;\n\t}\n\treturn false;\n}\n\nfunction isMutatingSegment(segment: string): boolean {\n\tlet tokens = segment.split(/\\s+/).filter((t) => t.length > 0);\n\t// Skip leading env assignments (FOO=bar cmd …) and command wrappers we can't see through.\n\twhile (tokens.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0])) {\n\t\ttokens = tokens.slice(1);\n\t}\n\tif (tokens.length === 0) return false;\n\n\tconst command = basenameOf(tokens[0]).toLowerCase();\n\tconst args = tokens.slice(1);\n\n\tif (ALWAYS_MUTATING_MANAGERS.has(command)) return true;\n\n\tif (command === \"sed\") {\n\t\treturn args.some((a) => a === \"-i\" || a.startsWith(\"-i\") || a.startsWith(\"--in-place\") || /^-[^-]*i/.test(a));\n\t}\n\tif (command === \"find\") {\n\t\treturn args.some((a) => [\"-delete\", \"-exec\", \"-execdir\", \"-ok\", \"-okdir\"].includes(a));\n\t}\n\tif (command === \"git\") {\n\t\treturn isMutatingGit(args);\n\t}\n\tif (command === \"gh\") {\n\t\t// Allow read-only views; block everything that changes remote state.\n\t\tconst sub = `${args[0] ?? \"\"} ${args[1] ?? \"\"}`;\n\t\treturn ![\n\t\t\t\"pr view\",\n\t\t\t\"pr list\",\n\t\t\t\"pr status\",\n\t\t\t\"issue view\",\n\t\t\t\"issue list\",\n\t\t\t\"issue status\",\n\t\t\t\"repo view\",\n\t\t\t\"release view\",\n\t\t\t\"release list\",\n\t\t\t\"run list\",\n\t\t\t\"run view\",\n\t\t\t\"status\",\n\t\t].includes(sub.trim());\n\t}\n\tif (command === \"sh\" || command === \"bash\" || command === \"zsh\" || command === \"dash\") {\n\t\t// `sh -c '…'` runs arbitrary code; bare `sh script` also executes. Block -c; allow\n\t\t// nothing else is too restrictive for `bash --version`, so allow flag-only invocations.\n\t\treturn args.some((a) => a === \"-c\" || a === \"-s\") || args.some((a) => !a.startsWith(\"-\"));\n\t}\n\n\tconst pkgMutations = PACKAGE_MANAGER_MUTATIONS[command];\n\tif (pkgMutations) {\n\t\tconst subcommand = args.find((a) => !a.startsWith(\"-\"))?.toLowerCase();\n\t\treturn subcommand !== undefined && pkgMutations.has(subcommand);\n\t}\n\n\t// Node / Python interpreters: flag-only, no executing flags, no positional scripts.\n\tif (command === \"node\") {\n\t\treturn args.some((a) => !a.startsWith(\"-\")) || args.some((a) => EXECUTING_NODE_FLAGS.has(a));\n\t}\n\tif (command === \"python\" || command === \"python3\") {\n\t\treturn args.some((a) => !a.startsWith(\"-\")) || args.some((a) => EXECUTING_PYTHON_FLAGS.has(a));\n\t}\n\n\t// Final gate: the command must be in the read-only allowlist.\n\treturn !ALLOWED_COMMANDS.has(command);\n}\n\n/** Global git options that take no value. Unknown leading flags are treated as mutating. */\nconst GIT_GLOBAL_FLAGS = new Set([\n\t\"--no-pager\",\n\t\"--paginate\",\n\t\"-p\",\n\t\"--version\",\n\t\"--help\",\n\t\"-h\",\n\t\"--bare\",\n\t\"--no-replace-objects\",\n\t\"--no-optional-locks\",\n\t\"--literal-pathspecs\",\n\t\"--glob-pathspecs\",\n\t\"--noglob-pathspecs\",\n\t\"--icase-pathspecs\",\n]);\n\n/** Walk past known `git` globals. Returns the subcommand index, or -1 to block. */\nfunction gitSubcommandIndex(args: string[]): number {\n\tlet index = 0;\n\twhile (index < args.length) {\n\t\tconst token = args[index];\n\t\tif (!token.startsWith(\"-\")) return index;\n\t\tif (token === \"--\") return index + 1;\n\t\tif (GIT_GLOBAL_FLAGS.has(token)) {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (token === \"-C\" || token === \"-c\" || token === \"--git-dir\" || token === \"--work-tree\" || token === \"--namespace\") {\n\t\t\tif (args[index + 1] === undefined) return -1;\n\t\t\tindex += 2;\n\t\t\tcontinue;\n\t\t}\n\t\tif (\n\t\t\ttoken.startsWith(\"--git-dir=\") ||\n\t\t\ttoken.startsWith(\"--work-tree=\") ||\n\t\t\ttoken.startsWith(\"--namespace=\") ||\n\t\t\ttoken.startsWith(\"--config-env=\")\n\t\t) {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\treturn -1;\n\t}\n\treturn index;\n}\n\nfunction isMutatingGit(args: string[]): boolean {\n\tconst index = gitSubcommandIndex(args);\n\tif (index < 0) return true;\n\tconst subcommand = args[index]?.toLowerCase();\n\tif (!subcommand) return false;\n\n\tif (MUTATING_GIT_SUBCOMMANDS.has(subcommand)) return true;\n\tif (CONDITIONAL_GIT_SUBCOMMANDS.has(subcommand)) {\n\t\tconst rest = args.slice(index + 1);\n\t\treturn rest.some((a) => !a.startsWith(\"-\") || CONDITIONAL_GIT_MUTATING_FLAGS.has(a));\n\t}\n\treturn false;\n}\n\nfunction basenameOf(token: string): string {\n\tconst parts = token.split(/[\\\\/]/);\n\treturn parts[parts.length - 1] ?? token;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"plan-mode.d.ts","sourceRoot":"","sources":["../../src/core/plan-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,kBAAkB,+RACyP,CAAC;AAEzR,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,gEAA2D,CAAC;AAwIhG,yFAAyF;AACzF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAcxF;AAOD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAe9D","sourcesContent":["/**\n * lunr: plan permission mode — read-only tool-gating heuristics + system-prompt addendum.\n *\n * Plan is a first-class permission mode (`PermissionMode = \"plan\"`). `gateToolCall`\n * in permissions.ts applies `planModeBlockReason` when the session is in plan:\n * `edit`/`write` and mutating `bash` are hard-blocked; read tools stay open.\n * InteractiveMode installs `PLAN_MODE_ADDENDUM` via the shared system-prompt\n * append slot (same slot auto mode uses).\n *\n * Bash heuristic (conservative, blocklist-based — NOT a security boundary):\n * - any `>`/`>>` redirect outside quotes blocks the whole command;\n * - the command is split on `;`, `|`, `&` separators (outside quotes) and every segment's\n * leading command must be read-only:\n * - known mutating commands (rm, mv, cp, mkdir, sed -i, tee, …) are blocked;\n * - `git` is limited to read-only subcommands (status/log/diff/show/… plus flag-only\n * branch/tag/remote); mutating subcommands (add/commit/push/checkout/…) are blocked;\n * - package managers are limited to read-only subcommands (install/add/remove/… blocked);\n * - arbitrary runners (sudo, xargs, npx, sh -c …) are blocked.\n * Unknown-but-not-listed commands are ALLOWED (blocklist, not allowlist) — the model is\n * additionally steered by the system-prompt addendum. False positives are expected; the\n * user can run the command themselves or exit plan mode with /plan off.\n */\n\n/** Appended to the system prompt while plan mode is active. */\nexport const PLAN_MODE_ADDENDUM =\n\t\"You are in plan mode. Investigate read-only, then present your plan by calling the present_plan tool with a concise summary — the user approves or declines it in a dialog. Do not make changes until the plan is approved. The user can also exit plan mode manually with /plan off.\";\n\n/** Error returned to the model when a tool call is blocked by plan mode. */\nexport const PLAN_MODE_BLOCK_MESSAGE = \"Plan mode is active — propose a plan; no file changes.\";\n\nconst BLOCKED_TOOLS = new Set([\"edit\", \"write\", \"memory_add\", \"memory_remove\", \"cron\"]);\n\n/** Small allowlist of read-only commands permitted in plan mode. Everything else is rejected. */\nconst ALLOWED_COMMANDS = new Set([\n\t\"ls\",\n\t\"ll\",\n\t\"cat\",\n\t\"tac\",\n\t\"grep\",\n\t\"rg\",\n\t\"find\",\n\t\"pwd\",\n\t\"echo\",\n\t\"printf\",\n\t\"head\",\n\t\"tail\",\n\t\"wc\",\n\t\"less\",\n\t\"more\",\n\t\"sort\",\n\t\"uniq\",\n\t\"diff\",\n\t\"cmp\",\n\t\"comm\",\n\t\"test\",\n\t\"[\",\n\t\"true\",\n\t\"false\",\n\t\"which\",\n\t\"whereis\",\n\t\"stat\",\n\t\"file\",\n\t\"id\",\n\t\"whoami\",\n\t\"who\",\n\t\"date\",\n\t\"cal\",\n\t\"env\",\n\t\"printenv\",\n\t\"uname\",\n\t\"hostname\",\n\t\"uptime\",\n\t\"nproc\",\n\t\"tput\",\n\t\"git\",\n\t\"gh\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"yarn\",\n\t\"bun\",\n]);\n\nconst MUTATING_GIT_SUBCOMMANDS = new Set([\n\t\"add\",\n\t\"commit\",\n\t\"push\",\n\t\"pull\",\n\t\"merge\",\n\t\"rebase\",\n\t\"reset\",\n\t\"checkout\",\n\t\"switch\",\n\t\"restore\",\n\t\"rm\",\n\t\"mv\",\n\t\"stash\",\n\t\"cherry-pick\",\n\t\"revert\",\n\t\"clean\",\n\t\"fetch\",\n\t\"config\",\n\t\"apply\",\n\t\"am\",\n\t\"init\",\n\t\"clone\",\n\t\"submodule\",\n\t\"worktree\",\n\t\"gc\",\n\t\"prune\",\n\t\"update-index\",\n]);\n\n// git subcommands that are read-only only when they carry no positional args or\n// mutating flags (`git branch` lists, `git branch foo` / `git branch -d foo` mutate).\nconst CONDITIONAL_GIT_SUBCOMMANDS = new Set([\"branch\", \"tag\", \"remote\"]);\nconst CONDITIONAL_GIT_MUTATING_FLAGS = new Set([\"-d\", \"-D\", \"-m\", \"-M\", \"-c\", \"-C\"]);\n\nconst PACKAGE_MANAGER_MUTATIONS: Record<string, Set<string>> = {\n\tnpm: new Set([\n\t\t\"install\",\n\t\t\"add\",\n\t\t\"remove\",\n\t\t\"uninstall\",\n\t\t\"update\",\n\t\t\"upgrade\",\n\t\t\"publish\",\n\t\t\"link\",\n\t\t\"unlink\",\n\t\t\"ci\",\n\t\t\"init\",\n\t]),\n\tpnpm: new Set([\"install\", \"add\", \"remove\", \"uninstall\", \"update\", \"upgrade\", \"publish\", \"link\", \"unlink\", \"init\"]),\n\tyarn: new Set([\"install\", \"add\", \"remove\", \"uninstall\", \"upgrade\", \"publish\", \"link\", \"unlink\", \"init\"]),\n\tbun: new Set([\"install\", \"add\", \"remove\", \"uninstall\", \"update\", \"upgrade\", \"publish\", \"link\", \"unlink\", \"init\"]),\n};\n\n// Almost every subcommand mutates the system.\nconst ALWAYS_MUTATING_MANAGERS = new Set([\n\t\"apt\",\n\t\"apt-get\",\n\t\"brew\",\n\t\"dnf\",\n\t\"yum\",\n\t\"pacman\",\n\t\"choco\",\n\t\"winget\",\n\t\"scoop\",\n]);\n\n/** Flags that cause an interpreter to execute code and are never allowed in plan mode. */\nconst EXECUTING_NODE_FLAGS = new Set([\n\t\"-e\",\n\t\"--eval\",\n\t\"-p\",\n\t\"--print\",\n\t\"-r\",\n\t\"--require\",\n\t\"--import\",\n\t\"-i\",\n\t\"--interactive\",\n]);\nconst EXECUTING_PYTHON_FLAGS = new Set([\"-c\", \"-m\", \"-i\", \"--interactive\"]);\n\n/** Apply-mode rewrite only. Default / omitted `dry_run` is preview and stays allowed. */\nexport function isCodeRewriteMutating(input: unknown): boolean {\n\treturn (input as { dry_run?: unknown } | undefined)?.dry_run === false;\n}\n\n/**\n * Returns the block reason when plan mode should block this tool call, else undefined.\n */\nexport function planModeBlockReason(toolName: string, input: unknown): string | undefined {\n\tif (BLOCKED_TOOLS.has(toolName)) {\n\t\treturn PLAN_MODE_BLOCK_MESSAGE;\n\t}\n\tif (toolName === \"code_rewrite\" && isCodeRewriteMutating(input)) {\n\t\treturn PLAN_MODE_BLOCK_MESSAGE;\n\t}\n\tif (toolName === \"bash\") {\n\t\tconst command = readBashCommand(input);\n\t\tif (command && isMutatingBashCommand(command)) {\n\t\t\treturn `${PLAN_MODE_BLOCK_MESSAGE} Blocked command: ${command}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction readBashCommand(input: unknown): string {\n\tconst command = (input as { command?: unknown } | undefined)?.command;\n\treturn typeof command === \"string\" ? command : \"\";\n}\n\n/**\n * Plan-mode bash allowlist. A command is mutating unless every segment is a\n * known read-only command used safely (no redirects, no command substitution,\n * no process substitution, no executing-interpreter flags).\n */\nexport function isMutatingBashCommand(command: string): boolean {\n\tif (!command.trim()) return false;\n\n\t// Any output redirect outside quotes can write a file.\n\tif (hasUnquotedRedirect(command)) return true;\n\n\t// Command substitution / process substitution / grouped redirect syntaxes\n\t// bypass a simple command-name check.\n\tif (hasShellSubstitution(command)) return true;\n\n\t// Every segment separated by ; | & && || must individually be read-only.\n\tfor (const segment of splitShellSegments(command)) {\n\t\tif (isMutatingSegment(segment)) return true;\n\t}\n\treturn false;\n}\n\nfunction hasUnquotedRedirect(command: string): boolean {\n\tlet quote: \"'\" | '\"' | undefined;\n\tlet escaped = false;\n\tfor (let i = 0; i < command.length; i++) {\n\t\tconst ch = command[i];\n\t\tif (escaped) {\n\t\t\tescaped = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\\\" && quote !== \"'\") {\n\t\t\tescaped = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tif (ch === quote) quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"'\" || ch === '\"') {\n\t\t\tquote = ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \">\") return true;\n\t}\n\treturn false;\n}\n\nfunction splitShellSegments(command: string): string[] {\n\tconst segments: string[] = [];\n\tlet quote: \"'\" | '\"' | undefined;\n\tlet escaped = false;\n\tlet start = 0;\n\tfor (let i = 0; i < command.length; i++) {\n\t\tconst ch = command[i];\n\t\tconst next = command[i + 1];\n\t\tif (escaped) {\n\t\t\tescaped = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\\\" && quote !== \"'\") {\n\t\t\tescaped = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tif (ch === quote) quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"'\" || ch === '\"') {\n\t\t\tquote = ch;\n\t\t\tcontinue;\n\t\t}\n\t\t// Combined operators && and || split as one boundary.\n\t\tif ((ch === \"&\" && next === \"&\") || (ch === \"|\" && next === \"|\")) {\n\t\t\tsegments.push(command.slice(start, i));\n\t\t\ti++;\n\t\t\tstart = i + 1;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \";\" || ch === \"|\" || ch === \"&\" || ch === \"\\n\") {\n\t\t\tsegments.push(command.slice(start, i));\n\t\t\tstart = i + 1;\n\t\t}\n\t}\n\tsegments.push(command.slice(start));\n\treturn segments.map((s) => s.trim()).filter((s) => s.length > 0);\n}\n\n/** True when the command contains command/process substitution outside quotes. */\nfunction hasShellSubstitution(command: string): boolean {\n\tlet quote: \"'\" | '\"' | undefined;\n\tlet escaped = false;\n\tfor (let i = 0; i < command.length; i++) {\n\t\tconst ch = command[i];\n\t\tif (escaped) {\n\t\t\tescaped = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"\\\\\" && quote !== \"'\") {\n\t\t\tescaped = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tif (ch === quote) quote = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"'\" || ch === '\"') {\n\t\t\tquote = ch;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === \"`\") return true;\n\t\tif (ch === \"$\" && command[i + 1] === \"(\") return true;\n\t\tif (ch === \"<\" && command[i + 1] === \"(\") return true;\n\t\tif (ch === \">\" && command[i + 1] === \"(\") return true;\n\t\tif (ch === \"&\" && command[i + 1] === \">\") return true;\n\t}\n\treturn false;\n}\n\nfunction isMutatingSegment(segment: string): boolean {\n\tlet tokens = segment.split(/\\s+/).filter((t) => t.length > 0);\n\t// Skip leading env assignments (FOO=bar cmd …) and command wrappers we can't see through.\n\twhile (tokens.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0])) {\n\t\ttokens = tokens.slice(1);\n\t}\n\tif (tokens.length === 0) return false;\n\n\tconst command = basenameOf(tokens[0]).toLowerCase();\n\tconst args = tokens.slice(1);\n\n\tif (ALWAYS_MUTATING_MANAGERS.has(command)) return true;\n\n\tif (command === \"sed\") {\n\t\treturn args.some((a) => a === \"-i\" || a.startsWith(\"-i\") || a.startsWith(\"--in-place\") || /^-[^-]*i/.test(a));\n\t}\n\tif (command === \"find\") {\n\t\treturn args.some((a) => [\"-delete\", \"-exec\", \"-execdir\", \"-ok\", \"-okdir\"].includes(a));\n\t}\n\tif (command === \"git\") {\n\t\treturn isMutatingGit(args);\n\t}\n\tif (command === \"gh\") {\n\t\t// Allow read-only views; block everything that changes remote state.\n\t\tconst sub = `${args[0] ?? \"\"} ${args[1] ?? \"\"}`;\n\t\treturn ![\n\t\t\t\"pr view\",\n\t\t\t\"pr list\",\n\t\t\t\"pr status\",\n\t\t\t\"issue view\",\n\t\t\t\"issue list\",\n\t\t\t\"issue status\",\n\t\t\t\"repo view\",\n\t\t\t\"release view\",\n\t\t\t\"release list\",\n\t\t\t\"run list\",\n\t\t\t\"run view\",\n\t\t\t\"status\",\n\t\t].includes(sub.trim());\n\t}\n\tif (command === \"sh\" || command === \"bash\" || command === \"zsh\" || command === \"dash\") {\n\t\t// `sh -c '…'` runs arbitrary code; bare `sh script` also executes. Block -c; allow\n\t\t// nothing else is too restrictive for `bash --version`, so allow flag-only invocations.\n\t\treturn args.some((a) => a === \"-c\" || a === \"-s\") || args.some((a) => !a.startsWith(\"-\"));\n\t}\n\n\tconst pkgMutations = PACKAGE_MANAGER_MUTATIONS[command];\n\tif (pkgMutations) {\n\t\tconst subcommand = args.find((a) => !a.startsWith(\"-\"))?.toLowerCase();\n\t\treturn subcommand !== undefined && pkgMutations.has(subcommand);\n\t}\n\n\t// Node / Python interpreters: flag-only, no executing flags, no positional scripts.\n\tif (command === \"node\") {\n\t\treturn args.some((a) => !a.startsWith(\"-\")) || args.some((a) => EXECUTING_NODE_FLAGS.has(a));\n\t}\n\tif (command === \"python\" || command === \"python3\") {\n\t\treturn args.some((a) => !a.startsWith(\"-\")) || args.some((a) => EXECUTING_PYTHON_FLAGS.has(a));\n\t}\n\n\t// Final gate: the command must be in the read-only allowlist.\n\treturn !ALLOWED_COMMANDS.has(command);\n}\n\n/** Global git options that take no value. Unknown leading flags are treated as mutating. */\nconst GIT_GLOBAL_FLAGS = new Set([\n\t\"--no-pager\",\n\t\"--paginate\",\n\t\"-p\",\n\t\"--version\",\n\t\"--help\",\n\t\"-h\",\n\t\"--bare\",\n\t\"--no-replace-objects\",\n\t\"--no-optional-locks\",\n\t\"--literal-pathspecs\",\n\t\"--glob-pathspecs\",\n\t\"--noglob-pathspecs\",\n\t\"--icase-pathspecs\",\n]);\n\n/** Walk past known `git` globals. Returns the subcommand index, or -1 to block. */\nfunction gitSubcommandIndex(args: string[]): number {\n\tlet index = 0;\n\twhile (index < args.length) {\n\t\tconst token = args[index];\n\t\tif (!token.startsWith(\"-\")) return index;\n\t\tif (token === \"--\") return index + 1;\n\t\tif (GIT_GLOBAL_FLAGS.has(token)) {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (token === \"-C\" || token === \"-c\" || token === \"--git-dir\" || token === \"--work-tree\" || token === \"--namespace\") {\n\t\t\tif (args[index + 1] === undefined) return -1;\n\t\t\tindex += 2;\n\t\t\tcontinue;\n\t\t}\n\t\tif (\n\t\t\ttoken.startsWith(\"--git-dir=\") ||\n\t\t\ttoken.startsWith(\"--work-tree=\") ||\n\t\t\ttoken.startsWith(\"--namespace=\") ||\n\t\t\ttoken.startsWith(\"--config-env=\")\n\t\t) {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\treturn -1;\n\t}\n\treturn index;\n}\n\nfunction isMutatingGit(args: string[]): boolean {\n\tconst index = gitSubcommandIndex(args);\n\tif (index < 0) return true;\n\tconst subcommand = args[index]?.toLowerCase();\n\tif (!subcommand) return false;\n\n\tif (MUTATING_GIT_SUBCOMMANDS.has(subcommand)) return true;\n\tif (CONDITIONAL_GIT_SUBCOMMANDS.has(subcommand)) {\n\t\tconst rest = args.slice(index + 1);\n\t\treturn rest.some((a) => !a.startsWith(\"-\") || CONDITIONAL_GIT_MUTATING_FLAGS.has(a));\n\t}\n\treturn false;\n}\n\nfunction basenameOf(token: string): string {\n\tconst parts = token.split(/[\\\\/]/);\n\treturn parts[parts.length - 1] ?? token;\n}\n"]}
|