teapot-coding-agent 0.29.7 → 0.29.9
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/acp/adapter.js +26 -49
- package/dist/agent/agent.js +17 -18
- package/dist/agent/llm.js +29 -58
- package/dist/agent/skills.js +20 -31
- package/dist/agent/tools.js +9 -4
- package/dist/config-schema.js +4 -4
- package/dist/home.js +15 -27
- package/dist/index.js +31 -68
- package/dist/master.js +100 -67
- package/dist/model-meta.js +0 -6
- package/dist/scheduler/cron.js +5 -6
- package/dist/server/term-size.js +2 -3
- package/package.json +1 -1
- package/public/assets/{index-DPfeY2n-.js → index-zmavtYYT.js} +5 -5
- package/public/assets/index-zmavtYYT.js.map +1 -0
- package/public/assets/{shiki-BQcHFGv4.js → shiki-DhnjYQM7.js} +2 -2
- package/public/assets/{shiki-BQcHFGv4.js.map → shiki-DhnjYQM7.js.map} +1 -1
- package/public/index.html +1 -1
- package/public/assets/index-DPfeY2n-.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -37,15 +37,6 @@ async function waitForPortFree(port, host, timeoutMs) {
|
|
|
37
37
|
return false;
|
|
38
38
|
}
|
|
39
39
|
/* ---------- live-update handover: keeping the operator's terminal (#35) ---------- */
|
|
40
|
-
/**
|
|
41
|
-
* The pid of the process that replaced US, or null if we are the server.
|
|
42
|
-
*
|
|
43
|
-
* `Master.restartServer()` spawns the replacement and records its pid here, so
|
|
44
|
-
* the outgoing process can supervise its own successor instead of exiting and
|
|
45
|
-
* handing the shell a prompt. A field on the master rather than an env var: both
|
|
46
|
-
* processes are separate, but this value is written and read in the SAME
|
|
47
|
-
* process, so there is no environment for the two of them to disagree over.
|
|
48
|
-
*/
|
|
49
40
|
/** is `pid` still running? (EPERM means alive but not ours to signal) */
|
|
50
41
|
function pidAlive(pid) {
|
|
51
42
|
try {
|
|
@@ -60,16 +51,13 @@ function pidAlive(pid) {
|
|
|
60
51
|
* Watch the replacement on the operator's behalf.
|
|
61
52
|
*
|
|
62
53
|
* The terminal is still wired to THIS process, so their Ctrl-C arrives here
|
|
63
|
-
* first
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* 1. forward the signal, so the replacement actually stops; and
|
|
67
|
-
* 2. when the replacement does exit, leave too — otherwise the shell waits on
|
|
68
|
-
* a supervisor with nothing left to supervise and Ctrl-C looks broken.
|
|
54
|
+
* first: forward the signal so the replacement actually stops, and leave once
|
|
55
|
+
* it exits — otherwise the shell waits on a supervisor with nothing left to
|
|
56
|
+
* supervise and Ctrl-C looks broken.
|
|
69
57
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
58
|
+
* shutdown() cannot be reused for this: its `shuttingDown` latch is already set
|
|
59
|
+
* by the SIGTERM that started the handover, so a later Ctrl-C would be swallowed
|
|
60
|
+
* and this process would never exit.
|
|
73
61
|
*/
|
|
74
62
|
function supervise(pid) {
|
|
75
63
|
let signalled = false;
|
|
@@ -94,11 +82,9 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
94
82
|
/**
|
|
95
83
|
* Parse `teapot [--port N] [-p N] [--host addr] [--config file] [config.json]`.
|
|
96
84
|
*
|
|
97
|
-
*
|
|
98
|
-
* which
|
|
99
|
-
*
|
|
100
|
-
* regression here starts the wrong server on the wrong port with no error anyone
|
|
101
|
-
* would read.
|
|
85
|
+
* Exported so it can be tested: the parser decides which config file is read and
|
|
86
|
+
* which port is bound, so a regression here starts the wrong server on the wrong
|
|
87
|
+
* port with no error anyone would read.
|
|
102
88
|
*
|
|
103
89
|
* The bare-word rule keeps the FIRST positional as the config path: a later bare
|
|
104
90
|
* word is a stray argument, not an override, and silently replacing the config
|
|
@@ -160,18 +146,13 @@ async function main() {
|
|
|
160
146
|
if (!Number.isNaN(fromPid) && fromPid !== process.pid) {
|
|
161
147
|
console.log(`[teapot] restart: releasing port from old process ${fromPid}...`);
|
|
162
148
|
try {
|
|
163
|
-
// Flush the old process's agents and let it release the socket. It
|
|
164
|
-
// NOT wait for us
|
|
165
|
-
// lingers only long enough for its own HTTP response to flush, then exits.
|
|
149
|
+
// Flush the old process's agents and let it release the socket. It must
|
|
150
|
+
// NOT wait for us — that would deadlock against our bind below.
|
|
166
151
|
process.kill(fromPid, "SIGTERM");
|
|
167
152
|
}
|
|
168
153
|
catch {
|
|
169
154
|
/* already gone */
|
|
170
155
|
}
|
|
171
|
-
// Wait until the socket is genuinely free. Binding while the old process
|
|
172
|
-
// still holds it fails with EADDRINUSE, and because index.ts swallows
|
|
173
|
-
// uncaught exceptions that left two processes running while neither served
|
|
174
|
-
// anything (#35).
|
|
175
156
|
if (!(await waitForPortFree(config.port, config.host, 30_000)))
|
|
176
157
|
console.warn(`[teapot] warning: could not confirm ${config.host}:${config.port} is free — binding anyway`);
|
|
177
158
|
}
|
|
@@ -209,32 +190,22 @@ async function main() {
|
|
|
209
190
|
if (shuttingDown)
|
|
210
191
|
return;
|
|
211
192
|
shuttingDown = true;
|
|
212
|
-
// SUPERVISOR MODE. When a live update replaced us
|
|
213
|
-
// server
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
// to the replacement, and only leave once it is gone. The operator's
|
|
221
|
-
// terminal — and their Ctrl-C — follow the server across the restart.
|
|
222
|
-
//
|
|
223
|
-
// The replacement is the child we spawned ourselves, so we hold it directly:
|
|
224
|
-
// no pid passed through the environment, and no way for the two processes
|
|
225
|
-
// to disagree about who supervises whom.
|
|
193
|
+
// SUPERVISOR MODE. When a live update replaced us we are no longer the
|
|
194
|
+
// server, but we are still the process the SHELL waits on and the one that
|
|
195
|
+
// owns the terminal; exiting here dumped the operator back to the prompt
|
|
196
|
+
// mid-session while the new server kept running in the background (#35). So
|
|
197
|
+
// we keep the job slot, forward signals to the replacement, and leave only
|
|
198
|
+
// once it is gone. The replacement is the child we spawned ourselves, so we
|
|
199
|
+
// hold it directly — no pid through the environment for the two processes to
|
|
200
|
+
// disagree over.
|
|
226
201
|
if (master.replacementPid !== null) {
|
|
227
202
|
const replacement = master.replacementPid;
|
|
228
203
|
console.log(`[teapot] handing the terminal to the new server (pid ${replacement})...`);
|
|
229
|
-
// Release the port FIRST
|
|
230
|
-
// waiting for exactly this, and it will SIGTERM us again if we do not go
|
|
231
|
-
// — but the difference now is that we stop SERVING rather than exiting,
|
|
232
|
-
// so the shell's foreground job survives the handover.
|
|
204
|
+
// Release the port FIRST: the replacement is blocked in waitForPortFree
|
|
205
|
+
// waiting for exactly this, and it will SIGTERM us again if we do not go.
|
|
233
206
|
await server.close();
|
|
234
207
|
console.log("[teapot] port released; supervising the new server");
|
|
235
208
|
supervise(replacement);
|
|
236
|
-
// Stay attached until the replacement exits. The shell is still waiting
|
|
237
|
-
// on US, so leaving early is exactly the bug.
|
|
238
209
|
while (pidAlive(replacement))
|
|
239
210
|
await sleep(500);
|
|
240
211
|
console.log("[teapot] new server exited; releasing the terminal");
|
|
@@ -255,35 +226,27 @@ async function main() {
|
|
|
255
226
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
256
227
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
257
228
|
}
|
|
258
|
-
// Only when RUN, not when imported
|
|
259
|
-
// this file for `parseArgs` started a whole server
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
// and `node dist/index.js` both still take this branch.
|
|
229
|
+
// Only when RUN, not when imported: `void main()` at module scope meant importing
|
|
230
|
+
// this file for `parseArgs` started a whole server. `node
|
|
231
|
+
// --experimental-strip-types src/index.ts` and `node dist/index.js` both take
|
|
232
|
+
// this branch.
|
|
263
233
|
function isEntryPoint() {
|
|
264
234
|
const arg = process.argv[1];
|
|
265
235
|
if (!arg)
|
|
266
236
|
return false;
|
|
267
|
-
// `npm i -g teapot-coding-agent` puts a SYMLINK at node_modules/.bin/teapot
|
|
268
|
-
// npm invokes the target THROUGH it
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
// in silence. Reporting `teapot` did nothing and needing `node dist/index.js`
|
|
272
|
-
// was the only symptom. `realpathSync` is what resolves it.
|
|
237
|
+
// `npm i -g teapot-coding-agent` puts a SYMLINK at node_modules/.bin/teapot and
|
|
238
|
+
// npm invokes the target THROUGH it, so `process.argv[1]` is the symlink path,
|
|
239
|
+
// NOT the file it points at: the installed command then exited 0 in silence.
|
|
240
|
+
// `realpathSync` is what resolves it.
|
|
273
241
|
try {
|
|
274
242
|
const self = realpathSync(fileURLToPath(import.meta.url));
|
|
275
243
|
const entry = realpathSync(arg);
|
|
276
244
|
if (self === entry)
|
|
277
245
|
return true;
|
|
278
246
|
// `self === entry` is the ONLY correct test: it is the definition of being the
|
|
279
|
-
// entry point.
|
|
280
|
-
//
|
|
281
|
-
// for EVERY test file that imports this module, so importing it booted a real
|
|
247
|
+
// entry point. A suffix check such as `endsWith("/index.ts")` is true for
|
|
248
|
+
// EVERY test file that imports this module, so importing it booted a real
|
|
282
249
|
// server on port 7788 and `cli-args.test.ts` failed with EADDRINUSE.
|
|
283
|
-
//
|
|
284
|
-
// Keep a basename check as a safety net for a bundler that rewrites paths, but
|
|
285
|
-
// require BOTH that we are the module AND that nothing else is: an argv[1] that
|
|
286
|
-
// resolves to a DIFFERENT file is by definition not this module's entry.
|
|
287
250
|
return false;
|
|
288
251
|
}
|
|
289
252
|
catch {
|
package/dist/master.js
CHANGED
|
@@ -835,40 +835,19 @@ export class Master {
|
|
|
835
835
|
if (!parent)
|
|
836
836
|
return;
|
|
837
837
|
// the waiter re-checks whether ITS targets are all settled, so waking on
|
|
838
|
-
// every movement is correct and cheap
|
|
839
|
-
|
|
840
|
-
|
|
838
|
+
// every movement is correct and cheap. ANCESTORS too: a waiter's targets
|
|
839
|
+
// now span the whole subtree, so a grandchild's movement must reach it.
|
|
840
|
+
this.wakeAncestorWaiters(ac.parent);
|
|
841
|
+
// #130: report only a SETTLED child, never its birth. A freshly spawned
|
|
842
|
+
// child is `idle` before `start()` runs, so that first idle->running flip
|
|
843
|
+
// would tell the parent "@kid is now idle" the instant it existed.
|
|
844
|
+
// Requiring a prior `running` is what filters it out; the wake above is
|
|
845
|
+
// unconditional, so `wait_children` is unaffected.
|
|
841
846
|
//
|
|
842
|
-
//
|
|
843
|
-
//
|
|
844
|
-
//
|
|
845
|
-
//
|
|
846
|
-
// transitions after a spawn are ["idle","running","idle"], so the first is
|
|
847
|
-
// always the spurious one.
|
|
848
|
-
//
|
|
849
|
-
// So the message waits for a child that has actually STARTED. A real
|
|
850
|
-
// completion still reports (it goes running -> idle), and the wake above is
|
|
851
|
-
// unconditional, so `wait_children` is unaffected either way.
|
|
852
|
-
// #130: a spawned child is `idle` until start() runs, and that lazy-load
|
|
853
|
-
// flip is its FIRST transition — so requiring a prior `running` is what stops
|
|
854
|
-
// the parent being told the child settled before it began.
|
|
855
|
-
// #135: reworded, because the old line was wrong in two ways.
|
|
856
|
-
//
|
|
857
|
-
// "Continue your task, or finish() if the work is done" was addressed to the
|
|
858
|
-
// PARENT — but the parent did not spawn this child for its own sake, and
|
|
859
|
-
// when the child DID call finish() the parent already receives a proper
|
|
860
|
-
// "Sub-agent X finished. Final report: …" (the `finished. Final report:`
|
|
861
|
-
// prompt below). So the nudge
|
|
862
|
-
// restated as an instruction what the report already stated as fact, and
|
|
863
|
-
// invited the parent to `finish()` ITS OWN goal over a child's status.
|
|
864
|
-
//
|
|
865
|
-
// The new wording says what happened and what to do about it, and points at
|
|
866
|
-
// the report when there is one:
|
|
867
|
-
//
|
|
868
|
-
// child gave a summary -> "…returned without calling finish(); its final
|
|
869
|
-
// message follows." (the report arrives separately)
|
|
870
|
-
// child said nothing -> "…went idle without reporting. If that is
|
|
871
|
-
// unexpected, message it or re-spawn it."
|
|
847
|
+
// #135: the wording addresses the CHILD's outcome, not the parent's goal.
|
|
848
|
+
// "Continue your task, or finish()..." invited the parent to finish its own
|
|
849
|
+
// goal over a child's status; the child's report already states what
|
|
850
|
+
// happened, so this points at it instead.
|
|
872
851
|
if (this.reportedRunning.has(agentId) && SETTLED.has(status))
|
|
873
852
|
parent.enqueuePrompt(`[harness] Sub-agent @${agentId} is now ${status} without having called finish(), so it has not reported a result.` +
|
|
874
853
|
(status === "error"
|
|
@@ -887,11 +866,28 @@ export class Master {
|
|
|
887
866
|
const hooks = {
|
|
888
867
|
depth: myDepth,
|
|
889
868
|
spawn: (o) => this.spawnChildFor(self, o),
|
|
890
|
-
list: () =>
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
869
|
+
list: () =>
|
|
870
|
+
// DIRECT children are the ones this agent can address (stop/message are
|
|
871
|
+
// refused for anything deeper), but a descendant still working is
|
|
872
|
+
// invisible to the parent unless it is LISTED — and the parent then
|
|
873
|
+
// resumes over it. So the whole subtree is listed, each row naming its
|
|
874
|
+
// own `parent` and flagged `nested`, which is what tells the model who
|
|
875
|
+
// to address instead of guessing a level (the error `message_agent`
|
|
876
|
+
// raises names the owner for the same reason).
|
|
877
|
+
this.subtreeOf(ac.id).flatMap(({ id, depth, parent }) => {
|
|
878
|
+
const inst = this.agents.get(id);
|
|
879
|
+
if (!inst)
|
|
880
|
+
return []; // suspended: not addressable, so not listed
|
|
881
|
+
return [
|
|
882
|
+
{
|
|
883
|
+
id,
|
|
884
|
+
status: inst.status,
|
|
885
|
+
goal: inst.goal.text,
|
|
886
|
+
parent,
|
|
887
|
+
nested: depth > 1,
|
|
888
|
+
},
|
|
889
|
+
];
|
|
890
|
+
}),
|
|
895
891
|
stop: (ids) => this.stopChildrenFor(ac.id, ids),
|
|
896
892
|
message: (id, text) => this.messageChild(ac.id, id, text),
|
|
897
893
|
wait: (ids, ms) =>
|
|
@@ -959,6 +955,35 @@ export class Master {
|
|
|
959
955
|
}
|
|
960
956
|
return out;
|
|
961
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* Every configured descendant of `id`, at any depth, with its depth (1 = direct
|
|
960
|
+
* child) and its own parent id.
|
|
961
|
+
*
|
|
962
|
+
* A parent's work is not finished while anything it caused is still running,
|
|
963
|
+
* and that includes a sub-agent's sub-agent: C can delegate to G and return its
|
|
964
|
+
* own turn, leaving C idle while G works. Deciding from DIRECT children alone
|
|
965
|
+
* therefore let an ancestor report "all settled" over a live grandchild — the
|
|
966
|
+
* reported "it delegates further and the parent never knows".
|
|
967
|
+
*
|
|
968
|
+
* Returns configured ids (not instances) so a suspended descendant still counts
|
|
969
|
+
* as part of the subtree. `seen` guards a cycle in `parent`.
|
|
970
|
+
*/
|
|
971
|
+
subtreeOf(id, depth = 1, seen = new Set()) {
|
|
972
|
+
const out = [];
|
|
973
|
+
for (const a of this.config.agents) {
|
|
974
|
+
if (a.parent !== id)
|
|
975
|
+
continue;
|
|
976
|
+
if (seen.has(a.id))
|
|
977
|
+
continue;
|
|
978
|
+
seen.add(a.id);
|
|
979
|
+
out.push({ id: a.id, depth, parent: id }, ...this.subtreeOf(a.id, depth + 1, seen));
|
|
980
|
+
}
|
|
981
|
+
return out;
|
|
982
|
+
}
|
|
983
|
+
/** the ids of every descendant of `id`, at any depth */
|
|
984
|
+
descendantsOf(id, seen = new Set()) {
|
|
985
|
+
return this.subtreeOf(id, 1, seen).map((x) => x.id);
|
|
986
|
+
}
|
|
962
987
|
/**
|
|
963
988
|
* Create a sub-agent on behalf of `parent`. context "fork" writes a
|
|
964
989
|
* sub_fork header into the child log pointing at the parent's current
|
|
@@ -1098,9 +1123,12 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1098
1123
|
* while parked. Falls back to a timeout so waits can never hang forever.
|
|
1099
1124
|
*/
|
|
1100
1125
|
waitChildren(parentId, ids, timeoutMs, signal) {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1126
|
+
// The whole DESCENDANT subtree, not just direct children: C can delegate to G
|
|
1127
|
+
// and return its own turn, so C reads idle while G works. Waiting on C must
|
|
1128
|
+
// therefore mean "C and everything C caused" — otherwise an ancestor resumes
|
|
1129
|
+
// over live work it cannot see (see descendantsOf).
|
|
1130
|
+
const named = ids && ids.length ? ids : this.descendantsOf(parentId);
|
|
1131
|
+
const targets = [...new Set(named.flatMap((id) => [id, ...this.descendantsOf(id)]))].filter((id) => this.agents.has(id));
|
|
1104
1132
|
if (targets.length === 0)
|
|
1105
1133
|
return Promise.resolve({ note: "no live sub-agents to wait for" });
|
|
1106
1134
|
// isBusy(), not the raw status: parkForTool overwrites a parked child's
|
|
@@ -1158,6 +1186,28 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1158
1186
|
catch { /* ignore */ }
|
|
1159
1187
|
}
|
|
1160
1188
|
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Wake the waiters of EVERY ancestor of an agent, not just its direct parent.
|
|
1191
|
+
*
|
|
1192
|
+
* A parked `wait_children` is woken by lifecycle events, and its targets now
|
|
1193
|
+
* span the whole subtree — so a grandchild settling has to reach the
|
|
1194
|
+
* grandparent, or the wake never arrives and the ancestor sits until its
|
|
1195
|
+
* timeout even though the work finished.
|
|
1196
|
+
*
|
|
1197
|
+
* `parentId` is passed in rather than looked up: the caller already has it, and
|
|
1198
|
+
* a spawned child that is not PERSISTED (a test, or a transient spawn) is absent
|
|
1199
|
+
* from `config.agents`, so a lookup would find no parent at all and the wake
|
|
1200
|
+
* would be silently skipped.
|
|
1201
|
+
*/
|
|
1202
|
+
wakeAncestorWaiters(parentId) {
|
|
1203
|
+
const seen = new Set();
|
|
1204
|
+
let cur = parentId;
|
|
1205
|
+
while (cur && !seen.has(cur)) {
|
|
1206
|
+
seen.add(cur);
|
|
1207
|
+
this.wakeParentWaiters(cur);
|
|
1208
|
+
cur = this.config.agents.find((a) => a.id === cur)?.parent;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1161
1211
|
async messageChild(parentId, childId, text) {
|
|
1162
1212
|
// childId is normally the live agent id — but an internal session id
|
|
1163
1213
|
// (sessions/<id>, the stable timeline handle) also resolves, so callers
|
|
@@ -1219,9 +1269,9 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1219
1269
|
if (!parentId)
|
|
1220
1270
|
return;
|
|
1221
1271
|
// any child lifecycle movement wakes a parked wait_children (the waiter
|
|
1222
|
-
// re-checks whether its targets are all settled)
|
|
1223
|
-
|
|
1224
|
-
|
|
1272
|
+
// re-checks whether its targets are all settled) — ancestors included, since
|
|
1273
|
+
// a waiter's targets span its whole subtree
|
|
1274
|
+
this.wakeAncestorWaiters(parentId);
|
|
1225
1275
|
const parent = this.agents.get(parentId);
|
|
1226
1276
|
if (!parent)
|
|
1227
1277
|
return;
|
|
@@ -1257,29 +1307,12 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
1257
1307
|
if (parent.status !== "running")
|
|
1258
1308
|
parent.start(`sub-agent ${ac.id} error`);
|
|
1259
1309
|
}
|
|
1260
|
-
// #63: a child's activity is NOT mirrored into the parent's log.
|
|
1261
|
-
//
|
|
1262
|
-
//
|
|
1263
|
-
//
|
|
1264
|
-
//
|
|
1265
|
-
//
|
|
1266
|
-
// 1. The rows were invisible, because `"sub"` was never in the web UI's
|
|
1267
|
-
// FEED_TYPES — so the filter dropped them and the feature did nothing
|
|
1268
|
-
// for as long as it existed (#40).
|
|
1269
|
-
// 2. Once it was made to work (#40), the parent's timeline was flooded:
|
|
1270
|
-
// the operator reads a delegation as "what is this chat doing", not as
|
|
1271
|
-
// a firehose of a child's work.
|
|
1272
|
-
//
|
|
1273
|
-
// The mirroring bought nothing. `sub` rows are display-only:
|
|
1274
|
-
// `rebuildMessagesFrom` never reads them, so the model never saw them and
|
|
1275
|
-
// the child's own history was never duplicated into the parent's context.
|
|
1276
|
-
// What the parent actually needs — the child's report — already arrives as
|
|
1277
|
-
// a harness prompt above, which is the channel the model reads.
|
|
1278
|
-
//
|
|
1279
|
-
// So the right fix is to stop writing them. A chat's timeline is its own.
|
|
1280
|
-
// If per-child detail is wanted in the UI later, it belongs behind an
|
|
1281
|
-
// explicit filter ("show sub-agent activity"), reading each child's own log
|
|
1282
|
-
// rather than interleaving a copy into the parent's.
|
|
1310
|
+
// #63: a child's activity is NOT mirrored into the parent's log. Mirroring
|
|
1311
|
+
// bought nothing — `sub` rows are display-only (`rebuildMessagesFrom` never
|
|
1312
|
+
// read them, so the model never saw them) and once they DID render (#40) the
|
|
1313
|
+
// parent's timeline was flooded with a child's transcript. What the parent
|
|
1314
|
+
// needs is the child's report, which already arrives as a harness prompt
|
|
1315
|
+
// above. A chat's timeline is its own.
|
|
1283
1316
|
}
|
|
1284
1317
|
/** sessions root + helpers */
|
|
1285
1318
|
sessionsRoot() {
|
package/dist/model-meta.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Used to auto-populate contextWindowTokens so compaction budgets derive
|
|
4
4
|
* from the real window instead of the safe default.
|
|
5
5
|
*/
|
|
6
|
-
import fs from "node:fs";
|
|
7
6
|
import { providerHeaders } from "./agent/llm.js";
|
|
8
7
|
const cache = new Map();
|
|
9
8
|
const inFlight = new Map();
|
|
@@ -155,8 +154,3 @@ export function resolveReasoningEffort(opts) {
|
|
|
155
154
|
return undefined;
|
|
156
155
|
return opts.effort;
|
|
157
156
|
}
|
|
158
|
-
/** test hook */
|
|
159
|
-
export function clearModelListCache() {
|
|
160
|
-
cache.clear();
|
|
161
|
-
void fs;
|
|
162
|
-
}
|
package/dist/scheduler/cron.js
CHANGED
|
@@ -13,8 +13,8 @@ const BOUNDS = [
|
|
|
13
13
|
function parseField(part, min, max) {
|
|
14
14
|
const out = new Set();
|
|
15
15
|
const clampOrThrow = (v) => {
|
|
16
|
-
// out-of-range
|
|
17
|
-
// that
|
|
16
|
+
// accepting an out-of-range value produces a set that never matches, i.e. a
|
|
17
|
+
// cron that silently never fires ("99 * * * *")
|
|
18
18
|
if (!Number.isInteger(v) || v < min || v > max)
|
|
19
19
|
throw new Error(`bad cron field: ${part} (${min}-${max} expected)`);
|
|
20
20
|
return v;
|
|
@@ -88,10 +88,9 @@ export function matches(schedule, d) {
|
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
90
|
* Next fire time after `from`, scanning minute by minute (cheap: a handful of
|
|
91
|
-
* integer compares per minute).
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* A Feb-29-only cron can still legitimately report null most of the year.
|
|
91
|
+
* integer compares per minute). The horizon is a year so an annual cron (a
|
|
92
|
+
* December date seen in August) still resolves; a Feb-29-only cron can
|
|
93
|
+
* legitimately report null most of the year.
|
|
95
94
|
*/
|
|
96
95
|
export function nextFireAt(schedule, from = new Date()) {
|
|
97
96
|
const startMinute = Math.floor(from.getTime() / 60_000) + 1; // next whole minute
|
package/dist/server/term-size.js
CHANGED
|
@@ -5,9 +5,8 @@ export const TERM_BUSY_MS = 400;
|
|
|
5
5
|
export class TermSizeTracker {
|
|
6
6
|
states = new Map();
|
|
7
7
|
/**
|
|
8
|
-
* Request a resize
|
|
9
|
-
*
|
|
10
|
-
* for tests and diagnostics.
|
|
8
|
+
* Request a resize; the sink write happens on the trailing edge of the
|
|
9
|
+
* debounce.
|
|
11
10
|
*/
|
|
12
11
|
request(child, rows, cols, write) {
|
|
13
12
|
const st = this.states.get(child) ?? { rows: 0, cols: 0, timer: null, busyUntil: 0 };
|