@cjhyy/code-shell-core 0.6.0-rc.8 → 0.7.0-beta.1

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 (177) hide show
  1. package/THIRD_PARTY_NOTICES.md +206 -0
  2. package/dist/automation/scheduler.d.ts +13 -7
  3. package/dist/automation/scheduler.js +116 -37
  4. package/dist/capability-control/service.d.ts +2 -0
  5. package/dist/capability-control/service.js +4 -2
  6. package/dist/cc-orchestrator/agent-adapter.d.ts +2 -0
  7. package/dist/cc-orchestrator/agent-adapter.js +7 -1
  8. package/dist/cc-orchestrator/codex-session-history.js +1 -1
  9. package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
  10. package/dist/cc-orchestrator/cwd-normalize.js +19 -0
  11. package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
  12. package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
  13. package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
  14. package/dist/cc-orchestrator/external-agent-driver.js +102 -51
  15. package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
  16. package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
  17. package/dist/cc-orchestrator/session-history.js +2 -2
  18. package/dist/cli/agent-server-stdio.js +9 -2
  19. package/dist/context/compaction.d.ts +38 -1
  20. package/dist/context/compaction.js +138 -0
  21. package/dist/context/manager.d.ts +34 -2
  22. package/dist/context/manager.js +254 -58
  23. package/dist/context/token-counter.js +13 -0
  24. package/dist/credentials/access.d.ts +56 -0
  25. package/dist/credentials/access.js +183 -0
  26. package/dist/credentials/index.d.ts +1 -0
  27. package/dist/credentials/index.js +1 -0
  28. package/dist/credentials/inject-credential-tool.d.ts +3 -1
  29. package/dist/credentials/inject-credential-tool.js +30 -11
  30. package/dist/credentials/types.d.ts +2 -2
  31. package/dist/credentials/use-credential-tool.d.ts +9 -1
  32. package/dist/credentials/use-credential-tool.js +58 -45
  33. package/dist/engine/engine.d.ts +38 -12
  34. package/dist/engine/engine.js +477 -163
  35. package/dist/engine/image-policy.d.ts +6 -0
  36. package/dist/engine/image-policy.js +17 -6
  37. package/dist/engine/input-attachments.d.ts +13 -0
  38. package/dist/engine/input-attachments.js +255 -0
  39. package/dist/engine/model-facade.d.ts +5 -2
  40. package/dist/engine/model-facade.js +6 -16
  41. package/dist/engine/parse-task.d.ts +10 -0
  42. package/dist/engine/parse-task.js +5 -0
  43. package/dist/engine/query.js +2 -0
  44. package/dist/engine/session-usage.d.ts +12 -0
  45. package/dist/engine/session-usage.js +56 -0
  46. package/dist/engine/steer-queue.d.ts +2 -1
  47. package/dist/engine/steer-queue.js +2 -2
  48. package/dist/engine/streaming-tool-queue.d.ts +11 -7
  49. package/dist/engine/streaming-tool-queue.js +11 -7
  50. package/dist/engine/turn-loop.d.ts +34 -2
  51. package/dist/engine/turn-loop.js +269 -52
  52. package/dist/engine/types.d.ts +8 -0
  53. package/dist/git/worktree/crud.d.ts +69 -0
  54. package/dist/git/worktree/crud.js +206 -0
  55. package/dist/git/worktree/diff.d.ts +14 -0
  56. package/dist/git/worktree/diff.js +82 -0
  57. package/dist/git/worktree/git-exec.d.ts +7 -0
  58. package/dist/git/worktree/git-exec.js +51 -0
  59. package/dist/git/worktree/index.d.ts +5 -0
  60. package/dist/git/worktree/index.js +5 -0
  61. package/dist/git/worktree/query.d.ts +42 -0
  62. package/dist/git/worktree/query.js +121 -0
  63. package/dist/git/worktree/slug.d.ts +11 -0
  64. package/dist/git/worktree/slug.js +58 -0
  65. package/dist/git/worktree.d.ts +1 -84
  66. package/dist/git/worktree.js +5 -230
  67. package/dist/index.d.ts +31 -28
  68. package/dist/index.js +27 -25
  69. package/dist/logging/logger.js +6 -6
  70. package/dist/logging/sanitize-messages.d.ts +10 -2
  71. package/dist/logging/sanitize-messages.js +21 -6
  72. package/dist/plugins/installer/checkUpdate.d.ts +4 -1
  73. package/dist/plugins/installer/checkUpdate.js +4 -2
  74. package/dist/plugins/installer/install.js +2 -0
  75. package/dist/plugins/installer/installFromSource.js +9 -1
  76. package/dist/plugins/installer/parseSource.d.ts +4 -1
  77. package/dist/plugins/installer/parseSource.js +28 -10
  78. package/dist/plugins/installer/sourcePath.d.ts +9 -0
  79. package/dist/plugins/installer/sourcePath.js +50 -0
  80. package/dist/plugins/installer/update.d.ts +4 -1
  81. package/dist/plugins/installer/update.js +5 -3
  82. package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
  83. package/dist/plugins/parseMarketplaceInput.js +4 -3
  84. package/dist/plugins/pluginInstaller.js +24 -22
  85. package/dist/preset/index.d.ts +1 -0
  86. package/dist/preset/index.js +16 -9
  87. package/dist/prompt/sections/base.md +1 -1
  88. package/dist/protocol/chat-session-manager.d.ts +2 -0
  89. package/dist/protocol/chat-session-manager.js +38 -2
  90. package/dist/protocol/chat-session.d.ts +5 -0
  91. package/dist/protocol/chat-session.js +2 -0
  92. package/dist/protocol/client.d.ts +13 -5
  93. package/dist/protocol/client.js +26 -3
  94. package/dist/protocol/server.d.ts +43 -12
  95. package/dist/protocol/server.js +326 -104
  96. package/dist/protocol/types.d.ts +51 -0
  97. package/dist/protocol/types.js +6 -0
  98. package/dist/run/FileRunStore.js +10 -1
  99. package/dist/run/Heartbeat.js +12 -0
  100. package/dist/run/RunApprovalBackend.d.ts +3 -0
  101. package/dist/run/RunApprovalBackend.js +41 -6
  102. package/dist/run/RunLock.js +2 -0
  103. package/dist/run/RunManager.d.ts +2 -0
  104. package/dist/run/RunManager.js +64 -24
  105. package/dist/run/ids.d.ts +2 -0
  106. package/dist/run/ids.js +23 -0
  107. package/dist/runtime/background-shell.d.ts +1 -0
  108. package/dist/runtime/background-shell.js +23 -13
  109. package/dist/runtime/safe-spawn.js +74 -11
  110. package/dist/runtime/spawn-common.js +10 -0
  111. package/dist/services/auto-dream.d.ts +15 -4
  112. package/dist/services/auto-dream.js +20 -20
  113. package/dist/services/dream-consolidation.d.ts +2 -3
  114. package/dist/services/dream-consolidation.js +65 -15
  115. package/dist/services/extract-memories.d.ts +14 -5
  116. package/dist/services/extract-memories.js +20 -3
  117. package/dist/services/global-dream-promotion.d.ts +23 -0
  118. package/dist/services/global-dream-promotion.js +112 -0
  119. package/dist/services/memory-orchestrator.js +347 -34
  120. package/dist/session/memory.d.ts +61 -18
  121. package/dist/session/memory.js +342 -79
  122. package/dist/session/session-manager.d.ts +35 -1
  123. package/dist/session/session-manager.js +197 -4
  124. package/dist/session/transcript.d.ts +5 -1
  125. package/dist/session/transcript.js +38 -5
  126. package/dist/settings/manager.d.ts +1 -0
  127. package/dist/settings/manager.js +87 -37
  128. package/dist/settings/schema-export.d.ts +2 -3
  129. package/dist/settings/schema-export.js +2 -3
  130. package/dist/settings/schema.d.ts +21 -0
  131. package/dist/settings/schema.js +12 -0
  132. package/dist/skills/scanner.d.ts +3 -2
  133. package/dist/skills/scanner.js +12 -9
  134. package/dist/tool-system/builtin/background-jobs.d.ts +10 -1
  135. package/dist/tool-system/builtin/background-jobs.js +12 -1
  136. package/dist/tool-system/builtin/background-work.d.ts +17 -18
  137. package/dist/tool-system/builtin/background-work.js +51 -5
  138. package/dist/tool-system/builtin/config.d.ts +2 -1
  139. package/dist/tool-system/builtin/config.js +16 -11
  140. package/dist/tool-system/builtin/drive-claude-code.d.ts +16 -2
  141. package/dist/tool-system/builtin/drive-claude-code.js +301 -47
  142. package/dist/tool-system/builtin/edit.js +5 -2
  143. package/dist/tool-system/builtin/generate-video.d.ts +1 -0
  144. package/dist/tool-system/builtin/generate-video.js +13 -4
  145. package/dist/tool-system/builtin/index.d.ts +8 -3
  146. package/dist/tool-system/builtin/index.js +37 -21
  147. package/dist/tool-system/builtin/lsp.d.ts +2 -1
  148. package/dist/tool-system/builtin/lsp.js +6 -3
  149. package/dist/tool-system/builtin/memory.js +40 -8
  150. package/dist/tool-system/builtin/notebook-edit.js +5 -2
  151. package/dist/tool-system/builtin/powershell.d.ts +5 -2
  152. package/dist/tool-system/builtin/powershell.js +11 -7
  153. package/dist/tool-system/builtin/read.js +118 -6
  154. package/dist/tool-system/builtin/view-image.d.ts +2 -2
  155. package/dist/tool-system/builtin/view-image.js +109 -19
  156. package/dist/tool-system/builtin/worktree.d.ts +4 -4
  157. package/dist/tool-system/builtin/worktree.js +297 -74
  158. package/dist/tool-system/builtin/write.js +5 -3
  159. package/dist/tool-system/context.d.ts +23 -1
  160. package/dist/tool-system/executor.d.ts +1 -5
  161. package/dist/tool-system/executor.js +94 -115
  162. package/dist/tool-system/mcp-manager.d.ts +18 -5
  163. package/dist/tool-system/mcp-manager.js +157 -74
  164. package/dist/tool-system/mcp-stdio-diagnostics.d.ts +9 -0
  165. package/dist/tool-system/mcp-stdio-diagnostics.js +93 -0
  166. package/dist/tool-system/path-policy.d.ts +2 -0
  167. package/dist/tool-system/path-policy.js +41 -12
  168. package/dist/tool-system/permission.d.ts +28 -7
  169. package/dist/tool-system/permission.js +130 -49
  170. package/dist/tool-system/registry.js +11 -4
  171. package/dist/tool-system/tool-result-redaction.d.ts +7 -0
  172. package/dist/tool-system/tool-result-redaction.js +48 -0
  173. package/dist/tool-system/workspace-bridge.d.ts +11 -0
  174. package/dist/tool-system/workspace-bridge.js +1 -0
  175. package/dist/types.d.ts +86 -5
  176. package/dist/utils/toolDisplay.js +1 -1
  177. package/package.json +4 -3
