agent-from-zero 0.27.0 → 0.28.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 +43 -38
- package/dist/agent.js +121 -14
- package/dist/background.js +160 -0
- package/dist/board.js +179 -0
- package/dist/editor.js +307 -0
- package/dist/loop.js +697 -5
- package/dist/markdown.js +152 -0
- package/dist/permissions.js +51 -3
- package/dist/prompt.js +16 -0
- package/dist/team.js +214 -0
- package/dist/todos.js +87 -0
- package/dist/tools.js +86 -1
- package/dist/ui.js +5 -1
- package/package.json +7 -3
package/dist/tools.js
CHANGED
|
@@ -5,6 +5,8 @@ import { spawn } from "node:child_process"; // async process execution (does NOT
|
|
|
5
5
|
import { validateArgs } from "./validate.js"; // schema validation for tool arguments
|
|
6
6
|
import { setPlanMode } from "./permissions.js"; // plan mode (Day 20): exit_plan_mode flips it off on approval
|
|
7
7
|
import { recordMutation } from "./undo.js"; // capture the before-state so /undo (Day 22) can restore it
|
|
8
|
+
import { parseTodos, setTodos, summarizeTodos } from "./todos.js"; // the agent's own checklist (Day 36)
|
|
9
|
+
import { startBackground, readBackground } from "./background.js"; // long-running commands that outlive the turn (Day 37)
|
|
8
10
|
// ============ Session state ============
|
|
9
11
|
// The foundation of "read before edit": which files has this session actually read?
|
|
10
12
|
// Files that were never read must not be edited — otherwise the model edits from
|
|
@@ -235,6 +237,45 @@ On error: failures return the error output — analyze it and try a different ap
|
|
|
235
237
|
});
|
|
236
238
|
}),
|
|
237
239
|
};
|
|
240
|
+
// ============ run_bash_background (Day 37) ============
|
|
241
|
+
// run_bash blocks and dies at its 30s watchdog — useless for anything truly
|
|
242
|
+
// slow. This is its asynchronous twin: it spawns the command and returns a task
|
|
243
|
+
// id IMMEDIATELY, so the agent keeps reasoning while the work happens. The loop
|
|
244
|
+
// injects a <task_notification> when the command finishes (background.ts). Use
|
|
245
|
+
// it for installs, builds, full test runs, or servers that never exit — exactly
|
|
246
|
+
// the keywords that signal a slow operation. The command goes through the SAME
|
|
247
|
+
// permission gate as run_bash (see permissions.ts), so the danger rules are
|
|
248
|
+
// identical; only the blocking behavior differs.
|
|
249
|
+
const runBashBackground = {
|
|
250
|
+
definition: def("run_bash_background", `Run a slow bash command in the BACKGROUND and return immediately with a task id (e.g. bg_1) — do NOT wait for it.
|
|
251
|
+
When to use: anything that would exceed run_bash's 30s limit or block you — installing dependencies (npm/pip install), builds (npm run build, cargo build, make), full test suites (pytest, npm test), deploys, or long-running servers (npm run dev) that never exit.
|
|
252
|
+
What you get back: just the task id. The command keeps running. When it finishes you'll receive a <task_notification> with its status and output tail — react to that. For a server or a job still running, poll it with bash_output.
|
|
253
|
+
Boundaries & permissions are identical to run_bash (dangerous commands are still gated or denied). For quick commands use run_bash instead — waiting on a one-off background job wastes a turn.`, { command: { type: "string", description: "The bash command to run in the background" } }, ["command"]),
|
|
254
|
+
// Fire-and-return: start the process, hand back the id. The output is captured
|
|
255
|
+
// by background.ts and delivered later as a notification — never inline here.
|
|
256
|
+
run: (args) => {
|
|
257
|
+
const id = startBackground(args.command);
|
|
258
|
+
return `Started background task ${id}: ${args.command}\nIt is running now; you will get a <task_notification> when it finishes. Keep working — use bash_output with task_id "${id}" to check on it (e.g. to confirm a server came up).`;
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
// ============ bash_output (Day 37) ============
|
|
262
|
+
// Poll a background task: its status and any output produced since the last
|
|
263
|
+
// poll. Essential for the case a notification can never cover — a server that
|
|
264
|
+
// runs forever (npm run dev) never "finishes", so the only way to see "ready on
|
|
265
|
+
// :3000" is to read its output. Reading advances a per-task cursor, so repeated
|
|
266
|
+
// polls show only what's new, not the whole log every time.
|
|
267
|
+
const bashOutput = {
|
|
268
|
+
definition: def("bash_output", `Check on a background task started with run_bash_background: returns its status and any NEW output since you last checked.
|
|
269
|
+
When to use: to see if a long job is done, or to read a long-running server's output (e.g. confirm "listening on :3000"). A finished task also sends you a <task_notification> automatically — you don't need to poll just to learn it ended.
|
|
270
|
+
Pass full=true to get the entire captured log instead of only the new lines. On an unknown task id you'll get the list of ids that exist.`, {
|
|
271
|
+
task_id: { type: "string", description: "The background task id, e.g. bg_1" },
|
|
272
|
+
full: { type: "boolean", description: "Return the entire captured output instead of only new output since the last check (default false)" },
|
|
273
|
+
}, ["task_id"]),
|
|
274
|
+
run: (args) => {
|
|
275
|
+
const full = args.full === true; // optional boolean
|
|
276
|
+
return spillIfHuge(readBackground(args.task_id, full), "background output");
|
|
277
|
+
},
|
|
278
|
+
};
|
|
238
279
|
// ============ exit_plan_mode ============
|
|
239
280
|
// Plan mode (Day 20) makes the agent research-only: the permission gate blocks
|
|
240
281
|
// every mutating tool until the model presents a plan the user approves. The
|
|
@@ -251,6 +292,47 @@ On approval, plan mode turns off and writing/executing tools become available. I
|
|
|
251
292
|
return "Plan approved by the user. Plan mode is OFF — implement the plan now; writing and executing tools are available again.";
|
|
252
293
|
},
|
|
253
294
|
};
|
|
295
|
+
// ============ todo_write ============
|
|
296
|
+
// The agent's planning tool (Day 36). The model sends the WHOLE list each time
|
|
297
|
+
// (it replaces the previous one); we store it, show it, and hand back a one-line
|
|
298
|
+
// tally. The array-of-objects schema is built by hand — the `def()` helper only
|
|
299
|
+
// does flat string params. State + rendering + the nag live in todos.ts.
|
|
300
|
+
const todoWrite = {
|
|
301
|
+
definition: {
|
|
302
|
+
type: "function",
|
|
303
|
+
function: {
|
|
304
|
+
name: "todo_write",
|
|
305
|
+
description: `Record and update a short plan for the CURRENT task as a checklist. This adds planning, NOT new abilities — it changes nothing on disk.
|
|
306
|
+
When to use: a task with 3+ distinct steps, or whenever the user gives several requirements. Skip it for trivial one-step asks.
|
|
307
|
+
How: pass the COMPLETE list every time (it replaces the previous one). Keep AT MOST ONE item in_progress. As you work, flip items to completed and move the next to in_progress — an out-of-date list is worse than none.`,
|
|
308
|
+
parameters: {
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
todos: {
|
|
312
|
+
type: "array",
|
|
313
|
+
description: "The complete todo list, in order. Replaces the previous list.",
|
|
314
|
+
items: {
|
|
315
|
+
type: "object",
|
|
316
|
+
properties: {
|
|
317
|
+
content: { type: "string", description: "The step, phrased as an action" },
|
|
318
|
+
status: { type: "string", enum: ["pending", "in_progress", "completed"], description: "pending | in_progress | completed (only one in_progress)" },
|
|
319
|
+
},
|
|
320
|
+
required: ["content", "status"],
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
required: ["todos"],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
run: (args) => {
|
|
329
|
+
const parsed = parseTodos(args.todos); // validate the model's list
|
|
330
|
+
if (parsed.error)
|
|
331
|
+
return fail(parsed.error); // precise repair message, not a crash
|
|
332
|
+
setTodos(parsed.todos); // store it (resets the nag clock); the loop renders it to the screen
|
|
333
|
+
return summarizeTodos(parsed.todos); // concise tally back to the model — the pretty list is for the human
|
|
334
|
+
},
|
|
335
|
+
};
|
|
254
336
|
// ============ Registry & dispatch ============
|
|
255
337
|
// The built-in tools. External tools (MCP servers, Day 15) are registered into
|
|
256
338
|
// the same record at startup, so they flow through the same dispatch and the
|
|
@@ -262,7 +344,10 @@ export const tools = {
|
|
|
262
344
|
edit_file: editFile,
|
|
263
345
|
search,
|
|
264
346
|
run_bash: runBash,
|
|
347
|
+
run_bash_background: runBashBackground,
|
|
348
|
+
bash_output: bashOutput,
|
|
265
349
|
exit_plan_mode: exitPlanMode,
|
|
350
|
+
todo_write: todoWrite,
|
|
266
351
|
};
|
|
267
352
|
// Register an external tool (e.g. one discovered from an MCP server). Kept in
|
|
268
353
|
// the same `tools` record so dispatch, the permission gate, and the tool-list
|
|
@@ -308,7 +393,7 @@ export async function dispatch(name, argsJson, signal) {
|
|
|
308
393
|
// own output (preview + spill-to-disk via spillIfHuge); its cap must leave
|
|
309
394
|
// room for the spill note, or dispatch would chop off the very pointer the
|
|
310
395
|
// model needs to read the rest.
|
|
311
|
-
const cap = name === "read_file" ? READ_LIMIT + 100 : name === "run_bash" ? BASH_OUTPUT_LIMIT + SPILL_NOTE_SLACK : TOOL_RESULT_LIMIT;
|
|
396
|
+
const cap = name === "read_file" ? READ_LIMIT + 100 : name === "run_bash" || name === "bash_output" ? BASH_OUTPUT_LIMIT + SPILL_NOTE_SLACK : TOOL_RESULT_LIMIT;
|
|
312
397
|
try {
|
|
313
398
|
const result = await tool.run(args, signal); // await covers both sync and async tools
|
|
314
399
|
return result.slice(0, cap); // cap the result size
|
package/dist/ui.js
CHANGED
|
@@ -28,7 +28,7 @@ export function banner(version, model, host) {
|
|
|
28
28
|
{ text: `mini-agent v${version}`, style: chalk.bold.cyan },
|
|
29
29
|
{ text: `${model} · ${host}`, style: chalk.dim },
|
|
30
30
|
{ text: "" },
|
|
31
|
-
{ text: "/help commands @file attach
|
|
31
|
+
{ text: "/help commands @file attach Esc interrupt Ctrl+C quit", style: chalk.dim },
|
|
32
32
|
]);
|
|
33
33
|
}
|
|
34
34
|
// The input prompt. A styled chevron, not a bare ">". Plan mode (Day 20) gets a
|
|
@@ -48,6 +48,10 @@ export const mark = {
|
|
|
48
48
|
hookBlock: (event) => chalk.red(` ⎿ blocked by ${event} hook`),
|
|
49
49
|
subAgentStart: (desc, tier) => chalk.blue(" ⎿ sub-agent") + (tier ? chalk.dim(` [${tier}]`) : "") + chalk.blue(`: ${desc}`),
|
|
50
50
|
subAgentDone: chalk.blue(" ⎿ sub-agent done"),
|
|
51
|
+
bgNote: (n) => chalk.magenta(" ⎿ ") + chalk.magenta(`${n} background task${n === 1 ? "" : "s"} finished — notified the agent`), // Day 37
|
|
52
|
+
teammateStart: (name, role) => chalk.magenta(" ⎿ teammate ") + chalk.bold(name) + chalk.magenta(` spawned`) + chalk.dim(` — ${role}`), // Day 38
|
|
53
|
+
teammateDone: (name, ok) => (ok ? chalk.magenta(" ⎿ teammate ") + chalk.bold(name) + chalk.magenta(" finished") : chalk.yellow(" ⎿ teammate ") + chalk.bold(name) + chalk.yellow(" ended (not done)")),
|
|
54
|
+
inbox: (n) => chalk.magenta(" ⎿ ") + chalk.magenta(`${n} team message${n === 1 ? "" : "s"} received`),
|
|
51
55
|
note: (s) => chalk.dim(s), // dim asides (resumed, attached, cleared…)
|
|
52
56
|
warn: (s) => chalk.yellow(s),
|
|
53
57
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-from-zero",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Build a Claude Code-style CLI agent from scratch, with AI as your pair programmer — companion code for a 10-day tutorial (DeepSeek by default, any OpenAI-compatible model)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"eval": "tsx --env-file=.env eval/run.ts",
|
|
17
17
|
"build": "tsc -p tsconfig.build.json",
|
|
18
18
|
"prepare": "npm run build",
|
|
19
|
-
"test": "tsx tests/tools.test.ts && tsx tests/permissions.test.ts && tsx tests/context.test.ts && tsx tests/hooks.test.ts && tsx tests/mcp.test.ts && tsx tests/judge.test.ts && tsx tests/memory.test.ts && tsx tests/cost.test.ts && tsx tests/validate.test.ts && tsx tests/plan.test.ts && tsx tests/diff.test.ts && tsx tests/undo.test.ts && tsx tests/tiering.test.ts && tsx tests/mcp-http.test.ts && tsx tests/changes.test.ts && tsx tests/mentions.test.ts && tsx tests/session.test.ts && tsx tests/ui.test.ts && tsx tests/approval.test.ts && tsx tests/form.test.ts && tsx tests/model.test.ts && tsx tests/reasoning.test.ts && tsx tests/skills.test.ts"
|
|
19
|
+
"test": "tsx tests/tools.test.ts && tsx tests/permissions.test.ts && tsx tests/context.test.ts && tsx tests/hooks.test.ts && tsx tests/mcp.test.ts && tsx tests/judge.test.ts && tsx tests/memory.test.ts && tsx tests/cost.test.ts && tsx tests/validate.test.ts && tsx tests/plan.test.ts && tsx tests/diff.test.ts && tsx tests/undo.test.ts && tsx tests/tiering.test.ts && tsx tests/mcp-http.test.ts && tsx tests/changes.test.ts && tsx tests/mentions.test.ts && tsx tests/session.test.ts && tsx tests/ui.test.ts && tsx tests/approval.test.ts && tsx tests/form.test.ts && tsx tests/model.test.ts && tsx tests/reasoning.test.ts && tsx tests/skills.test.ts && tsx tests/markdown.test.ts && tsx tests/editor.test.ts && tsx tests/todos.test.ts && tsx tests/background.test.ts && tsx tests/team.test.ts && tsx tests/board.test.ts"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20.6"
|
|
@@ -24,8 +24,12 @@
|
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"chalk": "^5.6.2",
|
|
27
|
+
"cli-table3": "^0.6.5",
|
|
28
|
+
"marked": "^15.0.12",
|
|
29
|
+
"marked-terminal": "^7.3.0",
|
|
27
30
|
"openai": "^6.42.0",
|
|
28
|
-
"ora": "^9.4.0"
|
|
31
|
+
"ora": "^9.4.0",
|
|
32
|
+
"wrap-ansi": "^10.0.0"
|
|
29
33
|
},
|
|
30
34
|
"devDependencies": {
|
|
31
35
|
"@types/node": "^25.9.2",
|