@ashx-j/lunr-dev 0.2.16-dev.9.1 → 0.2.19-dev.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -1
- package/README.md +7 -1
- package/dist/builtin-extensions/ashxj-thinking.d.ts +1 -0
- package/dist/builtin-extensions/ashxj-thinking.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-thinking.js +44 -19
- package/dist/builtin-extensions/ashxj-thinking.js.map +1 -1
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +1 -0
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-computer-use.d.ts +3 -0
- package/dist/builtin-extensions/lunr-computer-use.d.ts.map +1 -0
- package/dist/builtin-extensions/lunr-computer-use.js +157 -0
- package/dist/builtin-extensions/lunr-computer-use.js.map +1 -0
- package/dist/builtin-extensions/pi-lsp-extension/src/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/index.js +10 -3
- package/dist/builtin-extensions/pi-lsp-extension/src/index.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/runtime.d.ts +5 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/runtime.js +26 -4
- package/dist/builtin-extensions/pi-lsp-extension/src/runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.js +37 -27
- package/dist/builtin-extensions/pi-mcp-adapter/index.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/server-manager.d.ts +6 -1
- package/dist/builtin-extensions/pi-mcp-adapter/server-manager.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/server-manager.js +33 -8
- package/dist/builtin-extensions/pi-mcp-adapter/server-manager.js.map +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 +52 -23
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +6 -2
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +5 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/request-params.d.ts +10 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/request-params.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/request-params.js +38 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/request-params.js.map +1 -0
- 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 +3 -16
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +70 -30
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.js +103 -102
- package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/computer-use/adapter.d.ts +23 -0
- package/dist/core/computer-use/adapter.d.ts.map +1 -0
- package/dist/core/computer-use/adapter.js +116 -0
- package/dist/core/computer-use/adapter.js.map +1 -0
- package/dist/core/computer-use/lease.d.ts +18 -0
- package/dist/core/computer-use/lease.d.ts.map +1 -0
- package/dist/core/computer-use/lease.js +129 -0
- package/dist/core/computer-use/lease.js.map +1 -0
- package/dist/core/computer-use/macos-runtime.d.ts +14 -0
- package/dist/core/computer-use/macos-runtime.d.ts.map +1 -0
- package/dist/core/computer-use/macos-runtime.js +148 -0
- package/dist/core/computer-use/macos-runtime.js.map +1 -0
- package/dist/core/computer-use/policy.d.ts +19 -0
- package/dist/core/computer-use/policy.d.ts.map +1 -0
- package/dist/core/computer-use/policy.js +66 -0
- package/dist/core/computer-use/policy.js.map +1 -0
- package/dist/core/computer-use/release.generated.d.ts +31 -0
- package/dist/core/computer-use/release.generated.d.ts.map +1 -0
- package/dist/core/computer-use/release.generated.js +36 -0
- package/dist/core/computer-use/release.generated.js.map +1 -0
- package/dist/core/computer-use/runtime.d.ts +13 -0
- package/dist/core/computer-use/runtime.d.ts.map +1 -0
- package/dist/core/computer-use/runtime.js +158 -0
- package/dist/core/computer-use/runtime.js.map +1 -0
- package/dist/core/computer-use/schemas.d.ts +88 -0
- package/dist/core/computer-use/schemas.d.ts.map +1 -0
- package/dist/core/computer-use/schemas.js +80 -0
- package/dist/core/computer-use/schemas.js.map +1 -0
- package/dist/core/computer-use/windows-desktop.d.ts +3 -0
- package/dist/core/computer-use/windows-desktop.d.ts.map +1 -0
- package/dist/core/computer-use/windows-desktop.js +48 -0
- package/dist/core/computer-use/windows-desktop.js.map +1 -0
- package/dist/core/computer-use/workflow.d.ts +24 -0
- package/dist/core/computer-use/workflow.d.ts.map +1 -0
- package/dist/core/computer-use/workflow.js +330 -0
- package/dist/core/computer-use/workflow.js.map +1 -0
- package/dist/core/keybindings.d.ts +2 -2
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +2 -2
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-runtime.d.ts +0 -3
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +1 -10
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +1 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/permissions.d.ts +1 -1
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +23 -7
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +9 -0
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/rollback.d.ts.map +1 -1
- package/dist/core/rollback.js +13 -90
- package/dist/core/rollback.js.map +1 -1
- package/dist/core/session-manager.d.ts +0 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +21 -79
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +19 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/dev-cli.js +0 -0
- package/dist/gateway/agent-bridge.d.ts +6 -1
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +23 -6
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/cron.d.ts +1 -1
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +23 -7
- package/dist/gateway/cron.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +4 -2
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +23 -32
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/processes-selector.d.ts +2 -3
- package/dist/modes/interactive/components/processes-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/processes-selector.js +5 -10
- package/dist/modes/interactive/components/processes-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +20 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +18 -8
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/thinking-level-slash.d.ts +4 -0
- package/dist/modes/interactive/thinking-level-slash.d.ts.map +1 -0
- package/dist/modes/interactive/thinking-level-slash.js +23 -0
- package/dist/modes/interactive/thinking-level-slash.js.map +1 -0
- package/dist/node-runtime/adapter-AZMY335J.js +279 -0
- package/dist/node-runtime/{agent-session-services-IFJMSX7Z.js → agent-session-services-RSGP5GDN.js} +15 -12
- package/dist/node-runtime/{builtin-extensions-LYZ3BFWQ.js → builtin-extensions-7STLFW3A.js} +4 -3
- package/dist/node-runtime/chunk-2IDTZAG5.js +1415 -0
- package/dist/node-runtime/{chunk-7NBZ5P7I.js → chunk-4SJWFTUP.js} +1 -1
- package/dist/node-runtime/{chunk-AJX357CL.js → chunk-5NWQCSKR.js} +20 -1013
- package/dist/node-runtime/chunk-5OLJGVCB.js +68 -0
- package/dist/node-runtime/chunk-5Q45AVQG.js +108 -0
- package/dist/node-runtime/{chunk-TQNSPERU.js → chunk-5SQDLSZJ.js} +1 -1
- package/dist/node-runtime/{chunk-F4VQFJI2.js → chunk-6SPPH4FY.js} +4 -4
- package/dist/node-runtime/{chunk-PBC2Y6GS.js → chunk-6UB6NKA3.js} +29 -72
- package/dist/node-runtime/{chunk-A2KQXUJD.js → chunk-7AFOFSDY.js} +1 -1
- package/dist/node-runtime/chunk-BKAZ6BKX.js +2185 -0
- package/dist/node-runtime/{chunk-AHSTJBIB.js → chunk-EH2LJQPM.js} +4 -4
- package/dist/node-runtime/{chunk-N77MY65L.js → chunk-FLYMHKCS.js} +25 -25
- package/dist/node-runtime/{chunk-5P7PZMRU.js → chunk-HRS4HMDX.js} +1 -1
- package/dist/node-runtime/{chunk-IPA627GP.js → chunk-ISQDQIZN.js} +4 -4
- package/dist/node-runtime/{chunk-WJVFOIB5.js → chunk-J7QGQD45.js} +1 -1
- package/dist/node-runtime/{chunk-WOVTOV5P.js → chunk-JT6YCS5B.js} +29 -34
- package/dist/node-runtime/chunk-JZHEUQRL.js +180 -0
- package/dist/node-runtime/{chunk-ZKMFUD3Q.js → chunk-K72NVGUB.js} +3 -3
- package/dist/node-runtime/{chunk-ECD5L7YI.js → chunk-LCACDWHJ.js} +389 -15
- package/dist/node-runtime/{chunk-2QKLKMKC.js → chunk-LTXUQJI4.js} +1 -1
- package/dist/node-runtime/{chunk-MKKH27PH.js → chunk-LWJXTA6Z.js} +274 -2416
- package/dist/node-runtime/{chunk-EDBJNRSM.js → chunk-MGSB2C3M.js} +1 -1
- package/dist/node-runtime/{chunk-CDFZEWPU.js → chunk-MUMMMTW4.js} +6 -2
- package/dist/node-runtime/{chunk-YF63P5ZT.js → chunk-OTT6JMPU.js} +10 -67
- package/dist/node-runtime/{chunk-PGAJ5MO5.js → chunk-T5GM5BFH.js} +1 -1
- package/dist/node-runtime/{chunk-PCGNAY2O.js → chunk-TAP7QTUT.js} +38 -16
- package/dist/node-runtime/{chunk-4TLO4PO2.js → chunk-U6C7VTR5.js} +7 -1401
- package/dist/node-runtime/chunk-URD2SLIT.js +1019 -0
- package/dist/node-runtime/{chunk-QH42CDX3.js → chunk-W7S2REEG.js} +111 -84
- package/dist/node-runtime/{chunk-LZAW4ZX3.js → chunk-ZRDIMBEQ.js} +27 -9
- package/dist/node-runtime/{chunk-AD6VXAO7.js → chunk-ZSEZY3II.js} +5 -100
- package/dist/node-runtime/cli-runtime.js +1 -1
- package/dist/node-runtime/{command-L3LAHDIH.js → command-6UXQ6XZN.js} +1 -1
- package/dist/node-runtime/{commands-RL5YBW5I.js → commands-LGR5VT5X.js} +6 -4
- package/dist/node-runtime/{direct-tool-executor-JVDXTO7A.js → direct-tool-executor-TDJS7TBD.js} +9 -6
- package/dist/node-runtime/{export-html-KR26FOUZ.js → export-html-E4DT6ZJQ.js} +1 -1
- package/dist/node-runtime/{extract-XLQSJN4V.js → extract-IUCVZCWC.js} +2 -2
- package/dist/node-runtime/{gateway-X2TI5X7F.js → gateway-3OLC4GQN.js} +22 -367
- package/dist/node-runtime/{gemini-search-U7SUMPQA.js → gemini-search-NCIDF7NE.js} +1 -1
- package/dist/node-runtime/{gemini-url-context-PXWCBQHK.js → gemini-url-context-OKIN7AIP.js} +2 -2
- package/dist/node-runtime/image-resize-6ZPBFMWW.js +11 -0
- package/dist/node-runtime/index.js +53 -49
- package/dist/node-runtime/{init-NJV2T55N.js → init-EKE277AG.js} +6 -4
- package/dist/node-runtime/{interactive-mode-KOM35YF2.js → interactive-mode-S5M26CQ7.js} +20 -17
- package/dist/node-runtime/{interactive-view-U6OILKF3.js → interactive-view-N6F4KUIT.js} +3 -5
- package/dist/node-runtime/lunr-computer-use-4X3KUNF4.js +206 -0
- package/dist/node-runtime/{lunr-cron-YJ5UHYRD.js → lunr-cron-KKBOGAEF.js} +7 -1
- package/dist/node-runtime/main.js +24 -21
- package/dist/node-runtime/{mcp-auth-flow-QIXHW6QN.js → mcp-auth-flow-YL6RWPYG.js} +2 -1
- package/dist/node-runtime/{package-manager-cli-TZZHJCVI.js → package-manager-cli-C4EJOO5Q.js} +8 -6
- package/dist/node-runtime/{pi-mcp-adapter-QFB37AVN.js → pi-mcp-adapter-DPTJGAAL.js} +33 -27
- package/dist/node-runtime/{pi-subagents-7XJ4H53P.js → pi-subagents-VKCLYIBY.js} +103 -76
- package/dist/node-runtime/{pi-web-access-DPGAVKOA.js → pi-web-access-BV6DSZOP.js} +72 -69
- package/dist/node-runtime/{proxy-modes-HEEFK4KC.js → proxy-modes-R64LU6WZ.js} +12 -9
- package/dist/node-runtime/rpc-entry.js +24 -21
- package/dist/node-runtime/runtime-3SB5ATHQ.js +21 -0
- package/dist/node-runtime/{runtime-bridges-PPHNBFWL.js → runtime-bridges-UJ5WJJLN.js} +3 -3
- package/dist/node-runtime/{session-manager-MQNBYACL.js → session-manager-QEMZBMTM.js} +3 -5
- package/dist/node-runtime/{session-selector-5AZLJS6U.js → session-selector-URVJLOTK.js} +2 -2
- package/dist/node-runtime/{settings-manager-5LF2VQWC.js → settings-manager-K6IPIQTI.js} +3 -1
- package/dist/node-runtime/{src-Y7JSCK64.js → src-GPFAU3N6.js} +30 -10
- package/dist/node-runtime/{subagent-executor-5RNDEPIJ.js → subagent-executor-EFLTO32A.js} +39 -21
- package/dist/node-runtime/{update-cli-A253SZ7P.js → update-cli-I6OVDRPL.js} +3 -1
- package/dist/node-runtime/{video-extract-OYT5WKS5.js → video-extract-ETA5RA7U.js} +2 -2
- package/dist/node-runtime/workflow-SA34S4VX.js +325 -0
- package/dist/node-runtime/{youtube-extract-TQYKGFGS.js → youtube-extract-SRVGNN26.js} +2 -2
- package/dist/rpc.d.ts +3 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +18 -0
- package/dist/rpc.js.map +1 -0
- package/dist/startup/interactive-view.d.ts.map +1 -1
- package/dist/startup/interactive-view.js +1 -2
- package/dist/startup/interactive-view.js.map +1 -1
- package/docs/computer-use.md +277 -0
- package/docs/development.md +0 -32
- package/docs/features.md +2 -2
- package/docs/interactive-startup.md +10 -3
- package/docs/keybindings.md +2 -2
- package/docs/terminal-setup.md +1 -1
- package/docs/usage.md +1 -1
- 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/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/hidden-thinking-label.ts +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/install-lock/package-lock.json +3827 -0
- package/install-lock/package.json +18 -0
- package/native/computer-use/LICENSE.md +21 -0
- package/npm-shrinkwrap.json +3808 -0
- package/package.json +16 -10
- package/dist/startup/interactive-shell.d.ts +0 -55
- package/dist/startup/interactive-shell.d.ts.map +0 -1
- package/dist/startup/interactive-shell.js +0 -264
- package/dist/startup/interactive-shell.js.map +0 -1
- package/dist/node-runtime/{chunk-YSKSB3BJ.js → chunk-CH6OL6P2.js} +2 -2
- package/dist/node-runtime/{chunk-VSELYKWU.js → chunk-EHMBW7FA.js} +3 -3
|
@@ -55,7 +55,7 @@ export type ApprovalResponse = ApprovalDecision | {
|
|
|
55
55
|
export declare const NO_HANDLER_REASON = "Mutating tool blocked in manual mode: no approval channel available.";
|
|
56
56
|
export declare const LARGE_SUBAGENT_LAUNCH_REJECT_REASON = "Large subagent launch rejected by user.";
|
|
57
57
|
export declare const NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON = "Large subagent launch blocked: no approval channel available.";
|
|
58
|
-
export declare function createPermissionContext(sessionId: string, mode?: PermissionMode): void;
|
|
58
|
+
export declare function createPermissionContext(sessionId: string, mode?: PermissionMode, allowApprovals?: boolean): void;
|
|
59
59
|
export declare function deletePermissionContext(sessionId: string): void;
|
|
60
60
|
export declare function getPermissionMode(sessionId?: string): PermissionMode;
|
|
61
61
|
export declare function setPermissionMode(mode: PermissionMode, sessionId?: string): void;
|
|
@@ -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 { dirname, join, resolve } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { effectiveLargeSubagentLaunchCountForTurn, LARGE_SUBAGENT_LAUNCH_THRESHOLD } from \"./large-subagent-launch.ts\";\nimport { isUserInstructionsPath } from \"./model-instructions.ts\";\nimport { isCodeRewriteMutating, planModeBlockReason } from \"./plan-mode.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 * \"subagent-full\" for full-access children, or \"large-subagent-launch\" for aggregate confirmation. */\n\taction: string;\n\tdetail: string;\n\t/** \"large-subagent-launch\" for aggregate child confirmation, \"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?: \"large-subagent-launch\" | \"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\t\"settings_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 LARGE_SUBAGENT_LAUNCH_REJECT_REASON = \"Large subagent launch rejected by user.\";\nexport const NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON = \"Large subagent launch blocked: no approval channel available.\";\n/** Session-approval keys for child launches. */\nconst LARGE_SUBAGENT_LAUNCH_ACTION = \"large-subagent-launch\";\nconst FULL_CHILD_ACTION = \"subagent-full\";\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 aggregate prompt covers every sibling SINGLE `subagent` on the same assistant message. */\nconst turnLargeLaunchDecisions = new WeakMap<\n\tobject,\n\t{ decision: ApprovalDecision; feedback?: string; fullChildrenApproved: boolean }\n>();\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 mutating-tool and aggregate launch 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 agents instruction tree is user-managed. The agent cannot change ~/.lunr/agent/agents/.\";\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\tif (isUserInstructionsPath(target, getAgentDir())) {\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\ninterface RequestedChildLaunch {\n\tdescription: string;\n\tpermissions: \"full\" | \"read-only\";\n}\n\nfunction collectRequestedChildLaunches(value: unknown, launches: RequestedChildLaunch[]): void {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return;\n\tconst input = value as Record<string, unknown>;\n\tif (typeof input.task === \"string\" && input.task.trim()) {\n\t\tlaunches.push({\n\t\t\tdescription:\n\t\t\t\ttypeof input.description === \"string\" && input.description.trim()\n\t\t\t\t\t? input.description.trim()\n\t\t\t\t\t: input.task.trim(),\n\t\t\tpermissions: input.permissions === \"read-only\" ? \"read-only\" : \"full\",\n\t\t});\n\t\treturn;\n\t}\n\tfor (const key of [\"tasks\", \"chain\", \"parallel\"] as const) {\n\t\tconst children = input[key];\n\t\tif (Array.isArray(children)) {\n\t\t\tfor (const child of children) collectRequestedChildLaunches(child, launches);\n\t\t} else if (key === \"parallel\") {\n\t\t\tcollectRequestedChildLaunches(children, launches);\n\t\t}\n\t}\n}\n\nfunction getRequestedChildLaunches(input: Record<string, unknown>): RequestedChildLaunch[] {\n\tconst launches: RequestedChildLaunch[] = [];\n\tcollectRequestedChildLaunches(input, launches);\n\treturn launches;\n}\n\nfunction requiresManualApproval(toolName: string, input: Record<string, unknown>): boolean {\n\tif (isMutatingTool(toolName)) return true;\n\tif (toolName.startsWith(\"settings_\") && toolName !== \"settings_load\") return Object.keys(input).length > 0;\n\tif (toolName === \"subagent\") {\n\t\treturn getRequestedChildLaunches(input).some((launch) => launch.permissions === \"full\");\n\t}\n\treturn toolName === \"code_rewrite\" && isCodeRewriteMutating(input);\n}\n\nexport interface GateOptions {\n\t/** Global preference for aggregate confirmation. Defaults to enabled. */\n\tconfirmLargeSubagentLaunches?: boolean;\n\t/** Assistant message that issued this tool call. Same-turn sibling SINGLE\n\t * `subagent` calls count toward the aggregate 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 largeLaunchTaskSummary(\n\tinput: Record<string, unknown>,\n\tassistantMessage?: GateOptions[\"assistantMessage\"],\n): 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 * Aggregate launch gate. A launch above LARGE_SUBAGENT_LAUNCH_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.\n * Fail-closed without an approval handler, same as the mutating-tool gate.\n */\nasync function gateLargeSubagentLaunch(\n\tinput: Record<string, unknown>,\n\tctx: PermissionContext,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | { fullChildrenApproved: boolean } | undefined> {\n\tif (ctx.mode === \"auto\") return undefined;\n\tif (options?.confirmLargeSubagentLaunches === false) return undefined;\n\tconst count = effectiveLargeSubagentLaunchCountForTurn(input, options?.assistantMessage);\n\tif (count <= LARGE_SUBAGENT_LAUNCH_THRESHOLD) return undefined;\n\tif (ctx.approvals.has(LARGE_SUBAGENT_LAUNCH_ACTION)) return { fullChildrenApproved: false };\n\n\tconst turnKey = options?.assistantMessage;\n\tconst cached = turnKey ? turnLargeLaunchDecisions.get(turnKey) : undefined;\n\tif (cached) {\n\t\tif (cached.decision === \"reject\") {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\treason: cached.feedback\n\t\t\t\t\t? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${cached.feedback}`\n\t\t\t\t\t: LARGE_SUBAGENT_LAUNCH_REJECT_REASON,\n\t\t\t};\n\t\t}\n\t\treturn { fullChildrenApproved: cached.fullChildrenApproved };\n\t}\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON };\n\t}\n\n\tconst summary = largeLaunchTaskSummary(input, options?.assistantMessage);\n\tconst siblingLaunches = (options?.assistantMessage?.content ?? []).flatMap((block) => {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") return [];\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\treturn args ? getRequestedChildLaunches(args) : [];\n\t});\n\tconst launches = siblingLaunches.length > 0 ? siblingLaunches : getRequestedChildLaunches(input);\n\tconst fullChildrenApproved = launches.some((launch) => launch.permissions === \"full\");\n\tconst launchSummary = launches\n\t\t.map((launch) => `${launch.description}\\npermissions: ${launch.permissions}`)\n\t\t.join(\"\\n\");\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({\n\t\t\ttoolName: \"subagent\",\n\t\t\taction: LARGE_SUBAGENT_LAUNCH_ACTION,\n\t\t\tdetail: `large subagent launch (${count} children)${launchSummary || summary ? `\\n${launchSummary || summary}` : \"\"}`,\n\t\t\tkind: \"large-subagent-launch\",\n\t\t});\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_LARGE_SUBAGENT_LAUNCH_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) {\n\t\tturnLargeLaunchDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}), fullChildrenApproved });\n\t}\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(LARGE_SUBAGENT_LAUNCH_ACTION);\n\t\tif (fullChildrenApproved) ctx.approvals.add(FULL_CHILD_ACTION);\n\t\treturn { fullChildrenApproved };\n\t}\n\tif (decision === \"reject\") {\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: feedback ? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${feedback}` : LARGE_SUBAGENT_LAUNCH_REJECT_REASON,\n\t\t};\n\t}\n\treturn { fullChildrenApproved };\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// Aggregate launch confirmation runs before the mode early-return, including YOLO.\n\t// yolo mode too (only auto mode bypasses it).\n\tlet largeLaunchCoveredManualApproval = false;\n\tif (toolName === \"subagent\") {\n\t\tconst launchResult = await gateLargeSubagentLaunch(input, ctx, options);\n\t\tif (launchResult && \"block\" in launchResult) return launchResult;\n\t\tlargeLaunchCoveredManualApproval = ctx.mode === \"manual\" && launchResult?.fullChildrenApproved === true;\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) || largeLaunchCoveredManualApproval) 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 === \"subagent\") {\n\t\taction = FULL_CHILD_ACTION;\n\t\tconst launches = getRequestedChildLaunches(input).filter((launch) => launch.permissions === \"full\");\n\t\tdetail = sanitizeDetail(\n\t\t\tlaunches.map((launch) => `${launch.description}\\npermissions: ${launch.permissions}`).join(\"\\n\"),\n\t\t);\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 { computerPolicy } from \"./computer-use/policy.ts\";\nimport { effectiveLargeSubagentLaunchCountForTurn, LARGE_SUBAGENT_LAUNCH_THRESHOLD } from \"./large-subagent-launch.ts\";\nimport { isUserInstructionsPath } from \"./model-instructions.ts\";\nimport { isCodeRewriteMutating, planModeBlockReason } from \"./plan-mode.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 * \"subagent-full\" for full-access children, or \"large-subagent-launch\" for aggregate confirmation. */\n\taction: string;\n\tdetail: string;\n\t/** \"large-subagent-launch\" for aggregate child confirmation, \"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?: \"large-subagent-launch\" | \"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\t\"settings_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 LARGE_SUBAGENT_LAUNCH_REJECT_REASON = \"Large subagent launch rejected by user.\";\nexport const NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON = \"Large subagent launch blocked: no approval channel available.\";\n/** Session-approval keys for child launches. */\nconst LARGE_SUBAGENT_LAUNCH_ACTION = \"large-subagent-launch\";\nconst FULL_CHILD_ACTION = \"subagent-full\";\n\ninterface PermissionContext {\n\tallowApprovals?: boolean;\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 aggregate prompt covers every sibling SINGLE `subagent` on the same assistant message. */\nconst turnLargeLaunchDecisions = new WeakMap<\n\tobject,\n\t{ decision: ApprovalDecision; feedback?: string; fullChildrenApproved: boolean }\n>();\n\nfunction getContext(sessionId?: string): PermissionContext {\n\tif (!sessionId) return defaultContext;\n\treturn contexts.get(sessionId) ?? defaultContext;\n}\n\nexport function createPermissionContext(\n\tsessionId: string,\n\tmode: PermissionMode = defaultContext.mode,\n\tallowApprovals = true,\n): void {\n\tcontexts.set(sessionId, { mode, allowApprovals, 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 mutating-tool and aggregate launch 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 agents instruction tree is user-managed. The agent cannot change ~/.lunr/agent/agents/.\";\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\tif (isUserInstructionsPath(target, getAgentDir())) {\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\ninterface RequestedChildLaunch {\n\tdescription: string;\n\tpermissions: \"full\" | \"read-only\";\n}\n\nfunction collectRequestedChildLaunches(value: unknown, launches: RequestedChildLaunch[]): void {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return;\n\tconst input = value as Record<string, unknown>;\n\tif (typeof input.task === \"string\" && input.task.trim()) {\n\t\tlaunches.push({\n\t\t\tdescription:\n\t\t\t\ttypeof input.description === \"string\" && input.description.trim()\n\t\t\t\t\t? input.description.trim()\n\t\t\t\t\t: input.task.trim(),\n\t\t\tpermissions: input.permissions === \"read-only\" ? \"read-only\" : \"full\",\n\t\t});\n\t\treturn;\n\t}\n\tfor (const key of [\"tasks\", \"chain\", \"parallel\"] as const) {\n\t\tconst children = input[key];\n\t\tif (Array.isArray(children)) {\n\t\t\tfor (const child of children) collectRequestedChildLaunches(child, launches);\n\t\t} else if (key === \"parallel\") {\n\t\t\tcollectRequestedChildLaunches(children, launches);\n\t\t}\n\t}\n}\n\nfunction getRequestedChildLaunches(input: Record<string, unknown>): RequestedChildLaunch[] {\n\tconst launches: RequestedChildLaunch[] = [];\n\tcollectRequestedChildLaunches(input, launches);\n\treturn launches;\n}\n\nfunction requiresManualApproval(toolName: string, input: Record<string, unknown>): boolean {\n\tif (toolName.startsWith(\"computer_\")) return toolName !== \"computer_end\";\n\tif (isMutatingTool(toolName)) return true;\n\tif (toolName.startsWith(\"settings_\") && toolName !== \"settings_load\") return Object.keys(input).length > 0;\n\tif (toolName === \"subagent\") {\n\t\treturn getRequestedChildLaunches(input).some((launch) => launch.permissions === \"full\");\n\t}\n\treturn toolName === \"code_rewrite\" && isCodeRewriteMutating(input);\n}\n\nexport interface GateOptions {\n\t/** Global preference for aggregate confirmation. Defaults to enabled. */\n\tconfirmLargeSubagentLaunches?: boolean;\n\t/** Assistant message that issued this tool call. Same-turn sibling SINGLE\n\t * `subagent` calls count toward the aggregate 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 largeLaunchTaskSummary(\n\tinput: Record<string, unknown>,\n\tassistantMessage?: GateOptions[\"assistantMessage\"],\n): 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 * Aggregate launch gate. A launch above LARGE_SUBAGENT_LAUNCH_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.\n * Fail-closed without an approval handler, same as the mutating-tool gate.\n */\nasync function gateLargeSubagentLaunch(\n\tinput: Record<string, unknown>,\n\tctx: PermissionContext,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | { fullChildrenApproved: boolean } | undefined> {\n\tif (ctx.mode === \"auto\") return undefined;\n\tif (options?.confirmLargeSubagentLaunches === false) return undefined;\n\tconst count = effectiveLargeSubagentLaunchCountForTurn(input, options?.assistantMessage);\n\tif (count <= LARGE_SUBAGENT_LAUNCH_THRESHOLD) return undefined;\n\tif (ctx.approvals.has(LARGE_SUBAGENT_LAUNCH_ACTION)) return { fullChildrenApproved: false };\n\n\tconst turnKey = options?.assistantMessage;\n\tconst cached = turnKey ? turnLargeLaunchDecisions.get(turnKey) : undefined;\n\tif (cached) {\n\t\tif (cached.decision === \"reject\") {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\treason: cached.feedback\n\t\t\t\t\t? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${cached.feedback}`\n\t\t\t\t\t: LARGE_SUBAGENT_LAUNCH_REJECT_REASON,\n\t\t\t};\n\t\t}\n\t\treturn { fullChildrenApproved: cached.fullChildrenApproved };\n\t}\n\n\tif (!approvalHandler || ctx.allowApprovals === false) {\n\t\treturn { block: true, reason: NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON };\n\t}\n\n\tconst summary = largeLaunchTaskSummary(input, options?.assistantMessage);\n\tconst siblingLaunches = (options?.assistantMessage?.content ?? []).flatMap((block) => {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") return [];\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\treturn args ? getRequestedChildLaunches(args) : [];\n\t});\n\tconst launches = siblingLaunches.length > 0 ? siblingLaunches : getRequestedChildLaunches(input);\n\tconst fullChildrenApproved = launches.some((launch) => launch.permissions === \"full\");\n\tconst launchSummary = launches\n\t\t.map((launch) => `${launch.description}\\npermissions: ${launch.permissions}`)\n\t\t.join(\"\\n\");\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({\n\t\t\ttoolName: \"subagent\",\n\t\t\taction: LARGE_SUBAGENT_LAUNCH_ACTION,\n\t\t\tdetail: `large subagent launch (${count} children)${launchSummary || summary ? `\\n${launchSummary || summary}` : \"\"}`,\n\t\t\tkind: \"large-subagent-launch\",\n\t\t});\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_LARGE_SUBAGENT_LAUNCH_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) {\n\t\tturnLargeLaunchDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}), fullChildrenApproved });\n\t}\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(LARGE_SUBAGENT_LAUNCH_ACTION);\n\t\tif (fullChildrenApproved) ctx.approvals.add(FULL_CHILD_ACTION);\n\t\treturn { fullChildrenApproved };\n\t}\n\tif (decision === \"reject\") {\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: feedback ? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${feedback}` : LARGE_SUBAGENT_LAUNCH_REJECT_REASON,\n\t\t};\n\t}\n\treturn { fullChildrenApproved };\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 (toolName.startsWith(\"computer_\")) {\n\t\ttry {\n\t\t\tcomputerPolicy(toolName, input);\n\t\t} catch (error) {\n\t\t\treturn { block: true, reason: String(error) };\n\t\t}\n\t}\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// Aggregate launch confirmation runs before the mode early-return, including YOLO.\n\t// yolo mode too (only auto mode bypasses it).\n\tlet largeLaunchCoveredManualApproval = false;\n\tif (toolName === \"subagent\") {\n\t\tconst launchResult = await gateLargeSubagentLaunch(input, ctx, options);\n\t\tif (launchResult && \"block\" in launchResult) return launchResult;\n\t\tlargeLaunchCoveredManualApproval = ctx.mode === \"manual\" && launchResult?.fullChildrenApproved === true;\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) || largeLaunchCoveredManualApproval) return undefined;\n\n\tlet action: string;\n\tlet detail: string;\n\n\tif (toolName.startsWith(\"computer_\")) {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(JSON.stringify(input));\n\t} else if (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 === \"subagent\") {\n\t\taction = FULL_CHILD_ACTION;\n\t\tconst launches = getRequestedChildLaunches(input).filter((launch) => launch.permissions === \"full\");\n\t\tdetail = sanitizeDetail(\n\t\t\tlaunches.map((launch) => `${launch.description}\\npermissions: ${launch.permissions}`).join(\"\\n\"),\n\t\t);\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 (!toolName.startsWith(\"computer_\") && ctx.approvals.has(action)) return undefined;\n\n\tif (!approvalHandler || ctx.allowApprovals === false) {\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\tif (!toolName.startsWith(\"computer_\")) ctx.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
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { dirname, join, resolve } from "node:path";
|
|
22
22
|
import { getAgentDir } from "../config.js";
|
|
23
|
+
import { computerPolicy } from "./computer-use/policy.js";
|
|
23
24
|
import { effectiveLargeSubagentLaunchCountForTurn, LARGE_SUBAGENT_LAUNCH_THRESHOLD } from "./large-subagent-launch.js";
|
|
24
25
|
import { isUserInstructionsPath } from "./model-instructions.js";
|
|
25
26
|
import { isCodeRewriteMutating, planModeBlockReason } from "./plan-mode.js";
|
|
@@ -87,8 +88,8 @@ function getContext(sessionId) {
|
|
|
87
88
|
return defaultContext;
|
|
88
89
|
return contexts.get(sessionId) ?? defaultContext;
|
|
89
90
|
}
|
|
90
|
-
export function createPermissionContext(sessionId, mode = defaultContext.mode) {
|
|
91
|
-
contexts.set(sessionId, { mode, approvals: new Set() });
|
|
91
|
+
export function createPermissionContext(sessionId, mode = defaultContext.mode, allowApprovals = true) {
|
|
92
|
+
contexts.set(sessionId, { mode, allowApprovals, approvals: new Set() });
|
|
92
93
|
}
|
|
93
94
|
export function deletePermissionContext(sessionId) {
|
|
94
95
|
contexts.delete(sessionId);
|
|
@@ -226,6 +227,8 @@ function getRequestedChildLaunches(input) {
|
|
|
226
227
|
return launches;
|
|
227
228
|
}
|
|
228
229
|
function requiresManualApproval(toolName, input) {
|
|
230
|
+
if (toolName.startsWith("computer_"))
|
|
231
|
+
return toolName !== "computer_end";
|
|
229
232
|
if (isMutatingTool(toolName))
|
|
230
233
|
return true;
|
|
231
234
|
if (toolName.startsWith("settings_") && toolName !== "settings_load")
|
|
@@ -294,7 +297,7 @@ async function gateLargeSubagentLaunch(input, ctx, options) {
|
|
|
294
297
|
}
|
|
295
298
|
return { fullChildrenApproved: cached.fullChildrenApproved };
|
|
296
299
|
}
|
|
297
|
-
if (!approvalHandler) {
|
|
300
|
+
if (!approvalHandler || ctx.allowApprovals === false) {
|
|
298
301
|
return { block: true, reason: NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON };
|
|
299
302
|
}
|
|
300
303
|
const summary = largeLaunchTaskSummary(input, options?.assistantMessage);
|
|
@@ -357,6 +360,14 @@ function sanitizeDetail(value) {
|
|
|
357
360
|
*/
|
|
358
361
|
export async function gateToolCall(toolName, input, cwd, sessionId, options) {
|
|
359
362
|
const ctx = getContext(sessionId);
|
|
363
|
+
if (toolName.startsWith("computer_")) {
|
|
364
|
+
try {
|
|
365
|
+
computerPolicy(toolName, input);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
return { block: true, reason: String(error) };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
360
371
|
const protectedWriteReason = protectedFileWriteReason(toolName, input, cwd);
|
|
361
372
|
if (protectedWriteReason) {
|
|
362
373
|
return { block: true, reason: protectedWriteReason };
|
|
@@ -382,7 +393,11 @@ export async function gateToolCall(toolName, input, cwd, sessionId, options) {
|
|
|
382
393
|
return undefined;
|
|
383
394
|
let action;
|
|
384
395
|
let detail;
|
|
385
|
-
if (toolName
|
|
396
|
+
if (toolName.startsWith("computer_")) {
|
|
397
|
+
action = toolName;
|
|
398
|
+
detail = sanitizeDetail(JSON.stringify(input));
|
|
399
|
+
}
|
|
400
|
+
else if (toolName === "bash") {
|
|
386
401
|
action = "bash";
|
|
387
402
|
detail = sanitizeDetail(input.command);
|
|
388
403
|
}
|
|
@@ -418,9 +433,9 @@ export async function gateToolCall(toolName, input, cwd, sessionId, options) {
|
|
|
418
433
|
action = toolName;
|
|
419
434
|
detail = "";
|
|
420
435
|
}
|
|
421
|
-
if (ctx.approvals.has(action))
|
|
436
|
+
if (!toolName.startsWith("computer_") && ctx.approvals.has(action))
|
|
422
437
|
return undefined;
|
|
423
|
-
if (!approvalHandler) {
|
|
438
|
+
if (!approvalHandler || ctx.allowApprovals === false) {
|
|
424
439
|
return { block: true, reason: NO_HANDLER_REASON };
|
|
425
440
|
}
|
|
426
441
|
let resp;
|
|
@@ -433,7 +448,8 @@ export async function gateToolCall(toolName, input, cwd, sessionId, options) {
|
|
|
433
448
|
}
|
|
434
449
|
const decision = typeof resp === "string" ? resp : resp.decision;
|
|
435
450
|
if (decision === "session") {
|
|
436
|
-
|
|
451
|
+
if (!toolName.startsWith("computer_"))
|
|
452
|
+
ctx.approvals.add(action);
|
|
437
453
|
return undefined;
|
|
438
454
|
}
|
|
439
455
|
if (decision === "reject") {
|
|
@@ -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 { dirname, join, resolve } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { effectiveLargeSubagentLaunchCountForTurn, LARGE_SUBAGENT_LAUNCH_THRESHOLD } from \"./large-subagent-launch.ts\";\nimport { isUserInstructionsPath } from \"./model-instructions.ts\";\nimport { isCodeRewriteMutating, planModeBlockReason } from \"./plan-mode.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 * \"subagent-full\" for full-access children, or \"large-subagent-launch\" for aggregate confirmation. */\n\taction: string;\n\tdetail: string;\n\t/** \"large-subagent-launch\" for aggregate child confirmation, \"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?: \"large-subagent-launch\" | \"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\t\"settings_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 LARGE_SUBAGENT_LAUNCH_REJECT_REASON = \"Large subagent launch rejected by user.\";\nexport const NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON = \"Large subagent launch blocked: no approval channel available.\";\n/** Session-approval keys for child launches. */\nconst LARGE_SUBAGENT_LAUNCH_ACTION = \"large-subagent-launch\";\nconst FULL_CHILD_ACTION = \"subagent-full\";\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 aggregate prompt covers every sibling SINGLE `subagent` on the same assistant message. */\nconst turnLargeLaunchDecisions = new WeakMap<\n\tobject,\n\t{ decision: ApprovalDecision; feedback?: string; fullChildrenApproved: boolean }\n>();\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 mutating-tool and aggregate launch 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 agents instruction tree is user-managed. The agent cannot change ~/.lunr/agent/agents/.\";\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\tif (isUserInstructionsPath(target, getAgentDir())) {\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\ninterface RequestedChildLaunch {\n\tdescription: string;\n\tpermissions: \"full\" | \"read-only\";\n}\n\nfunction collectRequestedChildLaunches(value: unknown, launches: RequestedChildLaunch[]): void {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return;\n\tconst input = value as Record<string, unknown>;\n\tif (typeof input.task === \"string\" && input.task.trim()) {\n\t\tlaunches.push({\n\t\t\tdescription:\n\t\t\t\ttypeof input.description === \"string\" && input.description.trim()\n\t\t\t\t\t? input.description.trim()\n\t\t\t\t\t: input.task.trim(),\n\t\t\tpermissions: input.permissions === \"read-only\" ? \"read-only\" : \"full\",\n\t\t});\n\t\treturn;\n\t}\n\tfor (const key of [\"tasks\", \"chain\", \"parallel\"] as const) {\n\t\tconst children = input[key];\n\t\tif (Array.isArray(children)) {\n\t\t\tfor (const child of children) collectRequestedChildLaunches(child, launches);\n\t\t} else if (key === \"parallel\") {\n\t\t\tcollectRequestedChildLaunches(children, launches);\n\t\t}\n\t}\n}\n\nfunction getRequestedChildLaunches(input: Record<string, unknown>): RequestedChildLaunch[] {\n\tconst launches: RequestedChildLaunch[] = [];\n\tcollectRequestedChildLaunches(input, launches);\n\treturn launches;\n}\n\nfunction requiresManualApproval(toolName: string, input: Record<string, unknown>): boolean {\n\tif (isMutatingTool(toolName)) return true;\n\tif (toolName.startsWith(\"settings_\") && toolName !== \"settings_load\") return Object.keys(input).length > 0;\n\tif (toolName === \"subagent\") {\n\t\treturn getRequestedChildLaunches(input).some((launch) => launch.permissions === \"full\");\n\t}\n\treturn toolName === \"code_rewrite\" && isCodeRewriteMutating(input);\n}\n\nexport interface GateOptions {\n\t/** Global preference for aggregate confirmation. Defaults to enabled. */\n\tconfirmLargeSubagentLaunches?: boolean;\n\t/** Assistant message that issued this tool call. Same-turn sibling SINGLE\n\t * `subagent` calls count toward the aggregate 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 largeLaunchTaskSummary(\n\tinput: Record<string, unknown>,\n\tassistantMessage?: GateOptions[\"assistantMessage\"],\n): 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 * Aggregate launch gate. A launch above LARGE_SUBAGENT_LAUNCH_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.\n * Fail-closed without an approval handler, same as the mutating-tool gate.\n */\nasync function gateLargeSubagentLaunch(\n\tinput: Record<string, unknown>,\n\tctx: PermissionContext,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | { fullChildrenApproved: boolean } | undefined> {\n\tif (ctx.mode === \"auto\") return undefined;\n\tif (options?.confirmLargeSubagentLaunches === false) return undefined;\n\tconst count = effectiveLargeSubagentLaunchCountForTurn(input, options?.assistantMessage);\n\tif (count <= LARGE_SUBAGENT_LAUNCH_THRESHOLD) return undefined;\n\tif (ctx.approvals.has(LARGE_SUBAGENT_LAUNCH_ACTION)) return { fullChildrenApproved: false };\n\n\tconst turnKey = options?.assistantMessage;\n\tconst cached = turnKey ? turnLargeLaunchDecisions.get(turnKey) : undefined;\n\tif (cached) {\n\t\tif (cached.decision === \"reject\") {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\treason: cached.feedback\n\t\t\t\t\t? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${cached.feedback}`\n\t\t\t\t\t: LARGE_SUBAGENT_LAUNCH_REJECT_REASON,\n\t\t\t};\n\t\t}\n\t\treturn { fullChildrenApproved: cached.fullChildrenApproved };\n\t}\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON };\n\t}\n\n\tconst summary = largeLaunchTaskSummary(input, options?.assistantMessage);\n\tconst siblingLaunches = (options?.assistantMessage?.content ?? []).flatMap((block) => {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") return [];\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\treturn args ? getRequestedChildLaunches(args) : [];\n\t});\n\tconst launches = siblingLaunches.length > 0 ? siblingLaunches : getRequestedChildLaunches(input);\n\tconst fullChildrenApproved = launches.some((launch) => launch.permissions === \"full\");\n\tconst launchSummary = launches\n\t\t.map((launch) => `${launch.description}\\npermissions: ${launch.permissions}`)\n\t\t.join(\"\\n\");\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({\n\t\t\ttoolName: \"subagent\",\n\t\t\taction: LARGE_SUBAGENT_LAUNCH_ACTION,\n\t\t\tdetail: `large subagent launch (${count} children)${launchSummary || summary ? `\\n${launchSummary || summary}` : \"\"}`,\n\t\t\tkind: \"large-subagent-launch\",\n\t\t});\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_LARGE_SUBAGENT_LAUNCH_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) {\n\t\tturnLargeLaunchDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}), fullChildrenApproved });\n\t}\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(LARGE_SUBAGENT_LAUNCH_ACTION);\n\t\tif (fullChildrenApproved) ctx.approvals.add(FULL_CHILD_ACTION);\n\t\treturn { fullChildrenApproved };\n\t}\n\tif (decision === \"reject\") {\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: feedback ? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${feedback}` : LARGE_SUBAGENT_LAUNCH_REJECT_REASON,\n\t\t};\n\t}\n\treturn { fullChildrenApproved };\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// Aggregate launch confirmation runs before the mode early-return, including YOLO.\n\t// yolo mode too (only auto mode bypasses it).\n\tlet largeLaunchCoveredManualApproval = false;\n\tif (toolName === \"subagent\") {\n\t\tconst launchResult = await gateLargeSubagentLaunch(input, ctx, options);\n\t\tif (launchResult && \"block\" in launchResult) return launchResult;\n\t\tlargeLaunchCoveredManualApproval = ctx.mode === \"manual\" && launchResult?.fullChildrenApproved === true;\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) || largeLaunchCoveredManualApproval) 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 === \"subagent\") {\n\t\taction = FULL_CHILD_ACTION;\n\t\tconst launches = getRequestedChildLaunches(input).filter((launch) => launch.permissions === \"full\");\n\t\tdetail = sanitizeDetail(\n\t\t\tlaunches.map((launch) => `${launch.description}\\npermissions: ${launch.permissions}`).join(\"\\n\"),\n\t\t);\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 { computerPolicy } from \"./computer-use/policy.ts\";\nimport { effectiveLargeSubagentLaunchCountForTurn, LARGE_SUBAGENT_LAUNCH_THRESHOLD } from \"./large-subagent-launch.ts\";\nimport { isUserInstructionsPath } from \"./model-instructions.ts\";\nimport { isCodeRewriteMutating, planModeBlockReason } from \"./plan-mode.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 * \"subagent-full\" for full-access children, or \"large-subagent-launch\" for aggregate confirmation. */\n\taction: string;\n\tdetail: string;\n\t/** \"large-subagent-launch\" for aggregate child confirmation, \"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?: \"large-subagent-launch\" | \"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\t\"settings_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 LARGE_SUBAGENT_LAUNCH_REJECT_REASON = \"Large subagent launch rejected by user.\";\nexport const NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON = \"Large subagent launch blocked: no approval channel available.\";\n/** Session-approval keys for child launches. */\nconst LARGE_SUBAGENT_LAUNCH_ACTION = \"large-subagent-launch\";\nconst FULL_CHILD_ACTION = \"subagent-full\";\n\ninterface PermissionContext {\n\tallowApprovals?: boolean;\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 aggregate prompt covers every sibling SINGLE `subagent` on the same assistant message. */\nconst turnLargeLaunchDecisions = new WeakMap<\n\tobject,\n\t{ decision: ApprovalDecision; feedback?: string; fullChildrenApproved: boolean }\n>();\n\nfunction getContext(sessionId?: string): PermissionContext {\n\tif (!sessionId) return defaultContext;\n\treturn contexts.get(sessionId) ?? defaultContext;\n}\n\nexport function createPermissionContext(\n\tsessionId: string,\n\tmode: PermissionMode = defaultContext.mode,\n\tallowApprovals = true,\n): void {\n\tcontexts.set(sessionId, { mode, allowApprovals, 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 mutating-tool and aggregate launch 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 agents instruction tree is user-managed. The agent cannot change ~/.lunr/agent/agents/.\";\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\tif (isUserInstructionsPath(target, getAgentDir())) {\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\ninterface RequestedChildLaunch {\n\tdescription: string;\n\tpermissions: \"full\" | \"read-only\";\n}\n\nfunction collectRequestedChildLaunches(value: unknown, launches: RequestedChildLaunch[]): void {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return;\n\tconst input = value as Record<string, unknown>;\n\tif (typeof input.task === \"string\" && input.task.trim()) {\n\t\tlaunches.push({\n\t\t\tdescription:\n\t\t\t\ttypeof input.description === \"string\" && input.description.trim()\n\t\t\t\t\t? input.description.trim()\n\t\t\t\t\t: input.task.trim(),\n\t\t\tpermissions: input.permissions === \"read-only\" ? \"read-only\" : \"full\",\n\t\t});\n\t\treturn;\n\t}\n\tfor (const key of [\"tasks\", \"chain\", \"parallel\"] as const) {\n\t\tconst children = input[key];\n\t\tif (Array.isArray(children)) {\n\t\t\tfor (const child of children) collectRequestedChildLaunches(child, launches);\n\t\t} else if (key === \"parallel\") {\n\t\t\tcollectRequestedChildLaunches(children, launches);\n\t\t}\n\t}\n}\n\nfunction getRequestedChildLaunches(input: Record<string, unknown>): RequestedChildLaunch[] {\n\tconst launches: RequestedChildLaunch[] = [];\n\tcollectRequestedChildLaunches(input, launches);\n\treturn launches;\n}\n\nfunction requiresManualApproval(toolName: string, input: Record<string, unknown>): boolean {\n\tif (toolName.startsWith(\"computer_\")) return toolName !== \"computer_end\";\n\tif (isMutatingTool(toolName)) return true;\n\tif (toolName.startsWith(\"settings_\") && toolName !== \"settings_load\") return Object.keys(input).length > 0;\n\tif (toolName === \"subagent\") {\n\t\treturn getRequestedChildLaunches(input).some((launch) => launch.permissions === \"full\");\n\t}\n\treturn toolName === \"code_rewrite\" && isCodeRewriteMutating(input);\n}\n\nexport interface GateOptions {\n\t/** Global preference for aggregate confirmation. Defaults to enabled. */\n\tconfirmLargeSubagentLaunches?: boolean;\n\t/** Assistant message that issued this tool call. Same-turn sibling SINGLE\n\t * `subagent` calls count toward the aggregate 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 largeLaunchTaskSummary(\n\tinput: Record<string, unknown>,\n\tassistantMessage?: GateOptions[\"assistantMessage\"],\n): 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 * Aggregate launch gate. A launch above LARGE_SUBAGENT_LAUNCH_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.\n * Fail-closed without an approval handler, same as the mutating-tool gate.\n */\nasync function gateLargeSubagentLaunch(\n\tinput: Record<string, unknown>,\n\tctx: PermissionContext,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | { fullChildrenApproved: boolean } | undefined> {\n\tif (ctx.mode === \"auto\") return undefined;\n\tif (options?.confirmLargeSubagentLaunches === false) return undefined;\n\tconst count = effectiveLargeSubagentLaunchCountForTurn(input, options?.assistantMessage);\n\tif (count <= LARGE_SUBAGENT_LAUNCH_THRESHOLD) return undefined;\n\tif (ctx.approvals.has(LARGE_SUBAGENT_LAUNCH_ACTION)) return { fullChildrenApproved: false };\n\n\tconst turnKey = options?.assistantMessage;\n\tconst cached = turnKey ? turnLargeLaunchDecisions.get(turnKey) : undefined;\n\tif (cached) {\n\t\tif (cached.decision === \"reject\") {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\treason: cached.feedback\n\t\t\t\t\t? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${cached.feedback}`\n\t\t\t\t\t: LARGE_SUBAGENT_LAUNCH_REJECT_REASON,\n\t\t\t};\n\t\t}\n\t\treturn { fullChildrenApproved: cached.fullChildrenApproved };\n\t}\n\n\tif (!approvalHandler || ctx.allowApprovals === false) {\n\t\treturn { block: true, reason: NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON };\n\t}\n\n\tconst summary = largeLaunchTaskSummary(input, options?.assistantMessage);\n\tconst siblingLaunches = (options?.assistantMessage?.content ?? []).flatMap((block) => {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") return [];\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\treturn args ? getRequestedChildLaunches(args) : [];\n\t});\n\tconst launches = siblingLaunches.length > 0 ? siblingLaunches : getRequestedChildLaunches(input);\n\tconst fullChildrenApproved = launches.some((launch) => launch.permissions === \"full\");\n\tconst launchSummary = launches\n\t\t.map((launch) => `${launch.description}\\npermissions: ${launch.permissions}`)\n\t\t.join(\"\\n\");\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({\n\t\t\ttoolName: \"subagent\",\n\t\t\taction: LARGE_SUBAGENT_LAUNCH_ACTION,\n\t\t\tdetail: `large subagent launch (${count} children)${launchSummary || summary ? `\\n${launchSummary || summary}` : \"\"}`,\n\t\t\tkind: \"large-subagent-launch\",\n\t\t});\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_LARGE_SUBAGENT_LAUNCH_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) {\n\t\tturnLargeLaunchDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}), fullChildrenApproved });\n\t}\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(LARGE_SUBAGENT_LAUNCH_ACTION);\n\t\tif (fullChildrenApproved) ctx.approvals.add(FULL_CHILD_ACTION);\n\t\treturn { fullChildrenApproved };\n\t}\n\tif (decision === \"reject\") {\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: feedback ? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${feedback}` : LARGE_SUBAGENT_LAUNCH_REJECT_REASON,\n\t\t};\n\t}\n\treturn { fullChildrenApproved };\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 (toolName.startsWith(\"computer_\")) {\n\t\ttry {\n\t\t\tcomputerPolicy(toolName, input);\n\t\t} catch (error) {\n\t\t\treturn { block: true, reason: String(error) };\n\t\t}\n\t}\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// Aggregate launch confirmation runs before the mode early-return, including YOLO.\n\t// yolo mode too (only auto mode bypasses it).\n\tlet largeLaunchCoveredManualApproval = false;\n\tif (toolName === \"subagent\") {\n\t\tconst launchResult = await gateLargeSubagentLaunch(input, ctx, options);\n\t\tif (launchResult && \"block\" in launchResult) return launchResult;\n\t\tlargeLaunchCoveredManualApproval = ctx.mode === \"manual\" && launchResult?.fullChildrenApproved === true;\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) || largeLaunchCoveredManualApproval) return undefined;\n\n\tlet action: string;\n\tlet detail: string;\n\n\tif (toolName.startsWith(\"computer_\")) {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(JSON.stringify(input));\n\t} else if (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 === \"subagent\") {\n\t\taction = FULL_CHILD_ACTION;\n\t\tconst launches = getRequestedChildLaunches(input).filter((launch) => launch.permissions === \"full\");\n\t\tdetail = sanitizeDetail(\n\t\t\tlaunches.map((launch) => `${launch.description}\\npermissions: ${launch.permissions}`).join(\"\\n\"),\n\t\t);\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 (!toolName.startsWith(\"computer_\") && ctx.approvals.has(action)) return undefined;\n\n\tif (!approvalHandler || ctx.allowApprovals === false) {\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\tif (!toolName.startsWith(\"computer_\")) ctx.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;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,CAqBxF;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 (\n\t\ttoolName.startsWith(\"settings_\") &&\n\t\ttoolName !== \"settings_load\" &&\n\t\tObject.keys((input as object | undefined) ?? {}).length > 0\n\t) {\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 (\n\t\t\ttoken === \"-C\" ||\n\t\t\ttoken === \"-c\" ||\n\t\t\ttoken === \"--git-dir\" ||\n\t\t\ttoken === \"--work-tree\" ||\n\t\t\ttoken === \"--namespace\"\n\t\t) {\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":"AAEA;;;;;;;;;;;;;;;;;;;;;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,CA4BxF;AAOD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAe9D","sourcesContent":["import { computerPolicy } from \"./computer-use/policy.ts\";\n\n/**\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 (toolName.startsWith(\"computer_\")) {\n\t\ttry {\n\t\t\treturn computerPolicy(toolName, {})?.observation ? undefined : PLAN_MODE_BLOCK_MESSAGE;\n\t\t} catch {\n\t\t\treturn PLAN_MODE_BLOCK_MESSAGE;\n\t\t}\n\t}\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 (\n\t\ttoolName.startsWith(\"settings_\") &&\n\t\ttoolName !== \"settings_load\" &&\n\t\tObject.keys((input as object | undefined) ?? {}).length > 0\n\t) {\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 (\n\t\t\ttoken === \"-C\" ||\n\t\t\ttoken === \"-c\" ||\n\t\t\ttoken === \"--git-dir\" ||\n\t\t\ttoken === \"--work-tree\" ||\n\t\t\ttoken === \"--namespace\"\n\t\t) {\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"]}
|
package/dist/core/plan-mode.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { computerPolicy } from "./computer-use/policy.js";
|
|
1
2
|
/**
|
|
2
3
|
* lunr: plan permission mode — read-only tool-gating heuristics + system-prompt addendum.
|
|
3
4
|
*
|
|
@@ -159,6 +160,14 @@ export function isCodeRewriteMutating(input) {
|
|
|
159
160
|
* Returns the block reason when plan mode should block this tool call, else undefined.
|
|
160
161
|
*/
|
|
161
162
|
export function planModeBlockReason(toolName, input) {
|
|
163
|
+
if (toolName.startsWith("computer_")) {
|
|
164
|
+
try {
|
|
165
|
+
return computerPolicy(toolName, {})?.observation ? undefined : PLAN_MODE_BLOCK_MESSAGE;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return PLAN_MODE_BLOCK_MESSAGE;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
162
171
|
if (BLOCKED_TOOLS.has(toolName)) {
|
|
163
172
|
return PLAN_MODE_BLOCK_MESSAGE;
|
|
164
173
|
}
|