agent-from-zero 0.35.5 → 0.35.7
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 +31 -1
- package/dist/agent.js +92 -4
- package/dist/cli.js +11 -3
- package/dist/completion.js +1 -0
- package/dist/config.js +7 -1
- package/dist/goal.js +365 -0
- package/dist/hooks.js +58 -1
- package/dist/ink/app.js +90 -6
- package/dist/ink/commands.js +1 -0
- package/dist/ink/launch.js +1 -1
- package/dist/ink/setup.js +29 -3
- package/dist/loop.js +59 -10
- package/dist/permissions.js +4 -0
- package/dist/print.js +68 -0
- package/dist/session.js +12 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -133,17 +133,47 @@ export MINI_AGENT_MODEL=gpt-4.1-mini
|
|
|
133
133
|
|
|
134
134
|
所有数据都存在 `~/.config/mini-agent/sessions/` 下,只在本机,不经过网络。
|
|
135
135
|
|
|
136
|
+
### 目标模式 /goal
|
|
137
|
+
|
|
138
|
+
普通对话里,模型觉得做完了就停。`/goal` 把"做完没有"的判断从模型手里拿走:只要目标还没验证完成,模型每次停下,mini-agent 都会自动开下一轮让它接着干。
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
/goal 让 npm test 全部通过 --check "npm test"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- 模型只能用 `update_goal` 工具结束目标:`blocked` 表示需要你介入,`complete` 表示宣布完成。宣布完成要过两关:先跑你写的 `--check` 命令,退出码必须是 0,这条命令模型改不了;再由一次独立的模型调用对照最近的工具输出核对它的说法,拿不准就算没完成。
|
|
145
|
+
- 不设费用上限,靠"有没有进展"兜底:一轮目标回合里一个工具都没调,立刻暂停;连续 3 轮工作区毫无变化,也暂停。按 Esc 同样会暂停。
|
|
146
|
+
- 你随时可以插话,你的消息永远优先,处理完目标再继续。plan 模式下不会自动续跑。
|
|
147
|
+
- `/goal` 查看状态,`/goal pause`、`/goal resume`、`/goal clear` 管理生命周期。
|
|
148
|
+
- 目标存在会话文件里,`--resume` 或 `/resume` 回来以后,没做完的目标会接着跑。
|
|
149
|
+
|
|
136
150
|
### 非交互模式
|
|
137
151
|
|
|
138
152
|
```bash
|
|
139
153
|
npx agent-from-zero -p "总结当前改动"
|
|
140
154
|
cat error.log | npx agent-from-zero -p "分析这份日志"
|
|
141
155
|
npx agent-from-zero -p "总结当前改动" --output-format json
|
|
156
|
+
npx agent-from-zero -p "修一下这个 bug" --output-format stream-json
|
|
142
157
|
```
|
|
143
158
|
|
|
159
|
+
`stream-json` 和 `claude -p --output-format stream-json` 同一格式:每行一个 JSON 事件,依次是 `system/init`、`assistant`(text / thinking / tool_use 块)、`user`(tool_result)、最后一行 `result`。为了兼容 Claude 的脚本,`--verbose` 也能传,不影响输出。
|
|
160
|
+
|
|
161
|
+
### 让别的程序来驱动交互会话
|
|
162
|
+
|
|
163
|
+
编排器(比如 orca)可以把交互式的 mini-agent 放在终端里跑,用 hooks 看它的状态、替你审批:
|
|
164
|
+
|
|
165
|
+
- `MINI_AGENT_HOOKS_FILE=<文件>`:再加一层 hooks,格式和 settings.json 里的 `hooks` 一样,只对这一个进程生效,不改你的设置文件,也放宽不了权限。
|
|
166
|
+
- `--session-id <id>`:这个 id 的会话存在就接着聊,不存在就用它开新会话。
|
|
167
|
+
- 每个 hook 的输入都带 Claude Code 同名的字段:`session_id`、`transcript_path`(会话文件的绝对路径)、`cwd`、`hook_event_name`、这一轮的 `prompt_id`,工具事件另有 `tool_name`、`tool_input`。原来的字段照旧。
|
|
168
|
+
- `PermissionRequest`:一次工具调用要问你之前先问 hook。输出 `{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}` 就放行,`"behavior":"deny"` 加 `message` 或者退出码 2 就拒绝;没输出、超时、出错都算没决定,照常问你。规则要求必须由人决定的调用,hook 只能拒绝,放行还得你点。
|
|
169
|
+
- `Notification`:审批菜单弹出来时触发,`notification_type` 是 `permission_prompt`。
|
|
170
|
+
- `TurnEnd`:每一轮结束、会话存盘之后触发,被 Esc 打断或出错也会触发,带 `reason` 和 `last_assistant_message`。`Stop` 在存盘之前触发,打断时不触发,要知道"这一轮真的完了"就看 `TurnEnd`。
|
|
171
|
+
|
|
172
|
+
输入框里 `Ctrl+U` 删除光标前的这一行,行首再按会并到上一行,连按可以清空多行草稿。
|
|
173
|
+
|
|
144
174
|
## 常用命令
|
|
145
175
|
|
|
146
|
-
在会话里输入 `/help` 可以看到全部命令,比较常用的有:`/model` `/effort` `/plan` `/auto` `/skills` `/mcp` `/peers` `/memory` `/compact` `/diff` `/undo` `/resume` `/status` `/cost`。
|
|
176
|
+
在会话里输入 `/help` 可以看到全部命令,比较常用的有:`/goal` `/model` `/effort` `/plan` `/auto` `/skills` `/mcp` `/peers` `/memory` `/compact` `/diff` `/undo` `/resume` `/status` `/cost`。
|
|
147
177
|
|
|
148
178
|
## 开发
|
|
149
179
|
|
package/dist/agent.js
CHANGED
|
@@ -5,7 +5,7 @@ import chalk from "chalk"; // terminal colors
|
|
|
5
5
|
import readline from "node:readline/promises"; // promise-based terminal input
|
|
6
6
|
import { createRequire } from "node:module"; // to read package.json for --version
|
|
7
7
|
import { CONFIG, requireApiKey, saveGlobalSetting, PROJECT_SETTINGS_PATH, GLOBAL_SETTINGS_PATH } from "./config.js"; // provider-agnostic settings (.env loaded there)
|
|
8
|
-
import { runLoop, TerminateReason, MAX_RETRIES, killAllSubAgents } from "./loop.js"; // the state machine
|
|
8
|
+
import { runLoop, TerminateReason, MAX_RETRIES, killAllSubAgents, topLevelToolNames } from "./loop.js"; // the state machine
|
|
9
9
|
import { buildSystemMessage, readProjectInstructions } from "./prompt.js"; // the constitution + optional AGENT.md project memory
|
|
10
10
|
import { forgetFilesExcept, registerExternalTool } from "./tools.js"; // file-state reset + tool registration
|
|
11
11
|
import { compactHistory, compactThreshold, contextPercent, contextWindowFor } from "./context.js"; // /compact, /model info, the ctx % in the status line
|
|
@@ -18,7 +18,7 @@ import { Judge } from "./judge.js"; // optional LLM permission classifier
|
|
|
18
18
|
import { AutoMode } from "./auto.js";
|
|
19
19
|
import { effortMenu, setEffort, selectedEffort } from "./effort.js";
|
|
20
20
|
import { parseCli, applyCliOptions, readPrintPrompt, CliUsageError } from "./cli.js";
|
|
21
|
-
import { reservePrintOutput, formatPrintResult } from "./print.js";
|
|
21
|
+
import { reservePrintOutput, formatPrintResult, streamJsonInit, streamJsonMessage } from "./print.js";
|
|
22
22
|
import { FollowUpQueue } from "./follow-up.js";
|
|
23
23
|
import { reviewDebugCommand } from "./auto-debug.js";
|
|
24
24
|
import { isPlanMode, setPlanMode } from "./permissions.js"; // plan mode: research-only until the user approves a plan
|
|
@@ -41,6 +41,7 @@ import { launchInk } from "./ink/launch.js"; // the Ink REPL — the default fro
|
|
|
41
41
|
import { listBackground, hasRunningBackground, killAllBackground } from "./background.js"; // background tasks: /bg view + kill-on-exit (Day 37)
|
|
42
42
|
import { listTeam, resetTeam } from "./team.js"; // agent teams: /team view + reset on clear/resume (Day 38)
|
|
43
43
|
import { boardSummary, resetBoard } from "./board.js"; // task board: /tasks view + reset on clear/resume (Day 40)
|
|
44
|
+
import { goalCommand, getGoal, restoreGoal, clearGoal, startGoalTurn, settleGoalTurn } from "./goal.js"; // /goal (Day 41): keep working until the goal is verified done
|
|
44
45
|
import { loadDurableJobs, startCronScheduler, stopCronScheduler, listJobs } from "./cron.js"; // cron scheduler (Day s14)
|
|
45
46
|
// package.json sits one level above both src/ (dev) and dist/ (built) — same path either way.
|
|
46
47
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
@@ -51,6 +52,7 @@ const USAGE = `mini-agent ${pkg.version} — a Claude Code-style CLI agent (any
|
|
|
51
52
|
Usage:
|
|
52
53
|
mini-agent interactive session (REPL)
|
|
53
54
|
mini-agent -r | --resume continue the most recent session in this directory
|
|
55
|
+
mini-agent --session-id <id> continue that session, or start a new one with that id
|
|
54
56
|
mini-agent -p "<task>" one-shot: run a single task, print the result, exit
|
|
55
57
|
mini-agent exec "<task>" alias for non-interactive print mode
|
|
56
58
|
cat file | mini-agent -p "<task>" append piped text to the prompt
|
|
@@ -59,7 +61,7 @@ Usage:
|
|
|
59
61
|
--effort <level> supported effort for this model, or default
|
|
60
62
|
--permission-mode <mode> default | auto | bypassPermissions (this run only)
|
|
61
63
|
--dangerously-skip-permissions alias for --permission-mode bypassPermissions
|
|
62
|
-
--output-format text|json final answer
|
|
64
|
+
--output-format text|json|stream-json final answer, a JSON result, or Claude-style NDJSON events (print mode only)
|
|
63
65
|
mini-agent --auto review tool actions automatically (Jev or your current vendor)
|
|
64
66
|
mini-agent -v | --version print the version
|
|
65
67
|
mini-agent -h | --help this text
|
|
@@ -108,6 +110,7 @@ const SESSION_HELP = `commands:
|
|
|
108
110
|
/bg list background tasks this session (run_bash_background) and their status
|
|
109
111
|
/team list the agent team (spawn_teammate): each teammate's role, status, and pending inbox
|
|
110
112
|
/tasks show the shared task board (create_task/claim_task): each task's status, owner, and dependencies
|
|
113
|
+
/goal <objective> [--check <cmd>] keep working, turn after turn, until the goal is verified done (/goal alone shows it; pause | resume | clear)
|
|
111
114
|
/cron list scheduled cron jobs (schedule_cron) and their expressions
|
|
112
115
|
/peers list other mini-agent sessions on this machine (the agent can message them with send_message)
|
|
113
116
|
/rename <name> rename this session — the name other sessions use to reach it
|
|
@@ -152,7 +155,7 @@ async function main() {
|
|
|
152
155
|
// MINI_AGENT_NO_INK=1 to use the readline REPL below instead (a simpler
|
|
153
156
|
// reference front-end, and an escape hatch for terminals Ink misbehaves in).
|
|
154
157
|
if (printTask === null && process.env.MINI_AGENT_NO_INK !== "1") {
|
|
155
|
-
await launchInk({ resume: cli.resume });
|
|
158
|
+
await launchInk({ resume: cli.resume, sessionId: cli.sessionId });
|
|
156
159
|
return;
|
|
157
160
|
}
|
|
158
161
|
requireApiKey(); // fail fast with instructions, not a stack trace from inside the SDK
|
|
@@ -181,6 +184,8 @@ async function main() {
|
|
|
181
184
|
sessionId = prev.id; // keep appending to the same session file
|
|
182
185
|
initialTitle = prev.title;
|
|
183
186
|
console.log(chalk.dim(`(resumed session ${prev.id} — ${prev.messages.length} messages; files must be re-read before editing)`));
|
|
187
|
+
if (printTask === null)
|
|
188
|
+
restoreGoal(prev.goal); // Day 41: the goal comes back with its session (print mode doesn't run goals)
|
|
184
189
|
}
|
|
185
190
|
else {
|
|
186
191
|
console.log(chalk.dim("(no previous session here — starting fresh)")); // resume with nothing to resume is not an error
|
|
@@ -216,6 +221,15 @@ async function main() {
|
|
|
216
221
|
// The optional LLM permission judge, built once if a settings file enabled it.
|
|
217
222
|
const judge = CONFIG.judge.enabled && !CONFIG.bypassPermissions ? new Judge(client, CONFIG.judge.model || CONFIG.model) : undefined;
|
|
218
223
|
const autoMode = new AutoMode(client, { projectInstructions });
|
|
224
|
+
// A restored ACTIVE goal (Day 41) starts running before the first prompt —
|
|
225
|
+
// say so loudly. Its /goal line was a genuine human request: auto mode re-learns it.
|
|
226
|
+
const restoredGoal = getGoal();
|
|
227
|
+
if (restoredGoal?.status === "active") {
|
|
228
|
+
autoMode.recordRequest(`/goal ${restoredGoal.objective}`);
|
|
229
|
+
console.log(chalk.cyan(`(◎ resumed an ACTIVE goal — the agent continues it now; Esc pauses: ${restoredGoal.objective.slice(0, 80)})`));
|
|
230
|
+
}
|
|
231
|
+
else if (restoredGoal)
|
|
232
|
+
console.log(chalk.dim(`(◎ goal restored [${restoredGoal.status}] — /goal to see it, /goal resume to continue)`));
|
|
219
233
|
for (const notice of autoMode.startupNotices())
|
|
220
234
|
console.log(chalk.dim(notice));
|
|
221
235
|
if (judge)
|
|
@@ -264,7 +278,21 @@ async function main() {
|
|
|
264
278
|
});
|
|
265
279
|
messages.push({ role: "user", content: printTask }); // the single task
|
|
266
280
|
autoMode.recordRequest(printTask);
|
|
281
|
+
// stream-json: an init line now (MCP is connected, so statuses are real),
|
|
282
|
+
// then every assistant turn / tool result as it enters history.
|
|
283
|
+
const streaming = cli.outputFormat === "stream-json";
|
|
284
|
+
const startedAt = Date.now();
|
|
285
|
+
let numTurns = 0;
|
|
286
|
+
if (streaming)
|
|
287
|
+
printOutput.write(streamJsonInit({ sessionId, model: CONFIG.model, tools: topLevelToolNames(),
|
|
288
|
+
mcpServers: listMcpServers().map((s) => ({ name: s.name, status: s.status })),
|
|
289
|
+
permissionMode: CONFIG.bypassPermissions ? "bypassPermissions" : CONFIG.autoMode.enabled ? "auto" : "default" }));
|
|
267
290
|
const result = await runLoop(messages, {
|
|
291
|
+
onMessage: streaming ? (m) => {
|
|
292
|
+
if (m.role === "assistant")
|
|
293
|
+
numTurns++;
|
|
294
|
+
printOutput.write(streamJsonMessage(sessionId, CONFIG.model, m));
|
|
295
|
+
} : undefined,
|
|
268
296
|
client,
|
|
269
297
|
model: CONFIG.model,
|
|
270
298
|
quiet: true, // final result only; no spinner, reasoning or intermediate prose
|
|
@@ -289,6 +317,7 @@ async function main() {
|
|
|
289
317
|
emit("agent_session_end"); // close the books
|
|
290
318
|
printOutput.write(formatPrintResult(cli.outputFormat, {
|
|
291
319
|
sessionId, model: CONFIG.model, effort: selectedEffort(CONFIG.model), result, usage: costMeter.snapshot(),
|
|
320
|
+
durationMs: Date.now() - startedAt, numTurns,
|
|
292
321
|
}));
|
|
293
322
|
if (result.reason !== TerminateReason.Done) {
|
|
294
323
|
console.error(chalk.yellow(EXIT_NOTES[result.reason])); // human note on stderr
|
|
@@ -569,6 +598,13 @@ async function main() {
|
|
|
569
598
|
console.log(await statusCommand(line, costMeter));
|
|
570
599
|
return true;
|
|
571
600
|
}
|
|
601
|
+
if (line === "/goal" || line.startsWith("/goal ")) {
|
|
602
|
+
const { message, request } = goalCommand(line.slice(5));
|
|
603
|
+
if (request)
|
|
604
|
+
autoMode.recordRequest(request); // the /goal you typed is the authorization; automatic goal turns never are
|
|
605
|
+
console.log(chalk.cyan(message)); // setting or resuming: the goal turns start before the next prompt
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
572
608
|
if (line.startsWith("/skills "))
|
|
573
609
|
line = `/skill ${line.slice(8).trim()}`;
|
|
574
610
|
if (line === "/effort" || line.startsWith("/effort ")) {
|
|
@@ -662,6 +698,7 @@ async function main() {
|
|
|
662
698
|
return true;
|
|
663
699
|
case "/clear":
|
|
664
700
|
autoMode.clearRequests();
|
|
701
|
+
clearGoal(); // the goal belonged to the old conversation (Day 41)
|
|
665
702
|
messages = [{ role: "system", content: systemMessage }]; // drop everything but the constitution
|
|
666
703
|
forgetFilesExcept([]); // the file read-state belongs to the conversation — clear it too
|
|
667
704
|
clearUndo(); // a fresh conversation should not undo the previous one's writes
|
|
@@ -805,6 +842,15 @@ async function main() {
|
|
|
805
842
|
resetTeam(); // a resumed conversation starts with no live team
|
|
806
843
|
resetBoard(); // ...and an empty task board (Day 40)
|
|
807
844
|
console.log(chalk.dim(`(resumed ${chosen.id} — ${chosen.messages.length} messages; files must be re-read before editing)`));
|
|
845
|
+
// Unlike the todos and the board, the goal comes BACK with its session (Day 41).
|
|
846
|
+
restoreGoal(chosen.goal);
|
|
847
|
+
const goal = getGoal();
|
|
848
|
+
if (goal?.status === "active") {
|
|
849
|
+
autoMode.recordRequest(`/goal ${goal.objective}`);
|
|
850
|
+
console.log(chalk.cyan(`◎ resumed an ACTIVE goal — the agent continues it now (Esc pauses): ${goal.objective.slice(0, 80)}`));
|
|
851
|
+
}
|
|
852
|
+
else if (goal)
|
|
853
|
+
console.log(chalk.dim(`(◎ goal restored [${goal.status}] — /goal to see it, /goal resume to continue)`));
|
|
808
854
|
return true;
|
|
809
855
|
}
|
|
810
856
|
case "/diff": {
|
|
@@ -884,7 +930,46 @@ async function main() {
|
|
|
884
930
|
}
|
|
885
931
|
});
|
|
886
932
|
}
|
|
933
|
+
// /goal (Day 41): the readline prompt blocks on your input, so it can't poll
|
|
934
|
+
// like the Ink REPL's idle processor. Instead, goal turns run HERE, before each
|
|
935
|
+
// prompt, one after another until the goal stops being active — verified
|
|
936
|
+
// complete, blocked, or paused by Esc / a failed turn / no progress. Plan mode
|
|
937
|
+
// never continues a goal: plan-only work can't move it forward.
|
|
938
|
+
const runGoalTurns = async () => {
|
|
939
|
+
for (let content = isPlanMode() ? null : startGoalTurn(); content; content = isPlanMode() ? null : startGoalTurn()) {
|
|
940
|
+
console.log(chalk.cyan(`\n◎ goal · turn ${getGoal().continuations}: ${getGoal().objective.slice(0, 70)}`));
|
|
941
|
+
messages.push({ role: "user", content }); // the goal turn (restates the objective — it survives compaction)
|
|
942
|
+
clearReasoning();
|
|
943
|
+
clearToolCalls();
|
|
944
|
+
running = true;
|
|
945
|
+
interrupted = false;
|
|
946
|
+
controller = new AbortController();
|
|
947
|
+
setSessionState("busy", CONFIG.model);
|
|
948
|
+
let reason = "error";
|
|
949
|
+
try {
|
|
950
|
+
const result = await runLoop(messages, {
|
|
951
|
+
client, model: CONFIG.model, signal: controller.signal, isInterrupted: () => interrupted, confirm, askUser, followUps,
|
|
952
|
+
onFollowUp: (text) => console.log(sentMessage(text)), subAgentModel: CONFIG.subAgentModel, judge, autoMode, canPrompt: !!process.stdin.isTTY,
|
|
953
|
+
});
|
|
954
|
+
reason = result.reason;
|
|
955
|
+
if (result.reason !== TerminateReason.Done)
|
|
956
|
+
console.log(chalk.yellow(`\n⚠️ ${EXIT_NOTES[result.reason]}`));
|
|
957
|
+
}
|
|
958
|
+
catch (err) {
|
|
959
|
+
console.log(chalk.red(`[error] ${err.message}`));
|
|
960
|
+
}
|
|
961
|
+
finally {
|
|
962
|
+
running = false;
|
|
963
|
+
setSessionState("idle");
|
|
964
|
+
}
|
|
965
|
+
const goalNote = settleGoalTurn(reason, true);
|
|
966
|
+
if (goalNote)
|
|
967
|
+
console.log(chalk.yellow(goalNote));
|
|
968
|
+
saveSession(sessionId, CONFIG.model, messages, pendingTitle); // the goal's progress is saved turn by turn
|
|
969
|
+
}
|
|
970
|
+
};
|
|
887
971
|
while (true) {
|
|
972
|
+
await runGoalTurns(); // an active /goal keeps going before you're asked for input
|
|
888
973
|
// The input area: a blank line for breathing room, the "❯" you type on, then
|
|
889
974
|
// a single status line (model · 📁 dir · 🌿 branch · ctx% · $ · time) pinned
|
|
890
975
|
// BELOW the input. We dropped the old top/bottom horizontal rules — two bare
|
|
@@ -985,6 +1070,9 @@ async function main() {
|
|
|
985
1070
|
});
|
|
986
1071
|
running = false; // back at the prompt — Ctrl+C means "exit" again
|
|
987
1072
|
setSessionState("idle"); // other sessions see this one as free again
|
|
1073
|
+
const goalNote = settleGoalTurn(result.reason, false); // Day 41: Esc on your own turn pauses an active goal too
|
|
1074
|
+
if (goalNote)
|
|
1075
|
+
console.log(chalk.yellow(goalNote));
|
|
988
1076
|
saveSession(sessionId, CONFIG.model, messages, pendingTitle); // snapshot after every turn — crash-safe by construction
|
|
989
1077
|
// Auto-extract memories (opt-in: settings.memory.autoExtract). A cheap pass
|
|
990
1078
|
// that saves durable facts — especially user corrections — so memory
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { CONFIG } from "./config.js";
|
|
3
3
|
import { effortMenu, setEffort } from "./effort.js";
|
|
4
|
+
import { isValidSessionId } from "./session.js"; // --session-id becomes a file name
|
|
4
5
|
export class CliUsageError extends Error {
|
|
5
6
|
}
|
|
6
7
|
// -p is a mode, not a boundary after which flags turn into task text. Node's
|
|
@@ -18,9 +19,11 @@ export function parseCli(args) {
|
|
|
18
19
|
"permission-mode": { type: "string" },
|
|
19
20
|
"dangerously-skip-permissions": { type: "boolean" },
|
|
20
21
|
resume: { type: "boolean", short: "r" },
|
|
22
|
+
"session-id": { type: "string" },
|
|
21
23
|
help: { type: "boolean", short: "h" },
|
|
22
24
|
version: { type: "boolean", short: "v" },
|
|
23
25
|
"output-format": { type: "string" },
|
|
26
|
+
verbose: { type: "boolean" }, // accepted for Claude compatibility: its stream-json requires --verbose; ours always streams
|
|
24
27
|
} });
|
|
25
28
|
}
|
|
26
29
|
catch (error) {
|
|
@@ -35,20 +38,25 @@ export function parseCli(args) {
|
|
|
35
38
|
// Match Claude's precedence: the explicit skip flag wins over a mode flag.
|
|
36
39
|
const permissionMode = values["dangerously-skip-permissions"] ? "bypassPermissions" : requestedMode;
|
|
37
40
|
const outputFormat = values["output-format"] ?? "text";
|
|
38
|
-
if (outputFormat !== "text" && outputFormat !== "json")
|
|
39
|
-
throw new CliUsageError("--output-format must be text or json");
|
|
41
|
+
if (outputFormat !== "text" && outputFormat !== "json" && outputFormat !== "stream-json")
|
|
42
|
+
throw new CliUsageError("--output-format must be text, json, or stream-json");
|
|
40
43
|
for (const flag of ["model", "effort", "prompt"]) {
|
|
41
44
|
if (typeof values[flag] === "string" && !values[flag].trim())
|
|
42
45
|
throw new CliUsageError(`--${flag} cannot be empty`);
|
|
43
46
|
}
|
|
44
47
|
if (values.prompt !== undefined && positionals.length)
|
|
45
48
|
throw new CliUsageError("Use either a positional prompt or --prompt, not both");
|
|
49
|
+
const sessionId = typeof values["session-id"] === "string" ? values["session-id"] : undefined;
|
|
50
|
+
if (sessionId !== undefined && !isValidSessionId(sessionId))
|
|
51
|
+
throw new CliUsageError("--session-id may only contain letters, digits, '.', '_' and '-'");
|
|
52
|
+
if (sessionId !== undefined && values.resume)
|
|
53
|
+
throw new CliUsageError("Use either --resume or --session-id, not both");
|
|
46
54
|
const prompt = typeof values.prompt === "string" ? values.prompt : positionals.join(" ");
|
|
47
55
|
if (!print && (prompt || values["output-format"] !== undefined))
|
|
48
56
|
throw new CliUsageError("Use -p / --print or exec with a prompt or --output-format");
|
|
49
57
|
return { print, prompt, outputFormat, permissionMode, model: typeof values.model === "string" ? values.model : undefined,
|
|
50
58
|
effort: typeof values.effort === "string" ? values.effort : undefined, auto: values.auto === true,
|
|
51
|
-
resume: values.resume === true, help: values.help === true, version: values.version === true };
|
|
59
|
+
resume: values.resume === true, sessionId, help: values.help === true, version: values.version === true };
|
|
52
60
|
}
|
|
53
61
|
// CLI overrides live only in this process; never save them to user settings.
|
|
54
62
|
// Validate against the selected model/endpoint before any request is started.
|
package/dist/completion.js
CHANGED
|
@@ -19,6 +19,7 @@ export const COMMANDS = [
|
|
|
19
19
|
{ value: "/bg", description: "Show background tasks" },
|
|
20
20
|
{ value: "/team", description: "Show teammates and status" },
|
|
21
21
|
{ value: "/tasks", description: "Show the shared task board" },
|
|
22
|
+
{ value: "/goal", description: "Keep working until a goal is verified done", children: true },
|
|
22
23
|
{ value: "/cron", description: "Show scheduled jobs" },
|
|
23
24
|
{ value: "/peers", description: "Talk to another mini-agent session, or find its window" },
|
|
24
25
|
{ value: "/rename", description: "Rename this session for other sessions", children: true },
|
package/dist/config.js
CHANGED
|
@@ -40,6 +40,12 @@ function readSettings(p) {
|
|
|
40
40
|
}
|
|
41
41
|
const globalSettings = readSettings(GLOBAL_SETTINGS_PATH); // the user's defaults
|
|
42
42
|
const projectSettings = readSettings(PROJECT_SETTINGS_PATH); // this project's rules
|
|
43
|
+
// A third, per-process layer of HOOKS only: MINI_AGENT_HOOKS_FILE points at a
|
|
44
|
+
// settings-shaped file ({"hooks": {...}}) whose hooks join the other two. It
|
|
45
|
+
// lets a program that launches the agent (an orchestrator such as orca) watch
|
|
46
|
+
// and steer this one process without editing the user's settings. Only hooks
|
|
47
|
+
// are read from it — it cannot loosen permissions.
|
|
48
|
+
const processHooks = process.env.MINI_AGENT_HOOKS_FILE ? readSettings(process.env.MINI_AGENT_HOOKS_FILE).hooks : undefined;
|
|
43
49
|
// Re-read ONLY the mcpServers map from both layers, mid-session (the MCP hot
|
|
44
50
|
// reload in mcp.ts). Unlike startup, a file that won't parse is NOT fatal here:
|
|
45
51
|
// an editor saving in two steps can leave half-written JSON for a moment, and
|
|
@@ -110,7 +116,7 @@ export const CONFIG = {
|
|
|
110
116
|
// a generic union so adding a new event needs no edit here.
|
|
111
117
|
hooks: (() => {
|
|
112
118
|
const merged = {};
|
|
113
|
-
for (const src of [globalSettings.hooks, projectSettings.hooks]) {
|
|
119
|
+
for (const src of [globalSettings.hooks, projectSettings.hooks, processHooks]) {
|
|
114
120
|
for (const [event, defs] of Object.entries(src ?? {}))
|
|
115
121
|
merged[event] = [...(merged[event] ?? []), ...(defs ?? [])];
|
|
116
122
|
}
|