min-agent 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +32 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +25 -8
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +7 -3
  6. package/skills/self-config/reference.md +8 -4
  7. package/dist/agent.js +0 -1342
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/exec.js +0 -35
  11. package/dist/cli/commands/history.js +0 -58
  12. package/dist/cli/commands/index.js +0 -241
  13. package/dist/cli/commands/init.js +0 -18
  14. package/dist/cli/commands/mcp.js +0 -173
  15. package/dist/cli/commands/memory.js +0 -87
  16. package/dist/cli/commands/models.js +0 -21
  17. package/dist/cli/commands/permission.js +0 -12
  18. package/dist/cli/commands/rules.js +0 -33
  19. package/dist/cli/commands/sandbox.js +0 -13
  20. package/dist/cli/commands/serve.js +0 -9
  21. package/dist/cli/commands/setup.js +0 -4
  22. package/dist/cli/commands/shared.js +0 -16
  23. package/dist/cli/commands/skills.js +0 -119
  24. package/dist/cli/commands/think.js +0 -12
  25. package/dist/cli/commands/update.js +0 -7
  26. package/dist/cli/commands/write-config.js +0 -52
  27. package/dist/cli/errors.js +0 -36
  28. package/dist/cli/exec-prompt.js +0 -26
  29. package/dist/cli/option-helpers.js +0 -65
  30. package/dist/cli/program.js +0 -217
  31. package/dist/clipboard.js +0 -142
  32. package/dist/code-mode.js +0 -184
  33. package/dist/compaction.js +0 -644
  34. package/dist/config.js +0 -470
  35. package/dist/confirm.js +0 -172
  36. package/dist/context-window.js +0 -246
  37. package/dist/doom-loop.js +0 -278
  38. package/dist/fetch-timeout.js +0 -152
  39. package/dist/http-approvals.js +0 -60
  40. package/dist/http.js +0 -119
  41. package/dist/instructions.js +0 -196
  42. package/dist/logger.js +0 -95
  43. package/dist/markdown.js +0 -259
  44. package/dist/mcp.js +0 -1036
  45. package/dist/memory-cli.js +0 -33
  46. package/dist/memory.js +0 -295
  47. package/dist/model-catalog.js +0 -285
  48. package/dist/output.js +0 -76
  49. package/dist/paste-handler.js +0 -41
  50. package/dist/permission-cli.js +0 -40
  51. package/dist/plugins.js +0 -131
  52. package/dist/pricing.js +0 -119
  53. package/dist/provider.js +0 -63
  54. package/dist/question-format.js +0 -60
  55. package/dist/reasoning-stream.js +0 -158
  56. package/dist/sandbox-cli.js +0 -79
  57. package/dist/sandbox.js +0 -403
  58. package/dist/save-throttle.js +0 -45
  59. package/dist/scope.js +0 -23
  60. package/dist/serve/common.js +0 -425
  61. package/dist/serve/routes-chat.js +0 -367
  62. package/dist/serve/routes-mcp.js +0 -212
  63. package/dist/serve/routes-memory.js +0 -95
  64. package/dist/serve/routes-meta.js +0 -233
  65. package/dist/serve/routes-sessions.js +0 -61
  66. package/dist/serve/routes-skills.js +0 -70
  67. package/dist/serve.js +0 -138
  68. package/dist/sessions.js +0 -265
  69. package/dist/skills.js +0 -599
  70. package/dist/structured-output.js +0 -29
  71. package/dist/synthetic.js +0 -7
  72. package/dist/think-cli.js +0 -36
  73. package/dist/thinking-wire.js +0 -228
  74. package/dist/thinking.js +0 -142
  75. package/dist/title-gen.js +0 -55
  76. package/dist/token-display.js +0 -39
  77. package/dist/tool-display.js +0 -178
  78. package/dist/tool-output.js +0 -126
  79. package/dist/tools/apply_patch.js +0 -265
  80. package/dist/tools/atomic-file.js +0 -35
  81. package/dist/tools/backend.js +0 -61
  82. package/dist/tools/bash.js +0 -231
  83. package/dist/tools/code_search.js +0 -98
  84. package/dist/tools/edit.js +0 -68
  85. package/dist/tools/explore.js +0 -232
  86. package/dist/tools/glob.js +0 -40
  87. package/dist/tools/grep.js +0 -174
  88. package/dist/tools/index.js +0 -34
  89. package/dist/tools/question.js +0 -54
  90. package/dist/tools/read.js +0 -109
  91. package/dist/tools/search-searxng.js +0 -223
  92. package/dist/tools/search-serper.js +0 -189
  93. package/dist/tools/task.js +0 -165
  94. package/dist/tools/todo.js +0 -213
  95. package/dist/tools/web_fetch.js +0 -195
  96. package/dist/tools/web_search.js +0 -228
  97. package/dist/tools/write.js +0 -55
  98. package/dist/tui/App.js +0 -126
  99. package/dist/tui/ConfirmBar.js +0 -66
  100. package/dist/tui/InputBar.js +0 -616
  101. package/dist/tui/MessageList.js +0 -718
  102. package/dist/tui/ModelPicker.js +0 -113
  103. package/dist/tui/QuestionBar.js +0 -136
  104. package/dist/tui/SessionPicker.js +0 -79
  105. package/dist/tui/Spinner.js +0 -6
  106. package/dist/tui/StatusBar.js +0 -22
  107. package/dist/tui/ThinkPicker.js +0 -77
  108. package/dist/tui/agent-runner.js +0 -223
  109. package/dist/tui/bracketed-paste.js +0 -37
  110. package/dist/tui/caret-pos.js +0 -179
  111. package/dist/tui/caret.js +0 -69
  112. package/dist/tui/click-count.js +0 -13
  113. package/dist/tui/diff-view.js +0 -61
  114. package/dist/tui/drag-state.js +0 -49
  115. package/dist/tui/hydrate.js +0 -129
  116. package/dist/tui/index.js +0 -258
  117. package/dist/tui/input-history.js +0 -125
  118. package/dist/tui/layout.js +0 -105
  119. package/dist/tui/mouse.js +0 -46
  120. package/dist/tui/overlay-input.js +0 -12
  121. package/dist/tui/paste-draft.js +0 -173
  122. package/dist/tui/prompt-queue.js +0 -24
  123. package/dist/tui/selection.js +0 -232
  124. package/dist/tui/session-switch.js +0 -28
  125. package/dist/tui/slash-commands.js +0 -123
  126. package/dist/tui/slash-handler.js +0 -589
  127. package/dist/tui/text-width.js +0 -113
  128. package/dist/tui/theme.js +0 -12
  129. package/dist/tui/token-info.js +0 -7
  130. package/dist/tui/tool-children.js +0 -19
  131. package/dist/tui/types.js +0 -1
  132. package/dist/tui/undo-stack.js +0 -14
  133. package/dist/tui/use-sgr-mouse.js +0 -29
  134. package/dist/tui-chat.js +0 -456
  135. package/dist/updater.js +0 -116
  136. package/dist/xml-search.js +0 -194
  137. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  138. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  139. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  140. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  141. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  142. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  143. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  144. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  145. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  146. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  147. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  148. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  149. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  150. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  151. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  152. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  153. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  154. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  155. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  156. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
