teapot-coding-agent 0.37.91 → 0.37.93
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
CHANGED
|
@@ -7,7 +7,6 @@ import { auditPrompt, parseAuditVerdict } from "./goal-audit.js";
|
|
|
7
7
|
import { createCatalog } from "./tool-catalog.js";
|
|
8
8
|
import { resetToolCatalog, executeTool, toolSpecs, currentSkills, safeJoin, isContextOverflow, isRequestNodeLimit, hasRunningBgShells, hasRunningBgShellsFor, killBgShells } from "./tools.js";
|
|
9
9
|
import { effortChoosable } from "./llm.js";
|
|
10
|
-
import { sessionTitleFrom } from "../session-title.js";
|
|
11
10
|
import { mcp } from "./mcp-registry.js";
|
|
12
11
|
import { flattenToolName } from "./mcp-registry.js";
|
|
13
12
|
import { metaToolSpecs } from "./meta-tools.js";
|
|
@@ -298,10 +297,6 @@ export class Agent {
|
|
|
298
297
|
skillsStamp = "";
|
|
299
298
|
lastCompactSummary;
|
|
300
299
|
compactRules = [];
|
|
301
|
-
setSessionTitle(title) {
|
|
302
|
-
this.sessionTitle = title?.trim() || undefined;
|
|
303
|
-
}
|
|
304
|
-
sessionTitle;
|
|
305
300
|
overflowRecoveries = 0;
|
|
306
301
|
consecutiveCompactFailures = 0;
|
|
307
302
|
memoryDigestText() {
|
|
@@ -747,9 +742,6 @@ export class Agent {
|
|
|
747
742
|
const last = lineage[lineage.length - 1];
|
|
748
743
|
const msgs = await rebuildMessagesFrom(lineage, this.imagesDir);
|
|
749
744
|
for (const e of own) {
|
|
750
|
-
if (this.sessionTitle === undefined && e.type === "prompt" && e.data?.source === "user") {
|
|
751
|
-
this.sessionTitle = sessionTitleFrom(e.data?.text) ?? undefined;
|
|
752
|
-
}
|
|
753
745
|
if (e.type === "state") {
|
|
754
746
|
const d = e.data;
|
|
755
747
|
if (d.detail === "llm turn start")
|
|
@@ -1059,6 +1051,16 @@ export class Agent {
|
|
|
1059
1051
|
});
|
|
1060
1052
|
}
|
|
1061
1053
|
snapshot() {
|
|
1054
|
+
/**
|
|
1055
|
+
* The session's name comes from ONE place — `config.sessionTitles`, via the resolver Master hands
|
|
1056
|
+
* down. The agent used to keep its own copy, seeded at creation and never updated when the map
|
|
1057
|
+
* learned the real title, so the panel showed the internal id while `config.json` had the name
|
|
1058
|
+
* ("config.jsonにチャットタイトルが生成されているのに表示が内部IDになってる").
|
|
1059
|
+
*
|
|
1060
|
+
* A copy that has to be PUSHED is a copy that goes stale; deriving it means there is nothing to
|
|
1061
|
+
* push. The session id is the fallback so the panel is never blank.
|
|
1062
|
+
*/
|
|
1063
|
+
const title = this.opts.sessionTitleOf?.(this.currentSession) || this.currentSession;
|
|
1062
1064
|
return {
|
|
1063
1065
|
id: this.opts.id,
|
|
1064
1066
|
status: this.status === "idle" && this.isLive() ? "waiting" : this.status,
|
|
@@ -1067,7 +1069,7 @@ export class Agent {
|
|
|
1067
1069
|
workspace: this.workspace,
|
|
1068
1070
|
workspaceMissing: this.isGhost,
|
|
1069
1071
|
session: this.currentSession,
|
|
1070
|
-
...(
|
|
1072
|
+
...(title ? { sessionTitle: title } : {}),
|
|
1071
1073
|
branch: this.currentBranch,
|
|
1072
1074
|
goal: {
|
|
1073
1075
|
status: this.goal.status,
|
|
@@ -1155,7 +1157,7 @@ export class Agent {
|
|
|
1155
1157
|
})
|
|
1156
1158
|
.then(() => {
|
|
1157
1159
|
if (source === "user")
|
|
1158
|
-
this.opts.onFirstUserPrompt?.(this.currentSession);
|
|
1160
|
+
this.opts.onFirstUserPrompt?.(this.currentSession, text);
|
|
1159
1161
|
})
|
|
1160
1162
|
.then(() => bus.emit("update", { kind: "agent-update", agentId: this.opts.id }))
|
|
1161
1163
|
.catch(() => { });
|
package/dist/master.js
CHANGED
|
@@ -282,32 +282,52 @@ export class Master {
|
|
|
282
282
|
this.configDirty = true;
|
|
283
283
|
return;
|
|
284
284
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
285
|
+
/**
|
|
286
|
+
* ONE list of the fields `config.json` mirrors from `this.config`.
|
|
287
|
+
*
|
|
288
|
+
* This used to be ad-hoc lines, and it was wrong in two ways that both read as "the setting does not
|
|
289
|
+
* work":
|
|
290
|
+
*
|
|
291
|
+
* - `progressIntervalMs` / `progressMinChars` were written only when `raw` did not already have
|
|
292
|
+
* them (`=== undefined`), so the FIRST save persisted and every later change was dropped —
|
|
293
|
+
* reported as "progress interval (min) が機能してなくない? 少なくともconfig.jsonには書き込まれて
|
|
294
|
+
* ない";
|
|
295
|
+
* - `contextTokenBudget`, `contextWindowTokens`, `maxSpawnDepth`, `maxTurnsPerRound`, `onError`
|
|
296
|
+
* and `retryDelayMs` were never written at all, so those runtime controls could not persist
|
|
297
|
+
* either.
|
|
298
|
+
*
|
|
299
|
+
* Undefined DELETES the key rather than leaving the old value lying in the file, so clearing a
|
|
300
|
+
* control in the UI actually clears it on disk.
|
|
301
|
+
*/
|
|
302
|
+
const mirrored = [
|
|
303
|
+
"agents",
|
|
304
|
+
"providers",
|
|
305
|
+
"defaultProvider",
|
|
306
|
+
"tasks",
|
|
307
|
+
"mcpServers",
|
|
308
|
+
"sessionTitles",
|
|
309
|
+
"customThemes",
|
|
310
|
+
"password",
|
|
311
|
+
"themePrefs",
|
|
312
|
+
"resumeAfterUpdate",
|
|
313
|
+
"shortContext",
|
|
314
|
+
"pluginsDir",
|
|
315
|
+
"progressIntervalMs",
|
|
316
|
+
"progressMinChars",
|
|
317
|
+
"contextTokenBudget",
|
|
318
|
+
"contextWindowTokens",
|
|
319
|
+
"maxSpawnDepth",
|
|
320
|
+
"maxTurnsPerRound",
|
|
321
|
+
"onError",
|
|
322
|
+
"retryDelayMs",
|
|
323
|
+
];
|
|
324
|
+
for (const key of mirrored) {
|
|
325
|
+
const value = this.config[key];
|
|
326
|
+
if (value === undefined)
|
|
327
|
+
delete this.raw[key];
|
|
328
|
+
else
|
|
329
|
+
this.raw[key] = value;
|
|
330
|
+
}
|
|
311
331
|
this.writeConfigAtomic(JSON.stringify(this.raw, null, 2) + "\n");
|
|
312
332
|
}
|
|
313
333
|
deferConfigWrites = false;
|
|
@@ -421,16 +441,13 @@ export class Master {
|
|
|
421
441
|
this.config.contextWindowTokens =
|
|
422
442
|
(patch.contextWindowTokens ?? 0) > 0 ? patch.contextWindowTokens : undefined;
|
|
423
443
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
else
|
|
432
|
-
this.raw[k] = v;
|
|
433
|
-
}
|
|
444
|
+
/**
|
|
445
|
+
* No `this.raw[...] = v` loop here — `saveConfig()` owns the mirror now (see its `mirrored` list).
|
|
446
|
+
*
|
|
447
|
+
* There used to be one, covering six of these keys and not the other two, which is exactly how the
|
|
448
|
+
* runtime controls ended up in different states: `contextTokenBudget` persisted, `progressIntervalMs`
|
|
449
|
+
* did not. One owner, one rule — and it runs once, at the end, after `tasks` is updated too.
|
|
450
|
+
*/
|
|
434
451
|
if (patch.tasks) {
|
|
435
452
|
this.config.tasks = patch.tasks;
|
|
436
453
|
this.tasks = patch.tasks.map((t) => ({
|
|
@@ -631,9 +648,11 @@ export class Master {
|
|
|
631
648
|
const agent = new Agent({
|
|
632
649
|
id: ac.id,
|
|
633
650
|
workspace: resolveWorkspace(ac.workspace, path.dirname(this.configPath)),
|
|
634
|
-
onFirstUserPrompt: (sessionId) => {
|
|
635
|
-
void this.ensureSessionTitle(ac.id, sessionId);
|
|
651
|
+
onFirstUserPrompt: (sessionId, firstText) => {
|
|
652
|
+
void this.ensureSessionTitle(ac.id, sessionId, firstText);
|
|
636
653
|
},
|
|
654
|
+
// ONE owner for a session's name: this map. The agent derives it on every snapshot.
|
|
655
|
+
sessionTitleOf: (sessionId) => this.readSessionTitle(sessionId) ?? undefined,
|
|
637
656
|
llm,
|
|
638
657
|
sessionDir,
|
|
639
658
|
// boot must touch ONLY config.json and skills; this agent's directory is opened when it is used
|
|
@@ -723,27 +742,13 @@ export class Master {
|
|
|
723
742
|
}
|
|
724
743
|
await agent.init();
|
|
725
744
|
/**
|
|
726
|
-
*
|
|
745
|
+
* No title is seeded, and nothing is derived here.
|
|
727
746
|
*
|
|
728
|
-
*
|
|
729
|
-
* `
|
|
730
|
-
*
|
|
747
|
+
* The agent derives its name from `config.sessionTitles` on every `snapshot()` (see
|
|
748
|
+
* `sessionTitleOf`), so there is no copy to seed. Deriving a missing one here would read
|
|
749
|
+
* `chat.jsonl`, and boot must not open session directories — "起動中に読んでいいのはconfig.jsonと
|
|
750
|
+
* skillsだけ / セッションディレクトリはセッションがロードされるまで開かない".
|
|
731
751
|
*/
|
|
732
|
-
const seedTitle = this.readSessionTitle(path.basename(sessionDir));
|
|
733
|
-
if (seedTitle)
|
|
734
|
-
agent.setSessionTitle(seedTitle);
|
|
735
|
-
else {
|
|
736
|
-
/**
|
|
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
|
-
* セッションがロードされるまで開かない".
|
|
740
|
-
*
|
|
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).
|
|
743
|
-
*/
|
|
744
|
-
const id = path.basename(sessionDir);
|
|
745
|
-
agent.setSessionTitle(this.readSessionTitle(id) ?? id);
|
|
746
|
-
}
|
|
747
752
|
this.agents.set(ac.id, agent);
|
|
748
753
|
if (opts.persist) {
|
|
749
754
|
this.config.agents.push(ac);
|
|
@@ -1088,6 +1093,17 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1088
1093
|
}
|
|
1089
1094
|
this.config.sessionTitles ??= {};
|
|
1090
1095
|
this.config.sessionTitles[sessionId] = title;
|
|
1096
|
+
/**
|
|
1097
|
+
* …and the LIVE agent has to be told, or the panel keeps showing the id.
|
|
1098
|
+
*
|
|
1099
|
+
* `addAgent` seeds an agent's title from this map when it is created, falling back to the session id.
|
|
1100
|
+
* A NEW session gets its entry written later (on the first prompt, from `chat.jsonl`), and nothing
|
|
1101
|
+
* pushed it back — so the agent still held the id and the panel displayed it while `config.json` had
|
|
1102
|
+
* the real name. Reported as "config.jsonにチャットタイトルが生成されているのに表示が内部IDになってる".
|
|
1103
|
+
*
|
|
1104
|
+
* Pushing here rather than in each caller means every writer (the derivation, a rename) is covered by
|
|
1105
|
+
* one rule: the map changed, so the agent that is showing it changes too.
|
|
1106
|
+
*/
|
|
1091
1107
|
this.saveConfig();
|
|
1092
1108
|
}
|
|
1093
1109
|
/**
|
|
@@ -1097,14 +1113,23 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1097
1113
|
* per session on every call (MEASURED earlier: 0.87s for one archive toggle). Writing it down once,
|
|
1098
1114
|
* where the session lives, keeps that scan off the hot path and keeps the answer in ONE place.
|
|
1099
1115
|
*/
|
|
1100
|
-
async ensureSessionTitle(agentId, sessionId) {
|
|
1116
|
+
async ensureSessionTitle(agentId, sessionId, firstText) {
|
|
1101
1117
|
const a = this.config.agents.find((x) => x.id === agentId);
|
|
1102
1118
|
if (!a || a.session !== sessionId)
|
|
1103
1119
|
return;
|
|
1120
|
+
// the map is the master: once it has a name, nothing may replace it
|
|
1104
1121
|
if (this.readSessionTitle(sessionId))
|
|
1105
1122
|
return;
|
|
1106
|
-
|
|
1107
|
-
|
|
1123
|
+
/**
|
|
1124
|
+
* A session is named from its FIRST USER MESSAGE — that is the whole point of this hook, and it must
|
|
1125
|
+
* keep working for a session created at runtime.
|
|
1126
|
+
*
|
|
1127
|
+
* The text arrives from the agent, which already has it, so this needs no read of `chat.jsonl`. The
|
|
1128
|
+
* log is only a fallback for a caller that could not supply it.
|
|
1129
|
+
*/
|
|
1130
|
+
const fromText = sessionTitleFrom(firstText) ?? undefined;
|
|
1131
|
+
const derived = fromText ?? this.sessionTitleOf(path.join(this.sessionsRoot(), sessionId)) ?? sessionId;
|
|
1132
|
+
this.writeSessionTitle(sessionId, derived);
|
|
1108
1133
|
}
|
|
1109
1134
|
sessionTitleOf(dirPath) {
|
|
1110
1135
|
let fd;
|
|
@@ -1338,7 +1363,7 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1338
1363
|
*/
|
|
1339
1364
|
this.writeSessionTitle(sessionId, clean);
|
|
1340
1365
|
delete a.sessionTitle;
|
|
1341
|
-
|
|
1366
|
+
// no field to set: the panel derives the name from the map, so the bus nudge is the whole update
|
|
1342
1367
|
bus.emit("update", { kind: "agent-update", agentId });
|
|
1343
1368
|
return { title: clean };
|
|
1344
1369
|
}
|