min-agent 0.3.0 → 0.4.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.
- package/README.md +111 -28
- package/dist/agent.js +1119 -256
- package/dist/cli/commands/chat.js +10 -0
- package/dist/cli/commands/exec.js +32 -0
- package/dist/cli/commands/history.js +58 -0
- package/dist/cli/commands/index.js +224 -0
- package/dist/cli/commands/init.js +18 -0
- package/dist/cli/commands/mcp.js +173 -0
- package/dist/cli/commands/memory.js +69 -0
- package/dist/cli/commands/models.js +21 -0
- package/dist/cli/commands/permission.js +12 -0
- package/dist/cli/commands/rules.js +33 -0
- package/dist/cli/commands/sandbox.js +13 -0
- package/dist/cli/commands/serve.js +9 -0
- package/dist/cli/commands/setup.js +4 -0
- package/dist/cli/commands/shared.js +16 -0
- package/dist/cli/commands/skills.js +119 -0
- package/dist/cli/commands/update.js +7 -0
- package/dist/cli/commands/write-config.js +30 -0
- package/dist/cli/errors.js +36 -0
- package/dist/cli/exec-prompt.js +26 -0
- package/dist/cli/option-helpers.js +53 -0
- package/dist/cli/program.js +180 -0
- package/dist/cli.js +5 -888
- package/dist/code-mode.js +32 -14
- package/dist/compaction.js +347 -160
- package/dist/config.js +119 -10
- package/dist/confirm.js +56 -9
- package/dist/context-window.js +107 -39
- package/dist/doom-loop.js +264 -29
- package/dist/fetch-timeout.js +152 -0
- package/dist/http-approvals.js +60 -0
- package/dist/instructions.js +21 -0
- package/dist/logger.js +33 -4
- package/dist/markdown.js +37 -11
- package/dist/mcp.js +328 -30
- package/dist/memory.js +97 -56
- package/dist/output.js +7 -5
- package/dist/permission-cli.js +43 -0
- package/dist/plugins.js +46 -8
- package/dist/pricing.js +4 -4
- package/dist/provider.js +23 -6
- package/dist/question-format.js +60 -0
- package/dist/sandbox-cli.js +82 -0
- package/dist/sandbox.js +403 -0
- package/dist/save-throttle.js +45 -0
- package/dist/serve/common.js +404 -0
- package/dist/serve/routes-chat.js +347 -0
- package/dist/serve/routes-mcp.js +212 -0
- package/dist/serve/routes-memory.js +66 -0
- package/dist/serve/routes-meta.js +205 -0
- package/dist/serve/routes-sessions.js +61 -0
- package/dist/serve/routes-skills.js +70 -0
- package/dist/serve.js +33 -883
- package/dist/sessions.js +53 -9
- package/dist/skills.js +82 -18
- package/dist/title-gen.js +8 -2
- package/dist/token-display.js +36 -0
- package/dist/tool-display.js +5 -0
- package/dist/tool-output.js +1 -3
- package/dist/tools/apply_patch.js +85 -11
- package/dist/tools/atomic-file.js +35 -0
- package/dist/tools/backend.js +2 -2
- package/dist/tools/bash.js +57 -19
- package/dist/tools/code_search.js +7 -1
- package/dist/tools/edit.js +11 -10
- package/dist/tools/explore.js +74 -14
- package/dist/tools/glob.js +4 -0
- package/dist/tools/grep.js +17 -10
- package/dist/tools/index.js +6 -21
- package/dist/tools/question.js +28 -9
- package/dist/tools/read.js +6 -4
- package/dist/tools/search-searxng.js +223 -0
- package/dist/tools/search-serper.js +189 -0
- package/dist/tools/task.js +84 -30
- package/dist/tools/todo.js +120 -19
- package/dist/tools/web_fetch.js +11 -3
- package/dist/tools/web_search.js +66 -556
- package/dist/tools/write.js +23 -6
- package/dist/tui/App.js +63 -14
- package/dist/tui/ConfirmBar.js +45 -13
- package/dist/tui/InputBar.js +150 -35
- package/dist/tui/MessageList.js +266 -125
- package/dist/tui/ModelPicker.js +8 -3
- package/dist/tui/QuestionBar.js +51 -19
- package/dist/tui/SessionPicker.js +79 -0
- package/dist/tui/StatusBar.js +8 -14
- package/dist/tui/agent-runner.js +142 -22
- package/dist/tui/caret-pos.js +48 -5
- package/dist/tui/caret.js +1 -1
- package/dist/tui/click-count.js +13 -0
- package/dist/tui/drag-state.js +8 -3
- package/dist/tui/hydrate.js +129 -0
- package/dist/tui/index.js +42 -13
- package/dist/tui/input-history.js +92 -11
- package/dist/tui/layout.js +75 -4
- package/dist/tui/prompt-queue.js +24 -0
- package/dist/tui/selection.js +113 -21
- package/dist/tui/session-switch.js +28 -0
- package/dist/tui/slash-commands.js +22 -6
- package/dist/tui/slash-handler.js +233 -58
- package/dist/tui/text-width.js +38 -16
- package/dist/tui/token-info.js +7 -0
- package/dist/tui/tool-children.js +19 -0
- package/dist/tui/undo-stack.js +1 -1
- package/dist/tui/use-sgr-mouse.js +3 -1
- package/dist/tui-chat.js +276 -40
- package/dist/updater.js +88 -29
- package/dist/xml-search.js +194 -0
- package/docs/API.md +257 -25
- package/docs/superpowers/plans/2026-08-20-tui-completeness.md +873 -0
- package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +631 -0
- package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +47 -0
- package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +37 -0
- package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +68 -0
- package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +273 -0
- package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +165 -0
- package/package.json +6 -1
- package/skills/self-config/SKILL.md +90 -0
- package/skills/self-config/reference.md +149 -0
package/dist/tools/todo.js
CHANGED
|
@@ -1,12 +1,111 @@
|
|
|
1
1
|
import { tool, jsonSchema } from "ai";
|
|
2
|
+
import { isSyntheticMessage } from "../synthetic.js";
|
|
2
3
|
export const TODO_STATUSES = ["pending", "in_progress", "done", "cancelled"];
|
|
3
|
-
|
|
4
|
+
const GOAL_MAX_CHARS = 4000;
|
|
5
|
+
export function emptyTaskState() {
|
|
6
|
+
return { goal: "", todos: [], nextId: 1 };
|
|
7
|
+
}
|
|
8
|
+
export function resetTaskState(state) {
|
|
9
|
+
state.goal = "";
|
|
10
|
+
state.todos.length = 0;
|
|
11
|
+
state.nextId = 1;
|
|
12
|
+
}
|
|
13
|
+
export function copyTaskState(from, into) {
|
|
14
|
+
if (!from) {
|
|
15
|
+
resetTaskState(into);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
into.goal = from.goal;
|
|
19
|
+
into.todos = from.todos.map((t) => ({ ...t }));
|
|
20
|
+
into.nextId = from.nextId;
|
|
21
|
+
}
|
|
22
|
+
function isTodoStatus(value) {
|
|
23
|
+
return typeof value === "string" && TODO_STATUSES.includes(value);
|
|
24
|
+
}
|
|
25
|
+
export function parseTaskState(raw) {
|
|
26
|
+
if (!raw || typeof raw !== "object")
|
|
27
|
+
return undefined;
|
|
28
|
+
const rec = raw;
|
|
29
|
+
const todos = [];
|
|
30
|
+
if (Array.isArray(rec.todos)) {
|
|
31
|
+
for (const item of rec.todos) {
|
|
32
|
+
if (!item || typeof item !== "object")
|
|
33
|
+
continue;
|
|
34
|
+
const t = item;
|
|
35
|
+
if (typeof t.id !== "number" || typeof t.text !== "string")
|
|
36
|
+
continue;
|
|
37
|
+
todos.push({
|
|
38
|
+
id: t.id,
|
|
39
|
+
text: t.text,
|
|
40
|
+
status: isTodoStatus(t.status) ? t.status : "pending",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const maxId = todos.reduce((m, t) => Math.max(m, t.id), 0);
|
|
45
|
+
const nextId = typeof rec.nextId === "number" && Number.isFinite(rec.nextId) && rec.nextId > maxId
|
|
46
|
+
? Math.floor(rec.nextId)
|
|
47
|
+
: maxId + 1;
|
|
48
|
+
return {
|
|
49
|
+
goal: typeof rec.goal === "string" ? rec.goal : "",
|
|
50
|
+
todos,
|
|
51
|
+
nextId,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function messageText(msg) {
|
|
55
|
+
if (typeof msg.content === "string")
|
|
56
|
+
return msg.content;
|
|
57
|
+
if (!Array.isArray(msg.content))
|
|
58
|
+
return "";
|
|
59
|
+
return msg.content
|
|
60
|
+
.filter((p) => p.type === "text" && typeof p.text === "string")
|
|
61
|
+
.map((p) => p.text)
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|
|
64
|
+
/** Keep the first real user request so compaction cannot drop the original goal. */
|
|
65
|
+
export function captureGoal(state, messages) {
|
|
66
|
+
if (state.goal.trim())
|
|
67
|
+
return;
|
|
68
|
+
for (const msg of messages) {
|
|
69
|
+
if (msg.role !== "user")
|
|
70
|
+
continue;
|
|
71
|
+
if (isSyntheticMessage(msg))
|
|
72
|
+
continue;
|
|
73
|
+
const text = messageText(msg).trim();
|
|
74
|
+
if (!text)
|
|
75
|
+
continue;
|
|
76
|
+
state.goal = text.slice(0, GOAL_MAX_CHARS);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function formatTaskStatePrompt(state) {
|
|
81
|
+
if (!state.goal && state.todos.length === 0)
|
|
82
|
+
return "";
|
|
83
|
+
const icons = {
|
|
84
|
+
pending: "pending",
|
|
85
|
+
in_progress: "in_progress",
|
|
86
|
+
done: "done",
|
|
87
|
+
cancelled: "cancelled",
|
|
88
|
+
};
|
|
89
|
+
const lines = [
|
|
90
|
+
"## Current Task",
|
|
91
|
+
"This block is the durable task state. It survives context compaction. Keep it accurate with the todo tool.",
|
|
92
|
+
];
|
|
93
|
+
if (state.goal) {
|
|
94
|
+
lines.push("", "### Original goal", state.goal);
|
|
95
|
+
}
|
|
96
|
+
if (state.todos.length > 0) {
|
|
97
|
+
lines.push("", "### Task list");
|
|
98
|
+
for (const t of state.todos) {
|
|
99
|
+
lines.push(`- [${icons[t.status]}] #${t.id} ${t.text}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
4
104
|
export function createTodoTool(opts) {
|
|
5
105
|
const silent = opts?.silent ?? false;
|
|
6
|
-
|
|
7
|
-
let nextId = 1;
|
|
106
|
+
const store = opts?.store ?? emptyTaskState();
|
|
8
107
|
const formatTodos = () => {
|
|
9
|
-
if (todos.length === 0)
|
|
108
|
+
if (store.todos.length === 0)
|
|
10
109
|
return "No tasks.";
|
|
11
110
|
const icons = {
|
|
12
111
|
pending: "○",
|
|
@@ -14,18 +113,19 @@ export function createTodoTool(opts) {
|
|
|
14
113
|
done: "●",
|
|
15
114
|
cancelled: "✕",
|
|
16
115
|
};
|
|
17
|
-
return todos
|
|
18
|
-
.map((t) => ` ${icons[t.status]} #${t.id} ${t.text}`)
|
|
19
|
-
.join("\n");
|
|
116
|
+
return store.todos.map((t) => ` ${icons[t.status]} #${t.id} ${t.text}`).join("\n");
|
|
20
117
|
};
|
|
21
118
|
const printTodos = () => {
|
|
22
|
-
if (silent || todos.length === 0)
|
|
119
|
+
if (silent || store.todos.length === 0)
|
|
23
120
|
return;
|
|
24
121
|
console.log(`\x1b[90m ┌─ Tasks${"─".repeat(36)}\x1b[0m`);
|
|
25
|
-
for (const t of todos) {
|
|
26
|
-
const icon = t.status === "done"
|
|
27
|
-
|
|
28
|
-
|
|
122
|
+
for (const t of store.todos) {
|
|
123
|
+
const icon = t.status === "done"
|
|
124
|
+
? "\x1b[32m●\x1b[0m"
|
|
125
|
+
: t.status === "in_progress"
|
|
126
|
+
? "\x1b[33m◐\x1b[0m"
|
|
127
|
+
: t.status === "cancelled"
|
|
128
|
+
? "\x1b[90m✕\x1b[0m"
|
|
29
129
|
: "\x1b[90m○\x1b[0m";
|
|
30
130
|
const dim = t.status === "done" || t.status === "cancelled" ? "\x1b[90m" : "";
|
|
31
131
|
const reset = dim ? "\x1b[0m" : "";
|
|
@@ -34,8 +134,9 @@ export function createTodoTool(opts) {
|
|
|
34
134
|
console.log(`\x1b[90m └${"─".repeat(44)}\x1b[0m`);
|
|
35
135
|
};
|
|
36
136
|
return tool({
|
|
37
|
-
description: `Create or update tasks to track progress. Use this FREQUENTLY to:
|
|
38
|
-
- Plan multi-step work by creating tasks upfront
|
|
137
|
+
description: `Create or update tasks to track progress. The list persists across context compaction and auto-continue. Use this FREQUENTLY to:
|
|
138
|
+
- Plan multi-step work by creating tasks upfront (research, then the deliverable)
|
|
139
|
+
- Mark research tasks done before more searching — then write the output
|
|
39
140
|
- Mark tasks as in_progress when starting them
|
|
40
141
|
- Mark tasks as done when completed
|
|
41
142
|
- Give the user visibility into your progress
|
|
@@ -65,7 +166,7 @@ You can mix creates and updates in one call.`,
|
|
|
65
166
|
const errors = [];
|
|
66
167
|
for (const item of items) {
|
|
67
168
|
if (item.id != null) {
|
|
68
|
-
const existing = todos.find((t) => t.id === item.id);
|
|
169
|
+
const existing = store.todos.find((t) => t.id === item.id);
|
|
69
170
|
if (!existing) {
|
|
70
171
|
errors.push(`task #${item.id} not found`);
|
|
71
172
|
continue;
|
|
@@ -77,10 +178,12 @@ You can mix creates and updates in one call.`,
|
|
|
77
178
|
}
|
|
78
179
|
existing.status = item.status;
|
|
79
180
|
}
|
|
181
|
+
if (item.text)
|
|
182
|
+
existing.text = item.text;
|
|
80
183
|
}
|
|
81
184
|
else if (item.text) {
|
|
82
|
-
todos.push({
|
|
83
|
-
id: nextId++,
|
|
185
|
+
store.todos.push({
|
|
186
|
+
id: store.nextId++,
|
|
84
187
|
text: item.text,
|
|
85
188
|
status: TODO_STATUSES.includes(item.status) ? item.status : "pending",
|
|
86
189
|
});
|
|
@@ -94,5 +197,3 @@ You can mix creates and updates in one call.`,
|
|
|
94
197
|
},
|
|
95
198
|
});
|
|
96
199
|
}
|
|
97
|
-
/** Shared placeholder — callers in code mode replace it with a per-run instance. */
|
|
98
|
-
export const todoTool = createTodoTool();
|
package/dist/tools/web_fetch.js
CHANGED
|
@@ -3,6 +3,7 @@ import { truncateToolOutput } from "../tool-output.js";
|
|
|
3
3
|
import { assertSafeUrl, readBodyLimited } from "../http.js";
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
5
|
import { configHint, describeFetchError, resolveBackendBase } from "./backend.js";
|
|
6
|
+
import { getEffectiveSandboxPolicy, networkDeniedMessage } from "../sandbox.js";
|
|
6
7
|
export const FIRECRAWL_BACKEND = {
|
|
7
8
|
envName: "MIN_AGENT_FIRECRAWL_URL",
|
|
8
9
|
configKey: "webFetchURL",
|
|
@@ -13,7 +14,7 @@ const DIRECT_TIMEOUT_MS = 8000;
|
|
|
13
14
|
const SCRAPE_TIMEOUT_MS = 35000;
|
|
14
15
|
const BROWSER_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
15
16
|
export const webFetchTool = tool({
|
|
16
|
-
description: "Fetch content from a URL. Use this to access web pages, APIs, or any HTTP resource. Returns the response body as text, or markdown for HTML pages (JavaScript-rendered pages go through a scrape backend).",
|
|
17
|
+
description: "Fetch content from a URL. Use this to access web pages, APIs, or any HTTP resource. Returns the response body as text, or markdown for HTML pages (JavaScript-rendered pages go through a scrape backend). Do not re-fetch URLs you already have; extract what you need and produce the user's deliverable.",
|
|
17
18
|
inputSchema: jsonSchema({
|
|
18
19
|
type: "object",
|
|
19
20
|
properties: {
|
|
@@ -23,6 +24,8 @@ export const webFetchTool = tool({
|
|
|
23
24
|
required: ["url"],
|
|
24
25
|
}),
|
|
25
26
|
execute: async ({ url, method }) => {
|
|
27
|
+
if (getEffectiveSandboxPolicy().network === "deny")
|
|
28
|
+
return `Error: ${networkDeniedMessage()}`;
|
|
26
29
|
const target = (url ?? "").trim();
|
|
27
30
|
if (!target)
|
|
28
31
|
return 'Error: "url" must be a non-empty string.';
|
|
@@ -160,13 +163,18 @@ async function firecrawlFetch(url) {
|
|
|
160
163
|
catch {
|
|
161
164
|
return {
|
|
162
165
|
kind: "unavailable",
|
|
163
|
-
reason: response.ok
|
|
166
|
+
reason: response.ok
|
|
167
|
+
? "the scrape backend returned a non-JSON response"
|
|
168
|
+
: `HTTP ${response.status} from the scrape backend`,
|
|
164
169
|
};
|
|
165
170
|
}
|
|
166
171
|
const record = typeof data === "object" && data !== null ? data : {};
|
|
167
172
|
if (!response.ok) {
|
|
168
173
|
const message = typeof record.error === "string" ? record.error : response.statusText;
|
|
169
|
-
return {
|
|
174
|
+
return {
|
|
175
|
+
kind: "unavailable",
|
|
176
|
+
reason: `HTTP ${response.status} from the scrape backend${message ? ` (${message})` : ""}`,
|
|
177
|
+
};
|
|
170
178
|
}
|
|
171
179
|
const payload = typeof record.data === "object" && record.data !== null ? record.data : {};
|
|
172
180
|
const markdown = typeof payload.markdown === "string" ? payload.markdown : typeof payload.content === "string" ? payload.content : "";
|