teapot-coding-agent 0.37.76 → 0.37.77
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 +42 -30
- package/dist/migrations/session-titles.js +22 -24
- package/package.json +1 -1
- package/public/assets/{index-CzLw5lqw.js → index-Betlb5cN.js} +4 -4
- package/public/assets/{index-CzLw5lqw.js.map → index-Betlb5cN.js.map} +1 -1
- package/public/assets/{shiki-C0hgNXdD.js → shiki-nwPgnhe1.js} +2 -2
- package/public/assets/{shiki-C0hgNXdD.js.map → shiki-nwPgnhe1.js.map} +1 -1
- package/public/index.html +1 -1
package/dist/master.js
CHANGED
|
@@ -287,6 +287,10 @@ export class Master {
|
|
|
287
287
|
this.raw.defaultProvider = this.config.defaultProvider;
|
|
288
288
|
this.raw.tasks = this.config.tasks;
|
|
289
289
|
this.raw.mcpServers = this.config.mcpServers;
|
|
290
|
+
if (this.config.sessionTitles !== undefined)
|
|
291
|
+
this.raw.sessionTitles = this.config.sessionTitles;
|
|
292
|
+
else
|
|
293
|
+
delete this.raw.sessionTitles;
|
|
290
294
|
if (this.config.customThemes !== undefined)
|
|
291
295
|
this.raw.customThemes = this.config.customThemes;
|
|
292
296
|
if (this.config.password !== undefined)
|
|
@@ -719,11 +723,23 @@ export class Master {
|
|
|
719
723
|
*
|
|
720
724
|
* `snapshot().sessionTitle` comes from the agent's own field, and that was only ever set by
|
|
721
725
|
* `renameSession` — so after a restart the UI fell back to the auto-generated name even though the
|
|
722
|
-
* title was on disk. The title
|
|
726
|
+
* title was on disk. The title comes from `config.sessionTitles`, which is the one copy.
|
|
723
727
|
*/
|
|
724
728
|
const seedTitle = this.readSessionTitle(path.basename(sessionDir));
|
|
725
729
|
if (seedTitle)
|
|
726
730
|
agent.setSessionTitle(seedTitle);
|
|
731
|
+
else {
|
|
732
|
+
/**
|
|
733
|
+
* Derive a missing title at BOOT, once, and write it down.
|
|
734
|
+
*
|
|
735
|
+
* Waiting for the session's next user prompt means an existing session shows no name until the
|
|
736
|
+
* operator types into it, and every left-panel read has to fall back. Doing it here keeps the
|
|
737
|
+
* derivation to one pass per session and off the read path.
|
|
738
|
+
*/
|
|
739
|
+
const id = path.basename(sessionDir);
|
|
740
|
+
this.writeSessionTitle(id, this.sessionTitleOf(sessionDir) ?? id);
|
|
741
|
+
agent.setSessionTitle(this.readSessionTitle(id) ?? id);
|
|
742
|
+
}
|
|
727
743
|
this.agents.set(ac.id, agent);
|
|
728
744
|
if (opts.persist) {
|
|
729
745
|
this.config.agents.push(ac);
|
|
@@ -1046,33 +1062,29 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1046
1062
|
}
|
|
1047
1063
|
/** The session's own name, from its manifest. One place, so it cannot go stale in another. */
|
|
1048
1064
|
readSessionTitle(sessionId) {
|
|
1049
|
-
if (!sessionId
|
|
1065
|
+
if (!sessionId)
|
|
1050
1066
|
return null;
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
const t = typeof raw.title === "string" ? raw.title.trim() : "";
|
|
1054
|
-
return t ? t : null;
|
|
1055
|
-
}
|
|
1056
|
-
catch {
|
|
1057
|
-
return null;
|
|
1058
|
-
}
|
|
1067
|
+
const t = this.config.sessionTitles?.[sessionId];
|
|
1068
|
+
return typeof t === "string" && t.trim() ? t.trim() : null;
|
|
1059
1069
|
}
|
|
1060
1070
|
writeSessionTitle(sessionId, title) {
|
|
1061
|
-
if (!sessionId
|
|
1071
|
+
if (!sessionId)
|
|
1072
|
+
return;
|
|
1073
|
+
/**
|
|
1074
|
+
* Never write when the data dir was overridden by `TEAPOT_DATA_DIR`.
|
|
1075
|
+
*
|
|
1076
|
+
* In that mode the sessions being resolved are NOT the ones this config describes, so recording
|
|
1077
|
+
* anything about them — a pointer, or now a title — corrupts a config the operator did not ask to
|
|
1078
|
+
* change (#143). The title is still derived in memory so the UI shows something.
|
|
1079
|
+
*/
|
|
1080
|
+
if (this.sessionsReadOnly) {
|
|
1081
|
+
this.config.sessionTitles ??= {};
|
|
1082
|
+
this.config.sessionTitles[sessionId] = title;
|
|
1062
1083
|
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
1084
|
}
|
|
1085
|
+
this.config.sessionTitles ??= {};
|
|
1086
|
+
this.config.sessionTitles[sessionId] = title;
|
|
1087
|
+
this.saveConfig();
|
|
1076
1088
|
}
|
|
1077
1089
|
/**
|
|
1078
1090
|
* Derive a session's title ONCE, into the session's own manifest.
|
|
@@ -1263,7 +1275,8 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1263
1275
|
size = st.size;
|
|
1264
1276
|
}
|
|
1265
1277
|
catch { }
|
|
1266
|
-
|
|
1278
|
+
// from the config map — no second per-directory read
|
|
1279
|
+
const title = owner ? this.readSessionTitle(d) ?? undefined : undefined;
|
|
1267
1280
|
out.push({ id: d, agentId: owner, mtimeMs: m, sizeBytes: size, ...(title ? { title } : {}) });
|
|
1268
1281
|
}
|
|
1269
1282
|
return out.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
@@ -1312,16 +1325,15 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1312
1325
|
if (!clean)
|
|
1313
1326
|
throw new Error("a session title cannot be empty");
|
|
1314
1327
|
/**
|
|
1315
|
-
* The title
|
|
1328
|
+
* The title is keyed by SESSION, not by agent.
|
|
1316
1329
|
*
|
|
1317
|
-
*
|
|
1318
|
-
* which is
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1330
|
+
* As `agents[].sessionTitle` it was per AGENT, so `recordSession()` had to delete it whenever the
|
|
1331
|
+
* agent moved session — which is what reverted a rename — and a session no agent points at could not
|
|
1332
|
+
* keep a name at all. The map fixes both AND keeps the left panel to one config read: storing it in
|
|
1333
|
+
* `<session>/session.json` instead was MEASURED at 13.2ms for 838 sessions versus 1.2ms here.
|
|
1321
1334
|
*/
|
|
1322
1335
|
this.writeSessionTitle(sessionId, clean);
|
|
1323
1336
|
delete a.sessionTitle;
|
|
1324
|
-
this.saveConfig();
|
|
1325
1337
|
this.agents.get(agentId)?.setSessionTitle(clean);
|
|
1326
1338
|
bus.emit("update", { kind: "agent-update", agentId });
|
|
1327
1339
|
return { title: clean };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync,
|
|
1
|
+
import { closeSync, openSync, readSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export const sessionTitles = {
|
|
4
4
|
order: 30,
|
|
@@ -7,6 +7,7 @@ export const sessionTitles = {
|
|
|
7
7
|
run(ctx) {
|
|
8
8
|
const raw = ctx.raw();
|
|
9
9
|
const agents = Array.isArray(raw.agents) ? raw.agents : [];
|
|
10
|
+
const isRecord = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
10
11
|
let changed = false;
|
|
11
12
|
/**
|
|
12
13
|
* Move the title out of `config.json` and into the session's own `session.json`.
|
|
@@ -15,32 +16,27 @@ export const sessionTitles = {
|
|
|
15
16
|
* and the copy that went stale. A title already present wins over a derived one, so an operator's
|
|
16
17
|
* rename is never replaced by the auto-generated name.
|
|
17
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* Move `agents[].sessionTitle` into the `sessionTitles` MAP, keyed by session id.
|
|
21
|
+
*
|
|
22
|
+
* Per-agent was the wrong key: an agent moves between sessions, so the field had to be cleared on
|
|
23
|
+
* every switch (which reverted a rename), and a session no agent points at could not keep a name.
|
|
24
|
+
* A map also keeps the left panel to ONE config read instead of a `session.json` per directory —
|
|
25
|
+
* MEASURED: 838 sessions = 13.2ms of directory reads vs 1.2ms for the config file.
|
|
26
|
+
*/
|
|
27
|
+
const titles = (raw.sessionTitles = isRecord(raw.sessionTitles) ? raw.sessionTitles : {});
|
|
18
28
|
for (const a of agents) {
|
|
19
29
|
const session = typeof a.session === "string" ? a.session : "";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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`);
|
|
30
|
+
const legacy = typeof a.sessionTitle === "string" ? a.sessionTitle.trim() : "";
|
|
31
|
+
if (session && !session.includes("/") && !session.includes("..")) {
|
|
32
|
+
const already = typeof titles[session] === "string" ? String(titles[session]).trim() : "";
|
|
33
|
+
if (!already) {
|
|
34
|
+
const derived = legacy ||
|
|
35
|
+
firstUserPromptText(path.join(ctx.dataDir, "sessions", session, "chat.jsonl")) ||
|
|
36
|
+
session;
|
|
37
|
+
titles[session] = derived;
|
|
38
|
+
changed = true;
|
|
40
39
|
}
|
|
41
|
-
catch {
|
|
42
|
-
}
|
|
43
|
-
changed = true;
|
|
44
40
|
}
|
|
45
41
|
/**
|
|
46
42
|
* Only report a change when one happened: a migration that claims success every boot rewrites
|
|
@@ -51,6 +47,8 @@ export const sessionTitles = {
|
|
|
51
47
|
changed = true;
|
|
52
48
|
}
|
|
53
49
|
}
|
|
50
|
+
if (Object.keys(titles).length === 0 && raw.sessionTitles !== undefined)
|
|
51
|
+
delete raw.sessionTitles;
|
|
54
52
|
if (changed)
|
|
55
53
|
ctx.touch();
|
|
56
54
|
return changed;
|