@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.
Files changed (87) hide show
  1. package/dist/capability-control/service.d.ts +2 -0
  2. package/dist/capability-control/service.js +4 -2
  3. package/dist/cc-orchestrator/codex-session-history.js +1 -1
  4. package/dist/cc-orchestrator/external-agent-bindings.js +1 -1
  5. package/dist/cc-orchestrator/external-agent-session-store.js +3 -1
  6. package/dist/cc-orchestrator/session-history.js +2 -2
  7. package/dist/cli/agent-server-stdio.js +7 -2
  8. package/dist/context/compaction.d.ts +8 -1
  9. package/dist/context/compaction.js +49 -4
  10. package/dist/context/manager.d.ts +11 -2
  11. package/dist/context/manager.js +64 -3
  12. package/dist/credentials/inject-credential-tool.d.ts +3 -1
  13. package/dist/credentials/inject-credential-tool.js +29 -10
  14. package/dist/credentials/use-credential-tool.d.ts +1 -0
  15. package/dist/credentials/use-credential-tool.js +3 -0
  16. package/dist/engine/engine.d.ts +9 -0
  17. package/dist/engine/engine.js +96 -17
  18. package/dist/engine/turn-loop.d.ts +3 -1
  19. package/dist/engine/turn-loop.js +4 -2
  20. package/dist/engine/types.d.ts +8 -0
  21. package/dist/git/worktree/crud.d.ts +69 -0
  22. package/dist/git/worktree/crud.js +206 -0
  23. package/dist/git/worktree/diff.d.ts +14 -0
  24. package/dist/git/worktree/diff.js +82 -0
  25. package/dist/git/worktree/git-exec.d.ts +7 -0
  26. package/dist/git/worktree/git-exec.js +51 -0
  27. package/dist/git/worktree/index.d.ts +5 -0
  28. package/dist/git/worktree/index.js +5 -0
  29. package/dist/git/worktree/query.d.ts +42 -0
  30. package/dist/git/worktree/query.js +121 -0
  31. package/dist/git/worktree/slug.d.ts +11 -0
  32. package/dist/git/worktree/slug.js +58 -0
  33. package/dist/git/worktree.d.ts +1 -127
  34. package/dist/git/worktree.js +5 -464
  35. package/dist/index.d.ts +28 -27
  36. package/dist/index.js +24 -24
  37. package/dist/plugins/installer/checkUpdate.d.ts +4 -1
  38. package/dist/plugins/installer/checkUpdate.js +4 -2
  39. package/dist/plugins/installer/install.js +2 -0
  40. package/dist/plugins/installer/parseSource.d.ts +4 -1
  41. package/dist/plugins/installer/parseSource.js +28 -10
  42. package/dist/plugins/installer/update.d.ts +4 -1
  43. package/dist/plugins/installer/update.js +5 -3
  44. package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
  45. package/dist/plugins/parseMarketplaceInput.js +4 -3
  46. package/dist/preset/index.d.ts +1 -0
  47. package/dist/preset/index.js +6 -0
  48. package/dist/prompt/sections/base.md +1 -1
  49. package/dist/protocol/chat-session-manager.js +7 -0
  50. package/dist/protocol/server.d.ts +10 -0
  51. package/dist/protocol/server.js +88 -4
  52. package/dist/protocol/types.d.ts +6 -0
  53. package/dist/protocol/types.js +2 -0
  54. package/dist/runtime/background-shell.js +2 -3
  55. package/dist/services/auto-dream.d.ts +15 -4
  56. package/dist/services/auto-dream.js +20 -20
  57. package/dist/services/dream-consolidation.d.ts +2 -3
  58. package/dist/services/dream-consolidation.js +65 -15
  59. package/dist/services/extract-memories.d.ts +14 -5
  60. package/dist/services/extract-memories.js +20 -3
  61. package/dist/services/global-dream-promotion.d.ts +23 -0
  62. package/dist/services/global-dream-promotion.js +112 -0
  63. package/dist/services/memory-orchestrator.js +347 -34
  64. package/dist/session/memory.d.ts +56 -17
  65. package/dist/session/memory.js +337 -78
  66. package/dist/session/session-manager.d.ts +1 -1
  67. package/dist/session/session-manager.js +6 -6
  68. package/dist/session/transcript.d.ts +1 -1
  69. package/dist/session/transcript.js +17 -5
  70. package/dist/settings/manager.js +43 -12
  71. package/dist/settings/schema.d.ts +21 -0
  72. package/dist/settings/schema.js +12 -0
  73. package/dist/tool-system/builtin/index.js +18 -8
  74. package/dist/tool-system/builtin/memory.js +40 -8
  75. package/dist/tool-system/builtin/view-image.d.ts +2 -2
  76. package/dist/tool-system/builtin/view-image.js +100 -19
  77. package/dist/tool-system/builtin/worktree.d.ts +2 -0
  78. package/dist/tool-system/builtin/worktree.js +59 -11
  79. package/dist/tool-system/context.d.ts +11 -1
  80. package/dist/tool-system/mcp-manager.d.ts +8 -5
  81. package/dist/tool-system/mcp-manager.js +85 -55
  82. package/dist/tool-system/path-policy.d.ts +2 -0
  83. package/dist/tool-system/path-policy.js +28 -12
  84. package/dist/tool-system/workspace-bridge.d.ts +11 -0
  85. package/dist/tool-system/workspace-bridge.js +1 -0
  86. package/dist/types.d.ts +14 -0
  87. 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, } from "./overlay.js";
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.slice(0, 4000) });
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.slice(0, 4000) });
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.slice(0, 4000), tools: tools.length ? tools : undefined });
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 = nextImageNumber++;
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[]): void;
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.
@@ -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
- this.lastActualTokens = inputTokens;
76
- this.lastActualAtMessageCount = messageCount;
77
- this.lastActualAnchorEstimate = messages ? estimateTokens(messages) : undefined;
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
- function readAutoApprove(cwd) {
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).listMasked().some((c) => c.type === "cookie");
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({ kind: "error", error: "缺少 id。先用 UseCredential(无参)列出凭证,再用 cookie 凭证的 id 调本工具。" });
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({ kind: "error", error: `凭证不存在: "${id}"。调用 UseCredential(无参)可列出可用凭证。` });
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 ? (q, opts) => ctx.askUser(q, opts) : undefined;
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;
@@ -203,3 +203,6 @@ function json(r) {
203
203
  export function __resetCredentialSessionAllowForTests() {
204
204
  sessionAllowByEngine.clear();
205
205
  }
206
+ export function clearCredentialSessionAllow(sessionId) {
207
+ sessionAllowByEngine.delete(sessionId);
208
+ }
@@ -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;