@cjhyy/code-shell-core 0.6.0-rc.11 → 0.6.0-rc.13
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/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-session-store.d.ts +23 -0
- package/dist/cc-orchestrator/external-agent-session-store.js +144 -0
- package/dist/context/manager.d.ts +3 -1
- package/dist/context/manager.js +24 -15
- package/dist/credentials/types.d.ts +2 -2
- package/dist/engine/engine.d.ts +5 -1
- package/dist/engine/engine.js +92 -51
- package/dist/engine/turn-loop.js +1 -1
- package/dist/git/worktree.d.ts +49 -6
- package/dist/git/worktree.js +265 -31
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/logging/logger.js +6 -6
- package/dist/plugins/installer/installFromSource.js +9 -1
- package/dist/plugins/installer/sourcePath.d.ts +9 -0
- package/dist/plugins/installer/sourcePath.js +50 -0
- package/dist/plugins/pluginInstaller.js +24 -22
- package/dist/protocol/chat-session-manager.d.ts +1 -0
- package/dist/protocol/chat-session-manager.js +13 -0
- package/dist/protocol/server.d.ts +8 -0
- package/dist/protocol/server.js +45 -8
- package/dist/protocol/types.d.ts +4 -0
- package/dist/protocol/types.js +4 -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/session/session-manager.d.ts +35 -1
- package/dist/session/session-manager.js +189 -2
- package/dist/settings/manager.d.ts +1 -0
- package/dist/settings/manager.js +45 -26
- package/dist/settings/schema-export.d.ts +2 -3
- package/dist/settings/schema-export.js +2 -3
- package/dist/tool-system/builtin/background-jobs.d.ts +8 -1
- package/dist/tool-system/builtin/background-jobs.js +8 -1
- package/dist/tool-system/builtin/config.d.ts +2 -1
- package/dist/tool-system/builtin/config.js +16 -11
- package/dist/tool-system/builtin/drive-claude-code.d.ts +15 -2
- package/dist/tool-system/builtin/drive-claude-code.js +174 -39
- 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.js +5 -1
- package/dist/tool-system/builtin/lsp.d.ts +2 -1
- package/dist/tool-system/builtin/lsp.js +6 -3
- package/dist/tool-system/builtin/notebook-edit.js +5 -2
- package/dist/tool-system/builtin/read.js +5 -2
- package/dist/tool-system/builtin/worktree.d.ts +2 -4
- package/dist/tool-system/builtin/worktree.js +250 -75
- package/dist/tool-system/builtin/write.js +5 -3
- package/dist/tool-system/context.d.ts +12 -0
- package/dist/tool-system/mcp-manager.d.ts +8 -0
- package/dist/tool-system/mcp-manager.js +32 -11
- package/dist/types.d.ts +12 -0
- package/dist/utils/toolDisplay.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
export function normalizeCwdPath(cwd) {
|
|
4
|
+
const resolved = resolve(cwd);
|
|
5
|
+
try {
|
|
6
|
+
return realpathSync(resolved);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return resolved;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function isExistingDirectory(cwd) {
|
|
13
|
+
try {
|
|
14
|
+
return statSync(cwd).isDirectory();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type ExternalAgentCli = "claude" | "codex";
|
|
2
|
+
export interface ExternalAgentRunBinding {
|
|
3
|
+
cli: ExternalAgentCli;
|
|
4
|
+
externalSessionId: string;
|
|
5
|
+
codeShellSessionId: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
worktreePath?: string;
|
|
8
|
+
worktreeBranch?: string;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
lastUsedAt: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function externalAgentBindingsPath(home?: string): string;
|
|
13
|
+
export declare class ExternalAgentBindingStore {
|
|
14
|
+
private readonly filePath?;
|
|
15
|
+
constructor(filePath?: string | undefined);
|
|
16
|
+
get(externalSessionId: string): ExternalAgentRunBinding | undefined;
|
|
17
|
+
upsert(next: Omit<ExternalAgentRunBinding, "createdAt" | "lastUsedAt">): ExternalAgentRunBinding;
|
|
18
|
+
private read;
|
|
19
|
+
private write;
|
|
20
|
+
private file;
|
|
21
|
+
}
|
|
22
|
+
export declare function detectExternalAgentWorktree(cwd: string): {
|
|
23
|
+
worktreePath?: string;
|
|
24
|
+
worktreeBranch?: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function externalAgentResumeCwdError(binding: ExternalAgentRunBinding): string | undefined;
|
|
27
|
+
export declare const externalAgentBindingStore: ExternalAgentBindingStore;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { codeShellHome, SessionManager } from "../session/session-manager.js";
|
|
5
|
+
export function externalAgentBindingsPath(home = codeShellHome()) {
|
|
6
|
+
return join(home, "external-agents", "bindings.json");
|
|
7
|
+
}
|
|
8
|
+
export class ExternalAgentBindingStore {
|
|
9
|
+
filePath;
|
|
10
|
+
constructor(filePath) {
|
|
11
|
+
this.filePath = filePath;
|
|
12
|
+
}
|
|
13
|
+
get(externalSessionId) {
|
|
14
|
+
return this.read({ failOnCorrupt: true }).bindings[externalSessionId];
|
|
15
|
+
}
|
|
16
|
+
upsert(next) {
|
|
17
|
+
const data = this.read({ failOnCorrupt: false });
|
|
18
|
+
const existing = data.bindings[next.externalSessionId];
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const binding = {
|
|
21
|
+
...next,
|
|
22
|
+
codeShellSessionId: next.codeShellSessionId || existing?.codeShellSessionId || "",
|
|
23
|
+
createdAt: existing?.createdAt ?? now,
|
|
24
|
+
lastUsedAt: now,
|
|
25
|
+
};
|
|
26
|
+
data.bindings[next.externalSessionId] = binding;
|
|
27
|
+
this.write(data);
|
|
28
|
+
return binding;
|
|
29
|
+
}
|
|
30
|
+
read(opts) {
|
|
31
|
+
const file = this.file();
|
|
32
|
+
if (!existsSync(file))
|
|
33
|
+
return { bindings: {} };
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(readFileSync(file, "utf-8"));
|
|
36
|
+
if (parsed && typeof parsed.bindings === "object" && parsed.bindings) {
|
|
37
|
+
return { bindings: parsed.bindings };
|
|
38
|
+
}
|
|
39
|
+
if (opts.failOnCorrupt) {
|
|
40
|
+
throw new Error(`external agent bindings file is corrupt: expected object with bindings`);
|
|
41
|
+
}
|
|
42
|
+
return { bindings: {} };
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (opts.failOnCorrupt) {
|
|
46
|
+
throw new Error(`external agent bindings file is corrupt or unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
47
|
+
}
|
|
48
|
+
return { bindings: {} };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
write(data) {
|
|
52
|
+
const file = this.file();
|
|
53
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
54
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
55
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
56
|
+
renameSync(tmp, file);
|
|
57
|
+
}
|
|
58
|
+
file() {
|
|
59
|
+
return this.filePath ?? externalAgentBindingsPath();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function detectExternalAgentWorktree(cwd) {
|
|
63
|
+
if (!existsSync(cwd))
|
|
64
|
+
return {};
|
|
65
|
+
try {
|
|
66
|
+
const root = git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
67
|
+
const branch = git(root, ["branch", "--show-current"]);
|
|
68
|
+
const entries = parseWorktreeList(git(root, ["worktree", "list", "--porcelain"]));
|
|
69
|
+
const current = entries.find((entry) => resolve(entry.path) === resolve(root));
|
|
70
|
+
const main = entries[0];
|
|
71
|
+
if (!current || !branch || !main || resolve(current.path) === resolve(main.path))
|
|
72
|
+
return {};
|
|
73
|
+
return { worktreePath: root, worktreeBranch: branch };
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function externalAgentResumeCwdError(binding) {
|
|
80
|
+
if (existsSync(binding.cwd))
|
|
81
|
+
return undefined;
|
|
82
|
+
const branch = binding.worktreeBranch;
|
|
83
|
+
if (branch && branchExistsForBinding(binding)) {
|
|
84
|
+
return (`Error: external ${binding.cli} session ${binding.externalSessionId} is bound to cwd ` +
|
|
85
|
+
`${binding.cwd}, but that directory no longer exists. Worktree branch ${branch} still ` +
|
|
86
|
+
`exists; recreate the worktree at ${binding.worktreePath ?? binding.cwd} before resuming.`);
|
|
87
|
+
}
|
|
88
|
+
return (`Error: external ${binding.cli} session ${binding.externalSessionId} is bound to cwd ` +
|
|
89
|
+
`${binding.cwd}, but the workspace deleted${branch ? ` and branch ${branch} is gone` : ""}. ` +
|
|
90
|
+
`Start a new external session.`);
|
|
91
|
+
}
|
|
92
|
+
function branchExistsForBinding(binding) {
|
|
93
|
+
if (!binding.worktreeBranch)
|
|
94
|
+
return false;
|
|
95
|
+
for (const cwd of candidateGitCwds(binding)) {
|
|
96
|
+
if (!cwd || !existsSync(cwd))
|
|
97
|
+
continue;
|
|
98
|
+
try {
|
|
99
|
+
const out = git(cwd, ["branch", "--list", binding.worktreeBranch]);
|
|
100
|
+
if (out.length > 0)
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Try the next candidate.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
function candidateGitCwds(binding) {
|
|
110
|
+
const candidates = new Set();
|
|
111
|
+
if (existsSync(binding.cwd))
|
|
112
|
+
candidates.add(binding.cwd);
|
|
113
|
+
if (binding.codeShellSessionId) {
|
|
114
|
+
const sessionCwd = new SessionManager().readCwd(binding.codeShellSessionId);
|
|
115
|
+
if (sessionCwd)
|
|
116
|
+
candidates.add(sessionCwd);
|
|
117
|
+
}
|
|
118
|
+
candidates.add(process.cwd());
|
|
119
|
+
candidates.add(dirname(binding.cwd));
|
|
120
|
+
candidates.add(dirname(dirname(binding.cwd)));
|
|
121
|
+
return [...candidates];
|
|
122
|
+
}
|
|
123
|
+
function parseWorktreeList(raw) {
|
|
124
|
+
if (!raw.trim())
|
|
125
|
+
return [];
|
|
126
|
+
const entries = [];
|
|
127
|
+
let current = { path: "", branch: "" };
|
|
128
|
+
for (const line of raw.split("\n")) {
|
|
129
|
+
if (line.startsWith("worktree ")) {
|
|
130
|
+
if (current.path)
|
|
131
|
+
entries.push(current);
|
|
132
|
+
current = { path: line.slice(9), branch: "" };
|
|
133
|
+
}
|
|
134
|
+
else if (line.startsWith("branch ")) {
|
|
135
|
+
current.branch = line.slice(7).replace("refs/heads/", "");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (current.path)
|
|
139
|
+
entries.push(current);
|
|
140
|
+
return entries;
|
|
141
|
+
}
|
|
142
|
+
function git(cwd, args) {
|
|
143
|
+
return execFileSync("git", args, {
|
|
144
|
+
cwd,
|
|
145
|
+
encoding: "utf-8",
|
|
146
|
+
timeout: 10000,
|
|
147
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
148
|
+
}).trim();
|
|
149
|
+
}
|
|
150
|
+
export const externalAgentBindingStore = new ExternalAgentBindingStore();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ExternalAgentCli = "claude" | "codex";
|
|
2
|
+
export interface ExternalAgentSessionBinding {
|
|
3
|
+
cli: ExternalAgentCli;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
cwd: string;
|
|
6
|
+
worktreePath?: string;
|
|
7
|
+
worktreeBranch?: string;
|
|
8
|
+
updatedAt: number;
|
|
9
|
+
}
|
|
10
|
+
export type ExternalAgentSessionRecord = Omit<ExternalAgentSessionBinding, "updatedAt"> & {
|
|
11
|
+
updatedAt?: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function defaultExternalAgentSessionStorePath(): string;
|
|
14
|
+
export declare class ExternalAgentSessionStore {
|
|
15
|
+
private readonly file;
|
|
16
|
+
constructor(file?: string);
|
|
17
|
+
get(cli: ExternalAgentCli, sessionId: string): ExternalAgentSessionBinding | undefined;
|
|
18
|
+
record(binding: ExternalAgentSessionRecord): void;
|
|
19
|
+
private load;
|
|
20
|
+
private save;
|
|
21
|
+
private withLock;
|
|
22
|
+
}
|
|
23
|
+
export declare const externalAgentSessionStore: ExternalAgentSessionStore;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { codeShellHome } from "../session/session-manager.js";
|
|
4
|
+
import { logger } from "../logging/logger.js";
|
|
5
|
+
import { normalizeCwdPath } from "./cwd-normalize.js";
|
|
6
|
+
const LOCK_WAIT_MS = 5_000;
|
|
7
|
+
const LOCK_STALE_MS = 30_000;
|
|
8
|
+
const LOCK_POLL_MS = 10;
|
|
9
|
+
export function defaultExternalAgentSessionStorePath() {
|
|
10
|
+
return join(codeShellHome(), "external-agent-sessions.json");
|
|
11
|
+
}
|
|
12
|
+
export class ExternalAgentSessionStore {
|
|
13
|
+
file;
|
|
14
|
+
constructor(file = defaultExternalAgentSessionStorePath()) {
|
|
15
|
+
this.file = file;
|
|
16
|
+
}
|
|
17
|
+
get(cli, sessionId) {
|
|
18
|
+
if (!sessionId)
|
|
19
|
+
return undefined;
|
|
20
|
+
return this.load().find((s) => s.cli === cli && s.sessionId === sessionId);
|
|
21
|
+
}
|
|
22
|
+
record(binding) {
|
|
23
|
+
if (!binding.sessionId || !binding.cwd)
|
|
24
|
+
return;
|
|
25
|
+
const next = {
|
|
26
|
+
cli: binding.cli,
|
|
27
|
+
sessionId: binding.sessionId,
|
|
28
|
+
cwd: normalizeCwdPath(binding.cwd),
|
|
29
|
+
...(binding.worktreePath ? { worktreePath: binding.worktreePath } : {}),
|
|
30
|
+
...(binding.worktreeBranch ? { worktreeBranch: binding.worktreeBranch } : {}),
|
|
31
|
+
updatedAt: binding.updatedAt ?? Date.now(),
|
|
32
|
+
};
|
|
33
|
+
this.withLock(() => {
|
|
34
|
+
const sessions = this.load().filter((s) => !(s.cli === binding.cli && s.sessionId === binding.sessionId));
|
|
35
|
+
sessions.push(next);
|
|
36
|
+
this.save(sessions);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
load() {
|
|
40
|
+
if (!existsSync(this.file))
|
|
41
|
+
return [];
|
|
42
|
+
try {
|
|
43
|
+
const raw = readFileSync(this.file, "utf-8");
|
|
44
|
+
const parsed = JSON.parse(raw);
|
|
45
|
+
if (!parsed || !Array.isArray(parsed.sessions))
|
|
46
|
+
return [];
|
|
47
|
+
return parsed.sessions.filter(isBinding).map(normalizeBinding);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
logger.warn("external_agent_session_store.load_failed", {
|
|
51
|
+
cat: "cc",
|
|
52
|
+
file: this.file,
|
|
53
|
+
error: err instanceof Error ? err.message : String(err),
|
|
54
|
+
});
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
save(sessions) {
|
|
59
|
+
const dir = dirname(this.file);
|
|
60
|
+
if (!existsSync(dir))
|
|
61
|
+
mkdirSync(dir, { recursive: true });
|
|
62
|
+
const snapshot = { version: 1, sessions };
|
|
63
|
+
const tmp = `${this.file}.${process.pid}.${Date.now()}.tmp`;
|
|
64
|
+
try {
|
|
65
|
+
writeFileSync(tmp, JSON.stringify(snapshot, null, 2) + "\n", "utf-8");
|
|
66
|
+
renameSync(tmp, this.file);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
rmSync(tmp, { force: true });
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
withLock(fn) {
|
|
74
|
+
const dir = dirname(this.file);
|
|
75
|
+
if (!existsSync(dir))
|
|
76
|
+
mkdirSync(dir, { recursive: true });
|
|
77
|
+
// TODO: move this sync polling lock to an async write queue; callers can
|
|
78
|
+
// otherwise block the event loop for up to LOCK_WAIT_MS under contention.
|
|
79
|
+
const lockDir = `${this.file}.lock`;
|
|
80
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
81
|
+
while (true) {
|
|
82
|
+
try {
|
|
83
|
+
mkdirSync(lockDir);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const code = err.code;
|
|
88
|
+
if (code !== "EEXIST")
|
|
89
|
+
throw err;
|
|
90
|
+
if (removeStaleLock(lockDir))
|
|
91
|
+
continue;
|
|
92
|
+
if (Date.now() >= deadline) {
|
|
93
|
+
throw new Error(`timed out waiting for external agent session store lock: ${lockDir}`);
|
|
94
|
+
}
|
|
95
|
+
sleepSync(LOCK_POLL_MS);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return fn();
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function normalizeBinding(binding) {
|
|
107
|
+
return { ...binding, cwd: normalizeCwdPath(binding.cwd) };
|
|
108
|
+
}
|
|
109
|
+
function removeStaleLock(lockDir) {
|
|
110
|
+
try {
|
|
111
|
+
if (Date.now() - statSync(lockDir).mtimeMs <= LOCK_STALE_MS)
|
|
112
|
+
return false;
|
|
113
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (err.code === "ENOENT")
|
|
118
|
+
return true;
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function sleepSync(ms) {
|
|
123
|
+
try {
|
|
124
|
+
const view = new Int32Array(new SharedArrayBuffer(4));
|
|
125
|
+
Atomics.wait(view, 0, 0, ms);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
const until = Date.now() + ms;
|
|
129
|
+
while (Date.now() < until) {
|
|
130
|
+
// fallback for runtimes where Atomics.wait is unavailable
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function isBinding(value) {
|
|
135
|
+
if (!value || typeof value !== "object")
|
|
136
|
+
return false;
|
|
137
|
+
const v = value;
|
|
138
|
+
return ((v.cli === "claude" || v.cli === "codex") &&
|
|
139
|
+
typeof v.sessionId === "string" &&
|
|
140
|
+
v.sessionId.length > 0 &&
|
|
141
|
+
typeof v.cwd === "string" &&
|
|
142
|
+
v.cwd.length > 0);
|
|
143
|
+
}
|
|
144
|
+
export const externalAgentSessionStore = new ExternalAgentSessionStore();
|
|
@@ -48,6 +48,8 @@ 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;
|
|
51
53
|
/** Path to session transcript — passed to summary compaction for on-demand access. */
|
|
52
54
|
private transcriptPath;
|
|
53
55
|
/** Notified whenever any compaction tier fires, including microcompact. */
|
|
@@ -65,7 +67,7 @@ export declare class ContextManager {
|
|
|
65
67
|
* Record actual token usage from API response.
|
|
66
68
|
* Used for hybrid estimation: actual + estimate for new messages.
|
|
67
69
|
*/
|
|
68
|
-
recordActualUsage(inputTokens: number, messageCount: number): void;
|
|
70
|
+
recordActualUsage(inputTokens: number, messageCount: number, messages?: Message[]): void;
|
|
69
71
|
/**
|
|
70
72
|
* Best-effort token estimate: uses actual API usage as base if available,
|
|
71
73
|
* plus estimation for messages added since the last API call.
|
package/dist/context/manager.js
CHANGED
|
@@ -48,6 +48,8 @@ export class ContextManager {
|
|
|
48
48
|
lastActualTokens;
|
|
49
49
|
/** Message count at the time lastActualTokens was recorded. */
|
|
50
50
|
lastActualAtMessageCount;
|
|
51
|
+
/** Heuristic token estimate for the same messages as lastActualTokens. */
|
|
52
|
+
lastActualAnchorEstimate;
|
|
51
53
|
/** Path to session transcript — passed to summary compaction for on-demand access. */
|
|
52
54
|
transcriptPath;
|
|
53
55
|
/** Notified whenever any compaction tier fires, including microcompact. */
|
|
@@ -69,23 +71,29 @@ export class ContextManager {
|
|
|
69
71
|
* Record actual token usage from API response.
|
|
70
72
|
* Used for hybrid estimation: actual + estimate for new messages.
|
|
71
73
|
*/
|
|
72
|
-
recordActualUsage(inputTokens, messageCount) {
|
|
74
|
+
recordActualUsage(inputTokens, messageCount, messages) {
|
|
73
75
|
this.lastActualTokens = inputTokens;
|
|
74
76
|
this.lastActualAtMessageCount = messageCount;
|
|
77
|
+
this.lastActualAnchorEstimate = messages ? estimateTokens(messages) : undefined;
|
|
75
78
|
}
|
|
76
79
|
/**
|
|
77
80
|
* Best-effort token estimate: uses actual API usage as base if available,
|
|
78
81
|
* plus estimation for messages added since the last API call.
|
|
79
82
|
*/
|
|
80
83
|
estimateTokensHybrid(messages) {
|
|
84
|
+
const currentEstimate = estimateTokens(messages);
|
|
81
85
|
if (this.lastActualTokens !== undefined &&
|
|
82
|
-
this.lastActualAtMessageCount !== undefined
|
|
83
|
-
this.lastActualAtMessageCount < messages.length) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
this.lastActualAtMessageCount !== undefined) {
|
|
87
|
+
if (this.lastActualAtMessageCount < messages.length) {
|
|
88
|
+
const newMessages = messages.slice(this.lastActualAtMessageCount);
|
|
89
|
+
const newTokens = estimateTokens(newMessages);
|
|
90
|
+
return this.lastActualTokens + newTokens;
|
|
91
|
+
}
|
|
92
|
+
if (this.lastActualAnchorEstimate !== undefined && this.lastActualAnchorEstimate > 0) {
|
|
93
|
+
return Math.round(this.lastActualTokens * (currentEstimate / this.lastActualAnchorEstimate));
|
|
94
|
+
}
|
|
87
95
|
}
|
|
88
|
-
return
|
|
96
|
+
return currentEstimate;
|
|
89
97
|
}
|
|
90
98
|
/**
|
|
91
99
|
* Set the summarize function (injected by Engine).
|
|
@@ -119,7 +127,7 @@ export class ContextManager {
|
|
|
119
127
|
return { messages, tokens: before, compacted: false, noProgress: true };
|
|
120
128
|
}
|
|
121
129
|
const compacted = applySummaryCompaction(messages, summary, keepRecentN, this.transcriptPath);
|
|
122
|
-
const after =
|
|
130
|
+
const after = this.estimateTokensHybrid(compacted);
|
|
123
131
|
if (after >= before) {
|
|
124
132
|
this.consecutiveSummaryFailures++;
|
|
125
133
|
logger.warn("context.summary_no_progress", {
|
|
@@ -342,6 +350,9 @@ export class ContextManager {
|
|
|
342
350
|
ratio >= this.config.microcompactFloorRatio &&
|
|
343
351
|
ratio < this.config.compactAtRatio;
|
|
344
352
|
const shouldEscalateNoOpMicro = noOpMicroSpinBand && !this.suppressNoOpMicroSummaryUntilCompact;
|
|
353
|
+
const snipGate = this.config.maxTokens * this.config.compactAtRatio;
|
|
354
|
+
const windowGate = this.config.maxTokens * (this.config.compactAtRatio + 0.05);
|
|
355
|
+
const emergencyGate = this.config.maxTokens * this.config.summarizeAtRatio;
|
|
345
356
|
// Tier 2: LLM summary if approaching limit, or if micro was the only tier
|
|
346
357
|
// available in the 0.70-0.85 band and it freed nothing.
|
|
347
358
|
if (ratio >= this.config.compactAtRatio || shouldEscalateNoOpMicro) {
|
|
@@ -350,7 +361,8 @@ export class ContextManager {
|
|
|
350
361
|
tokens = summarized.tokens;
|
|
351
362
|
if (summarized.compacted) {
|
|
352
363
|
this.suppressNoOpMicroSummaryUntilCompact = false;
|
|
353
|
-
|
|
364
|
+
if (tokens <= snipGate)
|
|
365
|
+
return result;
|
|
354
366
|
}
|
|
355
367
|
if (shouldEscalateNoOpMicro && summarized.noProgress) {
|
|
356
368
|
this.suppressNoOpMicroSummaryUntilCompact = true;
|
|
@@ -362,13 +374,10 @@ export class ContextManager {
|
|
|
362
374
|
});
|
|
363
375
|
}
|
|
364
376
|
}
|
|
365
|
-
// Reuse the `tokens` we already computed above. We
|
|
366
|
-
//
|
|
367
|
-
//
|
|
377
|
+
// Reuse the `tokens` we already computed above. We get here if no summary
|
|
378
|
+
// compacted the prompt, or if summary helped but the hybrid estimate is
|
|
379
|
+
// still above the fallback gate.
|
|
368
380
|
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
381
|
// Tier 2 fallback: snip first (cheapest sync option)
|
|
373
382
|
if (live > snipGate) {
|
|
374
383
|
const before = live;
|
|
@@ -39,8 +39,8 @@ export interface Credential {
|
|
|
39
39
|
/**
|
|
40
40
|
* link: 业务方 app 注册地址;cookie: 拓取所用平台与主域 + 抓取范围 + 切换策略。
|
|
41
41
|
* - scope="all" 表示该 jar 是整分区全量抓的(切换时整包导回);缺省/"domain" = 仅该域。
|
|
42
|
-
* - switchMode 决定「切换」时怎么写回浏览器:"
|
|
43
|
-
* "
|
|
42
|
+
* - switchMode 决定「切换」时怎么写回浏览器:"merge"(默认)只覆盖同名 cookie、保留
|
|
43
|
+
* 分区里其他站(不踢掉别的登录态);"clear" 先清空整分区再注入(干净换号,需显式选)。
|
|
44
44
|
*/
|
|
45
45
|
meta?: {
|
|
46
46
|
appUrl?: string;
|
package/dist/engine/engine.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { AskUserFn } from "../tool-system/builtin/ask-user.js";
|
|
|
12
12
|
import type { CapabilityOverride } from "../settings/schema.js";
|
|
13
13
|
import { type FeatureFlagName } from "../settings/feature-flags.js";
|
|
14
14
|
import type { ToolContext } from "../tool-system/context.js";
|
|
15
|
+
import { type SandboxBackend } from "../tool-system/sandbox/index.js";
|
|
15
16
|
import { ModelPool, type ModelEntry } from "../llm/model-pool.js";
|
|
16
17
|
import { AgentDefinitionRegistry } from "../agent/agent-definition-registry.js";
|
|
17
18
|
import { EngineRuntime } from "./runtime.js";
|
|
@@ -53,7 +54,7 @@ export declare function resolveChildLlm(modelKey: string | undefined, pool: Mode
|
|
|
53
54
|
* Names in `disabledAgents` are filtered out so the LLM never sees them.
|
|
54
55
|
*/
|
|
55
56
|
/**
|
|
56
|
-
* Resolve the working directory for a run. Precedence:
|
|
57
|
+
* Resolve the working directory for a run. Precedence for legacy sessions:
|
|
57
58
|
* options.cwd > resumed session's state.cwd > config.cwd > process.cwd()
|
|
58
59
|
*
|
|
59
60
|
* The session-cwd tier is what stops a project-bound session from being
|
|
@@ -570,6 +571,7 @@ export declare class Engine {
|
|
|
570
571
|
* by tests that want a ToolContext without a full run() cycle.
|
|
571
572
|
*/
|
|
572
573
|
private resolveSandboxWithoutRuntime;
|
|
574
|
+
private resolveSandboxConfigForCwd;
|
|
573
575
|
/**
|
|
574
576
|
* Build the shell env layered onto the Bash tool / background shells (see
|
|
575
577
|
* mergeShellEnv). Three user-configured sources, merged lowest → highest:
|
|
@@ -622,6 +624,8 @@ export declare class Engine {
|
|
|
622
624
|
linux?: string;
|
|
623
625
|
windows?: string;
|
|
624
626
|
} | undefined;
|
|
627
|
+
resolveWorktreeSetupSandbox(cwd: string): Promise<SandboxBackend | undefined>;
|
|
628
|
+
readWorktreeSetupShellEnv(cwd?: string): Record<string, string> | undefined;
|
|
625
629
|
buildToolContext(): ToolContext;
|
|
626
630
|
/**
|
|
627
631
|
* Read settings.disabledSkills + settings.disabledPlugins in a single
|