@cjhyy/code-shell-core 0.6.0-rc.9 → 0.7.0

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 (193) 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 +4 -0
  7. package/dist/cc-orchestrator/agent-adapter.js +11 -1
  8. package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
  9. package/dist/cc-orchestrator/codex-session-history.js +65 -5
  10. package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
  11. package/dist/cc-orchestrator/cwd-normalize.js +19 -0
  12. package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
  13. package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
  14. package/dist/cc-orchestrator/external-agent-changes.js +22 -5
  15. package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
  16. package/dist/cc-orchestrator/external-agent-driver.js +265 -50
  17. package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
  18. package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
  19. package/dist/cc-orchestrator/session-history.d.ts +35 -0
  20. package/dist/cc-orchestrator/session-history.js +98 -15
  21. package/dist/cli/agent-server-stdio.js +9 -2
  22. package/dist/context/compaction.d.ts +8 -1
  23. package/dist/context/compaction.js +49 -4
  24. package/dist/context/manager.d.ts +16 -2
  25. package/dist/context/manager.js +103 -19
  26. package/dist/credentials/access.d.ts +57 -0
  27. package/dist/credentials/access.js +185 -0
  28. package/dist/credentials/index.d.ts +3 -1
  29. package/dist/credentials/index.js +2 -0
  30. package/dist/credentials/inject-credential-tool.d.ts +3 -1
  31. package/dist/credentials/inject-credential-tool.js +30 -11
  32. package/dist/credentials/oauth.d.ts +20 -0
  33. package/dist/credentials/oauth.js +114 -0
  34. package/dist/credentials/store.d.ts +1 -0
  35. package/dist/credentials/store.js +3 -1
  36. package/dist/credentials/types.d.ts +49 -3
  37. package/dist/credentials/use-credential-tool.d.ts +9 -1
  38. package/dist/credentials/use-credential-tool.js +58 -45
  39. package/dist/engine/engine.d.ts +23 -3
  40. package/dist/engine/engine.js +380 -255
  41. package/dist/engine/goal.d.ts +17 -0
  42. package/dist/engine/goal.js +16 -6
  43. package/dist/engine/image-policy.d.ts +6 -0
  44. package/dist/engine/image-policy.js +17 -6
  45. package/dist/engine/input-attachments.d.ts +13 -0
  46. package/dist/engine/input-attachments.js +398 -0
  47. package/dist/engine/model-facade.d.ts +5 -2
  48. package/dist/engine/model-facade.js +4 -4
  49. package/dist/engine/parse-task.d.ts +10 -0
  50. package/dist/engine/parse-task.js +5 -0
  51. package/dist/engine/run-image-input.d.ts +22 -0
  52. package/dist/engine/run-image-input.js +195 -0
  53. package/dist/engine/steer-queue.d.ts +3 -1
  54. package/dist/engine/steer-queue.js +10 -2
  55. package/dist/engine/streaming-tool-queue.d.ts +11 -7
  56. package/dist/engine/streaming-tool-queue.js +11 -7
  57. package/dist/engine/turn-loop.d.ts +37 -2
  58. package/dist/engine/turn-loop.js +228 -43
  59. package/dist/engine/types.d.ts +8 -0
  60. package/dist/git/worktree/crud.d.ts +69 -0
  61. package/dist/git/worktree/crud.js +206 -0
  62. package/dist/git/worktree/diff.d.ts +14 -0
  63. package/dist/git/worktree/diff.js +82 -0
  64. package/dist/git/worktree/git-exec.d.ts +7 -0
  65. package/dist/git/worktree/git-exec.js +51 -0
  66. package/dist/git/worktree/index.d.ts +5 -0
  67. package/dist/git/worktree/index.js +5 -0
  68. package/dist/git/worktree/query.d.ts +42 -0
  69. package/dist/git/worktree/query.js +121 -0
  70. package/dist/git/worktree/slug.d.ts +11 -0
  71. package/dist/git/worktree/slug.js +58 -0
  72. package/dist/git/worktree.d.ts +1 -84
  73. package/dist/git/worktree.js +5 -230
  74. package/dist/hooks/goal-stop-hook.d.ts +33 -1
  75. package/dist/hooks/goal-stop-hook.js +202 -34
  76. package/dist/index.d.ts +31 -28
  77. package/dist/index.js +27 -25
  78. package/dist/logging/logger.js +6 -6
  79. package/dist/logging/sanitize-messages.d.ts +10 -2
  80. package/dist/logging/sanitize-messages.js +21 -6
  81. package/dist/plugins/installer/checkUpdate.d.ts +4 -1
  82. package/dist/plugins/installer/checkUpdate.js +4 -2
  83. package/dist/plugins/installer/install.js +2 -0
  84. package/dist/plugins/installer/installFromSource.js +9 -1
  85. package/dist/plugins/installer/parseSource.d.ts +4 -1
  86. package/dist/plugins/installer/parseSource.js +28 -10
  87. package/dist/plugins/installer/sourcePath.d.ts +9 -0
  88. package/dist/plugins/installer/sourcePath.js +50 -0
  89. package/dist/plugins/installer/update.d.ts +4 -1
  90. package/dist/plugins/installer/update.js +5 -3
  91. package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
  92. package/dist/plugins/parseMarketplaceInput.js +4 -3
  93. package/dist/plugins/pluginInstaller.js +24 -22
  94. package/dist/preset/index.d.ts +1 -0
  95. package/dist/preset/index.js +30 -13
  96. package/dist/prompt/sections/base.md +1 -1
  97. package/dist/protocol/chat-session-manager.d.ts +2 -0
  98. package/dist/protocol/chat-session-manager.js +38 -2
  99. package/dist/protocol/chat-session.d.ts +3 -0
  100. package/dist/protocol/chat-session.js +1 -0
  101. package/dist/protocol/client.d.ts +9 -4
  102. package/dist/protocol/client.js +18 -1
  103. package/dist/protocol/server.d.ts +30 -0
  104. package/dist/protocol/server.js +247 -47
  105. package/dist/protocol/types.d.ts +49 -0
  106. package/dist/protocol/types.js +6 -0
  107. package/dist/run/FileRunStore.js +10 -1
  108. package/dist/run/Heartbeat.js +12 -0
  109. package/dist/run/RunApprovalBackend.d.ts +3 -0
  110. package/dist/run/RunApprovalBackend.js +41 -6
  111. package/dist/run/RunLock.js +2 -0
  112. package/dist/run/RunManager.d.ts +2 -0
  113. package/dist/run/RunManager.js +64 -24
  114. package/dist/run/ids.d.ts +2 -0
  115. package/dist/run/ids.js +23 -0
  116. package/dist/runtime/background-shell.d.ts +1 -0
  117. package/dist/runtime/background-shell.js +23 -13
  118. package/dist/runtime/spawn-common.js +10 -0
  119. package/dist/services/auto-dream.d.ts +15 -4
  120. package/dist/services/auto-dream.js +20 -20
  121. package/dist/services/dream-consolidation.d.ts +2 -3
  122. package/dist/services/dream-consolidation.js +65 -15
  123. package/dist/services/extract-memories.d.ts +14 -5
  124. package/dist/services/extract-memories.js +20 -3
  125. package/dist/services/global-dream-promotion.d.ts +23 -0
  126. package/dist/services/global-dream-promotion.js +112 -0
  127. package/dist/services/memory-orchestrator.js +347 -34
  128. package/dist/session/memory.d.ts +61 -18
  129. package/dist/session/memory.js +342 -79
  130. package/dist/session/session-manager.d.ts +35 -1
  131. package/dist/session/session-manager.js +224 -4
  132. package/dist/session/transcript.d.ts +1 -1
  133. package/dist/session/transcript.js +17 -5
  134. package/dist/settings/manager.d.ts +1 -0
  135. package/dist/settings/manager.js +87 -37
  136. package/dist/settings/schema-export.d.ts +2 -3
  137. package/dist/settings/schema-export.js +2 -3
  138. package/dist/settings/schema.d.ts +21 -0
  139. package/dist/settings/schema.js +12 -0
  140. package/dist/skills/scanner.d.ts +3 -2
  141. package/dist/skills/scanner.js +12 -9
  142. package/dist/tool-system/builtin/agent-notifications.d.ts +11 -4
  143. package/dist/tool-system/builtin/agent-notifications.js +19 -7
  144. package/dist/tool-system/builtin/background-jobs.d.ts +35 -6
  145. package/dist/tool-system/builtin/background-jobs.js +116 -7
  146. package/dist/tool-system/builtin/background-work.d.ts +17 -18
  147. package/dist/tool-system/builtin/background-work.js +51 -5
  148. package/dist/tool-system/builtin/config.d.ts +2 -1
  149. package/dist/tool-system/builtin/config.js +16 -11
  150. package/dist/tool-system/builtin/cron-list.definition.d.ts +3 -0
  151. package/dist/tool-system/builtin/cron-list.definition.js +6 -0
  152. package/dist/tool-system/builtin/cron.d.ts +1 -2
  153. package/dist/tool-system/builtin/cron.js +9 -7
  154. package/dist/tool-system/builtin/drive-claude-code.d.ts +23 -2
  155. package/dist/tool-system/builtin/drive-claude-code.js +589 -48
  156. package/dist/tool-system/builtin/edit.js +5 -2
  157. package/dist/tool-system/builtin/generate-video.d.ts +1 -0
  158. package/dist/tool-system/builtin/generate-video.js +13 -4
  159. package/dist/tool-system/builtin/index.d.ts +8 -3
  160. package/dist/tool-system/builtin/index.js +50 -22
  161. package/dist/tool-system/builtin/lsp.d.ts +2 -1
  162. package/dist/tool-system/builtin/lsp.js +6 -3
  163. package/dist/tool-system/builtin/memory.js +40 -8
  164. package/dist/tool-system/builtin/notebook-edit.js +5 -2
  165. package/dist/tool-system/builtin/powershell.d.ts +5 -2
  166. package/dist/tool-system/builtin/powershell.js +11 -7
  167. package/dist/tool-system/builtin/read.js +118 -6
  168. package/dist/tool-system/builtin/sleep.d.ts +1 -2
  169. package/dist/tool-system/builtin/sleep.definition.d.ts +8 -0
  170. package/dist/tool-system/builtin/sleep.definition.js +28 -0
  171. package/dist/tool-system/builtin/sleep.js +1 -22
  172. package/dist/tool-system/builtin/view-image.d.ts +2 -2
  173. package/dist/tool-system/builtin/view-image.js +109 -19
  174. package/dist/tool-system/builtin/worktree.d.ts +4 -4
  175. package/dist/tool-system/builtin/worktree.js +297 -74
  176. package/dist/tool-system/builtin/write.js +5 -3
  177. package/dist/tool-system/context.d.ts +41 -1
  178. package/dist/tool-system/executor.d.ts +1 -5
  179. package/dist/tool-system/executor.js +94 -115
  180. package/dist/tool-system/mcp-manager.d.ts +31 -6
  181. package/dist/tool-system/mcp-manager.js +193 -78
  182. package/dist/tool-system/path-policy.d.ts +2 -0
  183. package/dist/tool-system/path-policy.js +41 -12
  184. package/dist/tool-system/permission.d.ts +28 -7
  185. package/dist/tool-system/permission.js +130 -49
  186. package/dist/tool-system/registry.js +11 -4
  187. package/dist/tool-system/tool-result-redaction.d.ts +7 -0
  188. package/dist/tool-system/tool-result-redaction.js +48 -0
  189. package/dist/tool-system/workspace-bridge.d.ts +11 -0
  190. package/dist/tool-system/workspace-bridge.js +1 -0
  191. package/dist/types.d.ts +78 -11
  192. package/dist/utils/toolDisplay.js +1 -1
  193. package/package.json +4 -3
