teapot-coding-agent 0.37.74 → 0.37.76
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/master.js +63 -6
- package/dist/migrations/session-titles.js +37 -6
- package/package.json +1 -1
- package/public/assets/{index-ckvhmau6.js → index-CzLw5lqw.js} +10 -10
- package/public/assets/index-CzLw5lqw.js.map +1 -0
- package/public/assets/{shiki-K-CpH9ll.js → shiki-C0hgNXdD.js} +2 -2
- package/public/assets/{shiki-K-CpH9ll.js.map → shiki-C0hgNXdD.js.map} +1 -1
- package/public/index.html +1 -1
- package/public/assets/index-ckvhmau6.js.map +0 -1
package/dist/master.js
CHANGED
|
@@ -714,6 +714,16 @@ export class Master {
|
|
|
714
714
|
agent.toolCtx.subAgents = hooks;
|
|
715
715
|
}
|
|
716
716
|
await agent.init();
|
|
717
|
+
/**
|
|
718
|
+
* Seed the title from the SESSION, so a restart shows the name the operator gave it.
|
|
719
|
+
*
|
|
720
|
+
* `snapshot().sessionTitle` comes from the agent's own field, and that was only ever set by
|
|
721
|
+
* `renameSession` — so after a restart the UI fell back to the auto-generated name even though the
|
|
722
|
+
* title was on disk. The title is read from `session.json`, which is the one copy.
|
|
723
|
+
*/
|
|
724
|
+
const seedTitle = this.readSessionTitle(path.basename(sessionDir));
|
|
725
|
+
if (seedTitle)
|
|
726
|
+
agent.setSessionTitle(seedTitle);
|
|
717
727
|
this.agents.set(ac.id, agent);
|
|
718
728
|
if (opts.persist) {
|
|
719
729
|
this.config.agents.push(ac);
|
|
@@ -730,6 +740,9 @@ export class Master {
|
|
|
730
740
|
if (!entry)
|
|
731
741
|
return;
|
|
732
742
|
entry.session = sessionId;
|
|
743
|
+
// The title is NOT deleted here: it now lives in the session's own manifest, so moving to another
|
|
744
|
+
// session simply means reading a different one. (Deleting it was how a rename reverted to the
|
|
745
|
+
// auto-generated name on the next switch.)
|
|
733
746
|
delete entry.sessionTitle;
|
|
734
747
|
delete ac.sessionTitle;
|
|
735
748
|
if (this.sessionsReadOnly)
|
|
@@ -1029,18 +1042,53 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1029
1042
|
const a = this.config.agents.find((x) => x.id === agentId);
|
|
1030
1043
|
if (!a || a.session !== sessionId)
|
|
1031
1044
|
return undefined;
|
|
1032
|
-
|
|
1033
|
-
return t ? t : undefined;
|
|
1045
|
+
return this.readSessionTitle(sessionId) ?? undefined;
|
|
1034
1046
|
}
|
|
1047
|
+
/** The session's own name, from its manifest. One place, so it cannot go stale in another. */
|
|
1048
|
+
readSessionTitle(sessionId) {
|
|
1049
|
+
if (!sessionId || sessionId.includes("/") || sessionId.includes(".."))
|
|
1050
|
+
return null;
|
|
1051
|
+
try {
|
|
1052
|
+
const raw = JSON.parse(readFileSync(path.join(this.sessionsRoot(), sessionId, "session.json"), "utf8"));
|
|
1053
|
+
const t = typeof raw.title === "string" ? raw.title.trim() : "";
|
|
1054
|
+
return t ? t : null;
|
|
1055
|
+
}
|
|
1056
|
+
catch {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
writeSessionTitle(sessionId, title) {
|
|
1061
|
+
if (!sessionId || sessionId.includes("/") || sessionId.includes(".."))
|
|
1062
|
+
return;
|
|
1063
|
+
const file = path.join(this.sessionsRoot(), sessionId, "session.json");
|
|
1064
|
+
let raw = { v: 1 };
|
|
1065
|
+
try {
|
|
1066
|
+
raw = JSON.parse(readFileSync(file, "utf8"));
|
|
1067
|
+
}
|
|
1068
|
+
catch {
|
|
1069
|
+
}
|
|
1070
|
+
raw.title = title;
|
|
1071
|
+
try {
|
|
1072
|
+
writeFileSync(file, `${JSON.stringify(raw, null, 2)}\n`);
|
|
1073
|
+
}
|
|
1074
|
+
catch {
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Derive a session's title ONCE, into the session's own manifest.
|
|
1079
|
+
*
|
|
1080
|
+
* Deriving it at read time would be worse: `sessionsOf`/`archivedAgents` would scan a `chat.jsonl`
|
|
1081
|
+
* per session on every call (MEASURED earlier: 0.87s for one archive toggle). Writing it down once,
|
|
1082
|
+
* where the session lives, keeps that scan off the hot path and keeps the answer in ONE place.
|
|
1083
|
+
*/
|
|
1035
1084
|
async ensureSessionTitle(agentId, sessionId) {
|
|
1036
1085
|
const a = this.config.agents.find((x) => x.id === agentId);
|
|
1037
1086
|
if (!a || a.session !== sessionId)
|
|
1038
1087
|
return;
|
|
1039
|
-
if (
|
|
1088
|
+
if (this.readSessionTitle(sessionId))
|
|
1040
1089
|
return;
|
|
1041
1090
|
const dir = path.join(this.sessionsRoot(), sessionId);
|
|
1042
|
-
|
|
1043
|
-
this.saveConfig();
|
|
1091
|
+
this.writeSessionTitle(sessionId, this.sessionTitleOf(dir) ?? sessionId);
|
|
1044
1092
|
}
|
|
1045
1093
|
sessionTitleOf(dirPath) {
|
|
1046
1094
|
let fd;
|
|
@@ -1263,7 +1311,16 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1263
1311
|
const clean = title.replace(/\s+/g, " ").trim().slice(0, 80);
|
|
1264
1312
|
if (!clean)
|
|
1265
1313
|
throw new Error("a session title cannot be empty");
|
|
1266
|
-
|
|
1314
|
+
/**
|
|
1315
|
+
* The title belongs to the SESSION, so it is stored with the session.
|
|
1316
|
+
*
|
|
1317
|
+
* It used to live in `config.json` — a second copy of a fact the session directory already owns,
|
|
1318
|
+
* which is how it went stale: `recordSession()` deletes `sessionTitle` whenever the agent moves
|
|
1319
|
+
* session, so a rename followed by any session switch silently reverted the name to the
|
|
1320
|
+
* auto-generated one, and `config.json` was the only place that ever knew better.
|
|
1321
|
+
*/
|
|
1322
|
+
this.writeSessionTitle(sessionId, clean);
|
|
1323
|
+
delete a.sessionTitle;
|
|
1267
1324
|
this.saveConfig();
|
|
1268
1325
|
this.agents.get(agentId)?.setSessionTitle(clean);
|
|
1269
1326
|
bus.emit("update", { kind: "agent-update", agentId });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, openSync, readSync } from "node:fs";
|
|
1
|
+
import { closeSync, mkdirSync, openSync, readFileSync, readSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export const sessionTitles = {
|
|
4
4
|
order: 30,
|
|
@@ -8,17 +8,48 @@ export const sessionTitles = {
|
|
|
8
8
|
const raw = ctx.raw();
|
|
9
9
|
const agents = Array.isArray(raw.agents) ? raw.agents : [];
|
|
10
10
|
let changed = false;
|
|
11
|
+
/**
|
|
12
|
+
* Move the title out of `config.json` and into the session's own `session.json`.
|
|
13
|
+
*
|
|
14
|
+
* It used to be stored in `config.json` — a second copy of something the session directory owns,
|
|
15
|
+
* and the copy that went stale. A title already present wins over a derived one, so an operator's
|
|
16
|
+
* rename is never replaced by the auto-generated name.
|
|
17
|
+
*/
|
|
11
18
|
for (const a of agents) {
|
|
12
19
|
const session = typeof a.session === "string" ? a.session : "";
|
|
13
20
|
if (!session)
|
|
14
21
|
continue;
|
|
15
|
-
if (typeof a.sessionTitle === "string" && a.sessionTitle.trim())
|
|
16
|
-
continue;
|
|
17
22
|
if (session.includes("/") || session.includes(".."))
|
|
18
23
|
continue;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
const dir = path.join(ctx.dataDir, "sessions", session);
|
|
25
|
+
const file = path.join(dir, "session.json");
|
|
26
|
+
let manifest = { v: 1 };
|
|
27
|
+
try {
|
|
28
|
+
manifest = JSON.parse(readFileSync(file, "utf8"));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
}
|
|
32
|
+
const already = typeof manifest.title === "string" ? manifest.title.trim() : "";
|
|
33
|
+
if (!already) {
|
|
34
|
+
const fromConfig = typeof a.sessionTitle === "string" ? a.sessionTitle.trim() : "";
|
|
35
|
+
const derived = fromConfig || firstUserPromptText(path.join(dir, "chat.jsonl")) || session;
|
|
36
|
+
manifest.title = derived;
|
|
37
|
+
try {
|
|
38
|
+
mkdirSync(dir, { recursive: true });
|
|
39
|
+
writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
}
|
|
43
|
+
changed = true;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Only report a change when one happened: a migration that claims success every boot rewrites
|
|
47
|
+
* the config file forever, and re-running must be a genuine no-op (the suite asserts it).
|
|
48
|
+
*/
|
|
49
|
+
if (a.sessionTitle !== undefined) {
|
|
50
|
+
delete a.sessionTitle;
|
|
51
|
+
changed = true;
|
|
52
|
+
}
|
|
22
53
|
}
|
|
23
54
|
if (changed)
|
|
24
55
|
ctx.touch();
|