agent-from-zero 0.35.7 → 0.37.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 +1 -1
- package/dist/agent.js +4 -3
- package/dist/auto-debug.js +5 -2
- package/dist/config.js +1 -0
- package/dist/ink/commands.js +2 -2
- package/dist/loop.js +7 -1
- package/dist/memory.js +14 -4
- package/dist/permissions.js +24 -1
- package/dist/session.js +11 -5
- package/dist/state.js +78 -0
- package/dist/telemetry.js +16 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -131,7 +131,7 @@ export MINI_AGENT_MODEL=gpt-4.1-mini
|
|
|
131
131
|
- 实例名跟着它在做的事走:发出第一条消息后会生成会话标题(比如 “Fix login bug”),实例名随之变成 `fix-login-bug`,标签页标题就是 `✳ Fix login bug`,和 `/peers` 里看到的一致。还没有标题时用目录名。`/rename <名字>` 可以手动指定名字,指定后标签页显示 `✳ 名字 · 标题`,之后不会再跟着标题变。
|
|
132
132
|
- agent 之间也能自己沟通:模型有 `list_peers` 工具,`send_message` 可以直接发给其他实例。对方空闲时会自动开一轮处理;忙的话,在它当前这一步工具调用结束后插进对话。你在 Talk 模式里打的字则会等对方这一轮结束再处理,因为回答要发回给你。另一个 agent 发来的消息不算用户的授权。为了防止两个 agent 互相回复停不下来,连续自动处理 5 轮之后会暂停,等你输入任何内容再继续。
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
peer 实例的数据存在 `~/.config/mini-agent/sessions/` 下,只在本机,不经过网络。会话快照和长期记忆按 cwd 分桶存在 `~/.config/mini-agent/projects/<cwd-slug>/` 下,不会在代码仓库里留下目录。
|
|
135
135
|
|
|
136
136
|
### 目标模式 /goal
|
|
137
137
|
|
package/dist/agent.js
CHANGED
|
@@ -34,7 +34,7 @@ import { gitBranch, toggleLastCollapsed, revealReasoning, clearReasoning, reveal
|
|
|
34
34
|
import { promptSelect, promptForm } from "./menu.js"; // arrow-key approval menu + multi-question form
|
|
35
35
|
import { editLine } from "./editor.js"; // our own line editor (keeps the status footer pinned even when input wraps)
|
|
36
36
|
import { normalizeDroppedPaths } from "./drop.js"; // drag-and-drop: a dropped file's path → a clean absolute path in the input
|
|
37
|
-
import { rememberTool, readMemory, readMemoryTyped, extractMemories,
|
|
37
|
+
import { rememberTool, readMemory, readMemoryTyped, extractMemories, memoryPath } from "./memory.js"; // long-term project memory + auto-extract
|
|
38
38
|
import { allSkills, currentSkills, findSkill, recordSkillUsage, skillLocked, skillMode, userSkillMessages } from "./skills.js"; // Markdown-as-plugin skills
|
|
39
39
|
import { initCostMeter, DEFAULT_PRICING } from "./cost.js"; // token & cost accounting for /cost
|
|
40
40
|
import { launchInk } from "./ink/launch.js"; // the Ink REPL — the default front-end for interactive sessions
|
|
@@ -171,7 +171,8 @@ async function main() {
|
|
|
171
171
|
const systemMessage = buildSystemMessage(projectInstructions);
|
|
172
172
|
let messages = [{ role: "system", content: systemMessage }]; // the whole conversation lives here, across turns
|
|
173
173
|
// ---- Session persistence ---------------------------------------------------------
|
|
174
|
-
// Every conversation is snapshotted to
|
|
174
|
+
// Every conversation is snapshotted to the project's home bucket
|
|
175
|
+
// (~/.config/mini-agent/projects/<slug>/sessions) after each turn;
|
|
175
176
|
// -r / --resume picks up the most recent one. The system message is NOT
|
|
176
177
|
// restored from disk — it is rebuilt fresh, because AGENT.md may have changed.
|
|
177
178
|
let sessionId = newSessionId(); // this session's identity (and file name)
|
|
@@ -777,7 +778,7 @@ async function main() {
|
|
|
777
778
|
case "/memory": {
|
|
778
779
|
const entries = readMemoryTyped(); // the durable, typed facts carried across sessions
|
|
779
780
|
console.log(chalk.dim(entries.length
|
|
780
|
-
? `long-term memory (${
|
|
781
|
+
? `long-term memory (${memoryPath()}):\n${entries.map((e) => ` [${e.type}] ${e.fact}`).join("\n")}`
|
|
781
782
|
: `(no long-term memory yet — the model saves facts with the remember tool${CONFIG.memory.autoExtract ? "" : "; turn on settings.memory.autoExtract to capture them automatically"})`));
|
|
782
783
|
return true;
|
|
783
784
|
}
|
package/dist/auto-debug.js
CHANGED
|
@@ -2,10 +2,13 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { emit } from "./telemetry.js";
|
|
5
|
+
import { migrateLegacyState, projectStateFile } from "./state.js"; // traces follow sessions/memory into the home bucket
|
|
5
6
|
// Explicit opt-in: unlike telemetry, this contains conversation and tool data.
|
|
6
7
|
export function reviewDebugPath() {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
if (process.env.MINI_AGENT_REVIEW_DEBUG !== "1")
|
|
9
|
+
return undefined;
|
|
10
|
+
migrateLegacyState(); // move an older ./.mini-agent/review-debug.jsonl into the bucket
|
|
11
|
+
return projectStateFile("review-debug.jsonl");
|
|
9
12
|
}
|
|
10
13
|
// Environment for commands the agent runs. Tracing is for THIS process only:
|
|
11
14
|
// a child `npm test` that inherited the flag appended its mocked reviews to the
|
package/dist/config.js
CHANGED
|
@@ -111,6 +111,7 @@ export const CONFIG = {
|
|
|
111
111
|
// the test suite injects rules here instead of writing temp files.
|
|
112
112
|
allow: [...(globalSettings.permissions?.allow ?? []), ...(projectSettings.permissions?.allow ?? [])],
|
|
113
113
|
deny: [...(globalSettings.permissions?.deny ?? []), ...(projectSettings.permissions?.deny ?? [])],
|
|
114
|
+
ask: [...(globalSettings.permissions?.ask ?? []), ...(projectSettings.permissions?.ask ?? [])],
|
|
114
115
|
},
|
|
115
116
|
// Hooks for every event, merged across layers (global first, then project) by
|
|
116
117
|
// a generic union so adding a new event needs no edit here.
|
package/dist/ink/commands.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { CONFIG } from "../config.js";
|
|
4
4
|
import { statsReport } from "../telemetry.js";
|
|
5
|
-
import { readMemoryTyped,
|
|
5
|
+
import { readMemoryTyped, memoryPath } from "../memory.js";
|
|
6
6
|
import { getTodos, renderTodos } from "../todos.js";
|
|
7
7
|
import { listBackground } from "../background.js";
|
|
8
8
|
import { listTeam } from "../team.js";
|
|
@@ -87,7 +87,7 @@ export function runInfoCommand(line, ctx) {
|
|
|
87
87
|
case "/memory": {
|
|
88
88
|
const entries = readMemoryTyped();
|
|
89
89
|
return chalk.dim(entries.length
|
|
90
|
-
? `long-term memory (${
|
|
90
|
+
? `long-term memory (${memoryPath()}):\n${entries.map((e) => ` [${e.type}] ${e.fact}`).join("\n")}`
|
|
91
91
|
: `(no long-term memory yet — the model saves facts with the remember tool${CONFIG.memory.autoExtract ? "" : "; turn on settings.memory.autoExtract to capture them automatically"})`);
|
|
92
92
|
}
|
|
93
93
|
case "/todos": {
|
package/dist/loop.js
CHANGED
|
@@ -744,8 +744,14 @@ async function authorizeCall(call, opts) {
|
|
|
744
744
|
emit("agent_tool_declined", { tool: call.name, ...(reviewId ? { reviewId } : {}) }); // the human said no — that is signal
|
|
745
745
|
if (!ok && !opts.quiet)
|
|
746
746
|
sink(opts).note(mark.declined); // make the refusal visible
|
|
747
|
-
if (!ok)
|
|
747
|
+
if (!ok) {
|
|
748
|
+
// Say what actually happened. An unattended worker was never declined by
|
|
749
|
+
// anyone — saying so teaches the model to rephrase and retry against a
|
|
750
|
+
// refusal that never existed.
|
|
751
|
+
if (opts.canPrompt === false)
|
|
752
|
+
return `[permission] Nobody is reachable to approve this here${reviewReason ? `: ${reviewReason}` : "."} Not executed. Report it in your result and continue with what the rules allow; do not retry the same effect through another tool, wrapper or worker.`;
|
|
748
753
|
return `[permission] Action not approved${reviewReason ? `: ${reviewReason}` : ". The user declined this action"}. Ask the user how to proceed, or choose a safer alternative.`;
|
|
754
|
+
}
|
|
749
755
|
}
|
|
750
756
|
if (opts.reviewBudget?.exhausted)
|
|
751
757
|
return "[permission] Review limit reached; not executed. Wait for human direction.";
|
package/dist/memory.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs"; // memory lives in a file on disk
|
|
2
2
|
import path from "node:path"; // path joining
|
|
3
|
+
import { migrateLegacyState, projectMemoryPath } from "./state.js"; // one bucket per cwd, kept out of the checkout
|
|
3
4
|
// Long-term memory: facts the agent should carry ACROSS sessions, not just
|
|
4
5
|
// within one conversation. Session persistence (Day 12) restores "what we were
|
|
5
6
|
// talking about"; this restores "what I should always know about this project".
|
|
@@ -13,7 +14,15 @@ import path from "node:path"; // path joining
|
|
|
13
14
|
// assistant: 90% the model gets right isn't worth saving, the 10% it got wrong
|
|
14
15
|
// and was corrected on is gold). Every fact is TYPED so the model can tell "this
|
|
15
16
|
// is who the user is" from "this is a correction" from "this is a project fact".
|
|
16
|
-
|
|
17
|
+
// The file lives in the cwd's home bucket (state.ts), not inside the checkout,
|
|
18
|
+
// so a repo never carries agent memory in git status. Resolved per call so a
|
|
19
|
+
// chdir (tests, --cd) is honoured, and so the first call can migrate an older
|
|
20
|
+
// build's ./.mini-agent/MEMORY.md. Still one Markdown file, human-readable and
|
|
21
|
+
// human-editable: not a vector database, not embeddings.
|
|
22
|
+
export function memoryPath() {
|
|
23
|
+
migrateLegacyState();
|
|
24
|
+
return projectMemoryPath();
|
|
25
|
+
}
|
|
17
26
|
// The four memory types (handbook §17). feedback is the most valuable.
|
|
18
27
|
export const MEMORY_TYPES = ["user", "feedback", "project", "reference"];
|
|
19
28
|
// Bounds, because unbounded memory dilutes the model's attention (Ch6: too many
|
|
@@ -38,7 +47,7 @@ function parseEntry(text) {
|
|
|
38
47
|
export function readMemoryTyped() {
|
|
39
48
|
try {
|
|
40
49
|
return fs
|
|
41
|
-
.readFileSync(
|
|
50
|
+
.readFileSync(memoryPath(), "utf8")
|
|
42
51
|
.split("\n")
|
|
43
52
|
.filter((l) => l.startsWith("- "))
|
|
44
53
|
.map((l) => parseEntry(l.slice(2).trim()))
|
|
@@ -91,8 +100,9 @@ export function addFact(fact, type = "project") {
|
|
|
91
100
|
entries.shift(); // still too big — drop the oldest until it fits
|
|
92
101
|
body = entries.map((e) => `- [${e.type}] ${e.fact}`).join("\n");
|
|
93
102
|
}
|
|
94
|
-
|
|
95
|
-
fs.
|
|
103
|
+
const file = memoryPath();
|
|
104
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
105
|
+
fs.writeFileSync(file, HEADER + body + "\n", "utf8");
|
|
96
106
|
return `Remembered (${t}). (${entries.length} fact${entries.length === 1 ? "" : "s"} stored.)`;
|
|
97
107
|
}
|
|
98
108
|
// The tool the model calls to save something worth keeping.
|
package/dist/permissions.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "node:fs"; // resolve symlinks before auto mode grants file acces
|
|
|
3
3
|
import os from "node:os"; // `cd ~` in the recoverable-shell analyzer
|
|
4
4
|
import { execFileSync } from "node:child_process"; // ask git whether a deletion target is disposable
|
|
5
5
|
import { CONFIG } from "./config.js"; // user-configured allow/deny rules from settings files
|
|
6
|
+
import { emit } from "./telemetry.js"; // a bypassed approval leaves an audit row, not silence
|
|
6
7
|
// ---- The no-fly zone --------------------------------------------------------
|
|
7
8
|
// Writes here are never OK, in any mode. .git can destroy history, shell rc
|
|
8
9
|
// files inject code into every future terminal, .ssh/.env leak credentials.
|
|
@@ -442,6 +443,18 @@ export function recoverableShell(command) {
|
|
|
442
443
|
}
|
|
443
444
|
return readOnly ? { readOnly, reason: "read-only command" } : { readOnly, reason: "recoverable: git can undo it, or it only removes gitignored or temp files" };
|
|
444
445
|
}
|
|
446
|
+
// ---- The user's prod guard (permissions.ask) ----------------------------------
|
|
447
|
+
// Text that always needs a human: a prod context, a deployment verb, a prod
|
|
448
|
+
// path. Checked BEFORE auto review and BEFORE the bypass rewrite, so neither
|
|
449
|
+
// can approve it. Case-insensitive substring, on the whole command or the path.
|
|
450
|
+
function guardHit(text) {
|
|
451
|
+
const haystack = text.toLowerCase();
|
|
452
|
+
for (const rule of CONFIG.permissions.ask ?? []) {
|
|
453
|
+
if (rule && haystack.includes(rule.toLowerCase()))
|
|
454
|
+
return rule;
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
445
458
|
// Decide what a bash command deserves: deny, ask, or allow.
|
|
446
459
|
function checkBash(command, auto = false) {
|
|
447
460
|
const summary = command.trim(); // what the user will see in the prompt
|
|
@@ -458,6 +471,9 @@ function checkBash(command, auto = false) {
|
|
|
458
471
|
for (const d of user.deny)
|
|
459
472
|
if (command.toLowerCase().includes(d.toLowerCase()))
|
|
460
473
|
return { decision: "deny", reason: `denied by your settings ("${d}")`, summary };
|
|
474
|
+
const guard = guardHit(command);
|
|
475
|
+
if (guard)
|
|
476
|
+
return { decision: "ask", requiresHuman: true, reason: `prod guard: "${guard}"`, summary };
|
|
461
477
|
// node/npm/npx, git aliases and tool-wide grants can run arbitrary code.
|
|
462
478
|
// Auto mode must inspect the actual command, including background commands.
|
|
463
479
|
if (auto) {
|
|
@@ -539,7 +555,11 @@ export function checkPermission(toolName, argsJson, auto = false) {
|
|
|
539
555
|
summary: base.summary,
|
|
540
556
|
};
|
|
541
557
|
}
|
|
542
|
-
if (CONFIG.bypassPermissions && base.decision === "ask" && toolName !== "exit_plan_mode" && toolName !== "ask_user") {
|
|
558
|
+
if (CONFIG.bypassPermissions && base.decision === "ask" && !base.requiresHuman && toolName !== "exit_plan_mode" && toolName !== "ask_user") {
|
|
559
|
+
// Bypass may skip an ordinary approval, never one the user's own guard list
|
|
560
|
+
// marked for a human. Every skip is recorded: a bypass run with no audit row
|
|
561
|
+
// is indistinguishable from a run that never checked.
|
|
562
|
+
emit("agent_permission_bypassed", { tool: toolName, reason: base.reason });
|
|
543
563
|
return { decision: "allow", reason: "permission approvals bypassed for this run", summary: base.summary };
|
|
544
564
|
}
|
|
545
565
|
return base;
|
|
@@ -645,6 +665,9 @@ function basePermission(toolName, argsJson, auto) {
|
|
|
645
665
|
const hit = noFlyHit(p); // is it in the no-fly zone?
|
|
646
666
|
if (hit)
|
|
647
667
|
return { decision: "deny", reason: hit, summary: p }; // hard stop — deny always wins
|
|
668
|
+
const guard = guardHit(p); // the user's prod guard, applied to the path
|
|
669
|
+
if (guard)
|
|
670
|
+
return { decision: "ask", requiresHuman: true, reason: `prod guard: "${guard}"`, summary: p };
|
|
648
671
|
// The user may pre-approve write tools ("tool:edit_file") to skip the
|
|
649
672
|
// prompt — note this runs AFTER the no-fly check, so it widens "ask",
|
|
650
673
|
// never "deny".
|
package/dist/session.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import fs from "node:fs"; // session files live on disk
|
|
2
2
|
import path from "node:path"; // path joining
|
|
3
3
|
import { getGoal } from "./goal.js"; // the /goal rides along in the session file (Day 41)
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
import { migrateLegacyState, projectSessionsDir } from "./state.js"; // one bucket per cwd, kept out of the checkout
|
|
5
|
+
// Sessions are per-project but not per-repo: <home>/mini-agent/projects/<slug>/sessions,
|
|
6
|
+
// where <slug> is the cwd flattened. Same reasoning as the old in-repo layout (a
|
|
7
|
+
// conversation is about a codebase, so resuming project A from inside project B
|
|
8
|
+
// would be wrong) without leaving an untracked directory in every checkout.
|
|
7
9
|
// Resolved lazily (a function, not a const) so it always reflects the current
|
|
8
|
-
// working directory
|
|
9
|
-
|
|
10
|
+
// working directory, and so tests can chdir into a scratch dir. The first touch
|
|
11
|
+
// migrates any ./.mini-agent/sessions left by an older build.
|
|
12
|
+
const sessionsDir = () => {
|
|
13
|
+
migrateLegacyState();
|
|
14
|
+
return projectSessionsDir();
|
|
15
|
+
};
|
|
10
16
|
const KEEP_SESSIONS = 20; // how many session files survive pruning
|
|
11
17
|
// Where a session's file lives — absolute, so it can be handed to hooks.
|
|
12
18
|
export function sessionPath(id) {
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from "node:fs"; // moving legacy state, creating the home bucket
|
|
2
|
+
import os from "node:os"; // the home directory root
|
|
3
|
+
import path from "node:path"; // joining and resolving paths
|
|
4
|
+
// Per-project state that is history rather than configuration — session
|
|
5
|
+
// transcripts, long-term memory, and the local event/trace logs — lives OUTSIDE
|
|
6
|
+
// the checkout, one bucket per working directory, mirroring how Claude Code
|
|
7
|
+
// keeps ~/.claude/projects/<slug>. A repo you open should not grow an untracked
|
|
8
|
+
// state directory just because an agent ran in it; configuration and tooling
|
|
9
|
+
// (settings, skills, task board, team mailboxes, cron state) stay project-local
|
|
10
|
+
// where they belong to the code.
|
|
11
|
+
//
|
|
12
|
+
// The bucket is keyed by the absolute cwd with separators flattened, so
|
|
13
|
+
// /Users/me/proj becomes -Users-me-proj, the same escaping Claude Code uses.
|
|
14
|
+
export const projectsRoot = () => process.env.MINI_AGENT_PROJECTS_DIR || path.join(os.homedir(), ".config", "mini-agent", "projects");
|
|
15
|
+
// One directory name per working directory. Windows drive colons are flattened
|
|
16
|
+
// too, so the name is portable and cannot escape the root. Symlinks are resolved
|
|
17
|
+
// first, so one project reached as /tmp/x and /private/tmp/x keeps ONE bucket
|
|
18
|
+
// instead of two that each hold half the history.
|
|
19
|
+
export function projectSlug(cwd = process.cwd()) {
|
|
20
|
+
let abs = path.resolve(cwd);
|
|
21
|
+
try {
|
|
22
|
+
abs = fs.realpathSync(abs);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* a path that does not exist yet still gets a stable slug */
|
|
26
|
+
}
|
|
27
|
+
return abs.replace(/[\\/:]/g, "-");
|
|
28
|
+
}
|
|
29
|
+
export function projectStateDir(cwd = process.cwd()) {
|
|
30
|
+
return path.join(projectsRoot(), projectSlug(cwd));
|
|
31
|
+
}
|
|
32
|
+
export function projectSessionsDir(cwd = process.cwd()) {
|
|
33
|
+
return path.join(projectStateDir(cwd), "sessions");
|
|
34
|
+
}
|
|
35
|
+
export function projectMemoryPath(cwd = process.cwd()) {
|
|
36
|
+
return path.join(projectStateDir(cwd), "MEMORY.md");
|
|
37
|
+
}
|
|
38
|
+
// Any other per-project log or file that should live beside sessions and memory
|
|
39
|
+
// rather than inside the checkout (telemetry, review-debug traces).
|
|
40
|
+
export function projectStateFile(name, cwd = process.cwd()) {
|
|
41
|
+
return path.join(projectStateDir(cwd), name);
|
|
42
|
+
}
|
|
43
|
+
// Move a path only when the destination is absent, so a second run is a no-op
|
|
44
|
+
// and a newly written home file is never clobbered by an old checkout copy.
|
|
45
|
+
// rename works on one filesystem; cp+rm covers the home-on-another-volume case.
|
|
46
|
+
function moveIfAbsent(from, to) {
|
|
47
|
+
if (fs.existsSync(to) || !fs.existsSync(from))
|
|
48
|
+
return;
|
|
49
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
50
|
+
try {
|
|
51
|
+
fs.renameSync(from, to);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
fs.cpSync(from, to, { recursive: true });
|
|
55
|
+
fs.rmSync(from, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
let migrated = false; // once per process; the check is idempotent anyway
|
|
59
|
+
// Older builds wrote sessions and memory into ./.mini-agent. Move them into the
|
|
60
|
+
// home bucket the first time anything touches the new location, so an upgrade
|
|
61
|
+
// keeps its history and the checkout ends up clean. Never throws: a failed
|
|
62
|
+
// migration must not break the CLI, it just leaves the old files in place.
|
|
63
|
+
export function migrateLegacyState(cwd = process.cwd()) {
|
|
64
|
+
if (migrated)
|
|
65
|
+
return;
|
|
66
|
+
migrated = true;
|
|
67
|
+
try {
|
|
68
|
+
moveIfAbsent(path.resolve(cwd, ".mini-agent", "sessions"), projectSessionsDir(cwd));
|
|
69
|
+
moveIfAbsent(path.resolve(cwd, ".mini-agent", "MEMORY.md"), projectMemoryPath(cwd));
|
|
70
|
+
// Logs are per-project too, and telemetry is usually the biggest file left
|
|
71
|
+
// behind by an older checkout.
|
|
72
|
+
moveIfAbsent(path.resolve(cwd, ".mini-agent", "telemetry.jsonl"), projectStateFile("telemetry.jsonl", cwd));
|
|
73
|
+
moveIfAbsent(path.resolve(cwd, ".mini-agent", "review-debug.jsonl"), projectStateFile("review-debug.jsonl", cwd));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* leave the legacy files where they are */
|
|
77
|
+
}
|
|
78
|
+
}
|
package/dist/telemetry.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import fs from "node:fs"; // the event log lives on disk
|
|
2
2
|
import path from "node:path"; // path joining
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
import { migrateLegacyState, projectStateFile } from "./state.js"; // one bucket per cwd, kept out of the checkout
|
|
4
|
+
// LOCAL-ONLY telemetry. Events go to the project's home bucket
|
|
5
|
+
// (~/.config/mini-agent/projects/<cwd-slug>/telemetry.jsonl) and NOWHERE else —
|
|
6
|
+
// nothing leaves the machine, ever. It exists because "no data" means every
|
|
7
|
+
// optimization is a guess: which errors actually happen, how often compaction
|
|
8
|
+
// fires, which tools get denied. Opt out entirely with MINI_AGENT_NO_TELEMETRY=1.
|
|
8
9
|
//
|
|
9
10
|
// Naming follows the unified-prefix convention (agent_<domain>_<action>) so
|
|
10
11
|
// the log stays greppable: `grep agent_api_error telemetry.jsonl | wc -l`.
|
|
11
|
-
|
|
12
|
+
// Resolved per call so a chdir is honoured, and so the first write migrates an
|
|
13
|
+
// older build's ./.mini-agent/telemetry.jsonl.
|
|
14
|
+
const telemetryFile = () => {
|
|
15
|
+
migrateLegacyState();
|
|
16
|
+
return projectStateFile("telemetry.jsonl");
|
|
17
|
+
};
|
|
12
18
|
let sessionId = null; // set by initTelemetry; emit() is a no-op before that
|
|
13
19
|
const tallies = new Map(); // per-session event counts, for /stats
|
|
14
20
|
// Arm telemetry for this session. Tests and the eval harness never call this,
|
|
@@ -25,8 +31,9 @@ export function emit(event, fields = {}) {
|
|
|
25
31
|
if (!sessionId)
|
|
26
32
|
return; // disarmed — counters only, no file
|
|
27
33
|
try {
|
|
28
|
-
|
|
29
|
-
fs.
|
|
34
|
+
const file = telemetryFile();
|
|
35
|
+
fs.mkdirSync(path.dirname(file), { recursive: true }); // ensure the directory
|
|
36
|
+
fs.appendFileSync(file, JSON.stringify({ ts: new Date().toISOString(), session: sessionId, event, ...fields }) + "\n"); // one JSON object per line
|
|
30
37
|
}
|
|
31
38
|
catch {
|
|
32
39
|
/* a full disk or a read-only checkout is not our problem to escalate */
|
|
@@ -39,5 +46,5 @@ export function statsReport() {
|
|
|
39
46
|
const lines = [...tallies.entries()]
|
|
40
47
|
.sort((a, b) => b[1] - a[1]) // busiest first
|
|
41
48
|
.map(([k, n]) => ` ${String(n).padStart(5)} ${k}`); // aligned counts
|
|
42
|
-
return ["events this session:", ...lines, `\nfull log: ${
|
|
49
|
+
return ["events this session:", ...lines, `\nfull log: ${telemetryFile()} (local only — nothing leaves this machine)`].join("\n");
|
|
43
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-from-zero",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mini-agent": "dist/agent.js"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"auto:report": "tsx eval/auto-report.ts",
|
|
18
18
|
"build": "tsc -p tsconfig.build.json",
|
|
19
19
|
"prepare": "npm run build",
|
|
20
|
-
"test": "export MINI_AGENT_REVIEW_DEBUG=0 && 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/auto.test.ts && tsx tests/auto-context.test.ts && tsx tests/auto-providers.test.ts && tsx tests/auto-cascade.test.ts && tsx tests/auto-rules.test.ts && tsx tests/auto-rule-cascade.test.ts && tsx tests/auto-debug.test.ts && tsx tests/auto-report.test.ts && tsx tests/auto-clarification.test.ts && tsx tests/memory.test.ts && tsx tests/cost.test.ts && tsx tests/cli.test.ts && tsx tests/print.test.ts && tsx tests/status.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/mcp-oauth.test.ts && tsx tests/changes.test.ts && tsx tests/mentions.test.ts && tsx tests/session.test.ts && tsx tests/title.test.ts && tsx tests/ui.test.ts && tsx tests/activity.test.ts && tsx tests/follow-up.test.ts && tsx tests/follow-up-ui.test.tsx && tsx tests/agents.test.ts && tsx tests/images.test.ts && tsx tests/approval.test.ts && tsx tests/form.test.ts && tsx tests/model.test.ts && tsx tests/reasoning.test.ts && tsx tests/effort.test.ts && tsx tests/completion.test.ts && tsx tests/pasted-text.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/peers.test.ts && tsx tests/suggestion.test.ts && tsx tests/board.test.ts && tsx tests/drop.test.ts && tsx tests/editline.test.ts && tsx tests/cron.test.ts && tsx tests/goal.test.ts"
|
|
20
|
+
"test": "export MINI_AGENT_REVIEW_DEBUG=0 && 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/auto.test.ts && tsx tests/auto-context.test.ts && tsx tests/auto-providers.test.ts && tsx tests/auto-cascade.test.ts && tsx tests/auto-rules.test.ts && tsx tests/auto-rule-cascade.test.ts && tsx tests/auto-debug.test.ts && tsx tests/auto-report.test.ts && tsx tests/auto-clarification.test.ts && tsx tests/memory.test.ts && tsx tests/cost.test.ts && tsx tests/cli.test.ts && tsx tests/print.test.ts && tsx tests/status.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/mcp-oauth.test.ts && tsx tests/changes.test.ts && tsx tests/mentions.test.ts && tsx tests/state.test.ts && tsx tests/session.test.ts && tsx tests/title.test.ts && tsx tests/ui.test.ts && tsx tests/activity.test.ts && tsx tests/follow-up.test.ts && tsx tests/follow-up-ui.test.tsx && tsx tests/agents.test.ts && tsx tests/images.test.ts && tsx tests/approval.test.ts && tsx tests/form.test.ts && tsx tests/model.test.ts && tsx tests/reasoning.test.ts && tsx tests/effort.test.ts && tsx tests/completion.test.ts && tsx tests/pasted-text.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/peers.test.ts && tsx tests/suggestion.test.ts && tsx tests/board.test.ts && tsx tests/drop.test.ts && tsx tests/editline.test.ts && tsx tests/cron.test.ts && tsx tests/goal.test.ts"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=20.6"
|