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

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 (64) 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/cc-orchestrator/agent-adapter.d.ts +2 -0
  5. package/dist/cc-orchestrator/agent-adapter.js +7 -1
  6. package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
  7. package/dist/cc-orchestrator/external-agent-driver.js +102 -51
  8. package/dist/cli/agent-server-stdio.js +2 -0
  9. package/dist/credentials/access.d.ts +56 -0
  10. package/dist/credentials/access.js +183 -0
  11. package/dist/credentials/index.d.ts +1 -0
  12. package/dist/credentials/index.js +1 -0
  13. package/dist/credentials/inject-credential-tool.js +5 -5
  14. package/dist/credentials/use-credential-tool.d.ts +8 -1
  15. package/dist/credentials/use-credential-tool.js +55 -45
  16. package/dist/engine/engine.d.ts +3 -0
  17. package/dist/engine/engine.js +40 -13
  18. package/dist/engine/image-policy.d.ts +6 -0
  19. package/dist/engine/image-policy.js +17 -6
  20. package/dist/engine/input-attachments.d.ts +13 -0
  21. package/dist/engine/input-attachments.js +255 -0
  22. package/dist/engine/model-facade.d.ts +5 -2
  23. package/dist/engine/model-facade.js +4 -4
  24. package/dist/engine/parse-task.d.ts +10 -0
  25. package/dist/engine/parse-task.js +5 -0
  26. package/dist/engine/streaming-tool-queue.d.ts +11 -7
  27. package/dist/engine/streaming-tool-queue.js +11 -7
  28. package/dist/engine/turn-loop.d.ts +4 -0
  29. package/dist/engine/turn-loop.js +106 -25
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +2 -2
  32. package/dist/logging/sanitize-messages.d.ts +10 -2
  33. package/dist/logging/sanitize-messages.js +21 -6
  34. package/dist/preset/index.js +10 -9
  35. package/dist/protocol/chat-session-manager.d.ts +1 -0
  36. package/dist/protocol/chat-session-manager.js +18 -2
  37. package/dist/protocol/chat-session.d.ts +3 -0
  38. package/dist/protocol/chat-session.js +1 -0
  39. package/dist/protocol/client.d.ts +9 -4
  40. package/dist/protocol/client.js +18 -1
  41. package/dist/protocol/server.d.ts +12 -0
  42. package/dist/protocol/server.js +116 -38
  43. package/dist/protocol/types.d.ts +37 -0
  44. package/dist/runtime/spawn-common.js +10 -0
  45. package/dist/tool-system/builtin/drive-claude-code.d.ts +1 -0
  46. package/dist/tool-system/builtin/drive-claude-code.js +137 -18
  47. package/dist/tool-system/builtin/index.d.ts +8 -3
  48. package/dist/tool-system/builtin/index.js +15 -13
  49. package/dist/tool-system/builtin/powershell.d.ts +5 -2
  50. package/dist/tool-system/builtin/powershell.js +11 -7
  51. package/dist/tool-system/builtin/read.js +114 -5
  52. package/dist/tool-system/builtin/view-image.js +9 -0
  53. package/dist/tool-system/executor.d.ts +1 -5
  54. package/dist/tool-system/executor.js +94 -115
  55. package/dist/tool-system/mcp-manager.d.ts +2 -0
  56. package/dist/tool-system/mcp-manager.js +23 -8
  57. package/dist/tool-system/path-policy.js +13 -0
  58. package/dist/tool-system/permission.d.ts +28 -7
  59. package/dist/tool-system/permission.js +130 -49
  60. package/dist/tool-system/registry.js +11 -4
  61. package/dist/tool-system/tool-result-redaction.d.ts +7 -0
  62. package/dist/tool-system/tool-result-redaction.js +48 -0
  63. package/dist/types.d.ts +23 -4
  64. package/package.json +4 -3
@@ -14,22 +14,33 @@ import { parsePatch } from "./builtin/apply-patch/parser.js";
14
14
  import { BUILTIN_TOOL_GUARDS } from "./builtin/index.js";
15
15
  import { COMPLETE_GOAL_TOOL_NAME } from "./builtin/complete-goal.js";
16
16
  import { CANCEL_GOAL_TOOL_NAME } from "./builtin/cancel-goal.js";