@@ -2,7 +2,7 @@
2
2
  * Permission system — classifier + approval backend.
3
3
  */
4
4
  import { resolve as resolvePath, dirname } from "node:path";
5
- import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, } from "node:fs";
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { logger as rootPermLogger } from "../logging/logger.js";
8
8
  import { READ_ONLY_TOOLS } from "./plan-mode-allowlist.js";
@@ -93,6 +93,17 @@ export class AutoApprovalBackend {
93
93
  return false;
94
94
  }
95
95
  }
96
+ /**
97
+ * Closed-session tombstones are a bounded replay guard. A prompt that started
98
+ * before close is still protected by the state-object identity check in
99
+ * isActiveSessionState(): clearSession deletes the captured state, so the late
100
+ * result cannot write into a new bucket even if an old tombstone has aged out.
101
+ * The tombstone only blocks fresh post-close calls carrying a recently closed
102
+ * sessionId from creating an empty bucket. 4096 is 256x the default
103
+ * ChatSessionManager.maxSessions (16), which covers normal late-result bursts
104
+ * while keeping long-lived processes from retaining every historical id.
105
+ */
106
+ export const CLOSED_SESSION_TOMBSTONE_LIMIT = 4096;
96
107
  /**
97
108
  * Interactive approval backend — prompts the user via a callback.
98
109
  *
@@ -106,29 +117,21 @@ export class AutoApprovalBackend {
106
117
  * the next time the user opens the project.
107
118
  */