@@ -1,79 +0,0 @@
1
- import { takeScopeFlags, scopeLabel } from "./scope.js";
2
- import { getSandboxSnapshot, setSandboxConfig } from "./config.js";
3
- import { parseNetworkPolicy, parseSandboxMode, getEffectiveSandboxPolicy, sandboxEnforcementCaveat, sandboxModeLabel, sandboxStatusLabel, setSandboxOverride, } from "./sandbox.js";
4
- export const SANDBOX_CLI_USAGE = [
5
- "Usage: min-agent sandbox [off|workspace|strict] [--project|--global]",
6
- " min-agent sandbox network allow|deny [--project|--global]",
7
- " min-agent --sandbox off|workspace|strict [--project|--global]",
8
- ].join("\n");
9
- function sourceLabel(source) {
10
- return source === "cli"
11
- ? "this run"
12
- : source === "env"
13
- ? "environment variable"
14
- : source === "project"
15
- ? "project"
16
- : source === "global"
17
- ? "global"
18
- : "default";
19
- }
20
- export function runSandboxCli(input) {
21
- const { scope: posScope, rest } = takeScopeFlags(input.positionals);
22
- const scope = posScope ?? input.scope ?? "global";
23
- if (rest[0] === "--help" || rest[0] === "-h") {
24
- return { ok: true, lines: [SANDBOX_CLI_USAGE] };
25
- }
26
- if (rest[0] === "network") {
27
- const parsed = parseNetworkPolicy(rest[1]);
28
- if (!parsed || rest.length > 2)
29
- return { ok: false, lines: [SANDBOX_CLI_USAGE] };
30
- setSandboxConfig({ network: parsed }, scope);
31
- setSandboxOverride({ network: parsed });
32
- return { ok: true, lines: [`✓ Network set to ${parsed === "deny" ? "denied" : "allowed"} (${scopeLabel(scope)})`] };
33
- }
34
- if (rest.length === 1) {
35
- const parsed = parseSandboxMode(rest[0]);
36
- if (!parsed)
37
- return { ok: false, lines: [SANDBOX_CLI_USAGE] };
38
- setSandboxConfig({ mode: parsed }, scope);
39
- setSandboxOverride({ mode: parsed });
40
- const caveat = sandboxEnforcementCaveat(parsed);
41
- return {
42
- ok: true,
43
- lines: [`✓ Sandbox set to ${sandboxModeLabel(parsed)} (${scopeLabel(scope)})`, ...(caveat ? [caveat] : [])],
44
- };
45
- }
46
- if (rest.length > 1)
47
- return { ok: false, lines: [SANDBOX_CLI_USAGE] };
48
- if (input.flagMode || input.flagNetwork) {
49
- setSandboxConfig({
50
- ...(input.flagMode ? { mode: input.flagMode } : {}),
51
- ...(input.flagNetwork ? { network: input.flagNetwork } : {}),
52
- }, scope);
53
- setSandboxOverride({
54
- ...(input.flagMode ? { mode: input.flagMode } : {}),
55
- ...(input.flagNetwork ? { network: input.flagNetwork } : {}),
56
- });
57
- const parts = [
58
- ...(input.flagMode ? [`sandbox set to ${sandboxModeLabel(input.flagMode)}`] : []),
59
- ...(input.flagNetwork ? [`network set to ${input.flagNetwork === "deny" ? "denied" : "allowed"}`] : []),
60
- ];
61
- const caveat = input.flagMode ? sandboxEnforcementCaveat(input.flagMode) : null;
62
- return { ok: true, lines: [`✓ ${parts.join(", ")} (${scopeLabel(scope)})`, ...(caveat ? [caveat] : [])] };
63
- }
64
- const snap = getSandboxSnapshot();
65
- const extras = [
66
- ...(snap.extraWriteRoots.length > 0 ? [`Extra writable dirs: ${snap.extraWriteRoots.join(", ")}`] : []),
67
- ...(snap.extraReadRoots.length > 0 ? [`Extra readable dirs: ${snap.extraReadRoots.join(", ")}`] : []),
68
- ];
69
- const caveat = sandboxEnforcementCaveat(getEffectiveSandboxPolicy().mode);
70
- return {
71
- ok: true,
72
- lines: [
73
- `Current sandbox: ${sandboxStatusLabel()} (${sourceLabel(snap.source)})`,
74
- ...extras,
75
- ...(caveat ? [caveat] : []),
76
- SANDBOX_CLI_USAGE,
77
- ],
78
- };
79
- }
package/dist/sandbox.js DELETED
@@ -1,403 +0,0 @@
1
- import { AsyncLocalStorage } from "async_hooks";
2
- import { existsSync, realpathSync } from "fs";
3
- import os from "os";
4
- import path from "path";
5
- import { getEffectiveConfig } from "./config.js";
6
- export const DEFAULT_SANDBOX_MODE = "off";
7
- export const DEFAULT_NETWORK_POLICY = "allow";
8
- const MODE_RANK = { off: 0, workspace: 1, strict: 2 };
9
- const sandboxAls = new AsyncLocalStorage();
10
- let processOverride = {};
11
- export function parseSandboxMode(value) {
12
- if (value === "off" || value === "workspace" || value === "strict")
13
- return value;
14
- return undefined;
15
- }
16
- export function parseNetworkPolicy(value) {
17
- if (value === "allow" || value === "deny")
18
- return value;
19
- return undefined;
20
- }
21
- export function parseSandboxConfig(raw) {
22
- if (raw == null)
23
- return undefined;
24
- if (typeof raw === "string") {
25
- const mode = parseSandboxMode(raw);
26
- return mode ? { mode } : undefined;
27
- }
28
- if (typeof raw !== "object")
29
- return undefined;
30
- const obj = raw;
31
- const mode = parseSandboxMode(obj.mode);
32
- const network = parseNetworkPolicy(obj.network);
33
- const extraWriteRoots = Array.isArray(obj.extraWriteRoots)
34
- ? obj.extraWriteRoots.filter((p) => typeof p === "string" && p.length > 0)
35
- : undefined;
36
- const extraReadRoots = Array.isArray(obj.extraReadRoots)
37
- ? obj.extraReadRoots.filter((p) => typeof p === "string" && p.length > 0)
38
- : undefined;
39
- if (!mode && !network && !extraWriteRoots && !extraReadRoots)
40
- return undefined;
41
- return { mode, network, extraWriteRoots, extraReadRoots };
42
- }
43
- export function mergeSandboxConfig(global, project) {
44
- if (!global && !project)
45
- return undefined;
46
- const extraWriteRoots = project?.extraWriteRoots ?? global?.extraWriteRoots;
47
- const extraReadRoots = project?.extraReadRoots ?? global?.extraReadRoots;
48
- return {
49
- mode: project?.mode ?? global?.mode,
50
- network: project?.network ?? global?.network,
51
- ...(extraWriteRoots ? { extraWriteRoots } : {}),
52
- ...(extraReadRoots ? { extraReadRoots } : {}),
53
- };
54
- }
55
- export function setSandboxOverride(partial) {
56
- processOverride = { ...processOverride, ...partial };
57
- }
58
- export function clearSandboxOverride() {
59
- processOverride = {};
60
- }
61
- export function getSandboxOverride() {
62
- return { ...processOverride };
63
- }
64
- export function sandboxLaunchSource() {
65
- if (processOverride.mode != null || processOverride.network != null)
66
- return "cli";
67
- if (envMode() != null || envNetwork() != null)
68
- return "env";
69
- return null;
70
- }
71
- export function runWithSandboxPolicy(policy, fn) {
72
- return sandboxAls.run(policy, fn);
73
- }
74
- export function workspaceRoot() {
75
- try {
76
- return realpathSync(process.cwd());
77
- }
78
- catch {
79
- return path.resolve(process.cwd());
80
- }
81
- }
82
- function envMode() {
83
- return parseSandboxMode(process.env.MIN_AGENT_SANDBOX);
84
- }
85
- function envNetwork() {
86
- return parseNetworkPolicy(process.env.MIN_AGENT_SANDBOX_NETWORK);
87
- }
88
- function basePolicyFromConfig() {
89
- const parsed = parseSandboxConfig(getEffectiveConfig().sandbox);
90
- return {
91
- mode: processOverride.mode ?? envMode() ?? parsed?.mode ?? DEFAULT_SANDBOX_MODE,
92
- network: processOverride.network ?? envNetwork() ?? parsed?.network ?? DEFAULT_NETWORK_POLICY,
93
- extraWriteRoots: processOverride.extraWriteRoots ?? parsed?.extraWriteRoots ?? [],
94
- extraReadRoots: processOverride.extraReadRoots ?? parsed?.extraReadRoots ?? [],
95
- workspaceRoot: workspaceRoot(),
96
- };
97
- }
98
- export function tightenSandbox(base, request) {
99
- const mode = request.mode && MODE_RANK[request.mode] > MODE_RANK[base.mode] ? request.mode : base.mode;
100
- const network = request.network === "deny" || base.network === "deny" ? "deny" : "allow";
101
- return { ...base, mode, network };
102
- }
103
- export function getEffectiveSandboxPolicy() {
104
- const base = basePolicyFromConfig();
105
- const als = sandboxAls.getStore();
106
- if (!als)
107
- return base;
108
- if (als.replace) {
109
- return {
110
- mode: als.mode ?? DEFAULT_SANDBOX_MODE,
111
- network: als.network ?? DEFAULT_NETWORK_POLICY,
112
- extraWriteRoots: als.extraWriteRoots ?? [],
113
- extraReadRoots: als.extraReadRoots ?? [],
114
- workspaceRoot: als.workspaceRoot ?? workspaceRoot(),
115
- };
116
- }
117
- return tightenSandbox(base, als);
118
- }
119
- export function isInsideRoot(target, root) {
120
- const normalize = (p) => {
121
- const resolved = path.resolve(p);
122
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
123
- };
124
- const child = normalize(target);
125
- const parent = normalize(root);
126
- if (child === parent)
127
- return true;
128
- const prefix = parent.endsWith(path.sep) ? parent : parent + path.sep;
129
- return child.startsWith(prefix);
130
- }
131
- export function resolveCheckedPath(userPath, cwd = process.cwd()) {
132
- const lexical = path.resolve(cwd, userPath);
133
- const parts = [];
134
- let cur = lexical;
135
- for (;;) {
136
- try {
137
- const realBase = realpathSync(cur);
138
- return { lexical, real: parts.length === 0 ? realBase : path.resolve(realBase, ...parts) };
139
- }
140
- catch {
141
- const next = path.dirname(cur);
142
- if (next === cur)
143
- return { lexical, real: lexical };
144
- parts.unshift(path.basename(cur));
145
- cur = next;
146
- }
147
- }
148
- }
149
- function homePath(...segments) {
150
- return path.join(os.homedir(), ...segments);
151
- }
152
- function sensitiveDirPrefixes() {
153
- return [
154
- homePath(".ssh"),
155
- homePath(".gnupg"),
156
- homePath(".aws"),
157
- path.join(homePath(".min-agent"), "config.json"),
158
- homePath(".netrc"),
159
- homePath(".npmrc"),
160
- homePath(".pypirc"),
161
- homePath(".git-credentials"),
162
- path.join(homePath(".config"), "gh"),
163
- path.join(homePath(".config"), "git", "credentials"),
164
- "/etc/shadow",
165
- "/etc/sudoers",
166
- "/etc/master.passwd",
167
- ];
168
- }
169
- function isSensitivePath(real) {
170
- return sensitiveDirPrefixes().some((root) => {
171
- try {
172
- const resolvedRoot = existsSync(root) ? realpathSync(root) : path.resolve(root);
173
- return isInsideRoot(real, resolvedRoot) || path.resolve(real) === path.resolve(root);
174
- }
175
- catch {
176
- return isInsideRoot(real, root);
177
- }
178
- });
179
- }
180
- function uniqueExistingAndLexical(paths) {
181
- const out = [];
182
- const seen = new Set();
183
- for (const raw of paths) {
184
- const lexical = path.resolve(raw);
185
- const aliases = [lexical];
186
- try {
187
- aliases.push(realpathSync(lexical));
188
- }
189
- catch {
190
- /* not created yet */
191
- }
192
- for (const item of aliases) {
193
- const key = process.platform === "win32" ? item.toLowerCase() : item;
194
- if (seen.has(key))
195
- continue;
196
- seen.add(key);
197
- out.push(item);
198
- }
199
- }
200
- return out;
201
- }
202
- function defaultCacheWriteRoots() {
203
- const home = os.homedir();
204
- return [
205
- path.join(home, ".npm"),
206
- path.join(home, ".bun"),
207
- path.join(home, ".cache"),
208
- path.join(home, ".local"),
209
- path.join(home, ".yarn"),
210
- path.join(home, "Library", "Caches"),
211
- path.join(home, "AppData", "Local"),
212
- path.join(home, "AppData", "Roaming"),
213
- ];
214
- }
215
- export function writeRoots(policy = getEffectiveSandboxPolicy()) {
216
- return uniqueExistingAndLexical([
217
- policy.workspaceRoot,
218
- os.tmpdir(),
219
- "/tmp",
220
- "/var/tmp",
221
- "/private/tmp",
222
- "/private/var/tmp",
223
- ...defaultCacheWriteRoots(),
224
- ...policy.extraWriteRoots.map((p) => path.resolve(policy.workspaceRoot, p)),
225
- ]);
226
- }
227
- export function readRoots(policy = getEffectiveSandboxPolicy()) {
228
- return uniqueExistingAndLexical([
229
- policy.workspaceRoot,
230
- ...writeRoots(policy),
231
- ...policy.extraReadRoots.map((p) => path.resolve(policy.workspaceRoot, p)),
232
- ]);
233
- }
234
- function inAnyRoot(real, roots) {
235
- return roots.some((root) => isInsideRoot(real, root));
236
- }
237
- export function checkPathAccess(userPath, access, cwd = process.cwd(), policy = getEffectiveSandboxPolicy()) {
238
- const { real } = resolveCheckedPath(userPath, cwd);
239
- if (isSensitivePath(real)) {
240
- return { ok: false, message: "该路径受保护,无法访问。" };
241
- }
242
- if (policy.mode === "off")
243
- return { ok: true, real };
244
- if (access === "write") {
245
- if (!inAnyRoot(real, writeRoots(policy))) {
246
- return { ok: false, message: `无法写入工作区以外的路径: ${userPath}` };
247
- }
248
- return { ok: true, real };
249
- }
250
- if (policy.mode === "strict" && !inAnyRoot(real, readRoots(policy))) {
251
- return { ok: false, message: `当前隔离级别不允许读取工作区以外的文件: ${userPath}` };
252
- }
253
- return { ok: true, real };
254
- }
255
- export function pathAccessError(userPath, access, cwd) {
256
- const result = checkPathAccess(userPath, access, cwd);
257
- return result.ok ? null : result.message;
258
- }
259
- export function isNetworkCommand(command) {
260
- return (/\b(curl|wget|nc|ncat|netcat|ssh|scp|sftp|ftp|telnet|nmap|ping|nslookup|dig|traceroute|rsync)\b/i.test(command) ||
261
- /\bgit\s+(clone|fetch|pull|push|ls-remote)\b/i.test(command) ||
262
- /\b(npm|pnpm|yarn|bun)\s+(install|add|publish|update|upgrade)\b/i.test(command));
263
- }
264
- export function networkDeniedMessage() {
265
- return "当前设置不允许访问网络。";
266
- }
267
- let backendCache;
268
- export function detectSandboxBackend() {
269
- if (backendCache)
270
- return backendCache;
271
- const forced = process.env.MIN_AGENT_SANDBOX_BACKEND;
272
- if (forced === "seatbelt" || forced === "bwrap" || forced === "none") {
273
- backendCache = forced;
274
- return backendCache;
275
- }
276
- if (process.platform === "darwin" && existsSync("/usr/bin/sandbox-exec")) {
277
- backendCache = "seatbelt";
278
- }
279
- else if (process.platform === "linux" && (existsSync("/usr/bin/bwrap") || existsSync("/usr/local/bin/bwrap"))) {
280
- backendCache = "bwrap";
281
- }
282
- else {
283
- backendCache = "none";
284
- }
285
- return backendCache;
286
- }
287
- export function resetSandboxBackendCache() {
288
- backendCache = undefined;
289
- }
290
- function seatbeltQuote(p) {
291
- return `"${p.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
292
- }
293
- export function buildSeatbeltProfile(policy) {
294
- const writes = writeRoots(policy)
295
- .map((root) => `(subpath ${seatbeltQuote(root)})`)
296
- .join("\n ");
297
- const denyReads = sensitiveDirPrefixes()
298
- .map((root) => `(subpath ${seatbeltQuote(path.resolve(root))})`)
299
- .join("\n ");
300
- const network = policy.network === "deny" ? "(deny network*)" : "";
301
- return `(version 1)
302
- (allow default)
303
- (deny file-write*)
304
- (allow file-write-data (literal "/dev/null"))
305
- (allow file-write*
306
- ${writes}
307
- )
308
- (deny file-read*
309
- ${denyReads}
310
- )
311
- ${network}
312
- `;
313
- }
314
- export function bwrapBin() {
315
- if (existsSync("/usr/bin/bwrap"))
316
- return "/usr/bin/bwrap";
317
- return "/usr/local/bin/bwrap";
318
- }
319
- export function buildBwrapArgs(policy, cwd, command) {
320
- const args = ["--die-with-parent", "--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc"];
321
- const bound = new Set();
322
- for (const root of writeRoots(policy)) {
323
- const key = process.platform === "win32" ? root.toLowerCase() : root;
324
- if (bound.has(key))
325
- continue;
326
- bound.add(key);
327
- if (existsSync(root))
328
- args.push("--bind", root, root);
329
- }
330
- if (policy.network === "deny")
331
- args.push("--unshare-net");
332
- args.push("--chdir", cwd, "/bin/sh", "-c", command);
333
- return args;
334
- }
335
- const NO_BACKEND_COMMAND_WARNING = "⚠ 当前平台不支持命令级隔离(需要 macOS 的 sandbox-exec 或 Linux 的 bwrap),bash 命令未在隔离环境中执行,工作区写入限制对它不生效(read/write/edit 等文件工具仍会校验路径)。";
336
- const NO_BACKEND_NETWORK_WARNING = "⚠ 当前平台不支持系统级网络隔离,禁止联网仅按命令名拦截,脚本或解释器仍可能访问网络。";
337
- const STRICT_NO_BACKEND_ERROR = "当前平台不支持命令级隔离(需要 macOS 的 sandbox-exec 或 Linux 的 bwrap),严格隔离模式下无法安全执行 bash 命令,已拒绝执行。可将隔离级别设为 workspace(文件工具仍会校验路径)或 off。";
338
- /** Config-time caveat when the platform cannot enforce command-level isolation for the given mode. */
339
- export function sandboxEnforcementCaveat(mode) {
340
- if (mode === "off" || detectSandboxBackend() !== "none")
341
- return null;
342
- return mode === "strict"
343
- ? "⚠ Command-level sandboxing isn't supported on this platform (requires macOS sandbox-exec or Linux bwrap); bash commands will be rejected in strict mode."
344
- : "⚠ Command-level sandboxing isn't supported on this platform (requires macOS sandbox-exec or Linux bwrap); bash commands won't be restricted to the workspace (file tools still validate paths).";
345
- }
346
- export function planSandboxedSpawn(command, cwd, env, policy = getEffectiveSandboxPolicy()) {
347
- if (policy.mode !== "off") {
348
- const cwdCheck = checkPathAccess(cwd, "write", process.cwd(), policy);
349
- if (!cwdCheck.ok)
350
- return { error: `无法在工作区以外的目录执行命令: ${cwd}` };
351
- }
352
- const backend = detectSandboxBackend();
353
- if (policy.network === "deny" && backend === "none" && isNetworkCommand(command)) {
354
- return { error: networkDeniedMessage() };
355
- }
356
- const isWin = process.platform === "win32";
357
- const actualCommand = isWin ? `chcp 65001 >nul && ${command}` : command;
358
- const childEnv = { ...env, ...(isWin ? { PYTHONIOENCODING: "utf-8" } : {}) };
359
- const shellPlan = () => ({ file: actualCommand, args: [], shell: true, cwd, env: childEnv });
360
- if (policy.mode === "off") {
361
- const plan = shellPlan();
362
- if (policy.network === "deny" && backend === "none")
363
- plan.warning = NO_BACKEND_NETWORK_WARNING;
364
- return plan;
365
- }
366
- if (backend === "seatbelt") {
367
- return {
368
- file: "/usr/bin/sandbox-exec",
369
- args: ["-p", buildSeatbeltProfile(policy), "/bin/sh", "-c", actualCommand],
370
- shell: false,
371
- cwd,
372
- env: childEnv,
373
- };
374
- }
375
- if (backend === "bwrap") {
376
- return {
377
- file: bwrapBin(),
378
- args: buildBwrapArgs(policy, cwd, actualCommand),
379
- shell: false,
380
- cwd,
381
- env: childEnv,
382
- };
383
- }
384
- // No OS-level sandbox available: strict cannot be honored at all, so refuse;
385
- // workspace still gets path checks from the file tools, so run with a caveat.
386
- if (policy.mode === "strict")
387
- return { error: STRICT_NO_BACKEND_ERROR };
388
- const plan = shellPlan();
389
- plan.warning =
390
- policy.network === "deny"
391
- ? `${NO_BACKEND_COMMAND_WARNING}\n${NO_BACKEND_NETWORK_WARNING}`
392
- : NO_BACKEND_COMMAND_WARNING;
393
- return plan;
394
- }
395
- export function sandboxModeLabel(mode) {
396
- return mode === "off" ? "off" : mode === "strict" ? "strict" : "workspace";
397
- }
398
- export function sandboxNetworkLabel(network) {
399
- return network === "deny" ? "network denied" : "network allowed";
400
- }
401
- export function sandboxStatusLabel(policy = getEffectiveSandboxPolicy()) {
402
- return `${sandboxModeLabel(policy.mode)}, ${sandboxNetworkLabel(policy.network)}`;
403
- }
@@ -1,45 +0,0 @@
1
- export function createSaveThrottle(save, intervalMs = 2000, now = Date.now) {
2
- let lastRun = 0;
3
- let timer;
4
- let pending = false;
5
- const run = () => {
6
- timer = undefined;
7
- pending = false;
8
- lastRun = now();
9
- try {
10
- save();
11
- }
12
- catch {
13
- /* persistence is best-effort */
14
- }
15
- };
16
- return {
17
- request() {
18
- if (timer) {
19
- pending = true;
20
- return;
21
- }
22
- const elapsed = now() - lastRun;
23
- if (elapsed >= intervalMs) {
24
- run();
25
- return;
26
- }
27
- pending = true;
28
- timer = setTimeout(run, intervalMs - elapsed);
29
- },
30
- flush() {
31
- if (timer) {
32
- clearTimeout(timer);
33
- timer = undefined;
34
- }
35
- if (pending)
36
- run();
37
- },
38
- cancel() {
39
- if (timer)
40
- clearTimeout(timer);
41
- timer = undefined;
42
- pending = false;
43
- },
44
- };
45
- }
package/dist/scope.js DELETED
@@ -1,23 +0,0 @@
1
- export function readScope(value) {
2
- if (value === undefined || value === null || value === "")
3
- return { ok: true, scope: undefined };
4
- if (value === "global" || value === "project")
5
- return { ok: true, scope: value };
6
- return { ok: false };
7
- }
8
- export function takeScopeFlags(tokens) {
9
- let scope;
10
- const rest = [];
11
- for (const t of tokens) {
12
- if (t === "--project")
13
- scope = "project";
14
- else if (t === "--global")
15
- scope = "global";
16
- else
17
- rest.push(t);
18
- }
19
- return { scope, rest };
20
- }
21
- export function scopeLabel(scope) {
22
- return scope === "project" ? "project" : "global";
23
- }