17
- // A1 hardening: hooks must never promote a non-`allow` classifier
18
- // decision to `allow`. They may otherwise adjust the decision freely
19
- // (e.g. tighten `allow` to `deny`/`ask`, or relax `deny` to `ask` to
20
- // request interactive confirmation — both legitimate audit patterns).
21
- // The user remains the only source of `allow` when the classifier
22
- // said `ask`/`deny`.
23
- //
24
- // See standard §S4 and spec docs/superpowers/specs/2026-05-26-a1-permission-hardening-design.md.
17
+ import { toolResultDisplayText } from "./tool-result-redaction.js";
18
+ const PERMISSION_DECISION_RANK = {
19
+ allow: 0,
20
+ ask: 1,
21
+ deny: 2,
22
+ };
23
+ // Permission hooks may only keep or tighten the current decision. The merge
24
+ // order is deny > ask > allow, so classifier deny/rules stay hard-deny even if
25
+ // a hook asks the user for confirmation.
25
26
  function clampHookDecision(classifier, hook) {
26
27
  if (!hook)
27
28
  return { decision: classifier, rejectedUpgrade: false };
28
- if (hook === "allow" && classifier !== "allow") {
29
+ if (PERMISSION_DECISION_RANK[hook] < PERMISSION_DECISION_RANK[classifier]) {
29
30
  return { decision: classifier, rejectedUpgrade: true };
30
31
  }
31
32
  return { decision: hook, rejectedUpgrade: false };
32
33
  }
34
+ function permissionAskReason(preHookMessages, permissionHookMessages) {
35
+ const parts = [];
36
+ if (preHookMessages?.length) {
37
+ parts.push(`pre_tool_use:\n${preHookMessages.join("\n")}`);
38
+ }
39
+ if (permissionHookMessages?.length) {
40
+ parts.push(`on_permission_check:\n${permissionHookMessages.join("\n")}`);
41
+ }
42
+ return parts.length > 0 ? parts.join("\n\n") : undefined;
43
+ }
33
44
  export class ToolExecutor {
34
45
  registry;
35
46
  permission;
@@ -132,7 +143,9 @@ export class ToolExecutor {
132
143
  };
133
144
  }
134
145
  const visibilityGuard = BUILTIN_TOOL_GUARDS.get(call.toolName);
135
- if (visibilityGuard && this.toolCtx?.toolVisibility && !visibilityGuard(this.toolCtx.toolVisibility)) {
146
+ if (visibilityGuard &&
147
+ this.toolCtx?.toolVisibility &&
148
+ !visibilityGuard(this.toolCtx.toolVisibility)) {
136
149
  return {
137
150
  id: call.id,
138
151
  toolName: call.toolName,
@@ -276,41 +289,16 @@ export class ToolExecutor {
276
289
  isError: true,
277
290
  };
278
291
  }
279
- // A1 hardening: pre_tool_use can no longer pre-approve a tool via
280
- // `decision === "allow"`. Hooks may relax `allow` and may force
281
- // `ask`/`deny`, but they cannot promote a `deny`/`ask` decision to
282
- // `allow`. The only paths to `allow` are the classifier (rules,
283
- // safe-read, allowlist) and the user (interactive approval).
284
- if (hookResult.decision === "allow") {
285
- this.log.info("permission.hook_upgrade_rejected", {
286
- cat: "permission",
287
- tool: call.toolName,
288
- site: "pre_tool_use",
289
- });
290
- }
291
- // pre_tool_use can request interactive confirmation via
292
- // decision: "ask". We invoke the same handleAsk path the classifier
293
- // uses, but feed the hook's messages so the user sees the handler's
294
- // reasoning. If the user approves, we skip the classifier below —
295
- // the hook and the user together have decided.
296
- if (hookResult.decision === "ask") {
297
- const reason = hookResult.messages?.join("\n") ?? undefined;
298
- const approved = await this.permission.handleAsk(call.toolName, call.args, reason, { sessionId: this.toolCtx?.sessionId });
299
- if (!approved) {
300
- return {
301
- id: call.id,
302
- toolName: call.toolName,
303
- error: `Tool call denied by user (pre_tool_use ask).`,
304
- isError: true,
305
- };
306
- }
307
- }
308
292
  // 0.7. Investigation guard — block redundant reads and tag soft reminders
309
293
  // onto results. See investigation-guard.ts for the rules; they enforce the
310
294
  // soft prompt guidance in coding.md ("never re-read", "3-call budget").
311
295
  const guardDecision = this.guard?.preToolCheck(call);
312
296
  if (guardDecision?.block) {
313
- this.log.info("guard.block", { cat: "guard", tool: call.toolName, reason: guardDecision.block.slice(0, 200) });
297
+ this.log.info("guard.block", {
298
+ cat: "guard",
299
+ tool: call.toolName,
300
+ reason: guardDecision.block.slice(0, 200),
301
+ });
314
302
  return {
315
303
  id: call.id,
316
304
  toolName: call.toolName,
@@ -318,68 +306,77 @@ export class ToolExecutor {
318
306
  isError: true,
319
307
  };
320
308
  }
321
- // 1. Permission check skipped only when pre_tool_use issued an
322
- // `ask` that the user just approved (we don't double-prompt).
323
- if (hookResult.decision !== "ask") {
324
- const classifierDecision = this.permission.classify(call.toolName, call.args);
325
- this.log.info("permission.classify", {
309
+ // 1. Permission check. `pre_tool_use: ask` only adds a stricter ask
310
+ // requirement; user approval for that ask cannot bypass classifier deny
311
+ // rules. Final merge order is deny > ask > allow, and at most one prompt
312
+ // is shown for the merged ask decision.
313
+ const classifierDecision = this.permission.classify(call.toolName, call.args);
314
+ this.log.info("permission.classify", {
315
+ cat: "permission",
316
+ tool: call.toolName,
317
+ decision: classifierDecision,
318
+ mode: this.permission.getMode(),
319
+ });
320
+ // on_permission_check hook: lets handlers audit and tighten the decision
321
+ // (e.g. `allow → ask/deny`). Attempts to relax classifier/pre-hook deny or
322
+ // ask are rejected by clampHookDecision.
323
+ const permHook = await this.hooks.emit("on_permission_check", {
324
+ toolName: call.toolName,
325
+ args: call.args,
326
+ toolCallId: call.id,
327
+ classifierDecision,
328
+ });
329
+ const preClamped = clampHookDecision(classifierDecision, hookResult.decision);
330
+ let decision = preClamped.decision;
331
+ if (preClamped.rejectedUpgrade) {
332
+ this.log.info("permission.hook_upgrade_rejected", {
333
+ cat: "permission",
334
+ tool: call.toolName,
335
+ site: "pre_tool_use",
336
+ attempted: hookResult.decision,
337
+ classifier: classifierDecision,
338
+ });
339
+ }
340
+ const permClamped = clampHookDecision(decision, permHook.decision);
341
+ decision = permClamped.decision;
342
+ if (permClamped.rejectedUpgrade) {
343
+ this.log.info("permission.hook_upgrade_rejected", {
344
+ cat: "permission",
345
+ tool: call.toolName,
346
+ site: "on_permission_check",
347
+ attempted: permHook.decision,
348
+ classifier: classifierDecision,
349
+ });
350
+ }
351
+ if (decision !== classifierDecision) {
352
+ this.log.info("permission.hook_override", {
326
353
  cat: "permission",
327
354
  tool: call.toolName,
328
- decision: classifierDecision,
329
- mode: this.permission.getMode(),
355
+ from: classifierDecision,
356
+ to: decision,
330
357
  });
331
- // on_permission_check hook: lets handlers audit and *downgrade*
332
- // the classifier decision (e.g. `allow → ask/deny`, `deny →
333
- // ask`). Promotion to `allow` is rejected by clampHookDecision
334
- // — only the classifier and the user can grant `allow`.
335
- const permHook = await this.hooks.emit("on_permission_check", {
358
+ }
359
+ if (decision === "deny") {
360
+ return {
361
+ id: call.id,
336
362
  toolName: call.toolName,
337
- args: call.args,
338
- toolCallId: call.id,
339
- classifierDecision,
363
+ error: `Permission denied for tool: ${call.toolName}`,
364
+ isError: true,
365
+ };
366
+ }
367
+ if (decision === "ask") {
368
+ const reason = permissionAskReason(hookResult.decision === "ask" ? hookResult.messages : undefined, permHook.decision === "ask" ? permHook.messages : undefined);
369
+ const approved = await this.permission.handleAsk(call.toolName, call.args, reason, {
370
+ sessionId: this.toolCtx?.sessionId,
340
371
  });
341
- // A1 hardening: clamp hook decision to downgrades only.
342
- const clamped = clampHookDecision(classifierDecision, permHook.decision);
343
- const decision = clamped.decision;
344
- if (clamped.rejectedUpgrade) {
345
- this.log.info("permission.hook_upgrade_rejected", {
346
- cat: "permission",
347
- tool: call.toolName,
348
- site: "on_permission_check",
349
- attempted: permHook.decision,
350
- classifier: classifierDecision,
351
- });
352
- }
353
- if (decision !== classifierDecision) {
354
- this.log.info("permission.hook_override", {
355
- cat: "permission",
356
- tool: call.toolName,
357
- from: classifierDecision,
358
- to: decision,
359
- });
360
- }
361
- if (decision === "deny") {
372
+ if (!approved) {
362
373
  return {
363
374
  id: call.id,
364
375
  toolName: call.toolName,
365
- error: `Permission denied for tool: ${call.toolName}`,
376
+ error: `Permission denied by user for tool: ${call.toolName}`,
366
377
  isError: true,
367
378
  };
368
379
  }
369
- if (decision === "ask") {
370
- const reason = permHook.messages?.join("\n");
371
- const approved = await this.permission.handleAsk(call.toolName, call.args, reason, {
372
- sessionId: this.toolCtx?.sessionId,
373
- });
374
- if (!approved) {
375
- return {
376
- id: call.id,
377
- toolName: call.toolName,
378
- error: `Permission denied by user for tool: ${call.toolName}`,
379
- isError: true,
380
- };
381
- }
382
- }
383
380
  }
384
381
  // 2. Pre-tool hook
385
382
  await this.hooks.emit("on_tool_start", {
@@ -415,7 +412,7 @@ export class ToolExecutor {
415
412
  toolName: call.toolName,
416
413
  ok: false,
417
414
  durationMs: Date.now() - toolStartedAt,
418
- error: err instanceof Error ? err.stack ?? err.message : String(err),
415
+ error: err instanceof Error ? (err.stack ?? err.message) : String(err),
419
416
  });
420
417
  // A model calling a tool that isn't in the registry (hallucinated name,
421
418
  // or a builtin missing from the active preset's whitelist) must not kill
@@ -437,7 +434,8 @@ export class ToolExecutor {
437
434
  if (guardDecision?.prepend && !result.error && result.result) {
438
435
  result.result = `${guardDecision.prepend}\n${result.result}`;
439
436
  }
440
- const payload = result.result ?? result.error ?? "";
437
+ const observerResult = toolResultDisplayText(result);
438
+ const payload = observerResult ?? result.error ?? "";
441
439
  span.end({
442
440
  ok: !result.error,
443
441
  chars: payload.length,
@@ -451,21 +449,21 @@ export class ToolExecutor {
451
449
  toolName: call.toolName,
452
450
  ok: !result.error,
453
451
  durationMs: Date.now() - toolStartedAt,
454
- output: result.error ? undefined : result.result,
452
+ output: result.error ? undefined : observerResult,
455
453
  error: result.error,
456
454
  });
457
455
  // 4. Post-tool hook
458
456
  await this.hooks.emit("on_tool_end", {
459
457
  toolName: call.toolName,
460
458
  toolCallId: call.id,
461
- result: result.result,
459
+ result: observerResult,
462
460
  error: result.error,
463
461
  });
464
462
  // 5. Post-tool-use hook (after execution, can observe/modify result)
465
463
  const postHook = await this.hooks.emit("post_tool_use", {
466
464
  toolName: call.toolName,
467
465
  toolCallId: call.id,
468
- result: result.result,
466
+ result: observerResult,
469
467
  error: result.error,
470
468
  });
471
469
  // Append handler-supplied context (linter output, type-check result,
@@ -574,23 +572,4 @@ export class ToolExecutor {
574
572
  return `Error: ${err.message}`;
575
573
  }
576
574
  }
577
- /**
578
- * Convert tool results into Message entries for the transcript.
579
- */
580
- resultsToMessages(toolCalls, results) {
581
- const blocks = [];
582
- for (const result of results) {
583
- blocks.push({
584
- type: "tool_result",
585
- tool_use_id: result.id,
586
- content: result.error ? `Error: ${result.error}` : (result.result ?? "(no output)"),
587
- });
588
- }
589
- return [
590
- {
591
- role: "user",
592
- content: blocks,
593
- },
594
- ];
595
- }
596
575
  }
@@ -6,6 +6,7 @@
6
6
  import type { Tool as McpTool } from "@modelcontextprotocol/sdk/types.js";
7
7
  import type { MCPServerConfig, RegisteredTool } from "../types.js";
8
8
  import { ToolRegistry } from "./registry.js";
9
+ import { type CredentialAccess } from "../credentials/access.js";
9
10
  interface MCPResourceInfo {
10
11
  uri: string;
11
12
  name: string;
@@ -34,6 +35,7 @@ export declare function buildStdioEnv(serverName: string, config: MCPServerConfi
34
35
  * and win on conflict. Pure + exported for unit testing.
35
36
  */
36
37
  export declare function buildHttpHeaders(serverName: string, config: MCPServerConfig, resolveCredential?: (id: string) => string | undefined): Record<string, string>;
38
+ export declare function buildHttpHeadersWithCredentialAccess(serverName: string, config: MCPServerConfig, access?: Pick<CredentialAccess, "resolveValue">): Promise<Record<string, string>>;
37
39
  /**
38
40
  * Infer the transport when the config doesn't name one: a url-only entry is
39
41
  * HTTP, everything else stdio. This is the CC `.mcp.json` convention —
@@ -7,7 +7,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
7
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8
8
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
9
9
  import { logger } from "../logging/logger.js";
10
- import { CredentialStore } from "../credentials/index.js";
10
+ import { getCredentialAccess } from "../credentials/access.js";
11
11
  import { ENV_ALLOWLIST } from "../runtime/spawn-common.js";
12
12
  import { mkdir, readdir, unlink, writeFile } from "node:fs/promises";
13
13
  import { join } from "node:path";
@@ -90,6 +90,20 @@ export function buildHttpHeaders(serverName, config, resolveCredential) {
90
90
  }
91
91
  return headers;
92
92
  }
93
+ export async function buildHttpHeadersWithCredentialAccess(serverName, config, access = getCredentialAccess()) {
94
+ if (!config.credentialRef)
95
+ return buildHttpHeaders(serverName, config);
96
+ if (!access.resolveValue) {
97
+ throw new Error(`MCP server "${serverName}": credential "${config.credentialRef}" not found or empty`);
98
+ }
99
+ const secret = await access.resolveValue({
100
+ cwd: undefined,
101
+ id: config.credentialRef,
102
+ scope: "full",
103
+ purpose: "mcp",
104
+ });
105
+ return buildHttpHeaders(serverName, config, (id) => id === config.credentialRef ? secret : undefined);
106
+ }
93
107
  /**
94
108
  * Infer the transport when the config doesn't name one: a url-only entry is
95
109
  * HTTP, everything else stdio. This is the CC `.mcp.json` convention —
@@ -228,7 +242,10 @@ export function buildRegisteredTool(serverName, tool) {
228
242
  return {
229
243
  name: toOpenAIToolName(`mcp_${serverName}_${tool.name}`),
230
244
  description: `[${serverName}] ${tool.description ?? tool.name}`,
231
- inputSchema: tool.inputSchema ?? { type: "object", properties: {} },
245
+ inputSchema: tool.inputSchema ?? {
246
+ type: "object",
247
+ properties: {},
248
+ },
232
249
  source: "mcp",
233
250
  serverName,
234
251
  permissionDefault: "ask",
@@ -403,12 +420,10 @@ export class MCPManager {
403
420
  if (!config.url) {
404
421
  throw new Error(`MCP server "${name}": url is required for ${transportType} transport`);
405
422
  }
406
- // credentialRef resolves against the user-scope CredentialStore. The
407
- // shared MCPManager pool has no per-session cwd (see desiredByOwner note),
408
- // and MCP-referenced credentials (e.g. a Figma token) are user-global by
409
- // nature, so user scope is the right resolution surface here.
410
- const credStore = new CredentialStore(undefined);
411
- const headers = buildHttpHeaders(name, config, (id) => credStore.resolve(id)?.secret);
423
+ // credentialRef resolves against user-scope credential access. The shared
424
+ // MCPManager pool has no per-session cwd, and MCP-referenced credentials
425
+ // (e.g. a Figma token) remain user-global by design.
426
+ const headers = await buildHttpHeadersWithCredentialAccess(name, config);
412
427
  transport = new StreamableHTTPClientTransport(new URL(config.url), {
413
428
  requestInit: Object.keys(headers).length ? { headers } : undefined,
414
429
  });
@@ -372,6 +372,12 @@ function isSafeCodeShellDiagnosticRead(resolved) {
372
372
  }
373
373
  return false;
374
374
  }
375
+ function isNoRepoAttachmentRead(resolved, operation) {
376
+ if (operation !== "read")
377
+ return false;
378
+ const root = join(homedir(), ".code-shell", "no-repo", ".code-shell", "attachments");
379
+ return isInsideDir(resolved, root);
380
+ }
375
381
  /**
376
382
  * Classify a file path against the workspace + sensitive-path policy.
377
383
  *
@@ -412,6 +418,13 @@ export function classifyPath(rawPath, opts) {
412
418
  // Sensitive: write is always denied, read always asks. Workspace placement
413
419
  // doesn't soften the rule — an `.env` in the project still asks on read.
414
420
  if (sensitiveLabel) {
421
+ if (isNoRepoAttachmentRead(resolved, opts.operation)) {
422
+ return {
423
+ decision: "allow",
424
+ reason: "no-repo attachment read",
425
+ resolvedPath: resolved,
426
+ };
427
+ }
415
428
  if (opts.operation === "read" && isSafeCodeShellDiagnosticRead(resolved)) {
416
429
  return {
417
430
  decision: "allow",
@@ -21,6 +21,17 @@ export declare class AutoApprovalBackend implements ApprovalBackend {
21
21
  requestApproval(req: ApprovalRequest): Promise<ApprovalResult>;
22
22
  private isSafeOperation;
23
23
  }
24
+ /**
25
+ * Closed-session tombstones are a bounded replay guard. A prompt that started
26
+ * before close is still protected by the state-object identity check in
27
+ * isActiveSessionState(): clearSession deletes the captured state, so the late
28
+ * result cannot write into a new bucket even if an old tombstone has aged out.
29
+ * The tombstone only blocks fresh post-close calls carrying a recently closed
30
+ * sessionId from creating an empty bucket. 4096 is 256x the default
31
+ * ChatSessionManager.maxSessions (16), which covers normal late-result bursts
32
+ * while keeping long-lived processes from retaining every historical id.
33
+ */
34
+ export declare const CLOSED_SESSION_TOMBSTONE_LIMIT = 4096;
24
35
  /**
25
36
  * Interactive approval backend — prompts the user via a callback.
26
37
  *
@@ -34,13 +45,11 @@ export declare class AutoApprovalBackend implements ApprovalBackend {
34
45
  * the next time the user opens the project.
35
46
  */
36
47
  export declare class InteractiveApprovalBackend implements ApprovalBackend {
37
- private sessionAllowRules;
38
- private sessionDenyRules;
48
+ private sessionStateById;
49
+ private closedSessionIds;
39
50
  private promptFn;
40
- private cwd;
41
- private savedProjectRules;
42
- private onProjectRules;
43
- private promptTurn;
51
+ private legacyPromptTurn;
52
+ private legacyContext;
44
53
  setPromptFn(fn: (request: ApprovalRequest) => Promise<ApprovalResult>): void;
45
54
  /**
46
55
  * Has someone installed a real prompt callback? Engine consults this
@@ -59,8 +68,18 @@ export declare class InteractiveApprovalBackend implements ApprovalBackend {
59
68
  * earlier approvals.
60
69
  */
61
70
  setOnProjectRules(fn: (rules: PermissionRule[]) => void): void;
71
+ setSessionContext(sessionId: string, context: {
72
+ cwd: string;
73
+ onProjectRules: (rules: PermissionRule[]) => void;
74
+ }): void;
75
+ openSession(sessionId: string): void;
76
+ clearSession(sessionId: string): void;
77
+ private rememberClosedSession;
78
+ private makeSessionState;
79
+ private getSessionState;
80
+ private isActiveSessionState;
62
81
  requestApproval(req: ApprovalRequest): Promise<ApprovalResult>;
63
- /** Session-rule lookup — operation-scoped (see sessionAllowRules doc). Deny
82
+ /** Session-rule lookup — operation-scoped (see sessionStateById doc). Deny
64
83
  * wins over allow if both somehow match (conservative). Null = no rule. */
65
84
  private checkSessionRules;
66
85
  /** The actual interactive ask + rule recording (runs inside the prompt turn). */
@@ -89,6 +108,8 @@ export declare function pathRuleArgsPattern(absPath: string, scope: "file" | "di
89
108
  */
90
109
  export declare function ruleMatches(rule: PermissionRule, toolName: string, args: Record<string, unknown>): boolean;
91
110
  export declare function getInteractiveApprovalBackend(): InteractiveApprovalBackend;
111
+ export declare function openInteractiveApprovalSession(sessionId: string): void;
112
+ export declare function clearInteractiveApprovalSession(sessionId: string): void;
92
113
  export declare function setInteractiveApprovalFn(fn: (request: ApprovalRequest) => Promise<ApprovalResult>): void;
93
114
  type BashSafetyLevel = "safe-read" | "safe-write" | "unsafe" | "dangerous";
94
115
  /**