108
119
  export class InteractiveApprovalBackend {
109
- // Session-scoped grants, keyed on the OPERATION (tool + narrowed argsPattern
110
- // via buildProjectRule), NOT just the tool name. Approving `git status` for
111
- // the session must not auto-allow `rm -rf /` (both are "Bash"). Allow and
112
- // deny are tracked separately so a one-off deny of `curl evil` never blocks
113
- // an unrelated `git status`. (TODO 5.1 会话级权限缓存.)
114
- sessionAllowRules = [];
115
- sessionDenyRules = [];
120
+ // Session-scoped grants are bucketed by ApprovalRequest.sessionId and then
121
+ // keyed on the OPERATION (tool + narrowed argsPattern via buildProjectRule),
122
+ // NOT just the tool name. Approving `git status` for one session must not
123
+ // auto-allow either `rm -rf /` in that same session or `git ...` in another.
124
+ // Allow and deny are tracked separately so a one-off deny of `curl evil`
125
+ // never blocks an unrelated `git status`.
126
+ sessionStateById = new Map();
127
+ closedSessionIds = new Set();
116
128
  promptFn = null;
117
- cwd = null;
118
- // Project rules saved during this session. Kept in-memory (not re-read from
119
- // settings.local.json) so a stream of approvals all stay applied — the
120
- // previous version passed only the freshly-added rule to the classifier,
121
- // which silently dropped earlier approvals from the live classifier within
122
- // the same session. We accumulate here and hand the full list to the callback.
123
- savedProjectRules = [];
124
- onProjectRules = null;
125
- // Serialize prompts so a burst of parallel tool calls doesn't queue N
126
- // identical cards: while one ask is outstanding, later requests wait here
127
- // and RE-CHECK the session rules when their turn comes — the first
128
- // "本会话一直允许" answer then silently absorbs the queued duplicates.
129
- // (All callers were already serialized at the UI anyway — the renderer
130
- // shows one approval card at a time.)
131
- promptTurn = Promise.resolve();
129
+ legacyPromptTurn = Promise.resolve();
130
+ legacyContext = {
131
+ cwd: null,
132
+ savedProjectRules: [],
133
+ onProjectRules: null,
134
+ };
132
135
  setPromptFn(fn) {
133
136
  this.promptFn = fn;
134
137
  }
@@ -144,7 +147,7 @@ export class InteractiveApprovalBackend {
144
147
  }
145
148
  /** Inject the project root so persistence writes to the right settings file. */
146
149
  setCwd(cwd) {
147
- this.cwd = cwd;
150
+ this.legacyContext.cwd = cwd;
148
151
  }
149
152
  /**
150
153
  * Inject a callback fired when the user approves "for this project". The
@@ -153,11 +156,68 @@ export class InteractiveApprovalBackend {
153
156
  * earlier approvals.
154
157
  */
155
158
  setOnProjectRules(fn) {
156
- this.onProjectRules = fn;
159
+ this.legacyContext.onProjectRules = fn;
160
+ }
161
+ setSessionContext(sessionId, context) {
162
+ const state = this.getSessionState(sessionId, true);
163
+ if (!state)
164
+ return;
165
+ state.cwd = context.cwd;
166
+ state.onProjectRules = context.onProjectRules;
167
+ }
168
+ openSession(sessionId) {
169
+ if (!sessionId)
170
+ return;
171
+ this.closedSessionIds.delete(sessionId);
172
+ }
173
+ clearSession(sessionId) {
174
+ if (!sessionId)
175
+ return;
176
+ this.sessionStateById.delete(sessionId);
177
+ this.rememberClosedSession(sessionId);
178
+ }
179
+ rememberClosedSession(sessionId) {
180
+ this.closedSessionIds.delete(sessionId);
181
+ this.closedSessionIds.add(sessionId);
182
+ while (this.closedSessionIds.size > CLOSED_SESSION_TOMBSTONE_LIMIT) {
183
+ const oldest = this.closedSessionIds.values().next().value;
184
+ if (oldest === undefined)
185
+ break;
186
+ this.closedSessionIds.delete(oldest);
187
+ }
188
+ }
189
+ makeSessionState() {
190
+ return {
191
+ allowRules: [],
192
+ denyRules: [],
193
+ promptTurn: Promise.resolve(),
194
+ cwd: null,
195
+ savedProjectRules: [],
196
+ onProjectRules: null,
197
+ };
198
+ }
199
+ getSessionState(sessionId, create) {
200
+ if (!sessionId)
201
+ return null;
202
+ if (this.closedSessionIds.has(sessionId))
203
+ return null;
204
+ const existing = this.sessionStateById.get(sessionId);
205
+ if (existing || !create)
206
+ return existing ?? null;
207
+ const next = this.makeSessionState();
208
+ this.sessionStateById.set(sessionId, next);
209
+ return next;
210
+ }
211
+ isActiveSessionState(sessionId, state) {
212
+ return (!!sessionId &&
213
+ !!state &&
214
+ !this.closedSessionIds.has(sessionId) &&
215
+ this.sessionStateById.get(sessionId) === state);
157
216
  }
158
217
  async requestApproval(req) {
218
+ const state = this.getSessionState(req.sessionId, true);
159
219
  // Fast path: the operation may already be covered by a session rule.
160
- const cached = this.checkSessionRules(req);
220
+ const cached = state ? this.checkSessionRules(req, state) : null;
161
221
  if (cached)
162
222
  return cached;
163
223
  if (!this.promptFn) {
@@ -167,51 +227,68 @@ export class InteractiveApprovalBackend {
167
227
  // field doc): a burst of parallel tool calls all passes the fast path
168
228
  // before the first decision lands; without the re-check the user had to
169
229
  // answer one card per duplicate.
170
- const prevTurn = this.promptTurn;
230
+ const prevTurn = state ? state.promptTurn : this.legacyPromptTurn;
171
231
  let release;
172
- this.promptTurn = new Promise((r) => (release = r));
232
+ const currentTurn = new Promise((r) => (release = r));
233
+ if (state) {
234
+ state.promptTurn = currentTurn;
235
+ }
236
+ else {
237
+ this.legacyPromptTurn = currentTurn;
238
+ }
173
239
  try {
174
240
  await prevTurn;
175
- const nowCached = this.checkSessionRules(req);
241
+ const activeState = this.isActiveSessionState(req.sessionId, state) ? state : null;
242
+ const nowCached = activeState ? this.checkSessionRules(req, activeState) : null;
176
243
  if (nowCached)
177
244
  return nowCached;
178
- return await this.promptAndRecord(req);
245
+ return await this.promptAndRecord(req, activeState);
179
246
  }
180
247
  finally {
181
248
  release();
182
249
  }
183
250
  }
184
- /** Session-rule lookup — operation-scoped (see sessionAllowRules doc). Deny
251
+ /** Session-rule lookup — operation-scoped (see sessionStateById doc). Deny
185
252
  * wins over allow if both somehow match (conservative). Null = no rule. */
186
- checkSessionRules(req) {
187
- if (this.sessionDenyRules.some((r) => ruleMatches(r, req.toolName, req.args))) {
253
+ checkSessionRules(req, state) {
254
+ if (state.denyRules.some((r) => ruleMatches(r, req.toolName, req.args))) {
188
255
  return { approved: false };
189
256
  }
190
- if (this.sessionAllowRules.some((r) => ruleMatches(r, req.toolName, req.args))) {
257
+ if (state.allowRules.some((r) => ruleMatches(r, req.toolName, req.args))) {
191
258
  return { approved: true };
192
259
  }
193
260
  return null;
194
261
  }
195
262
  /** The actual interactive ask + rule recording (runs inside the prompt turn). */
196
- async promptAndRecord(req) {
263
+ async promptAndRecord(req, state) {
197
264
  if (!this.promptFn) {
198
265
  return { approved: false, reason: "interactive approval backend has no prompt function" };
199
266
  }
200
267
  const result = await this.promptFn(req);
201
268
  const scope = result.scope ?? (result.always ? "session" : "once");
269
+ const activeState = this.isActiveSessionState(req.sessionId, state) ? state : null;
270
+ const context = activeState ?? (!req.sessionId ? this.legacyContext : null);
202
271
  // Path narrowing only rides on an APPROVE: a path-scoped deny is confusing
203
272
  // (deny stays tool-wide). pathScope is ignored by buildProjectRule for
204
273
  // non-file tools / when absent.
205
274
  const ruleOpts = result.approved
206
- ? { pathScope: result.pathScope, cwd: this.cwd ?? undefined }
275
+ ? { pathScope: result.pathScope, cwd: context?.cwd ?? undefined }
207
276
  : undefined;
208
277
  if (scope === "session" && result.always) {
278
+ if (!activeState) {
279
+ rootPermLogger.warn("permission.session_remember_ignored", {
280
+ cat: "permission",
281
+ tool: req.toolName,
282
+ reason: req.sessionId ? "session_closed" : "missing_session_id",
283
+ });
284
+ return result;
285
+ }
209
286
  // Narrow to the operation (Bash → head command, file tools → path scope)
210
287
  // so the session grant is scoped, not tool-wide. A rule with no
211
288
  // argsPattern keeps the prior tool-granularity behavior.
212
289
  const rule = buildProjectRule(req.toolName, req.args, ruleOpts);
213
290
  if (rule) {
214
- const target = result.approved ? this.sessionAllowRules : this.sessionDenyRules;
291
+ const target = result.approved ? activeState.allowRules : activeState.denyRules;
215
292
  const dup = target.some((r) => r.tool === rule.tool &&
216
293
  JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
217
294
  if (!dup) {
@@ -225,23 +302,23 @@ export class InteractiveApprovalBackend {
225
302
  // We only persist allow rules — denies stay session-only because a
226
303
  // persisted deny is harder to recover from than a session deny.
227
304
  const rule = buildProjectRule(req.toolName, req.args, ruleOpts);
228
- if (rule && this.cwd) {
305
+ if (rule && context?.cwd) {
229
306
  try {
230
- persistProjectRule(this.cwd, rule);
307
+ persistProjectRule(context.cwd, rule);
231
308
  // Dedup against the in-memory list using the same equality used by
232
309
  // persistProjectRule so re-prompts on the same tool/argsPattern
233
310
  // don't bloat the live rule set.
234
- const dup = this.savedProjectRules.some((r) => r.tool === rule.tool &&
311
+ const dup = context.savedProjectRules.some((r) => r.tool === rule.tool &&
235
312
  r.decision === rule.decision &&
236
313
  JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
237
314
  if (!dup)
238
- this.savedProjectRules.push(rule);
239
- this.onProjectRules?.(this.savedProjectRules);
315
+ context.savedProjectRules.push(rule);
316
+ context.onProjectRules?.(context.savedProjectRules);
240
317
  rootPermLogger.info("permission.persist", {
241
318
  cat: "permission",
242
319
  tool: rule.tool,
243
320
  decision: rule.decision,
244
- totalProjectRules: this.savedProjectRules.length,
321
+ totalProjectRules: context.savedProjectRules.length,
245
322
  duplicate: dup,
246
323
  });
247
324
  }
@@ -258,11 +335,11 @@ export class InteractiveApprovalBackend {
258
335
  // Also seed the session allow list (operation-scoped) so the rest of
259
336
  // this REPL session benefits even if the classifier path doesn't pick
260
337
  // the rule up immediately.
261
- if (rule) {
262
- const dup = this.sessionAllowRules.some((r) => r.tool === rule.tool &&
338
+ if (rule && activeState) {
339
+ const dup = activeState.allowRules.some((r) => r.tool === rule.tool &&
263
340
  JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
264
341
  if (!dup)
265
- this.sessionAllowRules.push(rule);
342
+ activeState.allowRules.push(rule);
266
343
  }
267
344
  }
268
345
  return result;
@@ -452,6 +529,12 @@ export function getInteractiveApprovalBackend() {
452
529
  }
453
530
  return _interactiveBackend;
454
531
  }
532
+ export function openInteractiveApprovalSession(sessionId) {
533
+ getInteractiveApprovalBackend().openSession(sessionId);
534
+ }
535
+ export function clearInteractiveApprovalSession(sessionId) {
536
+ getInteractiveApprovalBackend().clearSession(sessionId);
537
+ }
455
538
  export function setInteractiveApprovalFn(fn) {
456
539
  getInteractiveApprovalBackend().setPromptFn(fn);
457
540
  }
@@ -896,9 +979,7 @@ export class PermissionClassifier {
896
979
  let result;
897
980
  try {
898
981
  const baseDescription = this.describeToolCall(toolName, args);
899
- const description = reason
900
- ? `${baseDescription}\n\nReason (from pre_tool_use hook): ${reason}`
901
- : baseDescription;
982
+ const description = reason ? `${baseDescription}\n\nReason:\n${reason}` : baseDescription;
902
983
  result = await this.approvalBackend.requestApproval({
903
984
  ...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
904
985
  toolName,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Unified tool registry — built-in tools + MCP tools.
3
3
  */
4
- import { ConfigError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError } from "../exceptions.js";
4
+ import { ConfigError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, } from "../exceptions.js";
5
5
  import { BUILTIN_TOOLS } from "./builtin/index.js";
6
6
  import { validateToolMetadata } from "./validate-tool-metadata.js";
7
7
  /**
@@ -22,7 +22,9 @@ export class ToolRegistry {
22
22
  if (selectedNames) {
23
23
  const unknown = [...selectedNames].filter((name) => !availableNames.has(name));
24
24
  if (unknown.length > 0) {
25
- throw new ConfigError(`Unknown built-in tool(s): ${unknown.join(", ")}`, { unknownBuiltinTools: unknown });
25
+ throw new ConfigError(`Unknown built-in tool(s): ${unknown.join(", ")}`, {
26
+ unknownBuiltinTools: unknown,
27
+ });
26
28
  }
27
29
  }
28
30
  for (const tool of BUILTIN_TOOLS) {
@@ -120,12 +122,18 @@ export class ToolRegistry {
120
122
  // 各取所有,缺的字段为 undefined。
121
123
  const contentBlocks = "contentBlocks" in result ? result.contentBlocks : undefined;
122
124
  const sandbox = "sandbox" in result ? result.sandbox : undefined;
125
+ const sensitive = "sensitive" in result ? result.sensitive : undefined;
126
+ const displayResult = "displayResult" in result ? result.displayResult : undefined;
127
+ const transcriptResult = "transcriptResult" in result ? result.transcriptResult : undefined;
123
128
  return {
124
129
  id,
125
130
  toolName: name,
126
131
  result: result.result ?? (contentBlocks ? "(image)" : ""),
127
132
  contentBlocks,
128
133
  sandbox,
134
+ sensitive,
135
+ displayResult,
136
+ transcriptResult,
129
137
  };
130
138
  }
131
139
  catch (err) {
@@ -133,8 +141,7 @@ export class ToolRegistry {
133
141
  parentSignal?.removeEventListener("abort", onParentAbort);
134
142
  // Always return error as ToolResult, never throw
135
143
  let errorMsg;
136
- const isAbort = err?.name === "AbortError" ||
137
- parentSignal?.aborted === true;
144
+ const isAbort = err?.name === "AbortError" || parentSignal?.aborted === true;
138
145
  if (err instanceof ToolTimeoutError) {
139
146
  errorMsg = `Tool timed out after ${timeout}ms: ${name}`;
140
147
  }
@@ -0,0 +1,7 @@
1
+ import type { Message, ToolResult } from "../types.js";
2
+ export declare const SENSITIVE_TOOL_RESULT_PLACEHOLDER = "[credential value withheld]";
3
+ export declare function toolResultDisplayText(result: ToolResult): string | undefined;
4
+ export declare function toolResultTranscriptText(result: ToolResult): string | undefined;
5
+ export declare function toolResultForDisplay(result: ToolResult): ToolResult;
6
+ export declare function toolResultsForDisplay(results: ToolResult[]): ToolResult[];
7
+ export declare function redactSensitiveToolResultsInMessages(messages: Message[], redactions: ReadonlyMap<string, string>): Message[];
@@ -0,0 +1,48 @@
1
+ export const SENSITIVE_TOOL_RESULT_PLACEHOLDER = "[credential value withheld]";
2
+ export function toolResultDisplayText(result) {
3
+ if (!result.sensitive)
4
+ return result.result;
5
+ return result.displayResult ?? result.transcriptResult ?? SENSITIVE_TOOL_RESULT_PLACEHOLDER;
6
+ }
7
+ export function toolResultTranscriptText(result) {
8
+ if (!result.sensitive)
9
+ return result.result;
10
+ return result.transcriptResult ?? result.displayResult ?? SENSITIVE_TOOL_RESULT_PLACEHOLDER;
11
+ }
12
+ export function toolResultForDisplay(result) {
13
+ if (!result.sensitive)
14
+ return result;
15
+ const { displayResult: _displayResult, transcriptResult: _transcriptResult, ...rest } = result;
16
+ return {
17
+ ...rest,
18
+ result: toolResultDisplayText(result),
19
+ contentBlocks: undefined,
20
+ };
21
+ }
22
+ export function toolResultsForDisplay(results) {
23
+ return results.map((result) => toolResultForDisplay(result));
24
+ }
25
+ export function redactSensitiveToolResultsInMessages(messages, redactions) {
26
+ if (redactions.size === 0)
27
+ return messages;
28
+ let changed = false;
29
+ const out = messages.map((message) => {
30
+ if (!Array.isArray(message.content))
31
+ return message;
32
+ let contentChanged = false;
33
+ const content = message.content.map((block) => {
34
+ if (block.type !== "tool_result")
35
+ return block;
36
+ const replacement = typeof block.tool_use_id === "string" ? redactions.get(block.tool_use_id) : undefined;
37
+ if (replacement === undefined)
38
+ return block;
39
+ contentChanged = true;
40
+ return { ...block, content: replacement };
41
+ });
42
+ if (!contentChanged)
43
+ return message;
44
+ changed = true;
45
+ return { ...message, content };
46
+ });
47
+ return changed ? out : messages;
48
+ }
@@ -0,0 +1,11 @@
1
+ import type { SessionWorkspace } from "../types.js";
2
+ /**
3
+ * WorkspaceBridge — host-backed session workspace switching.
4
+ *
5
+ * Desktop implements this through Electron main so model-initiated switches use
6
+ * the same service path as the UI workspace switcher. Undefined in headless or
7
+ * non-desktop contexts.
8
+ */
9
+ export interface WorkspaceBridge {
10
+ switch(target: string): Promise<SessionWorkspace>;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Core type definitions for the code-shell orchestration framework.
3
3
  */
4
- import type { GoalConfig } from "./engine/goal.js";
4
+ import type { GoalConfig, GoalTerminal } from "./engine/goal.js";
5
5
  export interface ContentBlock {
6
6
  type: "text" | "tool_use" | "tool_result" | "image" | "reasoning";
7
7
  text?: string;
@@ -43,7 +43,15 @@ export interface ToolCall {
43
43
  export interface ToolResult {
44
44
  id: string;
45
45
  toolName: string;
46
+ /**
47
+ * Sensitive tool results keep `result` as the model-facing value for the
48
+ * current model round only. Persisted/displayed/streamed observers must use
49
+ * `transcriptResult`/`displayResult` (or the standard placeholder) instead.
50
+ */
51
+ sensitive?: boolean;
46
52
  result?: string;
53
+ displayResult?: string;
54
+ transcriptResult?: string;
47
55
  /**
48
56
  * 结构化结果块(目前仅图片)。存在时优先于 `result` 用作发给 LLM 的
49
57
  * tool_result content —— view_image 用它把本地图片以 image ContentBlock
@@ -54,13 +62,13 @@ export interface ToolResult {
54
62
  error?: string;
55
63
  isError?: boolean;
56
64
  /**
57
- * Set by tools that execute under the sandbox (Bash / background shell /
58
- * worktree) so the UI can show whether THIS call was isolated. `backend`
65
+ * Set by command-executing tools with sandbox visibility (Bash / background
66
+ * shell / worktree / PowerShell) so the UI can show whether THIS call was isolated. `backend`
59
67
  * "off" means the command ran un-sandboxed (we surface it explicitly so the
60
68
  * user sees "未隔离" rather than guessing from an absent badge); "seatbelt"
61
69
  * / "bwrap" mean OS-level isolation applied. `network` is the policy that
62
- * was in force (absent when off). Tools that don't touch the sandbox leave
63
- * this undefined and the UI renders no badge.
70
+ * was in force (absent when off). Tools with no sandbox visibility leave this
71
+ * undefined and the UI renders no badge.
64
72
  */
65
73
  sandbox?: {
66
74
  backend: "off" | "seatbelt" | "bwrap";
@@ -97,6 +105,12 @@ export interface RegisteredTool {
97
105
  inputSchema: Record<string, unknown>;
98
106
  source: ToolSource;
99
107
  serverName?: string;
108
+ /**
109
+ * Declarative UI/metadata hint for hosts, docs, and capability listings.
110
+ * It is not an execution-policy input: PermissionClassifier does not read
111
+ * RegisteredTool and runtime decisions come from explicit rules, permission
112
+ * mode, approval backend, and tool-specific gates.
113
+ */
100
114
  permissionDefault: PermissionDecision;
101
115
  isConcurrencySafe?: boolean;
102
116
  isReadOnly?: boolean;
@@ -119,7 +133,7 @@ export interface RegisteredTool {
119
133
  */
120
134
  timeoutMs?: number;
121
135
  }
122
- export type TranscriptEventType = "message" | "tool_use" | "tool_result" | "summary" | "content_replace" | "file_history" | "plan_operation" | "session_meta" | "subagent" | "turn_boundary" | "goal_progress" | "turn_stopped" | "error";
136
+ export type TranscriptEventType = "message" | "tool_use" | "tool_result" | "summary" | "content_replace" | "file_history" | "plan_operation" | "session_meta" | "subagent" | "external_file_changes" | "turn_boundary" | "goal_progress" | "turn_stopped" | "error";
123
137
  export interface TranscriptEvent {
124
138
  id: string;
125
139
  type: TranscriptEventType;
@@ -139,9 +153,29 @@ export type SessionStatus = "active" | "paused" | TerminalReason;
139
153
  /** Which host/context created a session — used by the desktop disk-rebuild to
140
154
  * filter the sidebar (only `desktop` + `automation` are shown). */
141
155
  export type SessionOrigin = "desktop" | "tui" | "automation" | "subagent";
156
+ export interface SessionWorkspace {
157
+ root: string;
158
+ kind: "main" | "worktree";
159
+ worktree?: {
160
+ path: string;
161
+ branch: string;
162
+ baseRef: string;
163
+ createdBy: "codeshell";
164
+ };
165
+ }
166
+ export interface ContextUsageAnchor {
167
+ promptTokens: number;
168
+ messageCount: number;
169
+ estimateAtAnchor?: number;
170
+ recordedAt: number;
171
+ provider?: string;
172
+ model?: string;
173
+ }
142
174
  export interface SessionState {
143
175
  sessionId: string;
144
176
  cwd: string;
177
+ /** Current session workspace pointer. Absent only on legacy state.json files. */
178
+ workspace?: SessionWorkspace;
145
179
  startedAt: number;
146
180
  model: string;
147
181
  provider: string;
@@ -150,6 +184,12 @@ export interface SessionState {
150
184
  * (for example model switches), so it must not drive whole-session metrics.
151
185
  */
152
186
  tokenUsage: TokenUsage;
187
+ /**
188
+ * Last provider-reported prompt-token count for the current context window.
189
+ * Separate from cumulative tokenUsage; used only to seed context-size estimates
190
+ * before the next provider response.
191
+ */
192
+ contextUsageAnchor?: ContextUsageAnchor;
153
193
  /**
154
194
  * Monotonic prompt-cache counters for the whole session. These only increase
155
195
  * from session start and are separate from the resettable tokenUsage window.
@@ -202,6 +242,12 @@ export interface SessionState {
202
242
  * baselines reset (matching CC). See engine.run goal-resolution.
203
243
  */
204
244
  activeGoal?: GoalConfig;
245
+ /**
246
+ * Last force-terminated goal instance. Kept separately from activeGoal so an
247
+ * old whole-state writer cannot make that same exhausted goal armable again.
248
+ * Optional for backward compatibility with pre-tombstone state.json files.
249
+ */
250
+ goalTerminal?: GoalTerminal;
205
251
  }
206
252
  export interface TokenUsage {
207
253
  promptTokens: number;
@@ -293,10 +339,14 @@ export interface TaskInfo {
293
339
  activeForm?: string;
294
340
  status: "pending" | "in_progress" | "completed" | "stopped";
295
341
  }
342
+ export type PromptTokenSource = "provider_usage" | "anchor_delta" | "anchor_rescale" | "calibrated_estimate" | "heuristic_estimate" | "session_cumulative";
343
+ export type PromptTokenConfidence = "high" | "medium" | "low";
296
344
  export type StreamEvent = {
297
345
  type: "session_started";
298
346
  sessionId: string;
299
347
  promptTokens: number;
348
+ promptTokensSource?: PromptTokenSource;
349
+ promptTokensConfidence?: PromptTokenConfidence;
300
350
  } | {
301
351
  type: "session_title";
302
352
  sessionId: string;
@@ -304,6 +354,7 @@ export type StreamEvent = {
304
354
  } | {
305
355
  type: "stream_request_start";
306
356
  turnNumber: number;
357
+ messageId?: string;
307
358
  agentId?: string;
308
359
  } | {
309
360
  type: "steer_injected";
@@ -330,6 +381,7 @@ export type StreamEvent = {
330
381
  } | {
331
382
  type: "assistant_message";
332
383
  message: Message;
384
+ messageId?: string;
333
385
  agentId?: string;
334
386
  } | {
335
387
  type: "turn_complete";
@@ -360,6 +412,7 @@ export type StreamEvent = {
360
412
  } | {
361
413
  type: "tombstone";
362
414
  messageId: string;
415
+ agentId?: string;
363
416
  } | {
364
417
  type: "task_update";
365
418
  tasks: TaskInfo[];
@@ -401,6 +454,8 @@ export type StreamEvent = {
401
454
  } | {
402
455
  type: "tool_summary";
403
456
  summary: string;
457
+ toolCallIds?: string[];
458
+ agentId?: string;
404
459
  } | {
405
460
  type: "context_compact";
406
461
  strategy: "micro" | "summary" | "window" | "snip" | "emergency" | "compacted";
@@ -410,6 +465,8 @@ export type StreamEvent = {
410
465
  } | {
411
466
  type: "usage_update";
412
467
  promptTokens: number;
468
+ promptTokensSource?: PromptTokenSource;
469
+ promptTokensConfidence?: PromptTokenConfidence;
413
470
  /**
414
471
  * Provider-reported prompt-cache counts, forwarded so the UI can show a
415
472
  * cache hit rate in the context-ring tooltip. Present only when the LLM
@@ -455,7 +512,7 @@ export type StreamEvent = {
455
512
  agentId: string;
456
513
  name?: string;
457
514
  description: string;
458
- status: "completed" | "failed";
515
+ status: "completed" | "failed" | "cancelled";
459
516
  /**
460
517
  * What kind of background work this was. Lets UIs localize the
461
518
  * completion toast (e.g. a shell shows "命令完成" not the raw English
@@ -467,8 +524,16 @@ export type StreamEvent = {
467
524
  command?: string;
468
525
  /** Final assistant text (status === "completed" only). */
469
526
  finalText?: string;
470
- /** Error message (status === "failed" only). */
527
+ /** Error message (status === "failed" or "cancelled" only). */
471
528
  error?: string;
529
+ /** External Claude/Codex session id, when workKind === "cc". */
530
+ ccSessionId?: string;
531
+ /** Files attributed to an external DriveAgent transcript. */
532
+ changedFiles?: string[];
533
+ /** Working directory used to canonicalize relative/absolute path aliases. */
534
+ cwd?: string;
535
+ /** Client id of the real user turn that launched this background work. */
536
+ originClientMessageId?: string;
472
537
  /** When the bg agent finished (Date.now() value). */
473
538
  enqueuedAt: number;
474
539
  };
@@ -601,9 +666,10 @@ export interface MCPServerConfig {
601
666
  */
602
667
  envHeaders?: Record<string, string>;
603
668
  /**
604
- * (HTTP) id of a stored credential (CredentialStore) to use as the Bearer
605
- * token. Resolved at connect time via a resolver passed to buildHttpHeaders;
606
- * the secret is never stored in the MCP config. Wins over bearerTokenEnvVar.
669
+ * (HTTP) id of a stored credential (token/link/oauth) to use as Bearer auth.
670
+ * OAuth credentials inject their current access token. Resolved at connect
671
+ * time via CredentialStore; the secret is never stored in MCP config. Wins
672
+ * over bearerTokenEnvVar.
607
673
  */
608
674
  credentialRef?: string;
609
675
  /**
@@ -626,6 +692,7 @@ export interface MCPServerConfig {
626
692
  export interface MCPServerOverride {
627
693
  env?: Record<string, string>;
628
694
  envVars?: string[];
695
+ /** (HTTP) id of a stored token/link/oauth credential used as Bearer auth. */
629
696
  credentialRef?: string;
630
697
  bearerTokenEnvVar?: string;
631
698
  envHeaders?: Record<string, string>;
@@ -57,7 +57,7 @@ const TOOL_ARG_KEYS = {
57
57
  Agent: ["description"],
58
58
  TodoWrite: ["todos"],
59
59
  Sleep: ["seconds"],
60
- EnterWorktree: ["slug"],
60
+ EnterWorktree: ["target", "slug"],
61
61
  CronCreate: ["name", "schedule"],
62
62
  LSP: ["action", "file_path"],
63
63
  NotebookEdit: ["action", "file_path"],