@cjhyy/code-shell-core 0.6.0-rc.14 → 0.6.0-rc.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) 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/manager.d.ts +14 -2
  9. package/dist/context/manager.js +83 -8
  10. package/dist/credentials/inject-credential-tool.d.ts +3 -1
  11. package/dist/credentials/inject-credential-tool.js +29 -10
  12. package/dist/credentials/use-credential-tool.d.ts +1 -0
  13. package/dist/credentials/use-credential-tool.js +3 -0
  14. package/dist/engine/engine.d.ts +9 -0
  15. package/dist/engine/engine.js +115 -25
  16. package/dist/engine/turn-loop.d.ts +3 -1
  17. package/dist/engine/turn-loop.js +11 -2
  18. package/dist/engine/types.d.ts +8 -0
  19. package/dist/git/worktree/crud.d.ts +69 -0
  20. package/dist/git/worktree/crud.js +206 -0
  21. package/dist/git/worktree/diff.d.ts +14 -0
  22. package/dist/git/worktree/diff.js +82 -0
  23. package/dist/git/worktree/git-exec.d.ts +7 -0
  24. package/dist/git/worktree/git-exec.js +51 -0
  25. package/dist/git/worktree/index.d.ts +5 -0
  26. package/dist/git/worktree/index.js +5 -0
  27. package/dist/git/worktree/query.d.ts +42 -0
  28. package/dist/git/worktree/query.js +121 -0
  29. package/dist/git/worktree/slug.d.ts +11 -0
  30. package/dist/git/worktree/slug.js +58 -0
  31. package/dist/git/worktree.d.ts +1 -127
  32. package/dist/git/worktree.js +5 -464
  33. package/dist/index.d.ts +28 -27
  34. package/dist/index.js +24 -24
  35. package/dist/plugins/installer/checkUpdate.d.ts +4 -1
  36. package/dist/plugins/installer/checkUpdate.js +4 -2
  37. package/dist/plugins/installer/install.js +2 -0
  38. package/dist/plugins/installer/parseSource.d.ts +4 -1
  39. package/dist/plugins/installer/parseSource.js +28 -10
  40. package/dist/plugins/installer/update.d.ts +4 -1
  41. package/dist/plugins/installer/update.js +5 -3
  42. package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
  43. package/dist/plugins/parseMarketplaceInput.js +4 -3
  44. package/dist/preset/index.d.ts +1 -0
  45. package/dist/preset/index.js +6 -0
  46. package/dist/prompt/sections/base.md +1 -1
  47. package/dist/protocol/chat-session-manager.js +7 -0
  48. package/dist/protocol/server.d.ts +10 -0
  49. package/dist/protocol/server.js +90 -5
  50. package/dist/protocol/types.d.ts +6 -0
  51. package/dist/protocol/types.js +2 -0
  52. package/dist/runtime/background-shell.d.ts +1 -0
  53. package/dist/runtime/background-shell.js +23 -13
  54. package/dist/services/auto-dream.d.ts +15 -4
  55. package/dist/services/auto-dream.js +20 -20
  56. package/dist/services/dream-consolidation.d.ts +2 -3
  57. package/dist/services/dream-consolidation.js +65 -15
  58. package/dist/services/extract-memories.d.ts +14 -5
  59. package/dist/services/extract-memories.js +20 -3
  60. package/dist/services/global-dream-promotion.d.ts +23 -0
  61. package/dist/services/global-dream-promotion.js +112 -0
  62. package/dist/services/memory-orchestrator.js +347 -34
  63. package/dist/session/memory.d.ts +61 -18
  64. package/dist/session/memory.js +342 -79
  65. package/dist/session/session-manager.d.ts +1 -1
  66. package/dist/session/session-manager.js +6 -6
  67. package/dist/settings/manager.js +43 -12
  68. package/dist/settings/schema.d.ts +21 -0
  69. package/dist/settings/schema.js +12 -0
  70. package/dist/skills/scanner.d.ts +3 -2
  71. package/dist/skills/scanner.js +12 -9
  72. package/dist/tool-system/builtin/background-jobs.d.ts +2 -0
  73. package/dist/tool-system/builtin/background-jobs.js +4 -0
  74. package/dist/tool-system/builtin/background-work.d.ts +17 -18
  75. package/dist/tool-system/builtin/background-work.js +51 -5
  76. package/dist/tool-system/builtin/index.js +18 -8
  77. package/dist/tool-system/builtin/memory.js +40 -8
  78. package/dist/tool-system/builtin/worktree.d.ts +2 -0
  79. package/dist/tool-system/builtin/worktree.js +59 -11
  80. package/dist/tool-system/context.d.ts +11 -1
  81. package/dist/tool-system/mcp-manager.d.ts +8 -5
  82. package/dist/tool-system/mcp-manager.js +85 -55
  83. package/dist/tool-system/path-policy.d.ts +2 -0
  84. package/dist/tool-system/path-policy.js +28 -12
  85. package/dist/tool-system/workspace-bridge.d.ts +11 -0
  86. package/dist/tool-system/workspace-bridge.js +1 -0
  87. package/dist/types.d.ts +20 -0
  88. 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).
