agent-from-zero 0.34.1 → 0.34.2
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/dist/agent.js +8 -1
- package/dist/ink/app.js +9 -1
- package/dist/ink/setup.js +3 -0
- package/dist/title.js +20 -0
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -9,7 +9,7 @@ import { buildSystemMessage } from "./prompt.js"; // the constitution + optional
|
|
|
9
9
|
import { forgetFilesExcept, registerExternalTool } from "./tools.js"; // file-state reset + tool registration
|
|
10
10
|
import { compactHistory, estimateHistoryTokens, COMPACT_AT } from "./context.js"; // for the manual /compact command
|
|
11
11
|
import { newSessionId, saveSession, latestSession, listSessions, loadSession, setSessionTitle } from "./session.js"; // conversation persistence (project-local) + the /resume picker
|
|
12
|
-
import { generateSessionTitle } from "./title.js"; // concise session name, generated after the first message
|
|
12
|
+
import { generateSessionTitle, setTerminalTitle } from "./title.js"; // concise session name, generated after the first message + the terminal tab that shows it
|
|
13
13
|
import { initTelemetry, emit, statsReport } from "./telemetry.js"; // local-only event log + /stats
|
|
14
14
|
import { runHooks } from "./hooks.js"; // SessionStart lifecycle hook
|
|
15
15
|
import { connectMcpServers, listMcpServers, mcpActionsFor, runMcpAction } from "./mcp.js"; // external tool servers (MCP) + /mcp
|
|
@@ -139,18 +139,22 @@ async function main() {
|
|
|
139
139
|
// -r / --resume picks up the most recent one. The system message is NOT
|
|
140
140
|
// restored from disk — it is rebuilt fresh, because AGENT.md may have changed.
|
|
141
141
|
let sessionId = newSessionId(); // this session's identity (and file name)
|
|
142
|
+
let initialTitle; // only a resumed session has one already
|
|
142
143
|
const sessionStartedAt = Date.now(); // for the status line's elapsed clock
|
|
143
144
|
if (argv.includes("-r") || argv.includes("--resume")) {
|
|
144
145
|
const prev = latestSession(); // newest snapshot in this directory, if any
|
|
145
146
|
if (prev) {
|
|
146
147
|
messages.push(...prev.messages); // the old conversation joins the fresh constitution
|
|
147
148
|
sessionId = prev.id; // keep appending to the same session file
|
|
149
|
+
initialTitle = prev.title;
|
|
148
150
|
console.log(chalk.dim(`(resumed session ${prev.id} — ${prev.messages.length} messages; files must be re-read before editing)`));
|
|
149
151
|
}
|
|
150
152
|
else {
|
|
151
153
|
console.log(chalk.dim("(no previous session here — starting fresh)")); // resume with nothing to resume is not an error
|
|
152
154
|
}
|
|
153
155
|
}
|
|
156
|
+
if (printTask === null)
|
|
157
|
+
setTerminalTitle(initialTitle); // name the terminal tab from the start (a one-shot -p run leaves the tab alone)
|
|
154
158
|
initTelemetry(sessionId); // arm the local event log (no-op under MINI_AGENT_NO_TELEMETRY=1)
|
|
155
159
|
emit("agent_session_start", { mode: printTask !== null ? "print" : "repl" }); // how this session was started
|
|
156
160
|
// Connect to any configured MCP servers and register their tools. This must
|
|
@@ -524,6 +528,7 @@ async function main() {
|
|
|
524
528
|
sessionId = newSessionId(); // a fresh conversation is a fresh session file
|
|
525
529
|
titleAttempted = false; // a fresh session gets its own title on its first message
|
|
526
530
|
pendingTitle = undefined;
|
|
531
|
+
setTerminalTitle(undefined); // the tab no longer describes the old conversation
|
|
527
532
|
console.log(chalk.dim("(history cleared)")); // confirm the reset
|
|
528
533
|
return true;
|
|
529
534
|
case "/todos": {
|
|
@@ -647,6 +652,7 @@ async function main() {
|
|
|
647
652
|
sessionId = chosen.id; // keep appending to the resumed session's file
|
|
648
653
|
titleAttempted = false; // a resumed session without a title gets one on its next message
|
|
649
654
|
pendingTitle = chosen.title;
|
|
655
|
+
setTerminalTitle(chosen.title ?? sessions[idx].title.slice(0, 40)); // the tab follows the resumed session (raw prompt if never titled)
|
|
650
656
|
forgetFilesExcept([]); // a resumed conversation must re-read files before editing them
|
|
651
657
|
clearUndo(); // the previous session's writes are not ours to undo
|
|
652
658
|
clearTodos(); // the resumed task starts without the old session's stale plan
|
|
@@ -782,6 +788,7 @@ async function main() {
|
|
|
782
788
|
if (title) {
|
|
783
789
|
pendingTitle = title;
|
|
784
790
|
setSessionTitle(sessionId, title);
|
|
791
|
+
setTerminalTitle(title); // and rename the terminal tab, like Claude Code does
|
|
785
792
|
}
|
|
786
793
|
})
|
|
787
794
|
.catch(() => { });
|
package/dist/ink/app.js
CHANGED
|
@@ -18,7 +18,7 @@ import { expandMentions } from "../mentions.js"; // @file mentions → attach fi
|
|
|
18
18
|
import { normalizeDroppedPaths } from "../drop.js"; // drag-and-drop a file → its absolute path in the input
|
|
19
19
|
import { cronItemsPending, consumeCronQueue, cronTriggerContent } from "../cron.js"; // cron scheduler (Day s14): fire scheduled jobs autonomously while idle
|
|
20
20
|
import { newSessionId, saveSession, listSessions, loadSession, setSessionTitle } from "../session.js";
|
|
21
|
-
import { generateSessionTitle } from "../title.js"; // concise session name, generated after the first message
|
|
21
|
+
import { generateSessionTitle, setTerminalTitle } from "../title.js"; // concise session name, generated after the first message + the terminal tab that shows it
|
|
22
22
|
import { isPlanMode, setPlanMode } from "../permissions.js";
|
|
23
23
|
import { findSkill, skillInstructions } from "../skills.js";
|
|
24
24
|
import { extractMemories } from "../memory.js";
|
|
@@ -125,6 +125,11 @@ export function App({ session, runTurn }) {
|
|
|
125
125
|
const id = setInterval(() => setTick((t) => t + 1), 1000);
|
|
126
126
|
return () => clearInterval(id);
|
|
127
127
|
}, []);
|
|
128
|
+
// Name the terminal tab from the first frame: the resumed session's title,
|
|
129
|
+
// or just "mini-agent" until the first message earns a real one.
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
setTerminalTitle(session.initialTitle);
|
|
132
|
+
}, [session.initialTitle]);
|
|
128
133
|
// Open a one-of-N menu and run `onChoose` with the picked index (-1 = cancel).
|
|
129
134
|
const openSelect = (header, options, onChoose) => {
|
|
130
135
|
setMenuSel(0);
|
|
@@ -242,6 +247,7 @@ export function App({ session, runTurn }) {
|
|
|
242
247
|
if (title) {
|
|
243
248
|
pendingTitle.current = title;
|
|
244
249
|
setSessionTitle(sessionId, title);
|
|
250
|
+
setTerminalTitle(title); // and rename the terminal tab, like Claude Code does
|
|
245
251
|
}
|
|
246
252
|
})
|
|
247
253
|
.catch(() => { });
|
|
@@ -372,6 +378,7 @@ export function App({ session, runTurn }) {
|
|
|
372
378
|
setSessionId(newSessionId());
|
|
373
379
|
titleAttempted.current = false; // a fresh session gets its own title on its first message
|
|
374
380
|
pendingTitle.current = undefined;
|
|
381
|
+
setTerminalTitle(undefined); // the tab no longer describes the old conversation
|
|
375
382
|
note(chalk.dim("(history cleared)"));
|
|
376
383
|
return true;
|
|
377
384
|
case "/plan":
|
|
@@ -422,6 +429,7 @@ export function App({ session, runTurn }) {
|
|
|
422
429
|
setSessionId(chosen.id);
|
|
423
430
|
titleAttempted.current = false; // a resumed session without a title gets one on its next message
|
|
424
431
|
pendingTitle.current = chosen.title;
|
|
432
|
+
setTerminalTitle(chosen.title ?? sessions[i].title.slice(0, 40)); // the tab follows the resumed session (raw prompt if never titled)
|
|
425
433
|
forgetFilesExcept([]);
|
|
426
434
|
clearUndo();
|
|
427
435
|
clearTodos();
|
package/dist/ink/setup.js
CHANGED
|
@@ -34,12 +34,14 @@ export async function buildInkSession(opts = {}) {
|
|
|
34
34
|
const messages = [{ role: "system", content: systemMessage }];
|
|
35
35
|
const notices = [];
|
|
36
36
|
let sessionId = newSessionId();
|
|
37
|
+
let initialTitle; // only a resumed session has one already
|
|
37
38
|
const startedAt = Date.now();
|
|
38
39
|
if (opts.resume) {
|
|
39
40
|
const prev = latestSession();
|
|
40
41
|
if (prev) {
|
|
41
42
|
messages.push(...prev.messages);
|
|
42
43
|
sessionId = prev.id; // keep appending to the same file
|
|
44
|
+
initialTitle = prev.title;
|
|
43
45
|
notices.push(`(resumed session ${prev.id} — ${prev.messages.length} messages; files must be re-read before editing)`);
|
|
44
46
|
}
|
|
45
47
|
else {
|
|
@@ -97,6 +99,7 @@ export async function buildInkSession(opts = {}) {
|
|
|
97
99
|
messages,
|
|
98
100
|
systemMessage,
|
|
99
101
|
initialSessionId: sessionId,
|
|
102
|
+
initialTitle,
|
|
100
103
|
startedAt,
|
|
101
104
|
costMeter,
|
|
102
105
|
skills,
|
package/dist/title.js
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
// producing a 3-7 word sentence-case title, stored as the session's aiTitle).
|
|
4
4
|
// It runs once, fire-and-forget, after the first real user message, so it never
|
|
5
5
|
// blocks the turn and a failure silently falls back to the raw first prompt.
|
|
6
|
+
//
|
|
7
|
+
// The title has two consumers: the /resume picker (read from the session file)
|
|
8
|
+
// and the TERMINAL TAB — the same OSC escape Claude Code uses to rename its tab
|
|
9
|
+
// to "✳ <title>", so a row of sessions is tellable apart in the terminal's
|
|
10
|
+
// sidebar/tabs without opening each one.
|
|
11
|
+
const TAB_PREFIX = "✳ "; // the glyph Claude Code puts before its tab titles — familiar to anyone who has both open
|
|
12
|
+
// The OSC 0 sequence that sets a terminal's window/tab title. Pure so the
|
|
13
|
+
// escape framing is unit-testable; newlines and control bytes are stripped so a
|
|
14
|
+
// malicious title can't inject a second escape.
|
|
15
|
+
export function terminalTitleSequence(title) {
|
|
16
|
+
const safe = title.replace(/[\x00-\x1f\x7f]/g, " ").replace(/\s+/g, " ").trim();
|
|
17
|
+
return `\x1b]0;${safe}\x07`;
|
|
18
|
+
}
|
|
19
|
+
// Rename the terminal tab. Silent when stdout isn't a terminal (pipes, tests,
|
|
20
|
+
// print mode) — an escape sequence in a log file is just noise.
|
|
21
|
+
export function setTerminalTitle(title) {
|
|
22
|
+
if (!process.stdout.isTTY)
|
|
23
|
+
return;
|
|
24
|
+
process.stdout.write(terminalTitleSequence(title ? `${TAB_PREFIX}${title}` : "mini-agent"));
|
|
25
|
+
}
|
|
6
26
|
// The prompt is quoted inside <prompt> tags and the system message says so
|
|
7
27
|
// explicitly: sent bare, a chat-tuned model reads "有没有app能…?" as a question
|
|
8
28
|
// for IT and answers it — the "title" then becomes the first line of an answer.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-from-zero",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.2",
|
|
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": {
|