min-agent 0.5.1 → 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 (169) hide show
  1. package/README.md +18 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +6 -6
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +4 -2
  6. package/skills/self-config/reference.md +4 -1
  7. package/dist/agent.js +0 -1388
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/ctx.js +0 -7
  11. package/dist/cli/commands/exec.js +0 -35
  12. package/dist/cli/commands/history.js +0 -58
  13. package/dist/cli/commands/index.js +0 -249
  14. package/dist/cli/commands/init.js +0 -18
  15. package/dist/cli/commands/mcp.js +0 -173
  16. package/dist/cli/commands/memory.js +0 -87
  17. package/dist/cli/commands/models.js +0 -21
  18. package/dist/cli/commands/permission.js +0 -12
  19. package/dist/cli/commands/rules.js +0 -33
  20. package/dist/cli/commands/sandbox.js +0 -13
  21. package/dist/cli/commands/serve.js +0 -9
  22. package/dist/cli/commands/setup.js +0 -56
  23. package/dist/cli/commands/shared.js +0 -25
  24. package/dist/cli/commands/skills.js +0 -119
  25. package/dist/cli/commands/think.js +0 -12
  26. package/dist/cli/commands/update.js +0 -7
  27. package/dist/cli/commands/write-config.js +0 -52
  28. package/dist/cli/errors.js +0 -36
  29. package/dist/cli/exec-prompt.js +0 -26
  30. package/dist/cli/option-helpers.js +0 -65
  31. package/dist/cli/program.js +0 -223
  32. package/dist/cli/setup/detect.js +0 -17
  33. package/dist/cli/setup/flags.js +0 -12
  34. package/dist/cli/setup/flow.js +0 -108
  35. package/dist/cli/setup/provider-form.js +0 -102
  36. package/dist/cli/setup/ui.js +0 -534
  37. package/dist/clipboard.js +0 -142
  38. package/dist/code-mode.js +0 -184
  39. package/dist/compaction.js +0 -644
  40. package/dist/config.js +0 -363
  41. package/dist/confirm.js +0 -172
  42. package/dist/context-window.js +0 -256
  43. package/dist/ctx-cli.js +0 -30
  44. package/dist/ctx.js +0 -80
  45. package/dist/doom-loop.js +0 -278
  46. package/dist/fetch-timeout.js +0 -152
  47. package/dist/http-approvals.js +0 -60
  48. package/dist/http.js +0 -119
  49. package/dist/instructions.js +0 -196
  50. package/dist/logger.js +0 -95
  51. package/dist/markdown.js +0 -259
  52. package/dist/mcp.js +0 -1036
  53. package/dist/memory-cli.js +0 -33
  54. package/dist/memory.js +0 -295
  55. package/dist/model-catalog.js +0 -285
  56. package/dist/ollama-model.js +0 -234
  57. package/dist/ollama-openai-bridge.js +0 -383
  58. package/dist/output.js +0 -76
  59. package/dist/paste-handler.js +0 -41
  60. package/dist/permission-cli.js +0 -40
  61. package/dist/plugins.js +0 -131
  62. package/dist/pricing.js +0 -119
  63. package/dist/provider.js +0 -63
  64. package/dist/question-format.js +0 -60
  65. package/dist/reasoning-stream.js +0 -158
  66. package/dist/sandbox-cli.js +0 -79
  67. package/dist/sandbox.js +0 -403
  68. package/dist/save-throttle.js +0 -45
  69. package/dist/scope.js +0 -23
  70. package/dist/serve/common.js +0 -425
  71. package/dist/serve/routes-chat.js +0 -367
  72. package/dist/serve/routes-mcp.js +0 -212
  73. package/dist/serve/routes-memory.js +0 -95
  74. package/dist/serve/routes-meta.js +0 -268
  75. package/dist/serve/routes-sessions.js +0 -61
  76. package/dist/serve/routes-skills.js +0 -70
  77. package/dist/serve.js +0 -138
  78. package/dist/sessions.js +0 -265
  79. package/dist/skills.js +0 -599
  80. package/dist/structured-output.js +0 -29
  81. package/dist/synthetic.js +0 -7
  82. package/dist/think-cli.js +0 -36
  83. package/dist/thinking-wire.js +0 -239
  84. package/dist/thinking.js +0 -166
  85. package/dist/title-gen.js +0 -55
  86. package/dist/token-display.js +0 -39
  87. package/dist/tool-display.js +0 -178
  88. package/dist/tool-output.js +0 -126
  89. package/dist/tools/apply_patch.js +0 -265
  90. package/dist/tools/atomic-file.js +0 -35
  91. package/dist/tools/backend.js +0 -61
  92. package/dist/tools/bash.js +0 -231
  93. package/dist/tools/code_search.js +0 -98
  94. package/dist/tools/edit.js +0 -68
  95. package/dist/tools/explore.js +0 -232
  96. package/dist/tools/glob.js +0 -40
  97. package/dist/tools/grep.js +0 -174
  98. package/dist/tools/index.js +0 -34
  99. package/dist/tools/question.js +0 -54
  100. package/dist/tools/read.js +0 -109
  101. package/dist/tools/search-searxng.js +0 -223
  102. package/dist/tools/search-serper.js +0 -189
  103. package/dist/tools/task.js +0 -165
  104. package/dist/tools/todo.js +0 -213
  105. package/dist/tools/web_fetch.js +0 -195
  106. package/dist/tools/web_search.js +0 -228
  107. package/dist/tools/write.js +0 -55
  108. package/dist/tui/App.js +0 -138
  109. package/dist/tui/ConfirmBar.js +0 -66
  110. package/dist/tui/CtxPicker.js +0 -68
  111. package/dist/tui/InputBar.js +0 -618
  112. package/dist/tui/MessageList.js +0 -718
  113. package/dist/tui/ModelPicker.js +0 -113
  114. package/dist/tui/QuestionBar.js +0 -136
  115. package/dist/tui/SessionPicker.js +0 -79
  116. package/dist/tui/Spinner.js +0 -6
  117. package/dist/tui/StatusBar.js +0 -22
  118. package/dist/tui/ThinkPicker.js +0 -75
  119. package/dist/tui/agent-runner.js +0 -223
  120. package/dist/tui/bracketed-paste.js +0 -37
  121. package/dist/tui/caret-pos.js +0 -179
  122. package/dist/tui/caret.js +0 -69
  123. package/dist/tui/click-count.js +0 -13
  124. package/dist/tui/diff-view.js +0 -61
  125. package/dist/tui/drag-state.js +0 -49
  126. package/dist/tui/hydrate.js +0 -129
  127. package/dist/tui/index.js +0 -264
  128. package/dist/tui/input-history.js +0 -125
  129. package/dist/tui/layout.js +0 -105
  130. package/dist/tui/mouse.js +0 -46
  131. package/dist/tui/overlay-input.js +0 -12
  132. package/dist/tui/paste-draft.js +0 -173
  133. package/dist/tui/prompt-queue.js +0 -24
  134. package/dist/tui/selection.js +0 -232
  135. package/dist/tui/session-switch.js +0 -28
  136. package/dist/tui/slash-commands.js +0 -129
  137. package/dist/tui/slash-handler.js +0 -615
  138. package/dist/tui/text-width.js +0 -113
  139. package/dist/tui/theme.js +0 -12
  140. package/dist/tui/token-info.js +0 -7
  141. package/dist/tui/tool-children.js +0 -19
  142. package/dist/tui/types.js +0 -1
  143. package/dist/tui/undo-stack.js +0 -14
  144. package/dist/tui/use-sgr-mouse.js +0 -29
  145. package/dist/tui-chat.js +0 -478
  146. package/dist/updater.js +0 -116
  147. package/dist/xml-search.js +0 -194
  148. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  149. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  150. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  151. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  152. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  153. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  154. package/docs/superpowers/plans/2026-08-23-cli-setup.md +0 -501
  155. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  156. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  157. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  158. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  159. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  160. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  161. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  162. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  163. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  164. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  165. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  166. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  167. package/docs/superpowers/specs/2026-08-23-cli-setup-design.md +0 -282
  168. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  169. 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
- }