@cjhyy/code-shell-core 0.6.0-rc.15 → 0.6.0-rc.16
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/context/manager.d.ts +4 -1
- package/dist/context/manager.js +19 -5
- package/dist/engine/engine.js +28 -17
- package/dist/engine/turn-loop.js +8 -1
- package/dist/protocol/server.js +2 -1
- package/dist/runtime/background-shell.d.ts +1 -0
- package/dist/runtime/background-shell.js +21 -10
- package/dist/session/memory.d.ts +5 -1
- package/dist/session/memory.js +5 -1
- package/dist/skills/scanner.d.ts +3 -2
- package/dist/skills/scanner.js +12 -9
- package/dist/tool-system/builtin/background-jobs.d.ts +2 -0
- package/dist/tool-system/builtin/background-jobs.js +4 -0
- package/dist/tool-system/builtin/background-work.d.ts +17 -18
- package/dist/tool-system/builtin/background-work.js +51 -5
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Tier 2: LLM summary (async) — generate summary of older messages via model call
|
|
6
6
|
* Tier 3: window compact (sync, emergency) — aggressive truncation fallback
|
|
7
7
|
*/
|
|
8
|
-
import type { Message, ContextUsageAnchor } from "../types.js";
|
|
8
|
+
import type { Message, ContextUsageAnchor, PromptTokenConfidence, PromptTokenSource } from "../types.js";
|
|
9
9
|
export interface ContextManagerConfig {
|
|
10
10
|
maxTokens: number;
|
|
11
11
|
compactAtRatio: number;
|
|
@@ -81,6 +81,7 @@ export declare class ContextManager {
|
|
|
81
81
|
* Best-effort token estimate: uses actual API usage as base if available,
|
|
82
82
|
* plus estimation for messages added since the last API call.
|
|
83
83
|
*/
|
|
84
|
+
private estimateTokensHybridInfo;
|
|
84
85
|
private estimateTokensHybrid;
|
|
85
86
|
/**
|
|
86
87
|
* Set the summarize function (injected by Engine).
|
|
@@ -150,5 +151,7 @@ export declare class ContextManager {
|
|
|
150
151
|
ratio: number;
|
|
151
152
|
needsCompact: boolean;
|
|
152
153
|
needsEmergency: boolean;
|
|
154
|
+
promptTokensSource: PromptTokenSource;
|
|
155
|
+
promptTokensConfidence: PromptTokenConfidence;
|
|
153
156
|
};
|
|
154
157
|
}
|
package/dist/context/manager.js
CHANGED
|
@@ -130,20 +130,31 @@ export class ContextManager {
|
|
|
130
130
|
* Best-effort token estimate: uses actual API usage as base if available,
|
|
131
131
|
* plus estimation for messages added since the last API call.
|
|
132
132
|
*/
|
|
133
|
-
|
|
133
|
+
estimateTokensHybridInfo(messages) {
|
|
134
134
|
const currentEstimate = estimateTokens(messages);
|
|
135
135
|
if (this.lastActualTokens !== undefined &&
|
|
136
136
|
this.lastActualAtMessageCount !== undefined) {
|
|
137
137
|
if (this.lastActualAtMessageCount < messages.length) {
|
|
138
138
|
const newMessages = messages.slice(this.lastActualAtMessageCount);
|
|
139
139
|
const newTokens = estimateTokens(newMessages);
|
|
140
|
-
return
|
|
140
|
+
return {
|
|
141
|
+
tokens: this.lastActualTokens + newTokens,
|
|
142
|
+
source: "anchor_delta",
|
|
143
|
+
confidence: "medium",
|
|
144
|
+
};
|
|
141
145
|
}
|
|
142
146
|
if (this.lastActualAnchorEstimate !== undefined && this.lastActualAnchorEstimate > 0) {
|
|
143
|
-
return
|
|
147
|
+
return {
|
|
148
|
+
tokens: Math.round(this.lastActualTokens * (currentEstimate / this.lastActualAnchorEstimate)),
|
|
149
|
+
source: "anchor_rescale",
|
|
150
|
+
confidence: "medium",
|
|
151
|
+
};
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
|
-
return currentEstimate;
|
|
154
|
+
return { tokens: currentEstimate, source: "heuristic_estimate", confidence: "low" };
|
|
155
|
+
}
|
|
156
|
+
estimateTokensHybrid(messages) {
|
|
157
|
+
return this.estimateTokensHybridInfo(messages).tokens;
|
|
147
158
|
}
|
|
148
159
|
/**
|
|
149
160
|
* Set the summarize function (injected by Engine).
|
|
@@ -577,13 +588,16 @@ export class ContextManager {
|
|
|
577
588
|
* Check if context is approaching limits.
|
|
578
589
|
*/
|
|
579
590
|
checkLimits(messages) {
|
|
580
|
-
const
|
|
591
|
+
const estimate = this.estimateTokensHybridInfo(messages);
|
|
592
|
+
const tokens = estimate.tokens;
|
|
581
593
|
const ratio = tokens / this.config.maxTokens;
|
|
582
594
|
return {
|
|
583
595
|
tokens,
|
|
584
596
|
ratio,
|
|
585
597
|
needsCompact: ratio >= this.config.compactAtRatio,
|
|
586
598
|
needsEmergency: ratio >= this.config.summarizeAtRatio,
|
|
599
|
+
promptTokensSource: estimate.source,
|
|
600
|
+
promptTokensConfidence: estimate.confidence,
|
|
587
601
|
};
|
|
588
602
|
}
|
|
589
603
|
}
|
package/dist/engine/engine.js
CHANGED
|
@@ -1332,27 +1332,34 @@ export class Engine {
|
|
|
1332
1332
|
persistedContextAnchor.model === this.config.llm.model) &&
|
|
1333
1333
|
(persistedContextAnchor.messageCount <= messages.length ||
|
|
1334
1334
|
persistedContextAnchor.estimateAtAnchor !== undefined);
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
//
|
|
1339
|
-
// before the first real usage_update arrives. The authoritative count
|
|
1335
|
+
if (contextAnchorCompatible) {
|
|
1336
|
+
contextManager.seedActualUsage(persistedContextAnchor);
|
|
1337
|
+
}
|
|
1338
|
+
// Best-effort token estimate of the full prompt so the UI's ctx bar isn't
|
|
1339
|
+
// 0% before the first real usage_update arrives. The authoritative count
|
|
1340
1340
|
// comes from `usage.promptTokens` after the first LLM response — this is
|
|
1341
|
-
// just a display-friendly approximation for the first frame
|
|
1341
|
+
// just a display-friendly approximation for the first frame, annotated
|
|
1342
|
+
// with source/confidence so consumers don't treat heuristics as truth.
|
|
1342
1343
|
//
|
|
1343
1344
|
// Only seed once per (process, sid). On subsequent turns the UI already
|
|
1344
|
-
// shows the previous turn's accurate ctx; overwriting it with
|
|
1345
|
-
//
|
|
1345
|
+
// shows the previous turn's accurate ctx; overwriting it with a fresh
|
|
1346
|
+
// best-effort estimate would make the bar visibly drop on every submit.
|
|
1346
1347
|
const sid = session.state.sessionId;
|
|
1347
1348
|
const needsCtxSeed = !this.ctxSeedSent.has(sid);
|
|
1348
|
-
const
|
|
1349
|
-
?
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1349
|
+
const ctxSeed = needsCtxSeed
|
|
1350
|
+
? (() => {
|
|
1351
|
+
const checked = contextManager.checkLimits(messages);
|
|
1352
|
+
return {
|
|
1353
|
+
tokens: checked.tokens,
|
|
1354
|
+
source: checked.promptTokensSource,
|
|
1355
|
+
confidence: checked.promptTokensConfidence,
|
|
1356
|
+
};
|
|
1357
|
+
})()
|
|
1358
|
+
: {
|
|
1359
|
+
tokens: 0,
|
|
1360
|
+
source: "heuristic_estimate",
|
|
1361
|
+
confidence: "low",
|
|
1362
|
+
};
|
|
1356
1363
|
if (needsCtxSeed)
|
|
1357
1364
|
this.ctxSeedSent.add(sid);
|
|
1358
1365
|
// Tell the client the sid *now* instead of waiting for run() to resolve.
|
|
@@ -1361,7 +1368,9 @@ export class Engine {
|
|
|
1361
1368
|
options?.onStream?.({
|
|
1362
1369
|
type: "session_started",
|
|
1363
1370
|
sessionId: sid,
|
|
1364
|
-
promptTokens:
|
|
1371
|
+
promptTokens: ctxSeed.tokens,
|
|
1372
|
+
promptTokensSource: ctxSeed.source,
|
|
1373
|
+
promptTokensConfidence: ctxSeed.confidence,
|
|
1365
1374
|
});
|
|
1366
1375
|
// Replay the last TodoWrite snapshot on resume so the UI's pinned
|
|
1367
1376
|
// task panel re-hydrates without the LLM needing to call TodoWrite
|
|
@@ -1847,6 +1856,8 @@ export class Engine {
|
|
|
1847
1856
|
options?.onStream?.({
|
|
1848
1857
|
type: "usage_update",
|
|
1849
1858
|
promptTokens: cumulative.cumulativePromptTokens,
|
|
1859
|
+
promptTokensSource: "session_cumulative",
|
|
1860
|
+
promptTokensConfidence: "high",
|
|
1850
1861
|
cumulativePromptTokens: cumulative.cumulativePromptTokens,
|
|
1851
1862
|
cumulativeCacheReadTokens: cumulative.cumulativeCacheReadTokens,
|
|
1852
1863
|
cumulativeCacheCreationTokens: cumulative.cumulativeCacheCreationTokens,
|
package/dist/engine/turn-loop.js
CHANGED
|
@@ -281,7 +281,12 @@ export class TurnLoop {
|
|
|
281
281
|
if (ctx === this.lastCtxEmit)
|
|
282
282
|
return;
|
|
283
283
|
this.lastCtxEmit = ctx;
|
|
284
|
-
this.config.onStream({
|
|
284
|
+
this.config.onStream({
|
|
285
|
+
type: "usage_update",
|
|
286
|
+
promptTokens: ctx,
|
|
287
|
+
promptTokensSource: overhead > 0 ? "calibrated_estimate" : "heuristic_estimate",
|
|
288
|
+
promptTokensConfidence: overhead > 0 ? "medium" : "low",
|
|
289
|
+
});
|
|
285
290
|
}
|
|
286
291
|
recordResponseUsage(usage) {
|
|
287
292
|
this.currentTurnUsage = addTokenUsage(this.currentTurnUsage, usage);
|
|
@@ -320,6 +325,8 @@ export class TurnLoop {
|
|
|
320
325
|
this.config.onStream({
|
|
321
326
|
type: "usage_update",
|
|
322
327
|
promptTokens,
|
|
328
|
+
promptTokensSource: "provider_usage",
|
|
329
|
+
promptTokensConfidence: "high",
|
|
323
330
|
...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),
|
|
324
331
|
...(usage.cacheCreationTokens !== undefined
|
|
325
332
|
? { cacheCreationTokens: usage.cacheCreationTokens }
|
package/dist/protocol/server.js
CHANGED
|
@@ -734,7 +734,8 @@ export class AgentServer {
|
|
|
734
734
|
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
735
735
|
return;
|
|
736
736
|
}
|
|
737
|
-
const
|
|
737
|
+
const scope = params.scope === "all" ? "all" : "session";
|
|
738
|
+
const items = listBackgroundWorkForUI(sessionId, { scope });
|
|
738
739
|
this.transport.send(createResponse(req.id, { items }));
|
|
739
740
|
}
|
|
740
741
|
// ─── CloseSession ───────────────────────────────────────────────
|
|
@@ -103,6 +103,7 @@ export declare class BackgroundShellManager {
|
|
|
103
103
|
/** Raw retained output (no cleaning) — test/UI helper. */
|
|
104
104
|
readOutputRaw(shellId: string): string | undefined;
|
|
105
105
|
listForSession(sessionId: string): BgShell[];
|
|
106
|
+
list(): BgShell[];
|
|
106
107
|
/**
|
|
107
108
|
* Read a shell's output. `mode=incremental` (default) returns text since the
|
|
108
109
|
* agent's last read; `mode=all` returns the full retained buffer. Output is
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { spawn } from "node:child_process";
|
|
26
26
|
import { join } from "node:path";
|
|
27
|
-
import { mkdirSync, writeFileSync, rmSync, existsSync, readdirSync, readFileSync
|
|
27
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync, readdirSync, readFileSync } from "node:fs";
|
|
28
28
|
import { StringDecoder } from "node:string_decoder";
|
|
29
29
|
import { utf8SafeCutLength } from "./utf8-cut.js";
|
|
30
|
-
import { resolveSpawnTarget, buildSandboxEnv, mergeShellEnv, killProcessGroup, groupAlive, defaultShellBinary } from "./spawn-common.js";
|
|
30
|
+
import { resolveSpawnTarget, buildSandboxEnv, mergeShellEnv, killProcessGroup, groupAlive, defaultShellBinary, } from "./spawn-common.js";
|
|
31
31
|
import { RingFile } from "./ring-file.js";
|
|
32
32
|
import { cleanOutput } from "./output-clean.js";
|
|
33
33
|
import { notificationQueue } from "../tool-system/builtin/agent-notifications.js";
|
|
@@ -46,7 +46,7 @@ function nextShellId() {
|
|
|
46
46
|
// Short, collision-resistant enough within a process: counter + base36 of
|
|
47
47
|
// a monotonic-ish value. Avoids Date.now()/Math.random() (banned in some
|
|
48
48
|
// contexts and unnecessary here).
|
|
49
|
-
return `bg_${shellCounter.toString(36)}${(shellCounter * 2654435761 % 0xffffff).toString(36)}`;
|
|
49
|
+
return `bg_${shellCounter.toString(36)}${((shellCounter * 2654435761) % 0xffffff).toString(36)}`;
|
|
50
50
|
}
|
|
51
51
|
function bgShellsRoot() {
|
|
52
52
|
return join(codeShellHome(), "bg-shells");
|
|
@@ -77,7 +77,11 @@ export class BackgroundShellManager {
|
|
|
77
77
|
// the bg-shells root — refuse rather than trust the caller. Mirrors
|
|
78
78
|
// SessionManager.assertSafeSessionId without importing across the layer.
|
|
79
79
|
const sid = opts.sessionId;
|
|
80
|
-
if (typeof sid !== "string" ||
|
|
80
|
+
if (typeof sid !== "string" ||
|
|
81
|
+
sid.length === 0 ||
|
|
82
|
+
sid.includes("..") ||
|
|
83
|
+
sid.includes("/") ||
|
|
84
|
+
sid.includes("\\")) {
|
|
81
85
|
return { ok: false, error: `invalid sessionId for background shell` };
|
|
82
86
|
}
|
|
83
87
|
// Per-session soft cap (fork-bomb guard, §7).
|
|
@@ -219,9 +223,7 @@ export class BackgroundShellManager {
|
|
|
219
223
|
}
|
|
220
224
|
enqueueExitNotification(sh) {
|
|
221
225
|
const ok = sh.exitCode === 0;
|
|
222
|
-
const exitDesc = sh.signal
|
|
223
|
-
? `signal ${sh.signal}`
|
|
224
|
-
: `exit ${sh.exitCode ?? "?"}`;
|
|
226
|
+
const exitDesc = sh.signal ? `signal ${sh.signal}` : `exit ${sh.exitCode ?? "?"}`;
|
|
225
227
|
notificationQueue.enqueue({
|
|
226
228
|
agentId: sh.shellId,
|
|
227
229
|
// English description is the agent-facing wakeup text. The UI ignores
|
|
@@ -233,7 +235,9 @@ export class BackgroundShellManager {
|
|
|
233
235
|
status: ok ? "completed" : "failed",
|
|
234
236
|
workKind: "shell",
|
|
235
237
|
command: sh.command,
|
|
236
|
-
error: ok
|
|
238
|
+
error: ok
|
|
239
|
+
? undefined
|
|
240
|
+
: `Background shell ${sh.shellId} exited with ${exitDesc}. Use BashOutput("${sh.shellId}") to inspect.`,
|
|
237
241
|
enqueuedAt: Date.now(),
|
|
238
242
|
}, sh.sessionId);
|
|
239
243
|
}
|
|
@@ -266,6 +270,9 @@ export class BackgroundShellManager {
|
|
|
266
270
|
.filter((s) => s.sessionId === sessionId)
|
|
267
271
|
.map((s) => this.toPublic(s));
|
|
268
272
|
}
|
|
273
|
+
list() {
|
|
274
|
+
return [...this.shells.values()].map((s) => this.toPublic(s));
|
|
275
|
+
}
|
|
269
276
|
/**
|
|
270
277
|
* Read a shell's output. `mode=incremental` (default) returns text since the
|
|
271
278
|
* agent's last read; `mode=all` returns the full retained buffer. Output is
|
|
@@ -419,7 +426,9 @@ export class BackgroundShellManager {
|
|
|
419
426
|
try {
|
|
420
427
|
rmSync(path, { force: true });
|
|
421
428
|
}
|
|
422
|
-
catch {
|
|
429
|
+
catch {
|
|
430
|
+
/* ignore */
|
|
431
|
+
}
|
|
423
432
|
continue;
|
|
424
433
|
}
|
|
425
434
|
// Skip ones we already track in-process (this worker's own).
|
|
@@ -433,7 +442,9 @@ export class BackgroundShellManager {
|
|
|
433
442
|
try {
|
|
434
443
|
rmSync(path, { force: true });
|
|
435
444
|
}
|
|
436
|
-
catch {
|
|
445
|
+
catch {
|
|
446
|
+
/* ignore */
|
|
447
|
+
}
|
|
437
448
|
}
|
|
438
449
|
}
|
|
439
450
|
}
|
package/dist/session/memory.d.ts
CHANGED
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
* auto-extracted entries (extract-memories output). LLM may
|
|
13
13
|
* only modify these through permission-gated tool calls.
|
|
14
14
|
* - dream/ The dream pipeline's workspace. The LLM is free to add /
|
|
15
|
-
* merge / delete entries here
|
|
15
|
+
* merge / delete entries here. The dream loop may ALSO maintain
|
|
16
|
+
* user/ entries whose origin is `auto` or `dream` (dedup/merge/
|
|
17
|
+
* improve), but origin:`manual` user entries — things the user
|
|
18
|
+
* explicitly asked to remember — are never touched by dream.
|
|
19
|
+
* See dream-consolidation.ts (the origin guard at ~:240).
|
|
16
20
|
*
|
|
17
21
|
* MEMORY.md is the index file (one per scope).
|
|
18
22
|
*
|
package/dist/session/memory.js
CHANGED
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
* auto-extracted entries (extract-memories output). LLM may
|
|
13
13
|
* only modify these through permission-gated tool calls.
|
|
14
14
|
* - dream/ The dream pipeline's workspace. The LLM is free to add /
|
|
15
|
-
* merge / delete entries here
|
|
15
|
+
* merge / delete entries here. The dream loop may ALSO maintain
|
|
16
|
+
* user/ entries whose origin is `auto` or `dream` (dedup/merge/
|
|
17
|
+
* improve), but origin:`manual` user entries — things the user
|
|
18
|
+
* explicitly asked to remember — are never touched by dream.
|
|
19
|
+
* See dream-consolidation.ts (the origin guard at ~:240).
|
|
16
20
|
*
|
|
17
21
|
* MEMORY.md is the index file (one per scope).
|
|
18
22
|
*
|
package/dist/skills/scanner.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skill scanner — discovers <base>/<name>/SKILL.md files from project
|
|
3
|
-
*
|
|
2
|
+
* Skill scanner — discovers <base>/<name>/SKILL.md files from project
|
|
3
|
+
* `.code-shell/skills`, project `.agents/skills`, user `.code-shell/skills`,
|
|
4
|
+
* and installed plugins. Mirrors Claude Code's
|
|
4
5
|
* `loadSkillsFromSkillsDir` (skills/loadSkillsDir.ts:407) plus plugin
|
|
5
6
|
* integration (utils/plugins/pluginLoader.ts).
|
|
6
7
|
*/
|
package/dist/skills/scanner.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skill scanner — discovers <base>/<name>/SKILL.md files from project
|
|
3
|
-
*
|
|
2
|
+
* Skill scanner — discovers <base>/<name>/SKILL.md files from project
|
|
3
|
+
* `.code-shell/skills`, project `.agents/skills`, user `.code-shell/skills`,
|
|
4
|
+
* and installed plugins. Mirrors Claude Code's
|
|
4
5
|
* `loadSkillsFromSkillsDir` (skills/loadSkillsDir.ts:407) plus plugin
|
|
5
6
|
* integration (utils/plugins/pluginLoader.ts).
|
|
6
7
|
*/
|
|
@@ -20,6 +21,7 @@ function userHome() {
|
|
|
20
21
|
function bases(cwd) {
|
|
21
22
|
return [
|
|
22
23
|
{ dir: join(cwd, ".code-shell", "skills"), source: "project" },
|
|
24
|
+
{ dir: join(cwd, ".agents", "skills"), source: "project" },
|
|
23
25
|
{ dir: join(userHome(), ".code-shell", "skills"), source: "user" },
|
|
24
26
|
];
|
|
25
27
|
}
|
|
@@ -158,13 +160,14 @@ function installedPluginsMtime() {
|
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
/**
|
|
161
|
-
* mtime of each local skills base dir (project + user). A
|
|
162
|
-
* changes when a child entry is added/removed, so installing
|
|
163
|
-
* `<cwd>/.code-shell/skills
|
|
164
|
-
* next scan — the just-installed
|
|
165
|
-
* without a restart. (Editing an
|
|
166
|
-
* dir mtime; install paths
|
|
167
|
-
* that, so this is the
|
|
163
|
+
* mtime of each local skills base dir (project .code-shell/.agents + user). A
|
|
164
|
+
* directory's mtime changes when a child entry is added/removed, so installing
|
|
165
|
+
* a new skill into `<cwd>/.code-shell/skills`, `<cwd>/.agents/skills`, or
|
|
166
|
+
* `~/.code-shell/skills` busts the cache on the next scan — the just-installed
|
|
167
|
+
* skill becomes visible to the running session without a restart. (Editing an
|
|
168
|
+
* existing skill's *contents* does not bump the dir mtime; install paths
|
|
169
|
+
* additionally call invalidateSkillCache() to cover that, so this is the
|
|
170
|
+
* passive half of a two-part guard.)
|
|
168
171
|
*/
|
|
169
172
|
function skillsDirsMtime(cwd) {
|
|
170
173
|
return bases(cwd)
|
|
@@ -69,6 +69,8 @@ declare class BackgroundJobRegistry {
|
|
|
69
69
|
listRunningByCwd(cwd: string): BackgroundJobEntry[];
|
|
70
70
|
/** All jobs (running + retained terminal) for `sessionId`. Feeds the panel. */
|
|
71
71
|
listForSession(sessionId: string): BackgroundJobEntry[];
|
|
72
|
+
/** All jobs (running + retained terminal) across sessions. Feeds all-scope UI. */
|
|
73
|
+
list(): BackgroundJobEntry[];
|
|
72
74
|
/** Drop every job of a session — called when the session is deleted/closed. */
|
|
73
75
|
dropForSession(sessionId: string): void;
|
|
74
76
|
subscribe: (cb: Listener) => (() => void);
|
|
@@ -82,6 +82,10 @@ class BackgroundJobRegistry {
|
|
|
82
82
|
listForSession(sessionId) {
|
|
83
83
|
return [...this.jobs.values()].filter((e) => e.sessionId === sessionId);
|
|
84
84
|
}
|
|
85
|
+
/** All jobs (running + retained terminal) across sessions. Feeds all-scope UI. */
|
|
86
|
+
list() {
|
|
87
|
+
return [...this.jobs.values()];
|
|
88
|
+
}
|
|
85
89
|
/** Drop every job of a session — called when the session is deleted/closed. */
|
|
86
90
|
dropForSession(sessionId) {
|
|
87
91
|
let removed = false;
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified view over the THREE kinds of async background work — background
|
|
3
|
-
* sub-agents (asyncAgentRegistry), background jobs / video polls
|
|
4
|
-
* (backgroundJobRegistry), and background shells (backgroundShellManager).
|
|
5
|
-
*
|
|
6
|
-
* Used by the goal-stop-hook to show the judge LLM what's still running, so it
|
|
7
|
-
* can tell "the goal is done except for a finite task that will wake me" (a
|
|
8
|
-
* download / video render → allow stop, wait for the wakeup) from "a long-lived
|
|
9
|
-
* service that the goal doesn't depend on" (a dev server → judge the goal
|
|
10
|
-
* normally). Replacing the old mechanical `hasRunningForSession` short-circuit:
|
|
11
|
-
* a boolean can't distinguish a finite download from a never-ending dev server,
|
|
12
|
-
* but the judge — given each task's kind + command — can.
|
|
13
|
-
*/
|
|
14
1
|
import { type AsyncAgentStatus } from "./agent-registry.js";
|
|
15
2
|
import { type BackgroundJobStatus } from "./background-jobs.js";
|
|
16
3
|
import { type BgShell } from "../../runtime/background-shell.js";
|
|
@@ -30,12 +17,21 @@ export interface BackgroundWorkItem {
|
|
|
30
17
|
/** List every still-running background work item spawned by `sessionId`. */
|
|
31
18
|
export declare function listRunningBackgroundWork(sessionId: string): BackgroundWorkItem[];
|
|
32
19
|
/** One background-work row for the desktop panel, discriminated by `kind`. */
|
|
33
|
-
export
|
|
20
|
+
export interface BackgroundWorkSourceSession {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
shortId: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
current: boolean;
|
|
25
|
+
}
|
|
26
|
+
type WithSource<T> = T & {
|
|
27
|
+
sourceSession: BackgroundWorkSourceSession;
|
|
28
|
+
};
|
|
29
|
+
export type BackgroundWorkEntry = WithSource<{
|
|
34
30
|
kind: "shell";
|
|
35
31
|
/** Full shell snapshot — the panel already renders this shape (output/kill
|
|
36
32
|
* still go through the dedicated agent/backgroundShells RPC by shellId). */
|
|
37
33
|
shell: BgShell;
|
|
38
|
-
} | {
|
|
34
|
+
}> | WithSource<{
|
|
39
35
|
kind: "subagent";
|
|
40
36
|
agentId: string;
|
|
41
37
|
name?: string;
|
|
@@ -44,7 +40,7 @@ export type BackgroundWorkEntry = {
|
|
|
44
40
|
status: AsyncAgentStatus;
|
|
45
41
|
startedAt: number;
|
|
46
42
|
finishedAt?: number;
|
|
47
|
-
} | {
|
|
43
|
+
}> | WithSource<{
|
|
48
44
|
kind: "job";
|
|
49
45
|
jobId: string;
|
|
50
46
|
description: string;
|
|
@@ -55,7 +51,7 @@ export type BackgroundWorkEntry = {
|
|
|
55
51
|
finalText?: string;
|
|
56
52
|
/** Files an external agent (DriveAgent) changed, parsed from its transcript. */
|
|
57
53
|
changedFiles?: string[];
|
|
58
|
-
}
|
|
54
|
+
}>;
|
|
59
55
|
/**
|
|
60
56
|
* Every background-work item spawned by `sessionId`, with per-kind detail, for
|
|
61
57
|
* the desktop panel. Includes finished sub-agents that are still within their
|
|
@@ -64,4 +60,7 @@ export type BackgroundWorkEntry = {
|
|
|
64
60
|
* keep them. Jobs are only ever present while running (the registry drops them
|
|
65
61
|
* on finish).
|
|
66
62
|
*/
|
|
67
|
-
export declare function listBackgroundWorkForUI(sessionId: string
|
|
63
|
+
export declare function listBackgroundWorkForUI(sessionId: string, opts?: {
|
|
64
|
+
scope?: "session" | "all";
|
|
65
|
+
}): BackgroundWorkEntry[];
|
|
66
|
+
export {};
|
|
@@ -11,9 +11,12 @@
|
|
|
11
11
|
* a boolean can't distinguish a finite download from a never-ending dev server,
|
|
12
12
|
* but the judge — given each task's kind + command — can.
|
|
13
13
|
*/
|
|
14
|
+
import { readFileSync, statSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
14
16
|
import { asyncAgentRegistry } from "./agent-registry.js";
|
|
15
17
|
import { backgroundJobRegistry } from "./background-jobs.js";
|
|
16
18
|
import { backgroundShellManager } from "../../runtime/background-shell.js";
|
|
19
|
+
import { codeShellHome } from "../../session/session-manager.js";
|
|
17
20
|
/** List every still-running background work item spawned by `sessionId`. */
|
|
18
21
|
export function listRunningBackgroundWork(sessionId) {
|
|
19
22
|
const items = [];
|
|
@@ -39,6 +42,38 @@ export function listRunningBackgroundWork(sessionId) {
|
|
|
39
42
|
}
|
|
40
43
|
return items;
|
|
41
44
|
}
|
|
45
|
+
const sessionTitleCache = new Map();
|
|
46
|
+
function shortSessionId(sessionId) {
|
|
47
|
+
return sessionId.length <= 10 ? sessionId : sessionId.slice(0, 10);
|
|
48
|
+
}
|
|
49
|
+
function readSessionTitle(sessionId) {
|
|
50
|
+
try {
|
|
51
|
+
const statePath = join(codeShellHome(), "sessions", sessionId, "state.json");
|
|
52
|
+
const st = statSync(statePath);
|
|
53
|
+
const cached = sessionTitleCache.get(sessionId);
|
|
54
|
+
if (cached && cached.mtimeMs === st.mtimeMs)
|
|
55
|
+
return cached.title;
|
|
56
|
+
const raw = JSON.parse(readFileSync(statePath, "utf8"));
|
|
57
|
+
const title = typeof raw.title === "string" && raw.title.trim()
|
|
58
|
+
? raw.title.trim()
|
|
59
|
+
: typeof raw.summary === "string" && raw.summary.trim()
|
|
60
|
+
? raw.summary.trim()
|
|
61
|
+
: undefined;
|
|
62
|
+
sessionTitleCache.set(sessionId, { mtimeMs: st.mtimeMs, title });
|
|
63
|
+
return title;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function sourceSession(currentSessionId, ownerSessionId) {
|
|
70
|
+
return {
|
|
71
|
+
sessionId: ownerSessionId,
|
|
72
|
+
shortId: shortSessionId(ownerSessionId),
|
|
73
|
+
title: readSessionTitle(ownerSessionId),
|
|
74
|
+
current: ownerSessionId === currentSessionId,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
42
77
|
/**
|
|
43
78
|
* Every background-work item spawned by `sessionId`, with per-kind detail, for
|
|
44
79
|
* the desktop panel. Includes finished sub-agents that are still within their
|
|
@@ -47,18 +82,24 @@ export function listRunningBackgroundWork(sessionId) {
|
|
|
47
82
|
* keep them. Jobs are only ever present while running (the registry drops them
|
|
48
83
|
* on finish).
|
|
49
84
|
*/
|
|
50
|
-
export function listBackgroundWorkForUI(sessionId) {
|
|
85
|
+
export function listBackgroundWorkForUI(sessionId, opts = {}) {
|
|
51
86
|
const entries = [];
|
|
52
|
-
|
|
53
|
-
|
|
87
|
+
const scope = opts.scope ?? "session";
|
|
88
|
+
const shells = scope === "all"
|
|
89
|
+
? backgroundShellManager.list()
|
|
90
|
+
: backgroundShellManager.listForSession(sessionId);
|
|
91
|
+
for (const s of shells) {
|
|
92
|
+
entries.push({ kind: "shell", shell: s, sourceSession: sourceSession(sessionId, s.sessionId) });
|
|
54
93
|
}
|
|
55
94
|
const now = Date.now();
|
|
56
|
-
|
|
95
|
+
const agents = scope === "all" ? asyncAgentRegistry.list() : asyncAgentRegistry.listForSession(sessionId);
|
|
96
|
+
for (const a of agents) {
|
|
57
97
|
// Keep running agents, plus finished ones still inside their fade window so
|
|
58
98
|
// a completion is briefly visible. (finishedFadeAt = finishedAt + 30s.)
|
|
59
99
|
const fresh = a.status === "running" || (a.finishedFadeAt != null && a.finishedFadeAt > now);
|
|
60
100
|
if (!fresh)
|
|
61
101
|
continue;
|
|
102
|
+
const ownerSessionId = a.sessionId ?? sessionId;
|
|
62
103
|
entries.push({
|
|
63
104
|
kind: "subagent",
|
|
64
105
|
agentId: a.agentId,
|
|
@@ -68,9 +109,13 @@ export function listBackgroundWorkForUI(sessionId) {
|
|
|
68
109
|
status: a.status,
|
|
69
110
|
startedAt: a.startedAt,
|
|
70
111
|
finishedAt: a.finishedAt,
|
|
112
|
+
sourceSession: sourceSession(sessionId, ownerSessionId),
|
|
71
113
|
});
|
|
72
114
|
}
|
|
73
|
-
|
|
115
|
+
const jobs = scope === "all"
|
|
116
|
+
? backgroundJobRegistry.list()
|
|
117
|
+
: backgroundJobRegistry.listForSession(sessionId);
|
|
118
|
+
for (const j of jobs) {
|
|
74
119
|
entries.push({
|
|
75
120
|
kind: "job",
|
|
76
121
|
jobId: j.jobId,
|
|
@@ -80,6 +125,7 @@ export function listBackgroundWorkForUI(sessionId) {
|
|
|
80
125
|
...(j.finishedAt != null ? { finishedAt: j.finishedAt } : {}),
|
|
81
126
|
...(j.finalText != null ? { finalText: j.finalText } : {}),
|
|
82
127
|
...(j.changedFiles && j.changedFiles.length ? { changedFiles: j.changedFiles } : {}),
|
|
128
|
+
sourceSession: sourceSession(sessionId, j.sessionId),
|
|
83
129
|
});
|
|
84
130
|
}
|
|
85
131
|
return entries;
|
package/dist/types.d.ts
CHANGED
|
@@ -319,10 +319,14 @@ export interface TaskInfo {
|
|
|
319
319
|
activeForm?: string;
|
|
320
320
|
status: "pending" | "in_progress" | "completed" | "stopped";
|
|
321
321
|
}
|
|
322
|
+
export type PromptTokenSource = "provider_usage" | "anchor_delta" | "anchor_rescale" | "calibrated_estimate" | "heuristic_estimate" | "session_cumulative";
|
|
323
|
+
export type PromptTokenConfidence = "high" | "medium" | "low";
|
|
322
324
|
export type StreamEvent = {
|
|
323
325
|
type: "session_started";
|
|
324
326
|
sessionId: string;
|
|
325
327
|
promptTokens: number;
|
|
328
|
+
promptTokensSource?: PromptTokenSource;
|
|
329
|
+
promptTokensConfidence?: PromptTokenConfidence;
|
|
326
330
|
} | {
|
|
327
331
|
type: "session_title";
|
|
328
332
|
sessionId: string;
|
|
@@ -436,6 +440,8 @@ export type StreamEvent = {
|
|
|
436
440
|
} | {
|
|
437
441
|
type: "usage_update";
|
|
438
442
|
promptTokens: number;
|
|
443
|
+
promptTokensSource?: PromptTokenSource;
|
|
444
|
+
promptTokensConfidence?: PromptTokenConfidence;
|
|
439
445
|
/**
|
|
440
446
|
* Provider-reported prompt-cache counts, forwarded so the UI can show a
|
|
441
447
|
* cache hit rate in the context-ring tooltip. Present only when the LLM
|
package/package.json
CHANGED