@cjhyy/code-shell-core 0.6.0-rc.13 → 0.6.0-rc.15
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/capability-control/service.d.ts +2 -0
- package/dist/capability-control/service.js +4 -2
- package/dist/cc-orchestrator/codex-session-history.js +1 -1
- package/dist/cc-orchestrator/external-agent-bindings.js +1 -1
- package/dist/cc-orchestrator/external-agent-session-store.js +3 -1
- package/dist/cc-orchestrator/session-history.js +2 -2
- package/dist/cli/agent-server-stdio.js +7 -2
- package/dist/context/compaction.d.ts +8 -1
- package/dist/context/compaction.js +49 -4
- package/dist/context/manager.d.ts +11 -2
- package/dist/context/manager.js +64 -3
- package/dist/credentials/inject-credential-tool.d.ts +3 -1
- package/dist/credentials/inject-credential-tool.js +29 -10
- package/dist/credentials/use-credential-tool.d.ts +1 -0
- package/dist/credentials/use-credential-tool.js +3 -0
- package/dist/engine/engine.d.ts +9 -0
- package/dist/engine/engine.js +96 -17
- package/dist/engine/turn-loop.d.ts +3 -1
- package/dist/engine/turn-loop.js +4 -2
- 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 -127
- package/dist/git/worktree.js +5 -464
- package/dist/index.d.ts +28 -27
- package/dist/index.js +24 -24
- 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/parseSource.d.ts +4 -1
- package/dist/plugins/installer/parseSource.js +28 -10
- 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/preset/index.d.ts +1 -0
- package/dist/preset/index.js +6 -0
- package/dist/prompt/sections/base.md +1 -1
- package/dist/protocol/chat-session-manager.js +7 -0
- package/dist/protocol/server.d.ts +10 -0
- package/dist/protocol/server.js +88 -4
- package/dist/protocol/types.d.ts +6 -0
- package/dist/protocol/types.js +2 -0
- package/dist/runtime/background-shell.js +2 -3
- 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 +56 -17
- package/dist/session/memory.js +337 -78
- package/dist/session/session-manager.d.ts +1 -1
- package/dist/session/session-manager.js +6 -6
- package/dist/session/transcript.d.ts +1 -1
- package/dist/session/transcript.js +17 -5
- package/dist/settings/manager.js +43 -12
- package/dist/settings/schema.d.ts +21 -0
- package/dist/settings/schema.js +12 -0
- package/dist/tool-system/builtin/index.js +18 -8
- package/dist/tool-system/builtin/memory.js +40 -8
- package/dist/tool-system/builtin/view-image.d.ts +2 -2
- package/dist/tool-system/builtin/view-image.js +100 -19
- package/dist/tool-system/builtin/worktree.d.ts +2 -0
- package/dist/tool-system/builtin/worktree.js +59 -11
- package/dist/tool-system/context.d.ts +11 -1
- package/dist/tool-system/mcp-manager.d.ts +8 -5
- package/dist/tool-system/mcp-manager.js +85 -55
- package/dist/tool-system/path-policy.d.ts +2 -0
- package/dist/tool-system/path-policy.js +28 -12
- package/dist/tool-system/workspace-bridge.d.ts +11 -0
- package/dist/tool-system/workspace-bridge.js +1 -0
- package/dist/types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -31,9 +31,11 @@ export interface CapabilityServiceDeps {
|
|
|
31
31
|
readInstalledPlugins: () => InstalledPluginsV2;
|
|
32
32
|
resolveBuiltinToolNames: (o?: {
|
|
33
33
|
preset?: string;
|
|
34
|
+
host?: "desktop";
|
|
34
35
|
enabledBuiltinTools?: string[];
|
|
35
36
|
disabledBuiltinTools?: string[];
|
|
36
37
|
}) => string[];
|
|
38
|
+
builtinToolHost?: "desktop";
|
|
37
39
|
}
|
|
38
40
|
export declare class CapabilityService {
|
|
39
41
|
private readonly deps;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { CapabilityNotFoundError } from "./types.js";
|
|
15
15
|
import { projectBuiltin, projectMcp, projectSkills, projectPlugins, projectAgents, } from "./project.js";
|
|
16
|
-
import { applyOverride, bucketForKind, overrideTokenForId, overrideFor
|
|
16
|
+
import { applyOverride, bucketForKind, overrideTokenForId, overrideFor } from "./overlay.js";
|
|
17
17
|
export class CapabilityService {
|
|
18
18
|
deps;
|
|
19
19
|
constructor(deps) {
|
|
@@ -30,12 +30,14 @@ export class CapabilityService {
|
|
|
30
30
|
const agent = (s.agent ?? {});
|
|
31
31
|
const tools = this.deps.registry.listToolsDetailed();
|
|
32
32
|
const preset = agent.preset;
|
|
33
|
+
const host = this.deps.builtinToolHost;
|
|
33
34
|
const base = [
|
|
34
35
|
...projectBuiltin({
|
|
35
36
|
tools: tools.filter((t) => t.source === "builtin"),
|
|
36
|
-
presetDefaults: this.deps.resolveBuiltinToolNames({ preset }),
|
|
37
|
+
presetDefaults: this.deps.resolveBuiltinToolNames({ preset, host }),
|
|
37
38
|
effective: this.deps.resolveBuiltinToolNames({
|
|
38
39
|
preset,
|
|
40
|
+
host,
|
|
39
41
|
enabledBuiltinTools: agent.enabledBuiltinTools ?? [],
|
|
40
42
|
disabledBuiltinTools: agent.disabledBuiltinTools ?? [],
|
|
41
43
|
}),
|
|
@@ -52,7 +52,7 @@ export function readCodexRecentHistory(cwd, threadId, limit, codexHome = join(ho
|
|
|
52
52
|
const t = textOf(p.content).trim();
|
|
53
53
|
if (!t || t.startsWith("<environment_context>"))
|
|
54
54
|
continue;
|
|
55
|
-
all.push({ role: p.role, text: t
|
|
55
|
+
all.push({ role: p.role, text: t });
|
|
56
56
|
}
|
|
57
57
|
else if (p.type === "function_call" || p.type === "custom_tool_call") {
|
|
58
58
|
const tool = { name: typeof p.name === "string" ? p.name : "tool", summary: summaryOf(p), args: argsOf(p) };
|
|
@@ -43,7 +43,7 @@ export class ExternalAgentBindingStore {
|
|
|
43
43
|
}
|
|
44
44
|
catch (err) {
|
|
45
45
|
if (opts.failOnCorrupt) {
|
|
46
|
-
throw new Error(`external agent bindings file is corrupt or unreadable: ${err instanceof Error ? err.message : String(err)}
|
|
46
|
+
throw new Error(`external agent bindings file is corrupt or unreadable: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
47
47
|
}
|
|
48
48
|
return { bindings: {} };
|
|
49
49
|
}
|
|
@@ -90,7 +90,9 @@ export class ExternalAgentSessionStore {
|
|
|
90
90
|
if (removeStaleLock(lockDir))
|
|
91
91
|
continue;
|
|
92
92
|
if (Date.now() >= deadline) {
|
|
93
|
-
throw new Error(`timed out waiting for external agent session store lock: ${lockDir}
|
|
93
|
+
throw new Error(`timed out waiting for external agent session store lock: ${lockDir}`, {
|
|
94
|
+
cause: err,
|
|
95
|
+
});
|
|
94
96
|
}
|
|
95
97
|
sleepSync(LOCK_POLL_MS);
|
|
96
98
|
}
|
|
@@ -51,14 +51,14 @@ export function readRecentHistory(cwd, sessionId, limit, claudeHome = join(homed
|
|
|
51
51
|
const t = textOf(d.message?.content).trim();
|
|
52
52
|
if (!t || NOISE.some((n) => t.startsWith(n)))
|
|
53
53
|
continue;
|
|
54
|
-
all.push({ role: "user", text: t
|
|
54
|
+
all.push({ role: "user", text: t });
|
|
55
55
|
}
|
|
56
56
|
else if (d.type === "assistant") {
|
|
57
57
|
const t = textOf(d.message?.content).trim();
|
|
58
58
|
const tools = toolsOf(d.message?.content);
|
|
59
59
|
if (!t && tools.length === 0)
|
|
60
60
|
continue;
|
|
61
|
-
all.push({ role: "assistant", text: t
|
|
61
|
+
all.push({ role: "assistant", text: t, tools: tools.length ? tools : undefined });
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
const lim = limit > 0 ? limit : 20;
|
|
@@ -106,8 +106,7 @@ catch (err) {
|
|
|
106
106
|
// knob; imageDetail from settings.images.detail).
|
|
107
107
|
const seedLlm = resolveLLMConfigForTag(settings, "text", settings.defaults?.text);
|
|
108
108
|
if (!seedLlm) {
|
|
109
|
-
console.error(`[agent-server] 没有可用的文本模型连接(defaults.text=${settings.defaults?.text ?? "未设置"})。` +
|
|
110
|
-
`请在「连接」页添加并填写凭证。`);
|
|
109
|
+
console.error(`[agent-server] 没有可用的文本模型连接(defaults.text=${settings.defaults?.text ?? "未设置"})。` + `请在「连接」页添加并填写凭证。`);
|
|
111
110
|
process.exit(1);
|
|
112
111
|
}
|
|
113
112
|
const llmConfig = seedLlm;
|
|
@@ -115,6 +114,10 @@ const llmConfig = seedLlm;
|
|
|
115
114
|
const seedEngine = new Engine({
|
|
116
115
|
llm: llmConfig,
|
|
117
116
|
cwd,
|
|
117
|
+
preset: settings.agent.preset,
|
|
118
|
+
enabledBuiltinTools: settings.agent.enabledBuiltinTools,
|
|
119
|
+
disabledBuiltinTools: settings.agent.disabledBuiltinTools,
|
|
120
|
+
builtinToolHost: "desktop",
|
|
118
121
|
settingsScope: "full",
|
|
119
122
|
// No runtime — Engine.populateModelPoolFromSettings() runs in ctor.
|
|
120
123
|
});
|
|
@@ -217,6 +220,7 @@ const chatManager = new ChatSessionManager({
|
|
|
217
220
|
// This stdio worker exists only to serve the desktop app, so every
|
|
218
221
|
// session it creates is a desktop-origin session.
|
|
219
222
|
origin: "desktop",
|
|
223
|
+
builtinToolHost: "desktop",
|
|
220
224
|
// Inherit full scope so spawned subagents read user config too.
|
|
221
225
|
settingsScope: "full",
|
|
222
226
|
// MCP servers from settings — the worker reads the full disk
|
|
@@ -286,6 +290,7 @@ const goalDiskReader = new SessionManager();
|
|
|
286
290
|
const agentServer = new AgentServer({
|
|
287
291
|
chatManager,
|
|
288
292
|
transport: stdioTransport,
|
|
293
|
+
workspaceBridge: true,
|
|
289
294
|
// Config hot-reload (layer 2) reads disk through the SAME closure the
|
|
290
295
|
// engineFactory uses for new sessions, so a reloaded running session and a
|
|
291
296
|
// 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 } from "../types.js";
|
|
9
9
|
export interface ContextManagerConfig {
|
|
10
10
|
maxTokens: number;
|
|
11
11
|
compactAtRatio: number;
|
|
@@ -50,6 +50,9 @@ export declare class ContextManager {
|
|
|
50
50
|
private lastActualAtMessageCount;
|
|
51
51
|
/** Heuristic token estimate for the same messages as lastActualTokens. */
|
|
52
52
|
private lastActualAnchorEstimate;
|
|
53
|
+
private lastActualRecordedAt;
|
|
54
|
+
private lastActualProvider;
|
|
55
|
+
private lastActualModel;
|
|
53
56
|
/** Path to session transcript — passed to summary compaction for on-demand access. */
|
|
54
57
|
private transcriptPath;
|
|
55
58
|
/** Notified whenever any compaction tier fires, including microcompact. */
|
|
@@ -67,7 +70,13 @@ export declare class ContextManager {
|
|
|
67
70
|
* Record actual token usage from API response.
|
|
68
71
|
* Used for hybrid estimation: actual + estimate for new messages.
|
|
69
72
|
*/
|
|
70
|
-
recordActualUsage(inputTokens: number, messageCount: number, messages?: Message[]):
|
|
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;
|
|
71
80
|
/**
|
|
72
81
|
* Best-effort token estimate: uses actual API usage as base if available,
|
|
73
82
|
* plus estimation for messages added since the last API call.
|
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;
|
|
@@ -50,6 +53,9 @@ export class ContextManager {
|
|
|
50
53
|
lastActualAtMessageCount;
|
|
51
54
|
/** Heuristic token estimate for the same messages as lastActualTokens. */
|
|
52
55
|
lastActualAnchorEstimate;
|
|
56
|
+
lastActualRecordedAt;
|
|
57
|
+
lastActualProvider;
|
|
58
|
+
lastActualModel;
|
|
53
59
|
/** Path to session transcript — passed to summary compaction for on-demand access. */
|
|
54
60
|
transcriptPath;
|
|
55
61
|
/** Notified whenever any compaction tier fires, including microcompact. */
|
|
@@ -72,9 +78,53 @@ export class ContextManager {
|
|
|
72
78
|
* Used for hybrid estimation: actual + estimate for new messages.
|
|
73
79
|
*/
|
|
74
80
|
recordActualUsage(inputTokens, messageCount, messages) {
|
|
75
|
-
|
|
76
|
-
this.
|
|
77
|
-
|
|
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
|
+
};
|
|
78
128
|
}
|
|
79
129
|
/**
|
|
80
130
|
* Best-effort token estimate: uses actual API usage as base if available,
|
|
@@ -306,6 +356,17 @@ export class ContextManager {
|
|
|
306
356
|
result = this.truncateToolResults(result);
|
|
307
357
|
// Tier 0c: Aggregate tool result budget (per-message)
|
|
308
358
|
result = applyToolResultBudget(result);
|
|
359
|
+
// Tier 0d: same always-on waste-removal passes as manage().
|
|
360
|
+
const dedup = dedupeFileReads(result);
|
|
361
|
+
if (dedup.clearedCount > 0) {
|
|
362
|
+
result = dedup.messages;
|
|
363
|
+
logger.info("context.dedupe_file_reads", { cleared: dedup.clearedCount });
|
|
364
|
+
}
|
|
365
|
+
const masked = maskOldObservations(result);
|
|
366
|
+
if (masked.maskedCount > 0) {
|
|
367
|
+
result = masked.messages;
|
|
368
|
+
logger.info("context.mask_browser_snapshots", { masked: masked.maskedCount });
|
|
369
|
+
}
|
|
309
370
|
// Tier 1: microcompact — see manage() for the rationale on the floor.
|
|
310
371
|
const preTier1Tokens = this.estimateTokensHybrid(result);
|
|
311
372
|
const microFloorGate = this.config.maxTokens * this.config.microcompactFloorRatio;
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { ToolDefinition } from "../types.js";
|
|
14
14
|
import type { ToolContext } from "../tool-system/context.js";
|
|
15
|
+
import { type SettingsScope } from "../settings/manager.js";
|
|
15
16
|
export declare const injectCredentialToolDef: ToolDefinition;
|
|
16
17
|
/** 该工具仅在有 cookie 凭证 且 宿主接了注入回调时才可见(BUILTIN_TOOL_GUARDS)。 */
|
|
17
|
-
export declare function isInjectCredentialAvailable(cwd: string): boolean;
|
|
18
|
+
export declare function isInjectCredentialAvailable(cwd: string, settingsScope?: SettingsScope): boolean;
|
|
18
19
|
export declare function injectCredentialTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
|
|
19
20
|
/** 测试钩子:清空会话 allow 集。 */
|
|
20
21
|
export declare function __resetInjectCredentialSessionAllowForTests(): void;
|
|
22
|
+
export declare function clearInjectCredentialSessionAllow(sessionId: string): void;
|
|
@@ -62,9 +62,14 @@ function sessionAllowFor(ctx) {
|
|
|
62
62
|
}
|
|
63
63
|
return set;
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
// CredentialStore only distinguishes full user+project from project-only.
|
|
66
|
+
// Isolated engines must be at least as restrictive as project-scoped engines.
|
|
67
|
+
function credentialScope(scope) {
|
|
68
|
+
return scope === "full" || scope === undefined ? "full" : "project";
|
|
69
|
+
}
|
|
70
|
+
function readAutoApprove(cwd, scope) {
|
|
66
71
|
try {
|
|
67
|
-
const s = new SettingsManager(cwd, "full").get();
|
|
72
|
+
const s = new SettingsManager(cwd, scope === "full" ? "full" : "project").get();
|
|
68
73
|
return s.credentialUse?.autoApprove === true;
|
|
69
74
|
}
|
|
70
75
|
catch {
|
|
@@ -72,9 +77,11 @@ function readAutoApprove(cwd) {
|
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
79
|
/** 该工具仅在有 cookie 凭证 且 宿主接了注入回调时才可见(BUILTIN_TOOL_GUARDS)。 */
|
|
75
|
-
export function isInjectCredentialAvailable(cwd) {
|
|
80
|
+
export function isInjectCredentialAvailable(cwd, settingsScope) {
|
|
76
81
|
try {
|
|
77
|
-
return new CredentialStore(cwd)
|
|
82
|
+
return new CredentialStore(cwd)
|
|
83
|
+
.listMasked(credentialScope(settingsScope))
|
|
84
|
+
.some((c) => c.type === "cookie");
|
|
78
85
|
}
|
|
79
86
|
catch {
|
|
80
87
|
return false;
|
|
@@ -82,10 +89,14 @@ export function isInjectCredentialAvailable(cwd) {
|
|
|
82
89
|
}
|
|
83
90
|
export async function injectCredentialTool(args, ctx) {
|
|
84
91
|
const cwd = ctx?.cwd ?? process.cwd();
|
|
92
|
+
const scope = credentialScope(ctx?.settingsScope);
|
|
85
93
|
const id = typeof args.id === "string" ? args.id.trim() : "";
|
|
86
94
|
const purpose = typeof args.purpose === "string" ? args.purpose : undefined;
|
|
87
95
|
if (!id) {
|
|
88
|
-
return json({
|
|
96
|
+
return json({
|
|
97
|
+
kind: "error",
|
|
98
|
+
error: "缺少 id。先用 UseCredential(无参)列出凭证,再用 cookie 凭证的 id 调本工具。",
|
|
99
|
+
});
|
|
89
100
|
}
|
|
90
101
|
if (!ctx?.injectCredentialToBrowser) {
|
|
91
102
|
return json({
|
|
@@ -93,17 +104,22 @@ export async function injectCredentialTool(args, ctx) {
|
|
|
93
104
|
error: "当前环境无内置浏览器(headless/无面板),无法注入。请改用 UseCredential 取 cookie 走 HTTP 请求。",
|
|
94
105
|
});
|
|
95
106
|
}
|
|
96
|
-
const cred = new CredentialStore(cwd).resolve(id);
|
|
107
|
+
const cred = new CredentialStore(cwd).resolve(id, scope);
|
|
97
108
|
if (!cred) {
|
|
98
|
-
return json({
|
|
109
|
+
return json({
|
|
110
|
+
kind: "error",
|
|
111
|
+
error: `凭证不存在: "${id}"。调用 UseCredential(无参)可列出可用凭证。`,
|
|
112
|
+
});
|
|
99
113
|
}
|
|
100
114
|
if (cred.type !== "cookie") {
|
|
101
115
|
return json({ kind: "error", error: `凭证「${cred.label}」不是 cookie 类型,不能注入浏览器。` });
|
|
102
116
|
}
|
|
103
117
|
// 过门:复用三档,但用该凭证的 autoInjectByAI(不是 autoUseByAI)。
|
|
104
|
-
const ask = ctx.askUser
|
|
118
|
+
const ask = ctx.askUser
|
|
119
|
+
? (q, opts) => ctx.askUser(q, opts)
|
|
120
|
+
: undefined;
|
|
105
121
|
const decision = await credentialUseGate({ id: cred.id, label: cred.label, purpose }, {
|
|
106
|
-
autoApprove: readAutoApprove(cwd),
|
|
122
|
+
autoApprove: readAutoApprove(cwd, scope),
|
|
107
123
|
credentialAutoUse: cred.autoInjectByAI === true,
|
|
108
124
|
sessionAllow: sessionAllowFor(ctx),
|
|
109
125
|
ask,
|
|
@@ -115,7 +131,7 @@ export async function injectCredentialTool(args, ctx) {
|
|
|
115
131
|
return json({ kind: "error", error: msg });
|
|
116
132
|
}
|
|
117
133
|
// 跨进程触发宿主注入。
|
|
118
|
-
const res = await ctx.injectCredentialToBrowser(cred.id);
|
|
134
|
+
const res = await ctx.injectCredentialToBrowser(cred.id, scope);
|
|
119
135
|
if (!res.ok) {
|
|
120
136
|
return json({ kind: "error", error: res.error ?? "注入浏览器失败(宿主未返回成功)。" });
|
|
121
137
|
}
|
|
@@ -128,3 +144,6 @@ function json(r) {
|
|
|
128
144
|
export function __resetInjectCredentialSessionAllowForTests() {
|
|
129
145
|
injectSessionAllowByEngine.clear();
|
|
130
146
|
}
|
|
147
|
+
export function clearInjectCredentialSessionAllow(sessionId) {
|
|
148
|
+
injectSessionAllowByEngine.delete(sessionId);
|
|
149
|
+
}
|
|
@@ -27,3 +27,4 @@ export declare function sweepStaleCredentialCookies(now?: number): void;
|
|
|
27
27
|
export declare function useCredentialTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
|
|
28
28
|
/** 测试钩子:清空会话 allow 集(避免跨用例污染)。 */
|
|
29
29
|
export declare function __resetCredentialSessionAllowForTests(): void;
|
|
30
|
+
export declare function clearCredentialSessionAllow(sessionId: string): void;
|
package/dist/engine/engine.d.ts
CHANGED
|
@@ -262,6 +262,8 @@ export declare class Engine {
|
|
|
262
262
|
* clear "no browser panel" error.
|
|
263
263
|
*/
|
|
264
264
|
setBrowserBridge(bridge: import("../tool-system/browser-bridge.js").BrowserBridge | undefined): void;
|
|
265
|
+
/** Inject the host-backed workspace bridge after construction. */
|
|
266
|
+
setWorkspaceBridge(bridge: import("../tool-system/workspace-bridge.js").WorkspaceBridge | undefined): void;
|
|
265
267
|
/**
|
|
266
268
|
* Queue a user message to be spliced into the in-flight run for `sessionId`
|
|
267
269
|
* at the next turn-loop step boundary — the 不打断 steering path (vs cancel +
|
|
@@ -461,6 +463,12 @@ export declare class Engine {
|
|
|
461
463
|
* session with no active goal is a no-op returning false.
|
|
462
464
|
*/
|
|
463
465
|
clearGoal(sessionId: string): boolean;
|
|
466
|
+
/**
|
|
467
|
+
* Reset a session's workspace pointer back to its main root. If the session is
|
|
468
|
+
* actively running, mutate that live SessionBundle first so the run's next
|
|
469
|
+
* saveState cannot resurrect a stale worktree pointer.
|
|
470
|
+
*/
|
|
471
|
+
releaseSessionWorkspace(sessionId: string): import("../types.js").SessionWorkspace | null;
|
|
464
472
|
injectContext(sessionId: string, content: string): void;
|
|
465
473
|
/**
|
|
466
474
|
* Force context compaction on a session.
|
|
@@ -624,6 +632,7 @@ export declare class Engine {
|
|
|
624
632
|
linux?: string;
|
|
625
633
|
windows?: string;
|
|
626
634
|
} | undefined;
|
|
635
|
+
readWorktreeBranchPrefix(cwd?: string): string | undefined;
|
|
627
636
|
resolveWorktreeSetupSandbox(cwd: string): Promise<SandboxBackend | undefined>;
|
|
628
637
|
readWorktreeSetupShellEnv(cwd?: string): Record<string, string> | undefined;
|
|
629
638
|
buildToolContext(): ToolContext;
|