@@ -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;
@@ -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,11 +70,18 @@ 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.
74
83
  */
84
+ private estimateTokensHybridInfo;
75
85
  private estimateTokensHybrid;
76
86
  /**
77
87
  * Set the summarize function (injected by Engine).
@@ -141,5 +151,7 @@ export declare class ContextManager {
141
151
  ratio: number;
142
152
  needsCompact: boolean;
143
153
  needsEmergency: boolean;
154
+ promptTokensSource: PromptTokenSource;
155
+ promptTokensConfidence: PromptTokenConfidence;
144
156
  };
145
157
  }
@@ -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,28 +78,83 @@ 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,
81
131
  * plus estimation for messages added since the last API call.
82
132
  */
83
- estimateTokensHybrid(messages) {
133
+ estimateTokensHybridInfo(messages) {
84
134
  const currentEstimate = estimateTokens(messages);
85
135
  if (this.lastActualTokens !== undefined &&
86
136
  this.lastActualAtMessageCount !== undefined) {
87
137
  if (this.lastActualAtMessageCount < messages.length) {
88
138
  const newMessages = messages.slice(this.lastActualAtMessageCount);
89
139
  const newTokens = estimateTokens(newMessages);
90
- return this.lastActualTokens + newTokens;
140
+ return {
141
+ tokens: this.lastActualTokens + newTokens,
142
+ source: "anchor_delta",
143
+ confidence: "medium",
144
+ };
91
145
  }
92
146
  if (this.lastActualAnchorEstimate !== undefined && this.lastActualAnchorEstimate > 0) {
93
- return Math.round(this.lastActualTokens * (currentEstimate / this.lastActualAnchorEstimate));
147
+ return {
148
+ tokens: Math.round(this.lastActualTokens * (currentEstimate / this.lastActualAnchorEstimate)),
149
+ source: "anchor_rescale",
150
+ confidence: "medium",
151
+ };
94
152
  }
95
153
  }
96
- return currentEstimate;
154
+ return { tokens: currentEstimate, source: "heuristic_estimate", confidence: "low" };
155
+ }
156
+ estimateTokensHybrid(messages) {
157
+ return this.estimateTokensHybridInfo(messages).tokens;
97
158
  }
98
159
  /**
99
160
  * Set the summarize function (injected by Engine).
@@ -306,6 +367,17 @@ export class ContextManager {
306
367
  result = this.truncateToolResults(result);
307
368
  // Tier 0c: Aggregate tool result budget (per-message)
308
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
+ }
309
381
  // Tier 1: microcompact — see manage() for the rationale on the floor.
310
382
  const preTier1Tokens = this.estimateTokensHybrid(result);
311
383
  const microFloorGate = this.config.maxTokens * this.config.microcompactFloorRatio;
@@ -516,13 +588,16 @@ export class ContextManager {
516
588
  * Check if context is approaching limits.
517
589
  */
518
590
  checkLimits(messages) {
519
- const tokens = this.estimateTokensHybrid(messages);
591
+ const estimate = this.estimateTokensHybridInfo(messages);
592
+ const tokens = estimate.tokens;
520
593
  const ratio = tokens / this.config.maxTokens;
521
594
  return {
522
595
  tokens,
523
596
  ratio,
524
597
  needsCompact: ratio >= this.config.compactAtRatio,
525
598
  needsEmergency: ratio >= this.config.summarizeAtRatio,
599
+ promptTokensSource: estimate.source,
600
+ promptTokensConfidence: estimate.confidence,
526
601
  };
527
602
  }
528
603
  }
@@ -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;