@ashx-j/lunr 0.2.14 → 0.2.16
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 +24 -1
- package/README.md +4 -4
- package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.js +4 -4
- package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +2 -0
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-settings-tools.d.ts +4 -0
- package/dist/builtin-extensions/lunr-settings-tools.d.ts.map +1 -0
- package/dist/builtin-extensions/lunr-settings-tools.js +120 -0
- package/dist/builtin-extensions/lunr-settings-tools.js.map +1 -0
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.d.ts.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js +6 -2
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/subagent-runtime.d.ts +2 -2
- package/dist/builtin-extensions/pi-prompt-template-model/subagent-runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/subagent-runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/subagent-step.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/subagent-step.js +3 -3
- package/dist/builtin-extensions/pi-prompt-template-model/subagent-step.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/api/delegation.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/api/delegation.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/api/delegation.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/api/delegation.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 +2 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +5 -11
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +12 -15
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +3 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +14 -27
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +6 -4
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +6 -4
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js +4 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js +3 -35
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +5 -12
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- 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 +14 -31
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +28 -22
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +9 -4
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js +2 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +4 -7
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts +3 -3
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js +7 -6
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-request.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-request.js +3 -3
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-request.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +6 -4
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +15 -10
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/fleet.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js +3 -2
- package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +75 -43
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +5 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +7 -2
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +5 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +68 -10
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +10 -5
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/context-breakdown.d.ts +2 -0
- package/dist/core/context-breakdown.d.ts.map +1 -1
- package/dist/core/context-breakdown.js +6 -2
- package/dist/core/context-breakdown.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/injected-prompt.d.ts +3 -3
- package/dist/core/injected-prompt.d.ts.map +1 -1
- package/dist/core/injected-prompt.js +2 -7
- package/dist/core/injected-prompt.js.map +1 -1
- package/dist/core/large-subagent-launch.d.ts +13 -0
- package/dist/core/large-subagent-launch.d.ts.map +1 -0
- package/dist/core/large-subagent-launch.js +54 -0
- package/dist/core/large-subagent-launch.js.map +1 -0
- package/dist/core/model-instructions.d.ts +24 -0
- package/dist/core/model-instructions.d.ts.map +1 -0
- package/dist/core/model-instructions.js +123 -0
- package/dist/core/model-instructions.js.map +1 -0
- package/dist/core/permissions.d.ts +10 -11
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +107 -40
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +10 -1
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +13 -4
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-bridges.d.ts +4 -0
- package/dist/core/runtime-bridges.d.ts.map +1 -0
- package/dist/core/runtime-bridges.js +16 -0
- package/dist/core/runtime-bridges.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +13 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +32 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-tools-bridge.d.ts +35 -0
- package/dist/core/settings-tools-bridge.d.ts.map +1 -0
- package/dist/core/settings-tools-bridge.js +56 -0
- package/dist/core/settings-tools-bridge.js.map +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +0 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +3 -2
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/approval.d.ts.map +1 -1
- package/dist/gateway/approval.js +4 -3
- package/dist/gateway/approval.js.map +1 -1
- package/dist/gateway/commands.d.ts +1 -1
- package/dist/gateway/commands.d.ts.map +1 -1
- package/dist/gateway/commands.js +1 -18
- package/dist/gateway/commands.js.map +1 -1
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +3 -2
- package/dist/gateway/cron.js.map +1 -1
- package/dist/gateway/router.d.ts +1 -1
- package/dist/gateway/router.d.ts.map +1 -1
- package/dist/gateway/router.js +1 -1
- package/dist/gateway/router.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -9
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +4 -0
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/injected-prompt-message.d.ts +1 -1
- package/dist/modes/interactive/components/injected-prompt-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/injected-prompt-message.js +1 -1
- package/dist/modes/interactive/components/injected-prompt-message.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +8 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +61 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -3
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +45 -54
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/compaction.md +5 -3
- package/docs/features.md +7 -7
- package/docs/quickstart.md +2 -1
- package/docs/settings.md +3 -1
- package/docs/termux.md +1 -1
- package/docs/usage.md +2 -2
- 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/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
- package/dist/core/swarm.d.ts +0 -54
- package/dist/core/swarm.d.ts.map +0 -1
- package/dist/core/swarm.js +0 -119
- package/dist/core/swarm.js.map +0 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync } from "node:fs";
|
|
3
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
export function getInstructionsRoot(agentDir) {
|
|
5
|
+
return join(resolve(agentDir), "agents");
|
|
6
|
+
}
|
|
7
|
+
export function getGlobalInstructionsPath(agentDir) {
|
|
8
|
+
return join(getInstructionsRoot(agentDir), "AGENTS.md");
|
|
9
|
+
}
|
|
10
|
+
export function modelInstructionFolderName(modelId) {
|
|
11
|
+
const finalSegment = modelId.trim().split(/[\\/]/).filter(Boolean).at(-1) ?? "model";
|
|
12
|
+
const withoutThinking = finalSegment.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, "");
|
|
13
|
+
let safe = withoutThinking.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "_").replace(/[. ]+$/g, "");
|
|
14
|
+
if (!safe || safe === "." || safe === "..")
|
|
15
|
+
safe = "model";
|
|
16
|
+
if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(safe))
|
|
17
|
+
safe = `_${safe}`;
|
|
18
|
+
if (safe.length > 100 || safe.toLowerCase() !== withoutThinking.toLowerCase()) {
|
|
19
|
+
const digest = createHash("sha256").update(withoutThinking.toLowerCase()).digest("hex").slice(0, 8);
|
|
20
|
+
safe = `${safe.slice(0, 90)}-${digest}`;
|
|
21
|
+
}
|
|
22
|
+
return safe.toLowerCase();
|
|
23
|
+
}
|
|
24
|
+
export function getModelInstructionsPath(agentDir, modelId) {
|
|
25
|
+
return join(getInstructionsRoot(agentDir), modelInstructionFolderName(modelId), "AGENTS.md");
|
|
26
|
+
}
|
|
27
|
+
export function isUserInstructionsPath(filePath, agentDir) {
|
|
28
|
+
const rel = relative(resolve(getInstructionsRoot(agentDir)), resolve(filePath));
|
|
29
|
+
return rel === "" || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`));
|
|
30
|
+
}
|
|
31
|
+
export function migrateLegacyGlobalInstructions(agentDir) {
|
|
32
|
+
const legacyPath = join(resolve(agentDir), "AGENTS.md");
|
|
33
|
+
const destinationPath = getGlobalInstructionsPath(agentDir);
|
|
34
|
+
if (!existsSync(legacyPath))
|
|
35
|
+
return { status: "absent" };
|
|
36
|
+
if (existsSync(destinationPath)) {
|
|
37
|
+
return {
|
|
38
|
+
status: "conflict",
|
|
39
|
+
diagnostic: `Both legacy '${legacyPath}' and current '${destinationPath}' exist. Kept both and will use the current file.`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(getInstructionsRoot(agentDir), { recursive: true });
|
|
44
|
+
renameSync(legacyPath, destinationPath);
|
|
45
|
+
return { status: "migrated" };
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
status: "failed",
|
|
50
|
+
diagnostic: `Could not migrate '${legacyPath}' to '${destinationPath}': ${error instanceof Error ? error.message : String(error)}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function concreteModelId(value) {
|
|
55
|
+
if (typeof value !== "string")
|
|
56
|
+
return undefined;
|
|
57
|
+
const trimmed = value.trim();
|
|
58
|
+
if (!trimmed || /[*?{}]/.test(trimmed))
|
|
59
|
+
return undefined;
|
|
60
|
+
return trimmed.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, "");
|
|
61
|
+
}
|
|
62
|
+
export function ensureModelInstructionDirs(agentDir, settings, currentModel) {
|
|
63
|
+
const modelIds = new Set();
|
|
64
|
+
const add = (value) => {
|
|
65
|
+
const id = concreteModelId(value);
|
|
66
|
+
if (id)
|
|
67
|
+
modelIds.add(id);
|
|
68
|
+
};
|
|
69
|
+
add(currentModel?.id);
|
|
70
|
+
add(settings.defaultModel);
|
|
71
|
+
for (const value of settings.enabledModels ?? [])
|
|
72
|
+
add(value);
|
|
73
|
+
add(settings.modelTiers?.light);
|
|
74
|
+
add(settings.modelTiers?.standard);
|
|
75
|
+
add(settings.modelTiers?.heavy);
|
|
76
|
+
const diagnostics = [];
|
|
77
|
+
for (const id of modelIds) {
|
|
78
|
+
try {
|
|
79
|
+
mkdirSync(join(getInstructionsRoot(agentDir), modelInstructionFolderName(id)), { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
diagnostics.push(`Could not prepare model-instruction folder for '${id}': ${error instanceof Error ? error.message : String(error)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return diagnostics;
|
|
86
|
+
}
|
|
87
|
+
function safeReadInstruction(path) {
|
|
88
|
+
try {
|
|
89
|
+
if (!existsSync(path) || !statSync(path).isFile())
|
|
90
|
+
return undefined;
|
|
91
|
+
return { path, content: readFileSync(path, "utf8") };
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function loadSelectedUserInstructions(options) {
|
|
98
|
+
ensureModelInstructionDirs(options.agentDir, options.settingsManager.getGlobalSettings(), options.model);
|
|
99
|
+
const enabled = options.settingsManager.getModelInstructionsEnabled();
|
|
100
|
+
const mode = options.settingsManager.getModelInstructionsMode();
|
|
101
|
+
const files = [];
|
|
102
|
+
if (!enabled || mode === "both") {
|
|
103
|
+
const globalFile = safeReadInstruction(getGlobalInstructionsPath(options.agentDir));
|
|
104
|
+
if (globalFile)
|
|
105
|
+
files.push(globalFile);
|
|
106
|
+
}
|
|
107
|
+
if (enabled && options.model) {
|
|
108
|
+
const modelFile = safeReadInstruction(getModelInstructionsPath(options.agentDir, options.model.id));
|
|
109
|
+
if (modelFile)
|
|
110
|
+
files.push(modelFile);
|
|
111
|
+
}
|
|
112
|
+
return files;
|
|
113
|
+
}
|
|
114
|
+
export function instructionPathLabel(filePath, agentDir) {
|
|
115
|
+
const resolved = resolve(filePath);
|
|
116
|
+
if (resolved.toLowerCase() === resolve(getGlobalInstructionsPath(agentDir)).toLowerCase()) {
|
|
117
|
+
return "Global AGENTS.md";
|
|
118
|
+
}
|
|
119
|
+
if (!isUserInstructionsPath(resolved, agentDir))
|
|
120
|
+
return undefined;
|
|
121
|
+
return `${basename(resolve(resolved, ".."))} AGENTS.md`;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=model-instructions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-instructions.js","sourceRoot":"","sources":["../../src/core/model-instructions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACpF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAW/E,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAU;IAC7D,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,CACzC;AAED,MAAM,UAAU,yBAAyB,CAAC,QAAgB,EAAU;IACnE,OAAO,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,CAAC;AAAA,CACxD;AAED,MAAM,UAAU,0BAA0B,CAAC,OAAe,EAAU;IACnE,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC;IACrF,MAAM,eAAe,GAAG,YAAY,CAAC,OAAO,CAAC,8CAA8C,EAAE,EAAE,CAAC,CAAC;IACjG,IAAI,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC7F,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;QAAE,IAAI,GAAG,OAAO,CAAC;IAC3D,IAAI,iDAAiD,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACpF,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,eAAe,CAAC,WAAW,EAAE,EAAE,CAAC;QAC/E,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpG,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC;IACzC,CAAC;IACD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAAA,CAC1B;AAED,MAAM,UAAU,wBAAwB,CAAC,QAAgB,EAAE,OAAe,EAAU;IACnF,OAAO,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,0BAA0B,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAC;AAAA,CAC7F;AAED,MAAM,UAAU,sBAAsB,CAAC,QAAgB,EAAE,QAAgB,EAAW;IACnF,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChF,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC;AAAA,CACvF;AAED,MAAM,UAAU,+BAA+B,CAAC,QAAgB,EAAoC;IACnG,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,CAAC;IACxD,MAAM,eAAe,GAAG,yBAAyB,CAAC,QAAQ,CAAC,CAAC;IAC5D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACzD,IAAI,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACjC,OAAO;YACN,MAAM,EAAE,UAAU;YAClB,UAAU,EAAE,gBAAgB,UAAU,kBAAkB,eAAe,mDAAmD;SAC1H,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACJ,SAAS,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,UAAU,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QACxC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO;YACN,MAAM,EAAE,QAAQ;YAChB,UAAU,EAAE,sBAAsB,UAAU,SAAS,eAAe,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;SAClI,CAAC;IACH,CAAC;AAAA,CACD;AAED,SAAS,eAAe,CAAC,KAAc,EAAsB;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACzD,OAAO,OAAO,CAAC,OAAO,CAAC,8CAA8C,EAAE,EAAE,CAAC,CAAC;AAAA,CAC3E;AAED,MAAM,UAAU,0BAA0B,CACzC,QAAgB,EAChB,QAAyE,EACzE,YAAqC,EAC1B;IACX,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,GAAG,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC;QAC/B,MAAM,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,EAAE;YAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAA,CACzB,CAAC;IACF,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACtB,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,aAAa,IAAI,EAAE;QAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IAC7D,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAChC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACnC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAEhC,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,CAAC;YACJ,SAAS,CAAC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,0BAA0B,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrG,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,WAAW,CAAC,IAAI,CACf,mDAAmD,EAAE,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACnH,CAAC;QACH,CAAC;IACF,CAAC;IACD,OAAO,WAAW,CAAC;AAAA,CACnB;AAED,SAAS,mBAAmB,CAAC,IAAY,EAAiD;IACzF,IAAI,CAAC;QACJ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE;YAAE,OAAO,SAAS,CAAC;QACpE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AAAA,CACD;AAED,MAAM,UAAU,4BAA4B,CAAC,OAI5C,EAA4C;IAC5C,0BAA0B,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,eAAe,CAAC,iBAAiB,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACzG,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,2BAA2B,EAAE,CAAC;IACtE,MAAM,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,wBAAwB,EAAE,CAAC;IAChE,MAAM,KAAK,GAA6C,EAAE,CAAC;IAC3D,IAAI,CAAC,OAAO,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,mBAAmB,CAAC,yBAAyB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpF,IAAI,UAAU;YAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,mBAAmB,CAAC,wBAAwB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACpG,IAAI,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,QAAgB,EAAsB;IAC5F,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,QAAQ,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QAC3F,OAAO,kBAAkB,CAAC;IAC3B,CAAC;IACD,IAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,YAAY,CAAC;AAAA,CACxD","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, statSync } from \"node:fs\";\nimport { basename, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport type { Model } from \"@ashx-j/lunr-ai\";\nimport type { Settings, SettingsManager } from \"./settings-manager.ts\";\n\nexport type ModelInstructionsMode = \"both\" | \"model-only\";\n\nexport interface ModelInstructionsMigrationResult {\n\tstatus: \"absent\" | \"migrated\" | \"conflict\" | \"failed\";\n\tdiagnostic?: string;\n}\n\nexport function getInstructionsRoot(agentDir: string): string {\n\treturn join(resolve(agentDir), \"agents\");\n}\n\nexport function getGlobalInstructionsPath(agentDir: string): string {\n\treturn join(getInstructionsRoot(agentDir), \"AGENTS.md\");\n}\n\nexport function modelInstructionFolderName(modelId: string): string {\n\tconst finalSegment = modelId.trim().split(/[\\\\/]/).filter(Boolean).at(-1) ?? \"model\";\n\tconst withoutThinking = finalSegment.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, \"\");\n\tlet safe = withoutThinking.replace(/[<>:\"/\\\\|?*\\u0000-\\u001f]/g, \"_\").replace(/[. ]+$/g, \"\");\n\tif (!safe || safe === \".\" || safe === \"..\") safe = \"model\";\n\tif (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\\.|$)/i.test(safe)) safe = `_${safe}`;\n\tif (safe.length > 100 || safe.toLowerCase() !== withoutThinking.toLowerCase()) {\n\t\tconst digest = createHash(\"sha256\").update(withoutThinking.toLowerCase()).digest(\"hex\").slice(0, 8);\n\t\tsafe = `${safe.slice(0, 90)}-${digest}`;\n\t}\n\treturn safe.toLowerCase();\n}\n\nexport function getModelInstructionsPath(agentDir: string, modelId: string): string {\n\treturn join(getInstructionsRoot(agentDir), modelInstructionFolderName(modelId), \"AGENTS.md\");\n}\n\nexport function isUserInstructionsPath(filePath: string, agentDir: string): boolean {\n\tconst rel = relative(resolve(getInstructionsRoot(agentDir)), resolve(filePath));\n\treturn rel === \"\" || (!isAbsolute(rel) && rel !== \"..\" && !rel.startsWith(`..${sep}`));\n}\n\nexport function migrateLegacyGlobalInstructions(agentDir: string): ModelInstructionsMigrationResult {\n\tconst legacyPath = join(resolve(agentDir), \"AGENTS.md\");\n\tconst destinationPath = getGlobalInstructionsPath(agentDir);\n\tif (!existsSync(legacyPath)) return { status: \"absent\" };\n\tif (existsSync(destinationPath)) {\n\t\treturn {\n\t\t\tstatus: \"conflict\",\n\t\t\tdiagnostic: `Both legacy '${legacyPath}' and current '${destinationPath}' exist. Kept both and will use the current file.`,\n\t\t};\n\t}\n\ttry {\n\t\tmkdirSync(getInstructionsRoot(agentDir), { recursive: true });\n\t\trenameSync(legacyPath, destinationPath);\n\t\treturn { status: \"migrated\" };\n\t} catch (error) {\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tdiagnostic: `Could not migrate '${legacyPath}' to '${destinationPath}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t};\n\t}\n}\n\nfunction concreteModelId(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\tif (!trimmed || /[*?{}]/.test(trimmed)) return undefined;\n\treturn trimmed.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/i, \"\");\n}\n\nexport function ensureModelInstructionDirs(\n\tagentDir: string,\n\tsettings: Pick<Settings, \"defaultModel\" | \"enabledModels\" | \"modelTiers\">,\n\tcurrentModel?: Pick<Model<any>, \"id\">,\n): string[] {\n\tconst modelIds = new Set<string>();\n\tconst add = (value: unknown) => {\n\t\tconst id = concreteModelId(value);\n\t\tif (id) modelIds.add(id);\n\t};\n\tadd(currentModel?.id);\n\tadd(settings.defaultModel);\n\tfor (const value of settings.enabledModels ?? []) add(value);\n\tadd(settings.modelTiers?.light);\n\tadd(settings.modelTiers?.standard);\n\tadd(settings.modelTiers?.heavy);\n\n\tconst diagnostics: string[] = [];\n\tfor (const id of modelIds) {\n\t\ttry {\n\t\t\tmkdirSync(join(getInstructionsRoot(agentDir), modelInstructionFolderName(id)), { recursive: true });\n\t\t} catch (error) {\n\t\t\tdiagnostics.push(\n\t\t\t\t`Could not prepare model-instruction folder for '${id}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t}\n\treturn diagnostics;\n}\n\nfunction safeReadInstruction(path: string): { path: string; content: string } | undefined {\n\ttry {\n\t\tif (!existsSync(path) || !statSync(path).isFile()) return undefined;\n\t\treturn { path, content: readFileSync(path, \"utf8\") };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function loadSelectedUserInstructions(options: {\n\tagentDir: string;\n\tsettingsManager: SettingsManager;\n\tmodel?: Pick<Model<any>, \"id\">;\n}): Array<{ path: string; content: string }> {\n\tensureModelInstructionDirs(options.agentDir, options.settingsManager.getGlobalSettings(), options.model);\n\tconst enabled = options.settingsManager.getModelInstructionsEnabled();\n\tconst mode = options.settingsManager.getModelInstructionsMode();\n\tconst files: Array<{ path: string; content: string }> = [];\n\tif (!enabled || mode === \"both\") {\n\t\tconst globalFile = safeReadInstruction(getGlobalInstructionsPath(options.agentDir));\n\t\tif (globalFile) files.push(globalFile);\n\t}\n\tif (enabled && options.model) {\n\t\tconst modelFile = safeReadInstruction(getModelInstructionsPath(options.agentDir, options.model.id));\n\t\tif (modelFile) files.push(modelFile);\n\t}\n\treturn files;\n}\n\nexport function instructionPathLabel(filePath: string, agentDir: string): string | undefined {\n\tconst resolved = resolve(filePath);\n\tif (resolved.toLowerCase() === resolve(getGlobalInstructionsPath(agentDir)).toLowerCase()) {\n\t\treturn \"Global AGENTS.md\";\n\t}\n\tif (!isUserInstructionsPath(resolved, agentDir)) return undefined;\n\treturn `${basename(resolve(resolved, \"..\"))} AGENTS.md`;\n}\n"]}
|
|
@@ -33,13 +33,13 @@ export interface ApprovalRequest {
|
|
|
33
33
|
toolName: string;
|
|
34
34
|
/** "bash" (detail = command), "edit"/"write" (detail = path),
|
|
35
35
|
* "edit-outside"/"write-outside" when path escapes cwd,
|
|
36
|
-
* "
|
|
36
|
+
* "subagent-full" for full-access children, or "large-subagent-launch" for aggregate confirmation. */
|
|
37
37
|
action: string;
|
|
38
38
|
detail: string;
|
|
39
|
-
/** "
|
|
39
|
+
/** "large-subagent-launch" for aggregate child confirmation, "plan" for the
|
|
40
40
|
* present_plan plan-approval prompt — the UI renders a dedicated dialog
|
|
41
41
|
* for each instead of the generic tool dialog. */
|
|
42
|
-
kind?: "
|
|
42
|
+
kind?: "large-subagent-launch" | "plan";
|
|
43
43
|
}
|
|
44
44
|
export type ApprovalDecision = "once" | "session" | "reject";
|
|
45
45
|
/** Handlers may return a bare decision string, a reject carrying user feedback
|
|
@@ -53,8 +53,8 @@ export type ApprovalResponse = ApprovalDecision | {
|
|
|
53
53
|
feedback?: string;
|
|
54
54
|
};
|
|
55
55
|
export declare const NO_HANDLER_REASON = "Mutating tool blocked in manual mode: no approval channel available.";
|
|
56
|
-
export declare const
|
|
57
|
-
export declare const
|
|
56
|
+
export declare const LARGE_SUBAGENT_LAUNCH_REJECT_REASON = "Large subagent launch rejected by user.";
|
|
57
|
+
export declare const NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON = "Large subagent launch blocked: no approval channel available.";
|
|
58
58
|
export declare function createPermissionContext(sessionId: string, mode?: PermissionMode): void;
|
|
59
59
|
export declare function deletePermissionContext(sessionId: string): void;
|
|
60
60
|
export declare function getPermissionMode(sessionId?: string): PermissionMode;
|
|
@@ -68,7 +68,7 @@ export declare const PLAN_PASS_THROUGH_TEXT = "Plan presented. The user will rev
|
|
|
68
68
|
/** lunr: map an approval response to the model-facing present_plan result text. */
|
|
69
69
|
export declare function planApprovalResultText(resp: ApprovalResponse): string;
|
|
70
70
|
/**
|
|
71
|
-
* lunr: present_plan approval request. Unlike
|
|
71
|
+
* lunr: present_plan approval request. Unlike mutating-tool and aggregate launch gates
|
|
72
72
|
* this fails OPEN when no approval handler is registered (or none is reachable,
|
|
73
73
|
* e.g. a gateway turn without a chat context): headless sessions have no one to
|
|
74
74
|
* show the dialog to, so the plan is presented and the user replies in chat
|
|
@@ -80,14 +80,13 @@ export declare function clearSessionApprovals(sessionId?: string): void;
|
|
|
80
80
|
export declare function resetAllPermissionContexts(): void;
|
|
81
81
|
/** Appended to the system prompt while auto mode is active. */
|
|
82
82
|
export declare const AUTO_MODE_ADDENDUM = "You are running fully autonomously. Do not ask the user questions or wait for confirmation; make reasonable decisions and complete the task end-to-end.";
|
|
83
|
-
export declare const GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON = "The
|
|
83
|
+
export declare const GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON = "The agents instruction tree is user-managed. The agent cannot change ~/.lunr/agent/agents/.";
|
|
84
84
|
export declare const MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON = "Memory is model-managed through the memory tools. Do not directly change ~/.lunr/simple-memory/memory.md.";
|
|
85
85
|
export interface GateOptions {
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
explicitSwarmTurn?: boolean;
|
|
86
|
+
/** Global preference for aggregate confirmation. Defaults to enabled. */
|
|
87
|
+
confirmLargeSubagentLaunches?: boolean;
|
|
89
88
|
/** Assistant message that issued this tool call. Same-turn sibling SINGLE
|
|
90
|
-
* `subagent` calls count toward the
|
|
89
|
+
* `subagent` calls count toward the aggregate threshold. */
|
|
91
90
|
assistantMessage?: {
|
|
92
91
|
content?: ReadonlyArray<{
|
|
93
92
|
type?: string;
|
|
@@ -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 { isCodeRewriteMutating, planModeBlockReason } from \"./plan-mode.ts\";\nimport { effectiveSwarmCountForTurn, SWARM_APPROVAL_THRESHOLD } from \"./swarm.ts\";\n\n/** Shift+Tab cycle order. */\nexport const PERMISSION_MODES = [\"manual\", \"yolo\", \"plan\", \"auto\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\nexport function nextPermissionMode(current: PermissionMode): PermissionMode {\n\tconst i = PERMISSION_MODES.indexOf(current);\n\treturn PERMISSION_MODES[(i + 1) % PERMISSION_MODES.length];\n}\n\n/** True when the (default or session) permission mode is plan. */\nexport function isPlanModeActive(sessionId?: string): boolean {\n\treturn getPermissionMode(sessionId) === \"plan\";\n}\n\n/**\n * Destination mode when leaving plan via approve / `/plan off` / `/plan <text>`.\n * Explicit Shift+Tab or `/mode` picks do not use this — they already chose a mode.\n */\nexport function restorePermissionModeAfterPlan(\n\tprevious: PermissionMode | undefined,\n\tdefaultMode: PermissionMode,\n): PermissionMode {\n\tif (previous && previous !== \"plan\") return previous;\n\treturn defaultMode === \"plan\" ? \"yolo\" : defaultMode;\n}\n\nexport interface ApprovalRequest {\n\ttoolName: string;\n\t/** \"bash\" (detail = command), \"edit\"/\"write\" (detail = path),\n\t * \"edit-outside\"/\"write-outside\" when path escapes cwd,\n\t * \"swarm\" for auto-activated agent swarms (detail = summary lines). */\n\taction: string;\n\tdetail: string;\n\t/** \"swarm\" when this is the agent-swarm approval prompt, \"plan\" for the\n\t * present_plan plan-approval prompt — the UI renders a dedicated dialog\n\t * for each instead of the generic tool dialog. */\n\tkind?: \"swarm\" | \"plan\";\n}\n\nexport type ApprovalDecision = \"once\" | \"session\" | \"reject\";\n/** Handlers may return a bare decision string, a reject carrying user feedback\n * that becomes the block reason the model sees, or an approve carrying optional\n * feedback (plan approval with comments). */\nexport type ApprovalResponse =\n\t| ApprovalDecision\n\t| { decision: \"reject\"; feedback: string }\n\t| { decision: \"approve\"; feedback?: string };\n\n/** Read-only tools that never need approval. */\nconst READ_ONLY_TOOLS = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"web_search\",\n\t\"web_fetch\",\n\t\"fetch_content\",\n\t\"get_search_content\",\n\t\"sourcegraph\",\n\t\"repo_map\",\n\t\"symbol_outline\",\n\t\"read_block\",\n\t\"code_overview\",\n\t\"ast_search\",\n\t\"lsp_diagnostics\",\n\t\"lsp_hover\",\n\t\"lsp_definition\",\n\t\"lsp_references\",\n\t\"lsp_symbols\",\n\t\"lsp_code_actions\",\n\t\"lsp_completions\",\n\t\"memory_load\",\n]);\n\n/** Tools that mutate state and must be gated in manual mode. */\nconst MUTATING_TOOLS = new Set([\"bash\", \"edit\", \"write\", \"memory_add\", \"memory_remove\", \"cron\"]);\n\nconst REJECT_REASON = \"Rejected by user (permission mode: manual).\";\nexport const NO_HANDLER_REASON = \"Mutating tool blocked in manual mode: no approval channel available.\";\nexport const SWARM_REJECT_REASON = \"Agent swarm rejected by user.\";\nexport const NO_SWARM_HANDLER_REASON = \"Agent swarm blocked: no approval channel available.\";\n/** Session-approval key for auto-activated agent swarms. */\nconst SWARM_ACTION = \"swarm\";\n\ninterface PermissionContext {\n\tmode: PermissionMode;\n\tapprovals: Set<string>;\n}\n\nconst contexts = new Map<string, PermissionContext>();\nlet defaultContext: PermissionContext = { mode: \"manual\", approvals: new Set() };\nlet approvalHandler: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined;\n/** One swarm prompt covers every sibling SINGLE `subagent` on the same assistant message. */\nconst turnSwarmDecisions = new WeakMap<object, { decision: ApprovalDecision; feedback?: string }>();\n\nfunction getContext(sessionId?: string): PermissionContext {\n\tif (!sessionId) return defaultContext;\n\treturn contexts.get(sessionId) ?? defaultContext;\n}\n\nexport function createPermissionContext(sessionId: string, mode: PermissionMode = defaultContext.mode): void {\n\tcontexts.set(sessionId, { mode, approvals: new Set() });\n}\n\nexport function deletePermissionContext(sessionId: string): void {\n\tcontexts.delete(sessionId);\n}\n\nexport function getPermissionMode(sessionId?: string): PermissionMode {\n\treturn getContext(sessionId).mode;\n}\n\nexport function setPermissionMode(mode: PermissionMode, sessionId?: string): void {\n\tgetContext(sessionId).mode = mode;\n}\n\nexport function resetPermissions(defaultMode: PermissionMode = \"manual\", sessionId?: string): void {\n\tif (sessionId) {\n\t\tcontexts.set(sessionId, { mode: defaultMode, approvals: new Set() });\n\t} else {\n\t\tdefaultContext = { mode: defaultMode, approvals: new Set() };\n\t}\n}\n\nexport function registerApprovalHandler(fn: ((req: ApprovalRequest) => Promise<ApprovalResponse>) | undefined): void {\n\tapprovalHandler = fn;\n}\n\n/** Model-facing result texts for the present_plan tool. */\nexport const PLAN_APPROVED_TEXT = \"Plan approved — implement it now.\";\nexport const PLAN_DECLINED_TEXT = \"Plan declined — revise the plan and call present_plan again.\";\nexport const PLAN_PASS_THROUGH_TEXT = \"Plan presented. The user will review and reply.\";\n\n/** lunr: map an approval response to the model-facing present_plan result text. */\nexport function planApprovalResultText(resp: ApprovalResponse): string {\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (decision === \"reject\") {\n\t\treturn feedback\n\t\t\t? `Plan declined: ${feedback} — revise the plan and call present_plan again.`\n\t\t\t: PLAN_DECLINED_TEXT;\n\t}\n\treturn feedback ? `Plan approved with feedback: ${feedback} — implement it now.` : PLAN_APPROVED_TEXT;\n}\n\n/**\n * lunr: present_plan approval request. Unlike the mutating-tool and swarm gates\n * this fails OPEN when no approval handler is registered (or none is reachable,\n * e.g. a gateway turn without a chat context): headless sessions have no one to\n * show the dialog to, so the plan is presented and the user replies in chat\n * instead of deadlocking the turn. Everything else keeps fail-closed.\n */\nexport async function requestPlanApproval(summary: string): Promise<string> {\n\tif (!approvalHandler) return PLAN_PASS_THROUGH_TEXT;\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName: \"present_plan\", action: \"plan\", detail: summary, kind: \"plan\" });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"approval request failed\";\n\t\tif (message === NO_HANDLER_REASON) return PLAN_PASS_THROUGH_TEXT;\n\t\treturn `Plan declined: ${message} — revise the plan and call present_plan again.`;\n\t}\n\treturn planApprovalResultText(resp);\n}\n\nexport function clearSessionApprovals(sessionId?: string): void {\n\tgetContext(sessionId).approvals.clear();\n}\n\n/** Test seam: remove every per-session context and restore the default. */\nexport function resetAllPermissionContexts(): void {\n\tcontexts.clear();\n\tdefaultContext = { mode: \"manual\", approvals: new Set() };\n\tapprovalHandler = undefined;\n}\n\n/** Appended to the system prompt while auto mode is active. */\nexport const AUTO_MODE_ADDENDUM =\n\t\"You are running fully autonomously. Do not ask the user questions or wait for confirmation; make reasonable decisions and complete the task end-to-end.\";\n\nfunction isPathOutsideCwd(absPath: string, cwd: string): boolean {\n\tconst norm = (p: string) => p.replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n\tconst np = norm(absPath);\n\tconst nc = norm(cwd);\n\tif (np === nc) return false;\n\treturn !np.startsWith(`${nc}/`);\n}\n\nfunction resolvePath(cwd: string, p: unknown): string {\n\tif (typeof p !== \"string\") return \"\";\n\tif (!p) return \"\";\n\treturn resolve(cwd, p);\n}\n\nexport const GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON =\n\t\"The global AGENTS.md file is user-managed. The agent cannot change ~/.lunr/agent/AGENTS.md.\";\nexport const MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON =\n\t\"Memory is model-managed through the memory tools. Do not directly change ~/.lunr/simple-memory/memory.md.\";\n\nfunction protectedFileWriteReason(toolName: string, input: Record<string, unknown>, cwd: string): string | undefined {\n\tif (toolName !== \"edit\" && toolName !== \"write\" && toolName !== \"code_rewrite\") return undefined;\n\tconst target = resolvePath(cwd, input.path);\n\tif (!target) return undefined;\n\tconst normalize = (path: string) => {\n\t\tconst normalized = resolve(path).replace(/\\\\/g, \"/\").replace(/\\/$/, \"\");\n\t\treturn process.platform === \"win32\" ? normalized.toLowerCase() : normalized;\n\t};\n\tconst agentsPaths = [join(getAgentDir(), \"AGENTS.md\"), join(getAgentDir(), \"AGENTS.MD\")];\n\tif (agentsPaths.some((candidate) => normalize(target) === normalize(candidate))) {\n\t\treturn GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON;\n\t}\n\tif (normalize(target) === normalize(join(dirname(getAgentDir()), \"simple-memory\", \"memory.md\"))) {\n\t\treturn MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON;\n\t}\n\treturn undefined;\n}\n\nfunction isMutatingTool(toolName: string): boolean {\n\treturn MUTATING_TOOLS.has(toolName);\n}\n\nfunction requiresManualApproval(toolName: string, input: Record<string, unknown>): boolean {\n\tif (isMutatingTool(toolName)) return true;\n\treturn toolName === \"code_rewrite\" && isCodeRewriteMutating(input);\n}\n\nexport interface GateOptions {\n\t/** True when the current turn started from an explicit /swarm prompt — the\n\t * user already asked for the swarm, so the swarm approval gate is skipped. */\n\texplicitSwarmTurn?: boolean;\n\t/** Assistant message that issued this tool call. Same-turn sibling SINGLE\n\t * `subagent` calls count toward the swarm threshold. */\n\tassistantMessage?: {\n\t\tcontent?: ReadonlyArray<{ type?: string; name?: string; arguments?: unknown }>;\n\t};\n}\n\nfunction firstTaskText(value: unknown): string {\n\tif (typeof value === \"string\" && value.trim()) return sanitizeDetail(value);\n\treturn \"\";\n}\n\n/** First task text of a parallel fan-out or sibling SINGLE, used as the dialog summary. */\nfunction swarmTaskSummary(input: Record<string, unknown>, assistantMessage?: GateOptions[\"assistantMessage\"]): string {\n\tif (Array.isArray(input.tasks)) {\n\t\tfor (const task of input.tasks) {\n\t\t\tconst text = task && typeof task === \"object\" ? firstTaskText((task as { task?: unknown }).task) : \"\";\n\t\t\tif (text) return text;\n\t\t}\n\t}\n\tconst own = firstTaskText(input.task);\n\tif (own) return own;\n\tfor (const block of assistantMessage?.content ?? []) {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") continue;\n\t\tconst args =\n\t\t\tblock.arguments && typeof block.arguments === \"object\" && !Array.isArray(block.arguments)\n\t\t\t\t? (block.arguments as Record<string, unknown>)\n\t\t\t\t: undefined;\n\t\tconst text = firstTaskText(args?.task);\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\n/**\n * Agent-swarm gate. An auto-activated swarm (more than SWARM_APPROVAL_THRESHOLD\n * parallel subagents in one `tasks`/`chain.parallel` call, or that many same-turn\n * SINGLE `subagent` calls) requires user approval in manual AND yolo modes; auto\n * mode runs it unconditionally and explicit /swarm turns are pre-approved.\n * Fail-closed without an approval handler, same as the mutating-tool gate.\n */\nasync function gateSwarmCall(\n\tinput: Record<string, unknown>,\n\tctx: PermissionContext,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tif (ctx.mode === \"auto\") return undefined;\n\tif (options?.explicitSwarmTurn) return undefined;\n\tconst count = effectiveSwarmCountForTurn(input, options?.assistantMessage);\n\tif (count <= SWARM_APPROVAL_THRESHOLD) return undefined;\n\tif (ctx.approvals.has(SWARM_ACTION)) return undefined;\n\n\tconst turnKey = options?.assistantMessage;\n\tconst cached = turnKey ? turnSwarmDecisions.get(turnKey) : undefined;\n\tif (cached) {\n\t\tif (cached.decision === \"reject\") {\n\t\t\treturn { block: true, reason: cached.feedback ? `${SWARM_REJECT_REASON} ${cached.feedback}` : SWARM_REJECT_REASON };\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_SWARM_HANDLER_REASON };\n\t}\n\n\tconst summary = swarmTaskSummary(input, options?.assistantMessage);\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({\n\t\t\ttoolName: \"subagent\",\n\t\t\taction: SWARM_ACTION,\n\t\t\tdetail: summary ? `agent swarm (${count} subagents)\\n${summary}` : `agent swarm (${count} subagents)`,\n\t\t\tkind: \"swarm\",\n\t\t});\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_SWARM_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst rawDecision = typeof resp === \"string\" ? resp : resp.decision;\n\tconst decision: ApprovalDecision = rawDecision === \"approve\" ? \"once\" : rawDecision;\n\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\tif (turnKey) turnSwarmDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}) });\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(SWARM_ACTION);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\treturn { block: true, reason: feedback ? `${SWARM_REJECT_REASON} ${feedback}` : SWARM_REJECT_REASON };\n\t}\n\treturn undefined;\n}\n\nfunction sanitizeDetail(value: unknown): string {\n\tconst text = String(value ?? \"\").trim();\n\treturn text.length > 200 ? `${text.slice(0, 200)}…` : text;\n}\n\n/**\n * Returns a block result when the tool call should be blocked, else undefined.\n * Read-only tools always pass. YOLO and Auto always pass mutating tools. Plan\n * mode hard-blocks mutating tools (no prompt). Manual mode gates every mutating\n * tool; if an approval handler is registered it is asked, otherwise the call is\n * blocked (fail-closed — no silent allow).\n */\nexport async function gateToolCall(\n\ttoolName: string,\n\tinput: Record<string, unknown>,\n\tcwd: string,\n\tsessionId?: string,\n\toptions?: GateOptions,\n): Promise<{ block: true; reason: string } | undefined> {\n\tconst ctx = getContext(sessionId);\n\tconst protectedWriteReason = protectedFileWriteReason(toolName, input, cwd);\n\tif (protectedWriteReason) {\n\t\treturn { block: true, reason: protectedWriteReason };\n\t}\n\tif (READ_ONLY_TOOLS.has(toolName)) return undefined;\n\n\t// lunr: agent-swarm gate runs before the mode early-return — it applies in\n\t// yolo mode too (only auto mode bypasses it).\n\tif (toolName === \"subagent\") {\n\t\tconst swarmBlock = await gateSwarmCall(input, ctx, options);\n\t\tif (swarmBlock) return swarmBlock;\n\t}\n\n\tif (ctx.mode === \"plan\") {\n\t\tconst reason = planModeBlockReason(toolName, input);\n\t\treturn reason ? { block: true, reason } : undefined;\n\t}\n\n\tif (ctx.mode !== \"manual\") return undefined;\n\tif (!requiresManualApproval(toolName, input)) return undefined;\n\n\tlet action: string;\n\tlet detail: string;\n\n\tif (toolName === \"bash\") {\n\t\taction = \"bash\";\n\t\tdetail = sanitizeDetail(input.command);\n\t} else if (toolName === \"edit\" || toolName === \"write\") {\n\t\tconst path = resolvePath(cwd, input.path);\n\t\tconst outside = path ? isPathOutsideCwd(path, cwd) : false;\n\t\taction = outside ? `${toolName}-outside` : toolName;\n\t\tdetail = path || String(input.path ?? \"\");\n\t} else if (toolName === \"memory_add\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.content);\n\t} else if (toolName === \"memory_remove\") {\n\t\taction = toolName;\n\t\tdetail = sanitizeDetail(input.line);\n\t} else if (toolName === \"cron\") {\n\t\taction = \"cron\";\n\t\tdetail = sanitizeDetail(input.action);\n\t} else if (toolName === \"code_rewrite\") {\n\t\taction = \"code_rewrite\";\n\t\tdetail = sanitizeDetail(input.path ?? input.pattern);\n\t} else {\n\t\t// All other mutating tools fall back to the tool name with no extra detail.\n\t\taction = toolName;\n\t\tdetail = \"\";\n\t}\n\n\tif (ctx.approvals.has(action)) return undefined;\n\n\tif (!approvalHandler) {\n\t\treturn { block: true, reason: NO_HANDLER_REASON };\n\t}\n\n\tlet resp: ApprovalResponse;\n\ttry {\n\t\tresp = await approvalHandler({ toolName, action, detail });\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : NO_HANDLER_REASON;\n\t\treturn { block: true, reason: message };\n\t}\n\n\tconst decision = typeof resp === \"string\" ? resp : resp.decision;\n\tif (decision === \"session\") {\n\t\tctx.approvals.add(action);\n\t\treturn undefined;\n\t}\n\tif (decision === \"reject\") {\n\t\tconst feedback = typeof resp === \"string\" ? undefined : resp.feedback?.trim();\n\t\treturn { block: true, reason: feedback ? `${REJECT_REASON} ${feedback}` : REJECT_REASON };\n\t}\n\t// \"once\" — allow this call only\n\treturn undefined;\n}\n"]}
|
|
1
|
+
{"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"]}
|
package/dist/core/permissions.js
CHANGED
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { dirname, join, resolve } from "node:path";
|
|
22
22
|
import { getAgentDir } from "../config.js";
|
|
23
|
+
import { effectiveLargeSubagentLaunchCountForTurn, LARGE_SUBAGENT_LAUNCH_THRESHOLD } from "./large-subagent-launch.js";
|
|
24
|
+
import { isUserInstructionsPath } from "./model-instructions.js";
|
|
23
25
|
import { isCodeRewriteMutating, planModeBlockReason } from "./plan-mode.js";
|
|
24
|
-
import { effectiveSwarmCountForTurn, SWARM_APPROVAL_THRESHOLD } from "./swarm.js";
|
|
25
26
|
/** Shift+Tab cycle order. */
|
|
26
27
|
export const PERMISSION_MODES = ["manual", "yolo", "plan", "auto"];
|
|
27
28
|
export function nextPermissionMode(current) {
|
|
@@ -65,20 +66,22 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
65
66
|
"lsp_code_actions",
|
|
66
67
|
"lsp_completions",
|
|
67
68
|
"memory_load",
|
|
69
|
+
"settings_load",
|
|
68
70
|
]);
|
|
69
71
|
/** Tools that mutate state and must be gated in manual mode. */
|
|
70
72
|
const MUTATING_TOOLS = new Set(["bash", "edit", "write", "memory_add", "memory_remove", "cron"]);
|
|
71
73
|
const REJECT_REASON = "Rejected by user (permission mode: manual).";
|
|
72
74
|
export const NO_HANDLER_REASON = "Mutating tool blocked in manual mode: no approval channel available.";
|
|
73
|
-
export const
|
|
74
|
-
export const
|
|
75
|
-
/** Session-approval
|
|
76
|
-
const
|
|
75
|
+
export const LARGE_SUBAGENT_LAUNCH_REJECT_REASON = "Large subagent launch rejected by user.";
|
|
76
|
+
export const NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON = "Large subagent launch blocked: no approval channel available.";
|
|
77
|
+
/** Session-approval keys for child launches. */
|
|
78
|
+
const LARGE_SUBAGENT_LAUNCH_ACTION = "large-subagent-launch";
|
|
79
|
+
const FULL_CHILD_ACTION = "subagent-full";
|
|
77
80
|
const contexts = new Map();
|
|
78
81
|
let defaultContext = { mode: "manual", approvals: new Set() };
|
|
79
82
|
let approvalHandler;
|
|
80
|
-
/** One
|
|
81
|
-
const
|
|
83
|
+
/** One aggregate prompt covers every sibling SINGLE `subagent` on the same assistant message. */
|
|
84
|
+
const turnLargeLaunchDecisions = new WeakMap();
|
|
82
85
|
function getContext(sessionId) {
|
|
83
86
|
if (!sessionId)
|
|
84
87
|
return defaultContext;
|
|
@@ -123,7 +126,7 @@ export function planApprovalResultText(resp) {
|
|
|
123
126
|
return feedback ? `Plan approved with feedback: ${feedback} — implement it now.` : PLAN_APPROVED_TEXT;
|
|
124
127
|
}
|
|
125
128
|
/**
|
|
126
|
-
* lunr: present_plan approval request. Unlike
|
|
129
|
+
* lunr: present_plan approval request. Unlike mutating-tool and aggregate launch gates
|
|
127
130
|
* this fails OPEN when no approval handler is registered (or none is reachable,
|
|
128
131
|
* e.g. a gateway turn without a chat context): headless sessions have no one to
|
|
129
132
|
* show the dialog to, so the plan is presented and the user replies in chat
|
|
@@ -170,7 +173,7 @@ function resolvePath(cwd, p) {
|
|
|
170
173
|
return "";
|
|
171
174
|
return resolve(cwd, p);
|
|
172
175
|
}
|
|
173
|
-
export const GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON = "The
|
|
176
|
+
export const GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON = "The agents instruction tree is user-managed. The agent cannot change ~/.lunr/agent/agents/.";
|
|
174
177
|
export const MEMORY_FILE_DIRECT_WRITE_BLOCK_REASON = "Memory is model-managed through the memory tools. Do not directly change ~/.lunr/simple-memory/memory.md.";
|
|
175
178
|
function protectedFileWriteReason(toolName, input, cwd) {
|
|
176
179
|
if (toolName !== "edit" && toolName !== "write" && toolName !== "code_rewrite")
|
|
@@ -182,8 +185,7 @@ function protectedFileWriteReason(toolName, input, cwd) {
|
|
|
182
185
|
const normalized = resolve(path).replace(/\\/g, "/").replace(/\/$/, "");
|
|
183
186
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
184
187
|
};
|
|
185
|
-
|
|
186
|
-
if (agentsPaths.some((candidate) => normalize(target) === normalize(candidate))) {
|
|
188
|
+
if (isUserInstructionsPath(target, getAgentDir())) {
|
|
187
189
|
return GLOBAL_AGENTS_FILE_WRITE_BLOCK_REASON;
|
|
188
190
|
}
|
|
189
191
|
if (normalize(target) === normalize(join(dirname(getAgentDir()), "simple-memory", "memory.md"))) {
|
|
@@ -194,9 +196,43 @@ function protectedFileWriteReason(toolName, input, cwd) {
|
|
|
194
196
|
function isMutatingTool(toolName) {
|
|
195
197
|
return MUTATING_TOOLS.has(toolName);
|
|
196
198
|
}
|
|
199
|
+
function collectRequestedChildLaunches(value, launches) {
|
|
200
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
201
|
+
return;
|
|
202
|
+
const input = value;
|
|
203
|
+
if (typeof input.task === "string" && input.task.trim()) {
|
|
204
|
+
launches.push({
|
|
205
|
+
description: typeof input.description === "string" && input.description.trim()
|
|
206
|
+
? input.description.trim()
|
|
207
|
+
: input.task.trim(),
|
|
208
|
+
permissions: input.permissions === "read-only" ? "read-only" : "full",
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
for (const key of ["tasks", "chain", "parallel"]) {
|
|
213
|
+
const children = input[key];
|
|
214
|
+
if (Array.isArray(children)) {
|
|
215
|
+
for (const child of children)
|
|
216
|
+
collectRequestedChildLaunches(child, launches);
|
|
217
|
+
}
|
|
218
|
+
else if (key === "parallel") {
|
|
219
|
+
collectRequestedChildLaunches(children, launches);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function getRequestedChildLaunches(input) {
|
|
224
|
+
const launches = [];
|
|
225
|
+
collectRequestedChildLaunches(input, launches);
|
|
226
|
+
return launches;
|
|
227
|
+
}
|
|
197
228
|
function requiresManualApproval(toolName, input) {
|
|
198
229
|
if (isMutatingTool(toolName))
|
|
199
230
|
return true;
|
|
231
|
+
if (toolName.startsWith("settings_") && toolName !== "settings_load")
|
|
232
|
+
return Object.keys(input).length > 0;
|
|
233
|
+
if (toolName === "subagent") {
|
|
234
|
+
return getRequestedChildLaunches(input).some((launch) => launch.permissions === "full");
|
|
235
|
+
}
|
|
200
236
|
return toolName === "code_rewrite" && isCodeRewriteMutating(input);
|
|
201
237
|
}
|
|
202
238
|
function firstTaskText(value) {
|
|
@@ -205,7 +241,7 @@ function firstTaskText(value) {
|
|
|
205
241
|
return "";
|
|
206
242
|
}
|
|
207
243
|
/** First task text of a parallel fan-out or sibling SINGLE, used as the dialog summary. */
|
|
208
|
-
function
|
|
244
|
+
function largeLaunchTaskSummary(input, assistantMessage) {
|
|
209
245
|
if (Array.isArray(input.tasks)) {
|
|
210
246
|
for (const task of input.tasks) {
|
|
211
247
|
const text = task && typeof task === "object" ? firstTaskText(task.task) : "";
|
|
@@ -229,60 +265,84 @@ function swarmTaskSummary(input, assistantMessage) {
|
|
|
229
265
|
return "";
|
|
230
266
|
}
|
|
231
267
|
/**
|
|
232
|
-
*
|
|
268
|
+
* Aggregate launch gate. A launch above LARGE_SUBAGENT_LAUNCH_THRESHOLD
|
|
233
269
|
* parallel subagents in one `tasks`/`chain.parallel` call, or that many same-turn
|
|
234
270
|
* SINGLE `subagent` calls) requires user approval in manual AND yolo modes; auto
|
|
235
|
-
* mode runs it unconditionally
|
|
271
|
+
* mode runs it unconditionally.
|
|
236
272
|
* Fail-closed without an approval handler, same as the mutating-tool gate.
|
|
237
273
|
*/
|
|
238
|
-
async function
|
|
274
|
+
async function gateLargeSubagentLaunch(input, ctx, options) {
|
|
239
275
|
if (ctx.mode === "auto")
|
|
240
276
|
return undefined;
|
|
241
|
-
if (options?.
|
|
242
|
-
return undefined;
|
|
243
|
-
const count = effectiveSwarmCountForTurn(input, options?.assistantMessage);
|
|
244
|
-
if (count <= SWARM_APPROVAL_THRESHOLD)
|
|
277
|
+
if (options?.confirmLargeSubagentLaunches === false)
|
|
245
278
|
return undefined;
|
|
246
|
-
|
|
279
|
+
const count = effectiveLargeSubagentLaunchCountForTurn(input, options?.assistantMessage);
|
|
280
|
+
if (count <= LARGE_SUBAGENT_LAUNCH_THRESHOLD)
|
|
247
281
|
return undefined;
|
|
282
|
+
if (ctx.approvals.has(LARGE_SUBAGENT_LAUNCH_ACTION))
|
|
283
|
+
return { fullChildrenApproved: false };
|
|
248
284
|
const turnKey = options?.assistantMessage;
|
|
249
|
-
const cached = turnKey ?
|
|
285
|
+
const cached = turnKey ? turnLargeLaunchDecisions.get(turnKey) : undefined;
|
|
250
286
|
if (cached) {
|
|
251
287
|
if (cached.decision === "reject") {
|
|
252
|
-
return {
|
|
288
|
+
return {
|
|
289
|
+
block: true,
|
|
290
|
+
reason: cached.feedback
|
|
291
|
+
? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${cached.feedback}`
|
|
292
|
+
: LARGE_SUBAGENT_LAUNCH_REJECT_REASON,
|
|
293
|
+
};
|
|
253
294
|
}
|
|
254
|
-
return
|
|
295
|
+
return { fullChildrenApproved: cached.fullChildrenApproved };
|
|
255
296
|
}
|
|
256
297
|
if (!approvalHandler) {
|
|
257
|
-
return { block: true, reason:
|
|
298
|
+
return { block: true, reason: NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON };
|
|
258
299
|
}
|
|
259
|
-
const summary =
|
|
300
|
+
const summary = largeLaunchTaskSummary(input, options?.assistantMessage);
|
|
301
|
+
const siblingLaunches = (options?.assistantMessage?.content ?? []).flatMap((block) => {
|
|
302
|
+
if (block.type !== "toolCall" || block.name !== "subagent")
|
|
303
|
+
return [];
|
|
304
|
+
const args = block.arguments && typeof block.arguments === "object" && !Array.isArray(block.arguments)
|
|
305
|
+
? block.arguments
|
|
306
|
+
: undefined;
|
|
307
|
+
return args ? getRequestedChildLaunches(args) : [];
|
|
308
|
+
});
|
|
309
|
+
const launches = siblingLaunches.length > 0 ? siblingLaunches : getRequestedChildLaunches(input);
|
|
310
|
+
const fullChildrenApproved = launches.some((launch) => launch.permissions === "full");
|
|
311
|
+
const launchSummary = launches
|
|
312
|
+
.map((launch) => `${launch.description}\npermissions: ${launch.permissions}`)
|
|
313
|
+
.join("\n");
|
|
260
314
|
let resp;
|
|
261
315
|
try {
|
|
262
316
|
resp = await approvalHandler({
|
|
263
317
|
toolName: "subagent",
|
|
264
|
-
action:
|
|
265
|
-
detail:
|
|
266
|
-
kind: "
|
|
318
|
+
action: LARGE_SUBAGENT_LAUNCH_ACTION,
|
|
319
|
+
detail: `large subagent launch (${count} children)${launchSummary || summary ? `\n${launchSummary || summary}` : ""}`,
|
|
320
|
+
kind: "large-subagent-launch",
|
|
267
321
|
});
|
|
268
322
|
}
|
|
269
323
|
catch (err) {
|
|
270
|
-
const message = err instanceof Error ? err.message :
|
|
324
|
+
const message = err instanceof Error ? err.message : NO_LARGE_SUBAGENT_LAUNCH_HANDLER_REASON;
|
|
271
325
|
return { block: true, reason: message };
|
|
272
326
|
}
|
|
273
327
|
const rawDecision = typeof resp === "string" ? resp : resp.decision;
|
|
274
328
|
const decision = rawDecision === "approve" ? "once" : rawDecision;
|
|
275
329
|
const feedback = typeof resp === "string" ? undefined : resp.feedback?.trim();
|
|
276
|
-
if (turnKey)
|
|
277
|
-
|
|
330
|
+
if (turnKey) {
|
|
331
|
+
turnLargeLaunchDecisions.set(turnKey, { decision, ...(feedback ? { feedback } : {}), fullChildrenApproved });
|
|
332
|
+
}
|
|
278
333
|
if (decision === "session") {
|
|
279
|
-
ctx.approvals.add(
|
|
280
|
-
|
|
334
|
+
ctx.approvals.add(LARGE_SUBAGENT_LAUNCH_ACTION);
|
|
335
|
+
if (fullChildrenApproved)
|
|
336
|
+
ctx.approvals.add(FULL_CHILD_ACTION);
|
|
337
|
+
return { fullChildrenApproved };
|
|
281
338
|
}
|
|
282
339
|
if (decision === "reject") {
|
|
283
|
-
return {
|
|
340
|
+
return {
|
|
341
|
+
block: true,
|
|
342
|
+
reason: feedback ? `${LARGE_SUBAGENT_LAUNCH_REJECT_REASON} ${feedback}` : LARGE_SUBAGENT_LAUNCH_REJECT_REASON,
|
|
343
|
+
};
|
|
284
344
|
}
|
|
285
|
-
return
|
|
345
|
+
return { fullChildrenApproved };
|
|
286
346
|
}
|
|
287
347
|
function sanitizeDetail(value) {
|
|
288
348
|
const text = String(value ?? "").trim();
|
|
@@ -303,12 +363,14 @@ export async function gateToolCall(toolName, input, cwd, sessionId, options) {
|
|
|
303
363
|
}
|
|
304
364
|
if (READ_ONLY_TOOLS.has(toolName))
|
|
305
365
|
return undefined;
|
|
306
|
-
//
|
|
366
|
+
// Aggregate launch confirmation runs before the mode early-return, including YOLO.
|
|
307
367
|
// yolo mode too (only auto mode bypasses it).
|
|
368
|
+
let largeLaunchCoveredManualApproval = false;
|
|
308
369
|
if (toolName === "subagent") {
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
311
|
-
return
|
|
370
|
+
const launchResult = await gateLargeSubagentLaunch(input, ctx, options);
|
|
371
|
+
if (launchResult && "block" in launchResult)
|
|
372
|
+
return launchResult;
|
|
373
|
+
largeLaunchCoveredManualApproval = ctx.mode === "manual" && launchResult?.fullChildrenApproved === true;
|
|
312
374
|
}
|
|
313
375
|
if (ctx.mode === "plan") {
|
|
314
376
|
const reason = planModeBlockReason(toolName, input);
|
|
@@ -316,7 +378,7 @@ export async function gateToolCall(toolName, input, cwd, sessionId, options) {
|
|
|
316
378
|
}
|
|
317
379
|
if (ctx.mode !== "manual")
|
|
318
380
|
return undefined;
|
|
319
|
-
if (!requiresManualApproval(toolName, input))
|
|
381
|
+
if (!requiresManualApproval(toolName, input) || largeLaunchCoveredManualApproval)
|
|
320
382
|
return undefined;
|
|
321
383
|
let action;
|
|
322
384
|
let detail;
|
|
@@ -342,6 +404,11 @@ export async function gateToolCall(toolName, input, cwd, sessionId, options) {
|
|
|
342
404
|
action = "cron";
|
|
343
405
|
detail = sanitizeDetail(input.action);
|
|
344
406
|
}
|
|
407
|
+
else if (toolName === "subagent") {
|
|
408
|
+
action = FULL_CHILD_ACTION;
|
|
409
|
+
const launches = getRequestedChildLaunches(input).filter((launch) => launch.permissions === "full");
|
|
410
|
+
detail = sanitizeDetail(launches.map((launch) => `${launch.description}\npermissions: ${launch.permissions}`).join("\n"));
|
|
411
|
+
}
|
|
345
412
|
else if (toolName === "code_rewrite") {
|
|
346
413
|
action = "code_rewrite";
|
|
347
414
|
detail = sanitizeDetail(input.path ?? input.pattern);
|