teapot-coding-agent 0.37.86 → 0.37.88
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/agent.js +53 -8
- package/dist/log/events.js +31 -0
- package/dist/master.js +7 -5
- package/dist/migrations/session-titles.js +10 -4
- package/dist/server/api.js +6 -1
- package/package.json +1 -1
- package/public/assets/{index-BhGRupOY.js → index-DHLnAV8S.js} +25 -25
- package/public/assets/index-DHLnAV8S.js.map +1 -0
- package/public/assets/{index-CGGvhbf1.css → index-T72qGQ56.css} +1 -1
- package/public/assets/{shiki-Dy4KrO5C.js → shiki-5__nbF2Q.js} +2 -2
- package/public/assets/{shiki-Dy4KrO5C.js.map → shiki-5__nbF2Q.js.map} +1 -1
- package/public/index.html +2 -2
- package/public/assets/index-BhGRupOY.js.map +0 -1
package/dist/agent/agent.js
CHANGED
|
@@ -508,26 +508,71 @@ export class Agent {
|
|
|
508
508
|
this.opts.llm = llm;
|
|
509
509
|
}
|
|
510
510
|
async init() {
|
|
511
|
-
|
|
511
|
+
/**
|
|
512
|
+
* NO `log.load()` here.
|
|
513
|
+
*
|
|
514
|
+
* It read up to 512KB of `chat.jsonl` for EVERY agent at boot — 445 of them on the operator's
|
|
515
|
+
* machine, MEASURED at 3.5s of a 7.8s start plus the parsed events on the heap (rss 1GB / heap
|
|
516
|
+
* 800MB at start-up). An agent that is never used needs none of it: the first `append` awaits
|
|
517
|
+
* `ensureLoaded()`, and `ensureReady()` reads it before restoring a session.
|
|
518
|
+
*/
|
|
519
|
+
/**
|
|
520
|
+
* …and no SESSION-DIRECTORY reads at boot either.
|
|
521
|
+
*
|
|
522
|
+
* This block read goal.json, todo.md and stat()ed the workspace for every agent, four calls each —
|
|
523
|
+
* MEASURED 1,780 `readFile`s costing 4.5s of the same boot. They are now done in `ensureReady()`,
|
|
524
|
+
* i.e. when the session is actually loaded or used. `goal` and `todo` already have empty defaults
|
|
525
|
+
* (declared above), so a panel that reads them before the load shows "nothing yet" rather than
|
|
526
|
+
* waiting — which is the right trade for a boot that touches only config.json and skills.
|
|
527
|
+
*/
|
|
528
|
+
if (!this.opts.deferSessionReads)
|
|
529
|
+
await this.loadSessionState();
|
|
530
|
+
if (this.opts.restoreSession) {
|
|
531
|
+
this.status = "stopped";
|
|
532
|
+
this.statusReason = "session not loaded — select it or send a prompt";
|
|
533
|
+
bus.emit("update", { kind: "agent-update", agentId: this.opts.id });
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Everything in the session directory that the panel needs: goal, todo, workspace presence.
|
|
538
|
+
*
|
|
539
|
+
* Idempotent, so `init()` and `ensureReady()` can both ask without reading twice.
|
|
540
|
+
*/
|
|
541
|
+
sessionStateLoaded = false;
|
|
542
|
+
async loadSessionState() {
|
|
543
|
+
if (this.sessionStateLoaded)
|
|
544
|
+
return;
|
|
545
|
+
this.sessionStateLoaded = true;
|
|
512
546
|
await this.migrateMemoryToAgentScope();
|
|
513
547
|
this.workspaceMissing = !(await fs
|
|
514
548
|
.stat(this.workspace)
|
|
515
549
|
.then(() => true)
|
|
516
550
|
.catch(() => false));
|
|
517
551
|
await this.migrateGoalFromWorkspace();
|
|
518
|
-
|
|
519
|
-
this.goal = stored ?? { text: "", status: "active", updatedAt: new Date().toISOString() };
|
|
552
|
+
this.goal = (await this.readGoalStore()) ?? this.goal;
|
|
520
553
|
this.todo = await fs.readFile(this.todoFile, "utf8").catch(() => "");
|
|
521
|
-
if (this.opts.restoreSession) {
|
|
522
|
-
this.status = "stopped";
|
|
523
|
-
this.statusReason = "session not loaded — select it or send a prompt";
|
|
524
|
-
bus.emit("update", { kind: "agent-update", agentId: this.opts.id });
|
|
525
|
-
}
|
|
526
554
|
}
|
|
527
555
|
ensureReady() {
|
|
528
556
|
if (!this.readyPromise) {
|
|
529
557
|
this.readyPromise = (async () => {
|
|
558
|
+
/**
|
|
559
|
+
* The session directory is opened HERE, on demand — see the note in `init()`.
|
|
560
|
+
*
|
|
561
|
+
* When it was DEFERRED (a booted agent, and every spawned child) this must not hold up the
|
|
562
|
+
* "running" transition that follows: the panel's goal/todo are not needed to run a turn, and
|
|
563
|
+
* awaiting them here delayed `setStatus("running")` past the point where a parent's
|
|
564
|
+
* `wait_children` inspects its children — it then reported "all sub-agents already settled"
|
|
565
|
+
* while the child was about to work. So it loads in the background and announces itself.
|
|
566
|
+
*/
|
|
567
|
+
const state = this
|
|
568
|
+
.loadSessionState()
|
|
569
|
+
.then(() => bus.emit("update", { kind: "agent-update", agentId: this.opts.id }))
|
|
570
|
+
.catch(() => { });
|
|
571
|
+
if (!this.opts.deferSessionReads)
|
|
572
|
+
await state;
|
|
530
573
|
if (this.opts.restoreSession) {
|
|
574
|
+
// the restore reads the log itself, but `seq`/`lastByBranch` must be known for later appends
|
|
575
|
+
await this.log.ensureLoaded();
|
|
531
576
|
await this.restoreFromLog();
|
|
532
577
|
if (this.status === "stopped")
|
|
533
578
|
this.setStatus("idle", "session loaded");
|
package/dist/log/events.js
CHANGED
|
@@ -17,6 +17,23 @@ export class EventLog {
|
|
|
17
17
|
this.agentId = agentId;
|
|
18
18
|
}
|
|
19
19
|
static LOAD_TAIL_BYTES = 512 * 1024;
|
|
20
|
+
/**
|
|
21
|
+
* Read the log TAIL to learn `seq` and the last event per branch.
|
|
22
|
+
*
|
|
23
|
+
* LAZY, and memoised. `Agent.init()` ran it for every agent at boot, which is the startup cost the
|
|
24
|
+
* operator reported: 445 agents × up to 512KB of `chat.jsonl` parsed before the first request —
|
|
25
|
+
* MEASURED 3.5s of a 7.8s boot, plus the parsed events on the heap ("起動直後のメモリバースト",
|
|
26
|
+
* rss 1GB / heap 800MB).
|
|
27
|
+
*
|
|
28
|
+
* Nothing needs it until something WRITES (`append` increments `seq`) or asks for a branch's last
|
|
29
|
+
* event, so it now happens then. The rule from the operator: "起動中に読んでいいのはconfig.jsonと
|
|
30
|
+
* skillsだけ / セッションディレクトリはセッションがロードされるまで開かない".
|
|
31
|
+
*/
|
|
32
|
+
ensureLoaded() {
|
|
33
|
+
this.loadPromise ??= this.load();
|
|
34
|
+
return this.loadPromise;
|
|
35
|
+
}
|
|
36
|
+
loadPromise;
|
|
20
37
|
async load() {
|
|
21
38
|
mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
22
39
|
try {
|
|
@@ -103,7 +120,21 @@ export class EventLog {
|
|
|
103
120
|
await new Promise((res) => s.end(res)).catch(() => {
|
|
104
121
|
});
|
|
105
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Queue a write, reading the log first if it has not been read yet.
|
|
125
|
+
*
|
|
126
|
+
* NOT `async` on purpose. An `async` function returns a NEW promise, so `void this.log.append(…)`
|
|
127
|
+
* — which is how most call sites fire a log write — would leave that wrapper promise unhandled when
|
|
128
|
+
* the write fails, and the failure surfaced as an `unhandledRejection` instead of the caller's
|
|
129
|
+
* problem. Returning the inner promise (which already has handlers attached below) keeps the old
|
|
130
|
+
* behaviour: awaiting still throws, ignoring stays silent.
|
|
131
|
+
*/
|
|
106
132
|
append(type, session, branch, data) {
|
|
133
|
+
const p = this.ensureLoaded().then(() => this.writeEvent(type, session, branch, data));
|
|
134
|
+
void p.catch(() => { });
|
|
135
|
+
return p;
|
|
136
|
+
}
|
|
137
|
+
async writeEvent(type, session, branch, data) {
|
|
107
138
|
const evt = {
|
|
108
139
|
v: 1,
|
|
109
140
|
id: `e${++this.seq}`,
|
package/dist/master.js
CHANGED
|
@@ -636,6 +636,8 @@ export class Master {
|
|
|
636
636
|
},
|
|
637
637
|
llm,
|
|
638
638
|
sessionDir,
|
|
639
|
+
// boot must touch ONLY config.json and skills; this agent's directory is opened when it is used
|
|
640
|
+
deferSessionReads: true,
|
|
639
641
|
progressIntervalMs: this.config.progressIntervalMs,
|
|
640
642
|
...(this.config.progressMinChars ? { progressMinChars: this.config.progressMinChars } : {}),
|
|
641
643
|
...(this.config.maxTurnsPerRound !== undefined ? { maxTurnsPerRound: this.config.maxTurnsPerRound } : {}),
|
|
@@ -732,14 +734,14 @@ export class Master {
|
|
|
732
734
|
agent.setSessionTitle(seedTitle);
|
|
733
735
|
else {
|
|
734
736
|
/**
|
|
735
|
-
*
|
|
737
|
+
* NOT derived here. Deriving a missing title reads `chat.jsonl`, and boot must not open session
|
|
738
|
+
* directories at all — "起動中に読んでいいのはconfig.jsonとskillsだけ / セッションディレクトリは
|
|
739
|
+
* セッションがロードされるまで開かない".
|
|
736
740
|
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
* derivation to one pass per session and off the read path.
|
|
741
|
+
* The session ID is a perfectly good fallback for the panel, and the real derivation happens
|
|
742
|
+
* when that session is actually loaded (`ensureSessionTitle`, via the agent's first prompt).
|
|
740
743
|
*/
|
|
741
744
|
const id = path.basename(sessionDir);
|
|
742
|
-
this.writeSessionTitle(id, this.sessionTitleOf(sessionDir) ?? id);
|
|
743
745
|
agent.setSessionTitle(this.readSessionTitle(id) ?? id);
|
|
744
746
|
}
|
|
745
747
|
this.agents.set(ac.id, agent);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { closeSync, openSync, readSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
export const sessionTitles = {
|
|
4
3
|
order: 30,
|
|
5
4
|
id: "agents-session-title",
|
|
@@ -31,9 +30,16 @@ export const sessionTitles = {
|
|
|
31
30
|
if (session && !session.includes("/") && !session.includes("..")) {
|
|
32
31
|
const already = typeof titles[session] === "string" ? String(titles[session]).trim() : "";
|
|
33
32
|
if (!already) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
/**
|
|
34
|
+
* NOT derived from `chat.jsonl` here.
|
|
35
|
+
*
|
|
36
|
+
* A migration runs for EVERY agent at boot, and reading each session's log is exactly what the
|
|
37
|
+
* operator ruled out — "起動中に読んでいいのはconfig.jsonとskillsだけ / セッションディレクトリは
|
|
38
|
+
* セッションがロードされるまで開かない". A legacy `sessionTitle` is used when present; otherwise
|
|
39
|
+
* the session id is a perfectly good name, and a real title is derived once when that session is
|
|
40
|
+
* actually opened (`Master.ensureSessionTitle`).
|
|
41
|
+
*/
|
|
42
|
+
const derived = legacy || session;
|
|
37
43
|
titles[session] = derived;
|
|
38
44
|
changed = true;
|
|
39
45
|
}
|
package/dist/server/api.js
CHANGED
|
@@ -1401,7 +1401,12 @@ export function buildApp(master) {
|
|
|
1401
1401
|
webp: "image/webp",
|
|
1402
1402
|
avif: "image/avif",
|
|
1403
1403
|
};
|
|
1404
|
-
|
|
1404
|
+
/**
|
|
1405
|
+
* 320 exists for the ONE place a wallpaper is drawn small: the background-image picker is 260px wide,
|
|
1406
|
+
* so a 640 floor made the browser fetch 2.5x the pixels it could show ("260pxなのに最小が640w").
|
|
1407
|
+
* The theme cards (~200px) benefit for the same reason.
|
|
1408
|
+
*/
|
|
1409
|
+
const ALLOWED_WIDTHS = [320, 640, 1280, 1920, 2560, 3840];
|
|
1405
1410
|
app.get("/api/assets/meta", (c) => c.json({ widths: ALLOWED_WIDTHS }));
|
|
1406
1411
|
/**
|
|
1407
1412
|
* Image bytes a session logged, served so the TIMELINE can show them.
|