@@ -0,0 +1,56 @@
1
+ import type { Credential, CredentialType } from "./types.js";
2
+ import type { SettingsScope } from "../settings/manager.js";
3
+ import type { Transport } from "../protocol/transport.js";
4
+ export type CredentialAccessScope = "full" | "project";
5
+ export interface CredentialMetadata {
6
+ id: string;
7
+ type: CredentialType;
8
+ label: string;
9
+ autoUseByAI?: boolean;
10
+ autoInjectByAI?: boolean;
11
+ exposeAsEnv?: string;
12
+ meta?: Credential["meta"];
13
+ hasSecret: boolean;
14
+ secretHint?: string;
15
+ }
16
+ export interface CredentialAccess {
17
+ listMasked(cwd: string | undefined, scope: CredentialAccessScope): CredentialMetadata[];
18
+ resolveMeta(cwd: string | undefined, id: string, scope: CredentialAccessScope): CredentialMetadata | undefined;
19
+ envExposures(cwd: string | undefined, scope: CredentialAccessScope): Record<string, string>;
20
+ resolveValue?(req: {
21
+ cwd?: string;
22
+ id: string;
23
+ scope: CredentialAccessScope;
24
+ purpose: "use" | "mcp";
25
+ }): Promise<string>;
26
+ materializeCookie?(req: {
27
+ cwd?: string;
28
+ id: string;
29
+ scope: CredentialAccessScope;
30
+ }): Promise<{
31
+ cookiesFile: string;
32
+ count: number;
33
+ }>;
34
+ }
35
+ export interface CredentialSnapshotEntry {
36
+ cwd?: string;
37
+ full: CredentialMetadata[];
38
+ project: CredentialMetadata[];
39
+ envFull: Record<string, string>;
40
+ envProject: Record<string, string>;
41
+ }
42
+ export interface CredentialSnapshot {
43
+ revision: number;
44
+ entries: CredentialSnapshotEntry[];
45
+ }
46
+ export declare function setDefaultCredentialAccess(access: CredentialAccess | null | undefined): void;
47
+ export declare function getCredentialAccess(): CredentialAccess;
48
+ export declare function createIpcCredentialAccess(transport: Pick<Transport, "send" | "onMessage">): CredentialAccess;
49
+ export declare function credentialAccessScope(scope: SettingsScope | undefined): CredentialAccessScope;
50
+ export declare function isCredentialSecretAvailable(secret: string | undefined): secret is string;
51
+ export declare const localCredentialAccess: CredentialAccess;
52
+ export declare function materializeCookieSecret(credentialId: string, secret: string): {
53
+ cookiesFile: string;
54
+ count: number;
55
+ };
56
+ export declare function sweepStaleCredentialCookieFiles(now?: number): void;
@@ -0,0 +1,183 @@
1
+ import { existsSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { randomUUID } from "node:crypto";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { CredentialStore } from "./store.js";
6
+ import { formatNetscapeCookies, parseCookieJar } from "./cookie-jar.js";
7
+ const COOKIE_FILE_PREFIX = "codeshell-cred-cookie-";
8
+ const COOKIE_FILE_MAX_AGE_MS = 30 * 60 * 1000;
9
+ let defaultCredentialAccess = null;
10
+ export function setDefaultCredentialAccess(access) {
11
+ defaultCredentialAccess = access ?? null;
12
+ }
13
+ export function getCredentialAccess() {
14
+ return defaultCredentialAccess ?? localCredentialAccess;
15
+ }
16
+ export function createIpcCredentialAccess(transport) {
17
+ let snapshot = { revision: 0, entries: [] };
18
+ let nextId = 1;
19
+ const pending = new Map();
20
+ transport.onMessage((msg) => {
21
+ if ("method" in msg && msg.method === "desktop/credentialSnapshot") {
22
+ const params = msg.params;
23
+ if (params && typeof params.revision === "number" && Array.isArray(params.entries)) {
24
+ snapshot = {
25
+ revision: params.revision,
26
+ entries: params.entries,
27
+ };
28
+ }
29
+ return;
30
+ }
31
+ if (!("id" in msg) || "method" in msg)
32
+ return;
33
+ const id = String(msg.id);
34
+ const waiter = pending.get(id);
35
+ if (!waiter)
36
+ return;
37
+ pending.delete(id);
38
+ clearTimeout(waiter.timer);
39
+ if ("error" in msg && msg.error) {
40
+ waiter.reject(new Error(msg.error.message));
41
+ }
42
+ else {
43
+ waiter.resolve(msg.result);
44
+ }
45
+ });
46
+ const request = (method, params) => {
47
+ const id = `cred-${nextId++}`;
48
+ return new Promise((resolve, reject) => {
49
+ const timer = setTimeout(() => {
50
+ pending.delete(id);
51
+ reject(new Error(`${method} timed out`));
52
+ }, 30_000);
53
+ pending.set(id, { resolve, reject, timer });
54
+ transport.send({ jsonrpc: "2.0", id, method, params });
55
+ });
56
+ };
57
+ const entryFor = (cwd) => {
58
+ const key = cwd ?? "";
59
+ return snapshot.entries.find((entry) => (entry.cwd ?? "") === key);
60
+ };
61
+ return {
62
+ listMasked(cwd, scope) {
63
+ const entry = entryFor(cwd);
64
+ if (!entry)
65
+ return [];
66
+ return scope === "project" ? cloneMetadata(entry.project) : cloneMetadata(entry.full);
67
+ },
68
+ resolveMeta(cwd, id, scope) {
69
+ const entry = entryFor(cwd);
70
+ if (!entry)
71
+ return undefined;
72
+ const list = scope === "project" ? entry.project : entry.full;
73
+ return cloneMetadata(list).find((cred) => cred.id === id);
74
+ },
75
+ envExposures(cwd, scope) {
76
+ const entry = entryFor(cwd);
77
+ if (!entry)
78
+ return {};
79
+ return { ...(scope === "project" ? entry.envProject : entry.envFull) };
80
+ },
81
+ async resolveValue(req) {
82
+ const result = (await request("desktop/credentialResolve", req));
83
+ if (typeof result?.value !== "string")
84
+ throw new Error(`credential "${req.id}" is unavailable`);
85
+ return result.value;
86
+ },
87
+ async materializeCookie(req) {
88
+ const result = (await request("desktop/credentialMaterializeCookie", req));
89
+ if (typeof result?.cookiesFile !== "string" || typeof result.count !== "number") {
90
+ throw new Error(`cookie credential "${req.id}" is unavailable`);
91
+ }
92
+ return { cookiesFile: result.cookiesFile, count: result.count };
93
+ },
94
+ };
95
+ }
96
+ export function credentialAccessScope(scope) {
97
+ return scope === "full" || scope === undefined ? "full" : "project";
98
+ }
99
+ export function isCredentialSecretAvailable(secret) {
100
+ return typeof secret === "string" && secret.length > 0 && !secret.startsWith("enc:");
101
+ }
102
+ function toMetadata(cred) {
103
+ const secret = cred.secret;
104
+ const available = isCredentialSecretAvailable(secret);
105
+ const { secret: _secret, ...rest } = cred;
106
+ return {
107
+ ...rest,
108
+ hasSecret: available,
109
+ secretHint: available ? (secret.length > 4 ? `****${secret.slice(-4)}` : "****") : undefined,
110
+ };
111
+ }
112
+ function cloneMetadata(list) {
113
+ return list.map((cred) => ({ ...cred, meta: cred.meta ? { ...cred.meta } : undefined }));
114
+ }
115
+ function storeFor(cwd) {
116
+ return new CredentialStore(cwd);
117
+ }
118
+ export const localCredentialAccess = {
119
+ listMasked(cwd, scope) {
120
+ return storeFor(cwd).list(scope).map(toMetadata);
121
+ },
122
+ resolveMeta(cwd, id, scope) {
123
+ const cred = storeFor(cwd).resolve(id, scope);
124
+ return cred ? toMetadata(cred) : undefined;
125
+ },
126
+ envExposures(cwd, scope) {
127
+ const out = {};
128
+ const creds = storeFor(cwd).list(scope);
129
+ for (const cred of creds) {
130
+ const name = cred.exposeAsEnv?.trim();
131
+ if (name && isCredentialSecretAvailable(cred.secret))
132
+ out[name] = cred.secret;
133
+ }
134
+ return out;
135
+ },
136
+ async resolveValue(req) {
137
+ const cred = storeFor(req.cwd).resolve(req.id, req.scope);
138
+ if (!cred || !isCredentialSecretAvailable(cred.secret)) {
139
+ throw new Error(`credential "${req.id}" is unavailable`);
140
+ }
141
+ return cred.secret;
142
+ },
143
+ async materializeCookie(req) {
144
+ const cred = storeFor(req.cwd).resolve(req.id, req.scope);
145
+ if (!cred || cred.type !== "cookie" || !isCredentialSecretAvailable(cred.secret)) {
146
+ throw new Error(`cookie credential "${req.id}" is unavailable`);
147
+ }
148
+ return materializeCookieSecret(cred.id, cred.secret);
149
+ },
150
+ };
151
+ export function materializeCookieSecret(credentialId, secret) {
152
+ const jar = parseCookieJar(secret);
153
+ if (jar.length === 0)
154
+ throw new Error("cookie jar is empty or invalid");
155
+ const file = join(tmpdir(), `${COOKIE_FILE_PREFIX}${safeFileName(credentialId)}-${process.pid}-${randomUUID()}.txt`);
156
+ writeFileSync(file, formatNetscapeCookies(jar), { mode: 0o600 });
157
+ return { cookiesFile: file, count: jar.length };
158
+ }
159
+ export function sweepStaleCredentialCookieFiles(now = Date.now()) {
160
+ const dir = tmpdir();
161
+ try {
162
+ if (!existsSync(dir))
163
+ return;
164
+ for (const f of readdirSync(dir)) {
165
+ if (!f.startsWith(COOKIE_FILE_PREFIX))
166
+ continue;
167
+ const p = join(dir, f);
168
+ try {
169
+ if (now - statSync(p).mtimeMs > COOKIE_FILE_MAX_AGE_MS)
170
+ rmSync(p, { force: true });
171
+ }
172
+ catch {
173
+ /* skip */
174
+ }
175
+ }
176
+ }
177
+ catch {
178
+ /* best-effort */
179
+ }
180
+ }
181
+ function safeFileName(s) {
182
+ return s.replace(/[^a-zA-Z0-9_.-]/g, "_");
183
+ }
@@ -1,6 +1,7 @@
1
1
  export { CredentialStore } from "./store.js";
2
2
  export type { CredentialScope, MaskedCredential } from "./store.js";
3
3
  export { type EncryptionCipher, PlaintextCipher, setDefaultCredentialCipher, getDefaultCredentialCipher, } from "./cipher.js";
4
+ export { getCredentialAccess, setDefaultCredentialAccess, createIpcCredentialAccess, localCredentialAccess, credentialAccessScope, isCredentialSecretAvailable, materializeCookieSecret, type CredentialAccess, type CredentialAccessScope, type CredentialMetadata, type CredentialSnapshot, type CredentialSnapshotEntry, } from "./access.js";
4
5
  export type { Credential, CredentialType, CredentialStoreFile } from "./types.js";
5
6
  export { formatNetscapeCookies, parseCookieJar, type CookieLike } from "./cookie-jar.js";
6
7
  export { useCredentialToolDef, useCredentialToolDefFor, useCredentialTool, sweepStaleCredentialCookies, } from "./use-credential-tool.js";
@@ -1,5 +1,6 @@
1
1
  export { CredentialStore } from "./store.js";
2
2
  export { PlaintextCipher, setDefaultCredentialCipher, getDefaultCredentialCipher, } from "./cipher.js";
3
+ export { getCredentialAccess, setDefaultCredentialAccess, createIpcCredentialAccess, localCredentialAccess, credentialAccessScope, isCredentialSecretAvailable, materializeCookieSecret, } from "./access.js";
3
4
  export { formatNetscapeCookies, parseCookieJar } from "./cookie-jar.js";
4
5
  export { useCredentialToolDef, useCredentialToolDefFor, useCredentialTool, sweepStaleCredentialCookies, } from "./use-credential-tool.js";
5
6
  export { credentialUseGate } from "./use-gate.js";
@@ -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;
@@ -10,9 +10,9 @@
10
10
  * 跨进程:实际的 restoreCookiesToBrowser 在 desktop main(core 够不到 Electron
11
11
  * session),经 `ctx.injectCredentialToBrowser` 回调(宿主注入,镜像 askUser)触发。
12
12
  */
13
- import { CredentialStore } from "./store.js";
14
13
  import { credentialUseGate, } from "./use-gate.js";
15
14
  import { SettingsManager } from "../settings/manager.js";
15
+ import { credentialAccessScope, getCredentialAccess } from "./access.js";
16
16
  const TOOL_NAME = "InjectCredential";
17
17
  const BASE_DESCRIPTION = "Inject a stored COOKIE credential into the built-in browser to restore its " +
18
18
  "login state, so you can then drive the page as that logged-in account with the " +
@@ -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 credentialAccessScope(scope);
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 getCredentialAccess()
83
+ .listMasked(cwd, 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 = getCredentialAccess().resolveMeta(cwd, 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
+ }
@@ -39,8 +39,8 @@ export interface Credential {
39
39
  /**
40
40
  * link: 业务方 app 注册地址;cookie: 拓取所用平台与主域 + 抓取范围 + 切换策略。
41
41
  * - scope="all" 表示该 jar 是整分区全量抓的(切换时整包导回);缺省/"domain" = 仅该域。
42
- * - switchMode 决定「切换」时怎么写回浏览器:"clear"(默认)先清空整分区再注入(干净换号);
43
- * "merge" 只覆盖同名 cookie、保留分区里其他站(不踢掉别的登录态)。
42
+ * - switchMode 决定「切换」时怎么写回浏览器:"merge"(默认)只覆盖同名 cookie、保留
43
+ * 分区里其他站(不踢掉别的登录态);"clear" 先清空整分区再注入(干净换号,需显式选)。
44
44
  */
45
45
  meta?: {
46
46
  appUrl?: string;
@@ -7,7 +7,8 @@
7
7
  * - token/link → `{ kind: "value", value }`
8
8
  * - cookie → 就地写临时 cookies.txt(0600),返回 `{ kind: "cookie", cookiesFile, count }`
9
9
  *
10
- * 取值全部 core 直读 CredentialStore(cookie 值第二期已进库),无跨进程。
10
+ * desktop 下通过 host credential access IPC 按需解析 secret;headless/SDK
11
+ * 仍可使用本地 CredentialStore。
11
12
  * 集中在 core/src/credentials/ 下,只经 ToolDefinition 注册 + ToolContext.askUser 耦合 core,
12
13
  * 满足设计稿 §1「可整块外移」约束。
13
14
  */
@@ -25,5 +26,12 @@ export declare function useCredentialToolDefFor(cwd: string): ToolDefinition;
25
26
  */
26
27
  export declare function sweepStaleCredentialCookies(now?: number): void;
27
28
  export declare function useCredentialTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
29
+ export declare function useCredentialBuiltinTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string | {
30
+ result: string;
31
+ sensitive: true;
32
+ displayResult: string;
33
+ transcriptResult: string;
34
+ }>;
28
35
  /** 测试钩子:清空会话 allow 集(避免跨用例污染)。 */
29
36
  export declare function __resetCredentialSessionAllowForTests(): void;
37
+ export declare function clearCredentialSessionAllow(sessionId: string): void;
@@ -7,22 +7,17 @@
7
7
  * - token/link → `{ kind: "value", value }`
8
8
  * - cookie → 就地写临时 cookies.txt(0600),返回 `{ kind: "cookie", cookiesFile, count }`
9
9
  *
10
- * 取值全部 core 直读 CredentialStore(cookie 值第二期已进库),无跨进程。
10
+ * desktop 下通过 host credential access IPC 按需解析 secret;headless/SDK
11
+ * 仍可使用本地 CredentialStore。
11
12
  * 集中在 core/src/credentials/ 下,只经 ToolDefinition 注册 + ToolContext.askUser 耦合 core,
12
13
  * 满足设计稿 §1「可整块外移」约束。
13
14
  */
14
- import { writeFileSync, existsSync, readdirSync, statSync, rmSync } from "node:fs";
15
- import { join } from "node:path";
16
- import { tmpdir } from "node:os";
17
- import { randomUUID } from "node:crypto";
18
- import { CredentialStore } from "./store.js";
19
- import { formatNetscapeCookies, parseCookieJar } from "./cookie-jar.js";
15
+ import { SENSITIVE_TOOL_RESULT_PLACEHOLDER } from "../tool-system/tool-result-redaction.js";
20
16
  import { credentialUseGate, } from "./use-gate.js";
21
17
  import { SettingsManager } from "../settings/manager.js";
22
18
  import { logger } from "../logging/logger.js";
19
+ import { credentialAccessScope, getCredentialAccess, materializeCookieSecret, sweepStaleCredentialCookieFiles, } from "./access.js";
23
20
  const TOOL_NAME = "UseCredential";
24
- const COOKIE_FILE_PREFIX = "codeshell-cred-cookie-";
25
- const COOKIE_FILE_MAX_AGE_MS = 30 * 60 * 1000; // 30min 启动 sweep 上限
26
21
  const BASE_DESCRIPTION = "Use a stored credential (token / API key / login cookie) to run a command. " +
27
22
  "Call with NO arguments first to list available credentials (id + label + type); " +
28
23
  "then call again with `id` to fetch one. Token/link credentials return their secret " +
@@ -52,7 +47,7 @@ export const useCredentialToolDef = {
52
47
  */
53
48
  export function useCredentialToolDefFor(cwd) {
54
49
  try {
55
- const list = new CredentialStore(cwd).listMasked();
50
+ const list = getCredentialAccess().listMasked(cwd, "full");
56
51
  if (list.length === 0)
57
52
  return useCredentialToolDef;
58
53
  const names = list.map((c) => `${c.id} (${c.type})`).join(", ");
@@ -70,26 +65,7 @@ export function useCredentialToolDefFor(cwd) {
70
65
  * 不引入 lease 对象/定时器 —— 文件用完靠进程退出 + 这个轻量 sweep。
71
66
  */
72
67
  export function sweepStaleCredentialCookies(now = Date.now()) {
73
- const dir = tmpdir();
74
- try {
75
- if (!existsSync(dir))
76
- return;
77
- for (const f of readdirSync(dir)) {
78
- if (!f.startsWith(COOKIE_FILE_PREFIX))
79
- continue;
80
- const p = join(dir, f);
81
- try {
82
- if (now - statSync(p).mtimeMs > COOKIE_FILE_MAX_AGE_MS)
83
- rmSync(p, { force: true });
84
- }
85
- catch {
86
- /* skip */
87
- }
88
- }
89
- }
90
- catch {
91
- /* best-effort */
92
- }
68
+ sweepStaleCredentialCookieFiles(now);
93
69
  }
94
70
  /** 内存会话 allow 集:每个 Engine 一份(从 ctx.sessionId 键)。纯内存,关进程即忘。 */
95
71
  const sessionAllowByEngine = new Map();
@@ -112,7 +88,7 @@ function sessionAllowFor(ctx) {
112
88
  // (project-only). An "isolated" engine is at least as restrictive as project,
113
89
  // so it maps to "project" — it must never read the host user's ~/.code-shell.
114
90
  function credentialScope(scope) {
115
- return scope === "full" || scope === undefined ? "full" : "project";
91
+ return credentialAccessScope(scope);
116
92
  }
117
93
  function readAutoApprove(cwd, scope) {
118
94
  try {
@@ -128,19 +104,19 @@ function readAutoApprove(cwd, scope) {
128
104
  }
129
105
  export async function useCredentialTool(args, ctx) {
130
106
  const cwd = ctx?.cwd ?? process.cwd();
131
- const store = new CredentialStore(cwd);
107
+ const access = getCredentialAccess();
132
108
  const scope = credentialScope(ctx?.settingsScope);
133
109
  const id = typeof args.id === "string" ? args.id.trim() : "";
134
110
  const purpose = typeof args.purpose === "string" ? args.purpose : undefined;
135
111
  // 无 id → 清单(脱敏,权威实时源)。按 engine scope 过滤:project/isolated
136
112
  // 引擎不得列出宿主 user 层凭证。
137
113
  if (!id) {
138
- const credentials = store
139
- .listMasked(scope)
114
+ const credentials = access
115
+ .listMasked(cwd, scope)
140
116
  .map((c) => ({ id: c.id, label: c.label, type: c.type }));
141
117
  return json({ kind: "list", credentials });
142
118
  }
143
- const cred = store.resolve(id, scope);
119
+ const cred = access.resolveMeta(cwd, id, scope);
144
120
  if (!cred) {
145
121
  return json({ kind: "error", error: `凭证不存在: "${id}"。调用本工具(无参)可列出可用凭证。` });
146
122
  }
@@ -162,14 +138,20 @@ export async function useCredentialTool(args, ctx) {
162
138
  }
163
139
  // token/link → 直接返回值
164
140
  if (cred.type === "token" || cred.type === "link") {
165
- if (!cred.secret)
141
+ if (!cred.hasSecret || !access.resolveValue) {
142
+ return json({ kind: "error", error: `凭证「${cred.label}」没有可用的值。` });
143
+ }
144
+ try {
145
+ const value = await access.resolveValue({ cwd, id: cred.id, scope, purpose: "use" });
146
+ return json({ kind: "value", value });
147
+ }
148
+ catch {
166
149
  return json({ kind: "error", error: `凭证「${cred.label}」没有可用的值。` });
167
- return json({ kind: "value", value: cred.secret });
150
+ }
168
151
  }
169
152
  // cookie → 就地写临时 cookies.txt
170
153
  if (cred.type === "cookie") {
171
- const jar = parseCookieJar(cred.secret);
172
- if (jar.length === 0) {
154
+ if (!cred.hasSecret) {
173
155
  return json({
174
156
  kind: "error",
175
157
  error: `凭证「${cred.label}」的 cookie 为空或已失效,请在凭证页对该账号点「重拓」(重新登录后重新拓取)。`,
@@ -180,26 +162,57 @@ export async function useCredentialTool(args, ctx) {
180
162
  // same process — the second write would clobber the first and a caller could
181
163
  // read another account's cookies. A random component makes each file distinct.
182
164
  // Prefix is unchanged so the 30-min startup sweep still matches & cleans them.
183
- const file = join(tmpdir(), `${COOKIE_FILE_PREFIX}${safe(cred.id)}-${process.pid}-${randomUUID()}.txt`);
184
165
  try {
185
- writeFileSync(file, formatNetscapeCookies(jar), { mode: 0o600 });
166
+ const materialized = access.materializeCookie
167
+ ? await access.materializeCookie({ cwd, id: cred.id, scope })
168
+ : materializeCookieSecret(cred.id, await access.resolveValue({ cwd, id: cred.id, scope, purpose: "use" }));
169
+ return json({
170
+ kind: "cookie",
171
+ cookiesFile: materialized.cookiesFile,
172
+ count: materialized.count,
173
+ });
186
174
  }
187
175
  catch (e) {
176
+ if (String(e).includes("cookie jar is empty or invalid")) {
177
+ return json({
178
+ kind: "error",
179
+ error: `凭证「${cred.label}」的 cookie 为空或已失效,请在凭证页对该账号点「重拓」(重新登录后重新拓取)。`,
180
+ });
181
+ }
188
182
  logger.warn(`UseCredential: failed to write cookies.txt: ${String(e)}`);
189
183
  return json({ kind: "error", error: `写临时 cookie 文件失败: ${String(e)}` });
190
184
  }
191
- return json({ kind: "cookie", cookiesFile: file, count: jar.length });
192
185
  }
193
186
  return json({ kind: "error", error: `未知凭证类型: ${cred.type}` });
194
187
  }
195
- /** 文件名安全化(凭证 id 用了 `__`,这里只挡路径分隔符/控制字符) */
196
- function safe(s) {
197
- return s.replace(/[^a-zA-Z0-9_.-]/g, "_");
188
+ export async function useCredentialBuiltinTool(args, ctx) {
189
+ const result = await useCredentialTool(args, ctx);
190
+ if (!isValueResult(result))
191
+ return result;
192
+ const redacted = json({ kind: "value", value: SENSITIVE_TOOL_RESULT_PLACEHOLDER });
193
+ return {
194
+ result,
195
+ sensitive: true,
196
+ displayResult: redacted,
197
+ transcriptResult: redacted,
198
+ };
198
199
  }
199
200
  function json(r) {
200
201
  return JSON.stringify(r);
201
202
  }
203
+ function isValueResult(result) {
204
+ try {
205
+ const parsed = JSON.parse(result);
206
+ return parsed.kind === "value" && typeof parsed.value === "string";
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ }
202
212
  /** 测试钩子:清空会话 allow 集(避免跨用例污染)。 */
203
213
  export function __resetCredentialSessionAllowForTests() {
204
214
  sessionAllowByEngine.clear();
205
215
  }
216
+ export function clearCredentialSessionAllow(sessionId) {
217
+ sessionAllowByEngine.delete(sessionId);
218
+ }