@cjhyy/code-shell-core 0.6.0-rc.9 → 0.7.0
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/THIRD_PARTY_NOTICES.md +206 -0
- package/dist/automation/scheduler.d.ts +13 -7
- package/dist/automation/scheduler.js +116 -37
- package/dist/capability-control/service.d.ts +2 -0
- package/dist/capability-control/service.js +4 -2
- package/dist/cc-orchestrator/agent-adapter.d.ts +4 -0
- package/dist/cc-orchestrator/agent-adapter.js +11 -1
- package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
- package/dist/cc-orchestrator/codex-session-history.js +65 -5
- package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
- package/dist/cc-orchestrator/cwd-normalize.js +19 -0
- package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
- package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
- package/dist/cc-orchestrator/external-agent-changes.js +22 -5
- package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
- package/dist/cc-orchestrator/external-agent-driver.js +265 -50
- package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
- package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
- package/dist/cc-orchestrator/session-history.d.ts +35 -0
- package/dist/cc-orchestrator/session-history.js +98 -15
- package/dist/cli/agent-server-stdio.js +9 -2
- package/dist/context/compaction.d.ts +8 -1
- package/dist/context/compaction.js +49 -4
- package/dist/context/manager.d.ts +16 -2
- package/dist/context/manager.js +103 -19
- package/dist/credentials/access.d.ts +57 -0
- package/dist/credentials/access.js +185 -0
- package/dist/credentials/index.d.ts +3 -1
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/inject-credential-tool.d.ts +3 -1
- package/dist/credentials/inject-credential-tool.js +30 -11
- package/dist/credentials/oauth.d.ts +20 -0
- package/dist/credentials/oauth.js +114 -0
- package/dist/credentials/store.d.ts +1 -0
- package/dist/credentials/store.js +3 -1
- package/dist/credentials/types.d.ts +49 -3
- package/dist/credentials/use-credential-tool.d.ts +9 -1
- package/dist/credentials/use-credential-tool.js +58 -45
- package/dist/engine/engine.d.ts +23 -3
- package/dist/engine/engine.js +380 -255
- package/dist/engine/goal.d.ts +17 -0
- package/dist/engine/goal.js +16 -6
- package/dist/engine/image-policy.d.ts +6 -0
- package/dist/engine/image-policy.js +17 -6
- package/dist/engine/input-attachments.d.ts +13 -0
- package/dist/engine/input-attachments.js +398 -0
- package/dist/engine/model-facade.d.ts +5 -2
- package/dist/engine/model-facade.js +4 -4
- package/dist/engine/parse-task.d.ts +10 -0
- package/dist/engine/parse-task.js +5 -0
- package/dist/engine/run-image-input.d.ts +22 -0
- package/dist/engine/run-image-input.js +195 -0
- package/dist/engine/steer-queue.d.ts +3 -1
- package/dist/engine/steer-queue.js +10 -2
- package/dist/engine/streaming-tool-queue.d.ts +11 -7
- package/dist/engine/streaming-tool-queue.js +11 -7
- package/dist/engine/turn-loop.d.ts +37 -2
- package/dist/engine/turn-loop.js +228 -43
- package/dist/engine/types.d.ts +8 -0
- package/dist/git/worktree/crud.d.ts +69 -0
- package/dist/git/worktree/crud.js +206 -0
- package/dist/git/worktree/diff.d.ts +14 -0
- package/dist/git/worktree/diff.js +82 -0
- package/dist/git/worktree/git-exec.d.ts +7 -0
- package/dist/git/worktree/git-exec.js +51 -0
- package/dist/git/worktree/index.d.ts +5 -0
- package/dist/git/worktree/index.js +5 -0
- package/dist/git/worktree/query.d.ts +42 -0
- package/dist/git/worktree/query.js +121 -0
- package/dist/git/worktree/slug.d.ts +11 -0
- package/dist/git/worktree/slug.js +58 -0
- package/dist/git/worktree.d.ts +1 -84
- package/dist/git/worktree.js +5 -230
- package/dist/hooks/goal-stop-hook.d.ts +33 -1
- package/dist/hooks/goal-stop-hook.js +202 -34
- package/dist/index.d.ts +31 -28
- package/dist/index.js +27 -25
- package/dist/logging/logger.js +6 -6
- package/dist/logging/sanitize-messages.d.ts +10 -2
- package/dist/logging/sanitize-messages.js +21 -6
- package/dist/plugins/installer/checkUpdate.d.ts +4 -1
- package/dist/plugins/installer/checkUpdate.js +4 -2
- package/dist/plugins/installer/install.js +2 -0
- package/dist/plugins/installer/installFromSource.js +9 -1
- package/dist/plugins/installer/parseSource.d.ts +4 -1
- package/dist/plugins/installer/parseSource.js +28 -10
- package/dist/plugins/installer/sourcePath.d.ts +9 -0
- package/dist/plugins/installer/sourcePath.js +50 -0
- package/dist/plugins/installer/update.d.ts +4 -1
- package/dist/plugins/installer/update.js +5 -3
- package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
- package/dist/plugins/parseMarketplaceInput.js +4 -3
- package/dist/plugins/pluginInstaller.js +24 -22
- package/dist/preset/index.d.ts +1 -0
- package/dist/preset/index.js +30 -13
- package/dist/prompt/sections/base.md +1 -1
- package/dist/protocol/chat-session-manager.d.ts +2 -0
- package/dist/protocol/chat-session-manager.js +38 -2
- package/dist/protocol/chat-session.d.ts +3 -0
- package/dist/protocol/chat-session.js +1 -0
- package/dist/protocol/client.d.ts +9 -4
- package/dist/protocol/client.js +18 -1
- package/dist/protocol/server.d.ts +30 -0
- package/dist/protocol/server.js +247 -47
- package/dist/protocol/types.d.ts +49 -0
- package/dist/protocol/types.js +6 -0
- package/dist/run/FileRunStore.js +10 -1
- package/dist/run/Heartbeat.js +12 -0
- package/dist/run/RunApprovalBackend.d.ts +3 -0
- package/dist/run/RunApprovalBackend.js +41 -6
- package/dist/run/RunLock.js +2 -0
- package/dist/run/RunManager.d.ts +2 -0
- package/dist/run/RunManager.js +64 -24
- package/dist/run/ids.d.ts +2 -0
- package/dist/run/ids.js +23 -0
- package/dist/runtime/background-shell.d.ts +1 -0
- package/dist/runtime/background-shell.js +23 -13
- package/dist/runtime/spawn-common.js +10 -0
- package/dist/services/auto-dream.d.ts +15 -4
- package/dist/services/auto-dream.js +20 -20
- package/dist/services/dream-consolidation.d.ts +2 -3
- package/dist/services/dream-consolidation.js +65 -15
- package/dist/services/extract-memories.d.ts +14 -5
- package/dist/services/extract-memories.js +20 -3
- package/dist/services/global-dream-promotion.d.ts +23 -0
- package/dist/services/global-dream-promotion.js +112 -0
- package/dist/services/memory-orchestrator.js +347 -34
- package/dist/session/memory.d.ts +61 -18
- package/dist/session/memory.js +342 -79
- package/dist/session/session-manager.d.ts +35 -1
- package/dist/session/session-manager.js +224 -4
- package/dist/session/transcript.d.ts +1 -1
- package/dist/session/transcript.js +17 -5
- package/dist/settings/manager.d.ts +1 -0
- package/dist/settings/manager.js +87 -37
- package/dist/settings/schema-export.d.ts +2 -3
- package/dist/settings/schema-export.js +2 -3
- package/dist/settings/schema.d.ts +21 -0
- package/dist/settings/schema.js +12 -0
- package/dist/skills/scanner.d.ts +3 -2
- package/dist/skills/scanner.js +12 -9
- package/dist/tool-system/builtin/agent-notifications.d.ts +11 -4
- package/dist/tool-system/builtin/agent-notifications.js +19 -7
- package/dist/tool-system/builtin/background-jobs.d.ts +35 -6
- package/dist/tool-system/builtin/background-jobs.js +116 -7
- package/dist/tool-system/builtin/background-work.d.ts +17 -18
- package/dist/tool-system/builtin/background-work.js +51 -5
- package/dist/tool-system/builtin/config.d.ts +2 -1
- package/dist/tool-system/builtin/config.js +16 -11
- package/dist/tool-system/builtin/cron-list.definition.d.ts +3 -0
- package/dist/tool-system/builtin/cron-list.definition.js +6 -0
- package/dist/tool-system/builtin/cron.d.ts +1 -2
- package/dist/tool-system/builtin/cron.js +9 -7
- package/dist/tool-system/builtin/drive-claude-code.d.ts +23 -2
- package/dist/tool-system/builtin/drive-claude-code.js +589 -48
- package/dist/tool-system/builtin/edit.js +5 -2
- package/dist/tool-system/builtin/generate-video.d.ts +1 -0
- package/dist/tool-system/builtin/generate-video.js +13 -4
- package/dist/tool-system/builtin/index.d.ts +8 -3
- package/dist/tool-system/builtin/index.js +50 -22
- package/dist/tool-system/builtin/lsp.d.ts +2 -1
- package/dist/tool-system/builtin/lsp.js +6 -3
- package/dist/tool-system/builtin/memory.js +40 -8
- package/dist/tool-system/builtin/notebook-edit.js +5 -2
- package/dist/tool-system/builtin/powershell.d.ts +5 -2
- package/dist/tool-system/builtin/powershell.js +11 -7
- package/dist/tool-system/builtin/read.js +118 -6
- package/dist/tool-system/builtin/sleep.d.ts +1 -2
- package/dist/tool-system/builtin/sleep.definition.d.ts +8 -0
- package/dist/tool-system/builtin/sleep.definition.js +28 -0
- package/dist/tool-system/builtin/sleep.js +1 -22
- package/dist/tool-system/builtin/view-image.d.ts +2 -2
- package/dist/tool-system/builtin/view-image.js +109 -19
- package/dist/tool-system/builtin/worktree.d.ts +4 -4
- package/dist/tool-system/builtin/worktree.js +297 -74
- package/dist/tool-system/builtin/write.js +5 -3
- package/dist/tool-system/context.d.ts +41 -1
- package/dist/tool-system/executor.d.ts +1 -5
- package/dist/tool-system/executor.js +94 -115
- package/dist/tool-system/mcp-manager.d.ts +31 -6
- package/dist/tool-system/mcp-manager.js +193 -78
- package/dist/tool-system/path-policy.d.ts +2 -0
- package/dist/tool-system/path-policy.js +41 -12
- package/dist/tool-system/permission.d.ts +28 -7
- package/dist/tool-system/permission.js +130 -49
- package/dist/tool-system/registry.js +11 -4
- package/dist/tool-system/tool-result-redaction.d.ts +7 -0
- package/dist/tool-system/tool-result-redaction.js +48 -0
- package/dist/tool-system/workspace-bridge.d.ts +11 -0
- package/dist/tool-system/workspace-bridge.js +1 -0
- package/dist/types.d.ts +78 -11
- package/dist/utils/toolDisplay.js +1 -1
- package/package.json +4 -3
|
@@ -19,23 +19,28 @@ function toolsOf(content) {
|
|
|
19
19
|
const inp = p.input ?? {};
|
|
20
20
|
const summary = inp.command ?? inp.file_path ?? inp.path ?? inp.url ?? inp.pattern ?? inp.query ?? "";
|
|
21
21
|
const args = inp && typeof inp === "object" && Object.keys(inp).length > 0 ? inp : undefined;
|
|
22
|
-
out.push({
|
|
22
|
+
out.push({
|
|
23
|
+
name: typeof p.name === "string" ? p.name : "tool",
|
|
24
|
+
summary: String(summary).slice(0, 120),
|
|
25
|
+
args,
|
|
26
|
+
});
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
return out;
|
|
26
30
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
function toolResultText(content) {
|
|
32
|
+
if (typeof content === "string")
|
|
33
|
+
return content;
|
|
34
|
+
if (!Array.isArray(content))
|
|
35
|
+
return "";
|
|
36
|
+
return content
|
|
37
|
+
.map((part) => typeof part === "string" ? part : typeof part?.text === "string" ? part.text : "")
|
|
38
|
+
.join("");
|
|
39
|
+
}
|
|
40
|
+
/** Parse a bounded/raw Claude Code transcript snapshot. Exported so a live
|
|
41
|
+
* follower can take its initial snapshot and EOF cursor from the same read,
|
|
42
|
+
* eliminating the snapshot→subscribe race. */
|
|
43
|
+
export function parseRecentHistory(raw, limit) {
|
|
39
44
|
const all = [];
|
|
40
45
|
for (const line of raw.split("\n")) {
|
|
41
46
|
if (!line.trim())
|
|
@@ -51,17 +56,95 @@ export function readRecentHistory(cwd, sessionId, limit, claudeHome = join(homed
|
|
|
51
56
|
const t = textOf(d.message?.content).trim();
|
|
52
57
|
if (!t || NOISE.some((n) => t.startsWith(n)))
|
|
53
58
|
continue;
|
|
54
|
-
all.push({ role: "user", text: t
|
|
59
|
+
all.push({ role: "user", text: t });
|
|
55
60
|
}
|
|
56
61
|
else if (d.type === "assistant") {
|
|
57
62
|
const t = textOf(d.message?.content).trim();
|
|
58
63
|
const tools = toolsOf(d.message?.content);
|
|
59
64
|
if (!t && tools.length === 0)
|
|
60
65
|
continue;
|
|
61
|
-
all.push({ role: "assistant", text: t
|
|
66
|
+
all.push({ role: "assistant", text: t, tools: tools.length ? tools : undefined });
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
const lim = limit > 0 ? limit : 20;
|
|
65
70
|
const start = Math.max(0, all.length - lim);
|
|
66
71
|
return { messages: all.slice(start), hasMore: start > 0, totalCount: all.length };
|
|
67
72
|
}
|
|
73
|
+
/** Parse one newly-appended Claude Code transcript JSONL line into the compact
|
|
74
|
+
* event vocabulary consumed by the desktop room follower. */
|
|
75
|
+
export function parseClaudeTranscriptLine(line) {
|
|
76
|
+
let d;
|
|
77
|
+
try {
|
|
78
|
+
d = JSON.parse(line);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const out = [];
|
|
84
|
+
if (d.type === "user") {
|
|
85
|
+
const content = d.message?.content;
|
|
86
|
+
if (Array.isArray(content)) {
|
|
87
|
+
for (const part of content) {
|
|
88
|
+
if (part?.type !== "tool_result")
|
|
89
|
+
continue;
|
|
90
|
+
out.push({
|
|
91
|
+
type: "tool_result",
|
|
92
|
+
id: typeof part.tool_use_id === "string" ? part.tool_use_id : undefined,
|
|
93
|
+
result: toolResultText(part.content).slice(0, 4000),
|
|
94
|
+
isError: Boolean(part.is_error),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const text = textOf(content).trim();
|
|
99
|
+
if (text && !NOISE.some((noise) => text.startsWith(noise))) {
|
|
100
|
+
out.unshift({ type: "user", text });
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
if (d.type === "assistant" && Array.isArray(d.message?.content)) {
|
|
105
|
+
for (const part of d.message.content) {
|
|
106
|
+
if (part?.type === "text" && typeof part.text === "string" && part.text) {
|
|
107
|
+
out.push({ type: "assistant", text: part.text });
|
|
108
|
+
}
|
|
109
|
+
else if (part?.type === "tool_use") {
|
|
110
|
+
const input = part.input && typeof part.input === "object"
|
|
111
|
+
? part.input
|
|
112
|
+
: undefined;
|
|
113
|
+
out.push({
|
|
114
|
+
type: "tool",
|
|
115
|
+
id: typeof part.id === "string" ? part.id : undefined,
|
|
116
|
+
name: typeof part.name === "string" ? part.name : "tool",
|
|
117
|
+
summary: toolsOf([part])[0]?.summary ?? "",
|
|
118
|
+
args: input,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (d.message.stop_reason === "end_turn") {
|
|
123
|
+
out.push({ type: "turn_end", reason: "completed" });
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
if (d.type === "result") {
|
|
128
|
+
return [
|
|
129
|
+
{
|
|
130
|
+
type: "turn_end",
|
|
131
|
+
reason: typeof d.subtype === "string" ? d.subtype : "completed",
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
/** Read the last `limit` user/assistant messages from a claude session jsonl. */
|
|
138
|
+
export function readRecentHistory(cwd, sessionId, limit, claudeHome = join(homedir(), ".claude")) {
|
|
139
|
+
const file = join(claudeHome, "projects", encodeCwd(cwd), `${sessionId}.jsonl`);
|
|
140
|
+
if (!existsSync(file))
|
|
141
|
+
return { messages: [], hasMore: false, totalCount: 0 };
|
|
142
|
+
let raw;
|
|
143
|
+
try {
|
|
144
|
+
raw = readFileSync(file, "utf-8");
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return { messages: [], hasMore: false, totalCount: 0 };
|
|
148
|
+
}
|
|
149
|
+
return parseRecentHistory(raw, limit);
|
|
150
|
+
}
|
|
@@ -50,6 +50,7 @@ import { logger } from "../logging/logger.js";
|
|
|
50
50
|
import { cronScheduler } from "../automation/scheduler.js";
|
|
51
51
|
import { CronStore, defaultCronStorePath } from "../automation/store.js";
|
|
52
52
|
import { resolveLLMConfigForTag } from "../engine/resolve-llm-config.js";
|
|
53
|
+
import { createIpcCredentialAccess, setDefaultCredentialAccess } from "../credentials/access.js";
|
|
53
54
|
/**
|
|
54
55
|
* Resolve per-session agent config: protocol slice overrides win, else fall
|
|
55
56
|
* back to disk settings.agent.*. Fixes the bug where settings.agent.* never
|
|
@@ -106,8 +107,7 @@ catch (err) {
|
|
|
106
107
|
// knob; imageDetail from settings.images.detail).
|
|
107
108
|
const seedLlm = resolveLLMConfigForTag(settings, "text", settings.defaults?.text);
|
|
108
109
|
if (!seedLlm) {
|
|
109
|
-
console.error(`[agent-server] 没有可用的文本模型连接(defaults.text=${settings.defaults?.text ?? "未设置"})。` +
|
|
110
|
-
`请在「连接」页添加并填写凭证。`);
|
|
110
|
+
console.error(`[agent-server] 没有可用的文本模型连接(defaults.text=${settings.defaults?.text ?? "未设置"})。` + `请在「连接」页添加并填写凭证。`);
|
|
111
111
|
process.exit(1);
|
|
112
112
|
}
|
|
113
113
|
const llmConfig = seedLlm;
|
|
@@ -115,6 +115,10 @@ const llmConfig = seedLlm;
|
|
|
115
115
|
const seedEngine = new Engine({
|
|
116
116
|
llm: llmConfig,
|
|
117
117
|
cwd,
|
|
118
|
+
preset: settings.agent.preset,
|
|
119
|
+
enabledBuiltinTools: settings.agent.enabledBuiltinTools,
|
|
120
|
+
disabledBuiltinTools: settings.agent.disabledBuiltinTools,
|
|
121
|
+
builtinToolHost: "desktop",
|
|
118
122
|
settingsScope: "full",
|
|
119
123
|
// No runtime — Engine.populateModelPoolFromSettings() runs in ctor.
|
|
120
124
|
});
|
|
@@ -217,6 +221,7 @@ const chatManager = new ChatSessionManager({
|
|
|
217
221
|
// This stdio worker exists only to serve the desktop app, so every
|
|
218
222
|
// session it creates is a desktop-origin session.
|
|
219
223
|
origin: "desktop",
|
|
224
|
+
builtinToolHost: "desktop",
|
|
220
225
|
// Inherit full scope so spawned subagents read user config too.
|
|
221
226
|
settingsScope: "full",
|
|
222
227
|
// MCP servers from settings — the worker reads the full disk
|
|
@@ -270,6 +275,7 @@ cronScheduler.setExecutionEnabled(false);
|
|
|
270
275
|
cronScheduler.loadJobs();
|
|
271
276
|
// ─── Step 5: AgentServer over stdio ──────────────────────────────
|
|
272
277
|
const stdioTransport = new StdioTransport(process.stdin, process.stdout);
|
|
278
|
+
setDefaultCredentialAccess(createIpcCredentialAccess(stdioTransport));
|
|
273
279
|
// Cron jobs are persisted by this worker but only main arms/executes their
|
|
274
280
|
// timers (this worker keeps setExecutionEnabled(false) above). When an AI tool
|
|
275
281
|
// creates/deletes a cron job here, notify main over stdio so it reloads the
|
|
@@ -286,6 +292,7 @@ const goalDiskReader = new SessionManager();
|
|
|
286
292
|
const agentServer = new AgentServer({
|
|
287
293
|
chatManager,
|
|
288
294
|
transport: stdioTransport,
|
|
295
|
+
workspaceBridge: true,
|
|
289
296
|
// Config hot-reload (layer 2) reads disk through the SAME closure the
|
|
290
297
|
// engineFactory uses for new sessions, so a reloaded running session and a
|
|
291
298
|
// newly-created session converge on identical disk config (no divergence).
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* All slicing functions use adjustIndexToPreserveAPIInvariants()
|
|
11
11
|
* to prevent splitting tool_use / tool_result pairs.
|
|
12
12
|
*/
|
|
13
|
-
import type { Message } from "../types.js";
|
|
13
|
+
import type { ContentBlock, Message } from "../types.js";
|
|
14
14
|
/**
|
|
15
15
|
* Estimate token count from messages.
|
|
16
16
|
* Uses per-block-type estimation with 33% overhead padding.
|
|
@@ -34,6 +34,12 @@ export interface DowngradeImageHistoryResult {
|
|
|
34
34
|
messages: Message[];
|
|
35
35
|
replacedCount: number;
|
|
36
36
|
}
|
|
37
|
+
export interface Base64ImageHistoryEntry {
|
|
38
|
+
imageNumber: number;
|
|
39
|
+
block: ContentBlock;
|
|
40
|
+
}
|
|
41
|
+
export declare function collectBase64Images(messages: Message[]): Base64ImageHistoryEntry[];
|
|
42
|
+
export declare function findImageByNumber(messages: Message[], imageNumber: number): Base64ImageHistoryEntry | undefined;
|
|
37
43
|
/**
|
|
38
44
|
* Replace already-consumed image payload blocks with compact text markers.
|
|
39
45
|
*
|
|
@@ -45,6 +51,7 @@ export interface DowngradeImageHistoryResult {
|
|
|
45
51
|
*/
|
|
46
52
|
export declare function downgradeImagePayloadsInHistory(messages: Message[], options?: DowngradeImageHistoryOptions): DowngradeImageHistoryResult;
|
|
47
53
|
export declare function messageHasBase64ImagePayload(message: Message): boolean;
|
|
54
|
+
export declare function isBase64ImageBlock(block: ContentBlock): boolean;
|
|
48
55
|
/**
|
|
49
56
|
* Reconcile user-supplied compaction ratios into a safe ordering.
|
|
50
57
|
*
|
|
@@ -20,6 +20,36 @@ export function estimateTokens(messages) {
|
|
|
20
20
|
}
|
|
21
21
|
export const IMAGE_HISTORY_PLACEHOLDER_PREFIX = "[image #";
|
|
22
22
|
export const IMAGE_HISTORY_PLACEHOLDER_SUFFIX = ", 已处理 / already provided earlier]";
|
|
23
|
+
export function collectBase64Images(messages) {
|
|
24
|
+
const entries = [];
|
|
25
|
+
let nextImageNumber = 1;
|
|
26
|
+
const visitBlocks = (blocks) => {
|
|
27
|
+
for (const block of blocks) {
|
|
28
|
+
const placeholderNumber = imageHistoryPlaceholderNumber(block);
|
|
29
|
+
if (placeholderNumber !== undefined) {
|
|
30
|
+
nextImageNumber = Math.max(nextImageNumber, placeholderNumber + 1);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (isBase64ImageBlock(block)) {
|
|
34
|
+
entries.push({ imageNumber: nextImageNumber++, block });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (block.type === "tool_result" && Array.isArray(block.content)) {
|
|
38
|
+
visitBlocks(block.content);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
for (const msg of messages) {
|
|
43
|
+
if (Array.isArray(msg.content))
|
|
44
|
+
visitBlocks(msg.content);
|
|
45
|
+
}
|
|
46
|
+
return entries;
|
|
47
|
+
}
|
|
48
|
+
export function findImageByNumber(messages, imageNumber) {
|
|
49
|
+
if (!Number.isSafeInteger(imageNumber) || imageNumber <= 0)
|
|
50
|
+
return undefined;
|
|
51
|
+
return collectBase64Images(messages).find((entry) => entry.imageNumber === imageNumber);
|
|
52
|
+
}
|
|
23
53
|
/**
|
|
24
54
|
* Replace already-consumed image payload blocks with compact text markers.
|
|
25
55
|
*
|
|
@@ -30,9 +60,23 @@ export const IMAGE_HISTORY_PLACEHOLDER_SUFFIX = ", 已处理 / already provided
|
|
|
30
60
|
* nested inside tool_result.content arrays (view_image / browser screenshots).
|
|
31
61
|
*/
|
|
32
62
|
export function downgradeImagePayloadsInHistory(messages, options = {}) {
|
|
33
|
-
let nextImageNumber = 1;
|
|
34
63
|
let replacedCount = 0;
|
|
35
64
|
let changed = false;
|
|
65
|
+
const imageNumberQueues = new WeakMap();
|
|
66
|
+
for (const entry of collectBase64Images(messages)) {
|
|
67
|
+
const key = entry.block;
|
|
68
|
+
const existing = imageNumberQueues.get(key);
|
|
69
|
+
if (existing) {
|
|
70
|
+
existing.push(entry.imageNumber);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
imageNumberQueues.set(key, [entry.imageNumber]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const takeImageNumber = (block) => {
|
|
77
|
+
const queue = imageNumberQueues.get(block);
|
|
78
|
+
return queue?.shift();
|
|
79
|
+
};
|
|
36
80
|
const placeholderFor = (imageNumber) => ({
|
|
37
81
|
type: "text",
|
|
38
82
|
text: `${IMAGE_HISTORY_PLACEHOLDER_PREFIX}${imageNumber}${IMAGE_HISTORY_PLACEHOLDER_SUFFIX}`,
|
|
@@ -42,11 +86,12 @@ export function downgradeImagePayloadsInHistory(messages, options = {}) {
|
|
|
42
86
|
const out = blocks.map((block) => {
|
|
43
87
|
const placeholderNumber = imageHistoryPlaceholderNumber(block);
|
|
44
88
|
if (placeholderNumber !== undefined) {
|
|
45
|
-
nextImageNumber = Math.max(nextImageNumber, placeholderNumber + 1);
|
|
46
89
|
return block;
|
|
47
90
|
}
|
|
48
91
|
if (isBase64ImageBlock(block)) {
|
|
49
|
-
const imageNumber =
|
|
92
|
+
const imageNumber = takeImageNumber(block);
|
|
93
|
+
if (imageNumber === undefined)
|
|
94
|
+
return block;
|
|
50
95
|
if (preserve)
|
|
51
96
|
return block;
|
|
52
97
|
replacedCount++;
|
|
@@ -99,7 +144,7 @@ function imageHistoryPlaceholderNumber(block) {
|
|
|
99
144
|
const n = Number(match[1]);
|
|
100
145
|
return Number.isSafeInteger(n) && n > 0 ? n : undefined;
|
|
101
146
|
}
|
|
102
|
-
function isBase64ImageBlock(block) {
|
|
147
|
+
export function isBase64ImageBlock(block) {
|
|
103
148
|
if (block.type === "image" &&
|
|
104
149
|
block.source?.type === "base64" &&
|
|
105
150
|
typeof block.source.data === "string" &&
|
|
@@ -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 } 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;
|
|
@@ -48,6 +48,11 @@ export declare class ContextManager {
|
|
|
48
48
|
private lastActualTokens;
|
|
49
49
|
/** Message count at the time lastActualTokens was recorded. */
|
|
50
50
|
private lastActualAtMessageCount;
|
|
51
|
+
/** Heuristic token estimate for the same messages as lastActualTokens. */
|
|
52
|
+
private lastActualAnchorEstimate;
|
|
53
|
+
private lastActualRecordedAt;
|
|
54
|
+
private lastActualProvider;
|
|
55
|
+
private lastActualModel;
|
|
51
56
|
/** Path to session transcript — passed to summary compaction for on-demand access. */
|
|
52
57
|
private transcriptPath;
|
|
53
58
|
/** Notified whenever any compaction tier fires, including microcompact. */
|
|
@@ -65,11 +70,18 @@ export declare class ContextManager {
|
|
|
65
70
|
* Record actual token usage from API response.
|
|
66
71
|
* Used for hybrid estimation: actual + estimate for new messages.
|
|
67
72
|
*/
|
|
68
|
-
recordActualUsage(inputTokens: number, messageCount: number):
|
|
73
|
+
recordActualUsage(inputTokens: number, messageCount: number, messages?: Message[]): ContextUsageAnchor | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Seed actual prompt-token usage from persisted session state.
|
|
76
|
+
* Returns the normalized anchor when accepted; invalid legacy/tampered data is ignored.
|
|
77
|
+
*/
|
|
78
|
+
seedActualUsage(anchor: ContextUsageAnchor | undefined): ContextUsageAnchor | undefined;
|
|
79
|
+
getActualUsageAnchor(): ContextUsageAnchor | undefined;
|
|
69
80
|
/**
|
|
70
81
|
* Best-effort token estimate: uses actual API usage as base if available,
|
|
71
82
|
* plus estimation for messages added since the last API call.
|
|
72
83
|
*/
|
|
84
|
+
private estimateTokensHybridInfo;
|
|
73
85
|
private estimateTokensHybrid;
|
|
74
86
|
/**
|
|
75
87
|
* Set the summarize function (injected by Engine).
|
|
@@ -139,5 +151,7 @@ export declare class ContextManager {
|
|
|
139
151
|
ratio: number;
|
|
140
152
|
needsCompact: boolean;
|
|
141
153
|
needsEmergency: boolean;
|
|
154
|
+
promptTokensSource: PromptTokenSource;
|
|
155
|
+
promptTokensConfidence: PromptTokenConfidence;
|
|
142
156
|
};
|
|
143
157
|
}
|
package/dist/context/manager.js
CHANGED
|
@@ -37,6 +37,9 @@ const DEFAULT_CONFIG = {
|
|
|
37
37
|
function defaultKeepRecent(maxTokens) {
|
|
38
38
|
return Math.max(5, Math.floor(maxTokens / 100_000));
|
|
39
39
|
}
|
|
40
|
+
function positiveFinite(value) {
|
|
41
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
42
|
+
}
|
|
40
43
|
export class ContextManager {
|
|
41
44
|
config;
|
|
42
45
|
summarizeFn;
|
|
@@ -48,6 +51,11 @@ export class ContextManager {
|
|
|
48
51
|
lastActualTokens;
|
|
49
52
|
/** Message count at the time lastActualTokens was recorded. */
|
|
50
53
|
lastActualAtMessageCount;
|
|
54
|
+
/** Heuristic token estimate for the same messages as lastActualTokens. */
|
|
55
|
+
lastActualAnchorEstimate;
|
|
56
|
+
lastActualRecordedAt;
|
|
57
|
+
lastActualProvider;
|
|
58
|
+
lastActualModel;
|
|
51
59
|
/** Path to session transcript — passed to summary compaction for on-demand access. */
|
|
52
60
|
transcriptPath;
|
|
53
61
|
/** Notified whenever any compaction tier fires, including microcompact. */
|
|
@@ -69,23 +77,84 @@ export class ContextManager {
|
|
|
69
77
|
* Record actual token usage from API response.
|
|
70
78
|
* Used for hybrid estimation: actual + estimate for new messages.
|
|
71
79
|
*/
|
|
72
|
-
recordActualUsage(inputTokens, messageCount) {
|
|
73
|
-
|
|
74
|
-
this.
|
|
80
|
+
recordActualUsage(inputTokens, messageCount, messages) {
|
|
81
|
+
const estimateAtAnchor = messages ? estimateTokens(messages) : undefined;
|
|
82
|
+
return this.seedActualUsage({
|
|
83
|
+
promptTokens: inputTokens,
|
|
84
|
+
messageCount,
|
|
85
|
+
...(estimateAtAnchor !== undefined ? { estimateAtAnchor } : {}),
|
|
86
|
+
recordedAt: Date.now(),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Seed actual prompt-token usage from persisted session state.
|
|
91
|
+
* Returns the normalized anchor when accepted; invalid legacy/tampered data is ignored.
|
|
92
|
+
*/
|
|
93
|
+
seedActualUsage(anchor) {
|
|
94
|
+
if (!anchor)
|
|
95
|
+
return undefined;
|
|
96
|
+
if (!positiveFinite(anchor.promptTokens))
|
|
97
|
+
return undefined;
|
|
98
|
+
if (!Number.isSafeInteger(anchor.messageCount) || anchor.messageCount <= 0) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
if (anchor.estimateAtAnchor !== undefined && !positiveFinite(anchor.estimateAtAnchor)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
this.lastActualTokens = anchor.promptTokens;
|
|
105
|
+
this.lastActualAtMessageCount = anchor.messageCount;
|
|
106
|
+
this.lastActualAnchorEstimate = anchor.estimateAtAnchor;
|
|
107
|
+
this.lastActualRecordedAt = positiveFinite(anchor.recordedAt) ? anchor.recordedAt : Date.now();
|
|
108
|
+
this.lastActualProvider = anchor.provider;
|
|
109
|
+
this.lastActualModel = anchor.model;
|
|
110
|
+
return this.getActualUsageAnchor();
|
|
111
|
+
}
|
|
112
|
+
getActualUsageAnchor() {
|
|
113
|
+
if (this.lastActualTokens === undefined ||
|
|
114
|
+
this.lastActualAtMessageCount === undefined ||
|
|
115
|
+
this.lastActualRecordedAt === undefined) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
promptTokens: this.lastActualTokens,
|
|
120
|
+
messageCount: this.lastActualAtMessageCount,
|
|
121
|
+
...(this.lastActualAnchorEstimate !== undefined
|
|
122
|
+
? { estimateAtAnchor: this.lastActualAnchorEstimate }
|
|
123
|
+
: {}),
|
|
124
|
+
recordedAt: this.lastActualRecordedAt,
|
|
125
|
+
...(this.lastActualProvider ? { provider: this.lastActualProvider } : {}),
|
|
126
|
+
...(this.lastActualModel ? { model: this.lastActualModel } : {}),
|
|
127
|
+
};
|
|
75
128
|
}
|
|
76
129
|
/**
|
|
77
130
|
* Best-effort token estimate: uses actual API usage as base if available,
|
|
78
131
|
* plus estimation for messages added since the last API call.
|
|
79
132
|
*/
|
|
80
|
-
|
|
133
|
+
estimateTokensHybridInfo(messages) {
|
|
134
|
+
const currentEstimate = estimateTokens(messages);
|
|
81
135
|
if (this.lastActualTokens !== undefined &&
|
|
82
|
-
this.lastActualAtMessageCount !== undefined
|
|
83
|
-
this.lastActualAtMessageCount < messages.length) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
136
|
+
this.lastActualAtMessageCount !== undefined) {
|
|
137
|
+
if (this.lastActualAtMessageCount < messages.length) {
|
|
138
|
+
const newMessages = messages.slice(this.lastActualAtMessageCount);
|
|
139
|
+
const newTokens = estimateTokens(newMessages);
|
|
140
|
+
return {
|
|
141
|
+
tokens: this.lastActualTokens + newTokens,
|
|
142
|
+
source: "anchor_delta",
|
|
143
|
+
confidence: "medium",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (this.lastActualAnchorEstimate !== undefined && this.lastActualAnchorEstimate > 0) {
|
|
147
|
+
return {
|
|
148
|
+
tokens: Math.round(this.lastActualTokens * (currentEstimate / this.lastActualAnchorEstimate)),
|
|
149
|
+
source: "anchor_rescale",
|
|
150
|
+
confidence: "medium",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
87
153
|
}
|
|
88
|
-
return
|
|
154
|
+
return { tokens: currentEstimate, source: "heuristic_estimate", confidence: "low" };
|
|
155
|
+
}
|
|
156
|
+
estimateTokensHybrid(messages) {
|
|
157
|
+
return this.estimateTokensHybridInfo(messages).tokens;
|
|
89
158
|
}
|
|
90
159
|
/**
|
|
91
160
|
* Set the summarize function (injected by Engine).
|
|
@@ -119,7 +188,7 @@ export class ContextManager {
|
|
|
119
188
|
return { messages, tokens: before, compacted: false, noProgress: true };
|
|
120
189
|
}
|
|
121
190
|
const compacted = applySummaryCompaction(messages, summary, keepRecentN, this.transcriptPath);
|
|
122
|
-
const after =
|
|
191
|
+
const after = this.estimateTokensHybrid(compacted);
|
|
123
192
|
if (after >= before) {
|
|
124
193
|
this.consecutiveSummaryFailures++;
|
|
125
194
|
logger.warn("context.summary_no_progress", {
|
|
@@ -298,6 +367,17 @@ export class ContextManager {
|
|
|
298
367
|
result = this.truncateToolResults(result);
|
|
299
368
|
// Tier 0c: Aggregate tool result budget (per-message)
|
|
300
369
|
result = applyToolResultBudget(result);
|
|
370
|
+
// Tier 0d: same always-on waste-removal passes as manage().
|
|
371
|
+
const dedup = dedupeFileReads(result);
|
|
372
|
+
if (dedup.clearedCount > 0) {
|
|
373
|
+
result = dedup.messages;
|
|
374
|
+
logger.info("context.dedupe_file_reads", { cleared: dedup.clearedCount });
|
|
375
|
+
}
|
|
376
|
+
const masked = maskOldObservations(result);
|
|
377
|
+
if (masked.maskedCount > 0) {
|
|
378
|
+
result = masked.messages;
|
|
379
|
+
logger.info("context.mask_browser_snapshots", { masked: masked.maskedCount });
|
|
380
|
+
}
|
|
301
381
|
// Tier 1: microcompact — see manage() for the rationale on the floor.
|
|
302
382
|
const preTier1Tokens = this.estimateTokensHybrid(result);
|
|
303
383
|
const microFloorGate = this.config.maxTokens * this.config.microcompactFloorRatio;
|
|
@@ -342,6 +422,9 @@ export class ContextManager {
|
|
|
342
422
|
ratio >= this.config.microcompactFloorRatio &&
|
|
343
423
|
ratio < this.config.compactAtRatio;
|
|
344
424
|
const shouldEscalateNoOpMicro = noOpMicroSpinBand && !this.suppressNoOpMicroSummaryUntilCompact;
|
|
425
|
+
const snipGate = this.config.maxTokens * this.config.compactAtRatio;
|
|
426
|
+
const windowGate = this.config.maxTokens * (this.config.compactAtRatio + 0.05);
|
|
427
|
+
const emergencyGate = this.config.maxTokens * this.config.summarizeAtRatio;
|
|
345
428
|
// Tier 2: LLM summary if approaching limit, or if micro was the only tier
|
|
346
429
|
// available in the 0.70-0.85 band and it freed nothing.
|
|
347
430
|
if (ratio >= this.config.compactAtRatio || shouldEscalateNoOpMicro) {
|
|
@@ -350,7 +433,8 @@ export class ContextManager {
|
|
|
350
433
|
tokens = summarized.tokens;
|
|
351
434
|
if (summarized.compacted) {
|
|
352
435
|
this.suppressNoOpMicroSummaryUntilCompact = false;
|
|
353
|
-
|
|
436
|
+
if (tokens <= snipGate)
|
|
437
|
+
return result;
|
|
354
438
|
}
|
|
355
439
|
if (shouldEscalateNoOpMicro && summarized.noProgress) {
|
|
356
440
|
this.suppressNoOpMicroSummaryUntilCompact = true;
|
|
@@ -362,13 +446,10 @@ export class ContextManager {
|
|
|
362
446
|
});
|
|
363
447
|
}
|
|
364
448
|
}
|
|
365
|
-
// Reuse the `tokens` we already computed above. We
|
|
366
|
-
//
|
|
367
|
-
//
|
|
449
|
+
// Reuse the `tokens` we already computed above. We get here if no summary
|
|
450
|
+
// compacted the prompt, or if summary helped but the hybrid estimate is
|
|
451
|
+
// still above the fallback gate.
|
|
368
452
|
let live = tokens;
|
|
369
|
-
const snipGate = this.config.maxTokens * this.config.compactAtRatio;
|
|
370
|
-
const windowGate = this.config.maxTokens * (this.config.compactAtRatio + 0.05);
|
|
371
|
-
const emergencyGate = this.config.maxTokens * this.config.summarizeAtRatio;
|
|
372
453
|
// Tier 2 fallback: snip first (cheapest sync option)
|
|
373
454
|
if (live > snipGate) {
|
|
374
455
|
const before = live;
|
|
@@ -507,13 +588,16 @@ export class ContextManager {
|
|
|
507
588
|
* Check if context is approaching limits.
|
|
508
589
|
*/
|
|
509
590
|
checkLimits(messages) {
|
|
510
|
-
const
|
|
591
|
+
const estimate = this.estimateTokensHybridInfo(messages);
|
|
592
|
+
const tokens = estimate.tokens;
|
|
511
593
|
const ratio = tokens / this.config.maxTokens;
|
|
512
594
|
return {
|
|
513
595
|
tokens,
|
|
514
596
|
ratio,
|
|
515
597
|
needsCompact: ratio >= this.config.compactAtRatio,
|
|
516
598
|
needsEmergency: ratio >= this.config.summarizeAtRatio,
|
|
599
|
+
promptTokensSource: estimate.source,
|
|
600
|
+
promptTokensConfidence: estimate.confidence,
|
|
517
601
|
};
|
|
518
602
|
}
|
|
519
603
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Credential, CredentialType } from "./types.js";
|
|
2
|
+
import type { SettingsScope } from "../settings/manager.js";
|
|
3
|
+
import type { Transport } from "../protocol/transport.js";
|
|
4
|
+
export type CredentialAccessScope = "full" | "project";
|
|
5
|
+
export interface CredentialMetadata {
|
|
6
|
+
id: string;
|
|
7
|
+
type: CredentialType;
|
|
8
|
+
label: string;
|
|
9
|
+
autoUseByAI?: boolean;
|
|
10
|
+
autoInjectByAI?: boolean;
|
|
11
|
+
exposeAsEnv?: string;
|
|
12
|
+
meta?: Credential["meta"];
|
|
13
|
+
hasSecret: boolean;
|
|
14
|
+
secretHint?: string;
|
|
15
|
+
oauthStatus?: import("./types.js").OAuthCredentialPublicStatus;
|
|
16
|
+
}
|
|
17
|
+
export interface CredentialAccess {
|
|
18
|
+
listMasked(cwd: string | undefined, scope: CredentialAccessScope): CredentialMetadata[];
|
|
19
|
+
resolveMeta(cwd: string | undefined, id: string, scope: CredentialAccessScope): CredentialMetadata | undefined;
|
|
20
|
+
envExposures(cwd: string | undefined, scope: CredentialAccessScope): Record<string, string>;
|
|
21
|
+
resolveValue?(req: {
|
|
22
|
+
cwd?: string;
|
|
23
|
+
id: string;
|
|
24
|
+
scope: CredentialAccessScope;
|
|
25
|
+
purpose: "use" | "mcp";
|
|
26
|
+
}): Promise<string>;
|
|
27
|
+
materializeCookie?(req: {
|
|
28
|
+
cwd?: string;
|
|
29
|
+
id: string;
|
|
30
|
+
scope: CredentialAccessScope;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
cookiesFile: string;
|
|
33
|
+
count: number;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export interface CredentialSnapshotEntry {
|
|
37
|
+
cwd?: string;
|
|
38
|
+
full: CredentialMetadata[];
|
|
39
|
+
project: CredentialMetadata[];
|
|
40
|
+
envFull: Record<string, string>;
|
|
41
|
+
envProject: Record<string, string>;
|
|
42
|
+
}
|
|
43
|
+
export interface CredentialSnapshot {
|
|
44
|
+
revision: number;
|
|
45
|
+
entries: CredentialSnapshotEntry[];
|
|
46
|
+
}
|
|
47
|
+
export declare function setDefaultCredentialAccess(access: CredentialAccess | null | undefined): void;
|
|
48
|
+
export declare function getCredentialAccess(): CredentialAccess;
|
|
49
|
+
export declare function createIpcCredentialAccess(transport: Pick<Transport, "send" | "onMessage">): CredentialAccess;
|
|
50
|
+
export declare function credentialAccessScope(scope: SettingsScope | undefined): CredentialAccessScope;
|
|
51
|
+
export declare function isCredentialSecretAvailable(secret: string | undefined): secret is string;
|
|
52
|
+
export declare const localCredentialAccess: CredentialAccess;
|
|
53
|
+
export declare function materializeCookieSecret(credentialId: string, secret: string): {
|
|
54
|
+
cookiesFile: string;
|
|
55
|
+
count: number;
|
|
56
|
+
};
|
|
57
|
+
export declare function sweepStaleCredentialCookieFiles(now?: number): void;
|