qlogicagent 2.11.3 → 2.11.5

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 (34) hide show
  1. package/dist/agent.js +6 -6
  2. package/dist/cli.js +269 -262
  3. package/dist/index.js +265 -258
  4. package/dist/types/cli/permission-bootstrap.d.ts +7 -0
  5. package/dist/types/cli/session-coordinator.d.ts +9 -0
  6. package/dist/types/cli/tool-bootstrap.d.ts +33 -2
  7. package/dist/types/cli/turn-permission-sync.d.ts +1 -2
  8. package/dist/types/protocol/wire/acp-protocol.d.ts +1 -0
  9. package/dist/types/protocol/wire/chat-types.d.ts +6 -0
  10. package/dist/types/protocol/wire/notification-payloads.d.ts +12 -0
  11. package/dist/types/runtime/infra/default-path-service.d.ts +3 -0
  12. package/dist/types/runtime/infra/model-registry.d.ts +3 -0
  13. package/dist/types/runtime/infra/provider-catalog-adapter.d.ts +14 -0
  14. package/dist/types/runtime/permission-model.d.ts +19 -0
  15. package/dist/types/runtime/ports/path-service.d.ts +10 -0
  16. package/dist/types/runtime/ports/permission-contracts.d.ts +26 -3
  17. package/dist/types/runtime/ports/tool-contracts.d.ts +3 -0
  18. package/dist/types/runtime/ports/tool-risk.d.ts +7 -0
  19. package/dist/types/skills/permissions/community-sandbox-policy.d.ts +1 -1
  20. package/dist/types/skills/permissions/hook-runner.d.ts +16 -2
  21. package/dist/types/skills/permissions/operation-classifier.d.ts +43 -0
  22. package/dist/types/skills/permissions/rule-engine.d.ts +28 -14
  23. package/dist/types/skills/portable-tool.d.ts +18 -0
  24. package/dist/types/skills/tools/exec-tool.d.ts +7 -0
  25. package/dist/types/skills/tools/patch-tool.d.ts +7 -0
  26. package/dist/types/skills/tools/shell/sandbox/helper-resolver.d.ts +7 -0
  27. package/dist/types/skills/tools/shell/sandbox/landlock-argv.d.ts +6 -0
  28. package/dist/types/skills/tools/shell/sandbox/platform.d.ts +3 -0
  29. package/dist/types/skills/tools/shell/sandbox/sandbox-launcher.d.ts +15 -0
  30. package/dist/types/skills/tools/shell/sandbox/sandbox-scope.d.ts +6 -0
  31. package/dist/types/skills/tools/shell/sandbox/sandbox-types.d.ts +47 -0
  32. package/dist/types/skills/tools/shell/sandbox/seatbelt-profile.d.ts +6 -0
  33. package/dist/types/skills/tools/shell/shell-exec.d.ts +3 -0
  34. package/package.json +1 -1
@@ -15,6 +15,13 @@ export interface PermissionBootstrapDeps {
15
15
  sessionId: string;
16
16
  getTurnId(): string;
17
17
  getActiveProjectRoot(): string;
18
+ /**
19
+ * The live active tool workdir (where file/exec relative paths resolve). The
20
+ * cross-workspace classifier compares tool target paths against THIS, not
21
+ * getActiveProjectRoot — the latter is a (possibly snapshotted) project root
22
+ * used for memory/sessions and does not track the per-turn tool workdir.
23
+ */
24
+ getActiveWorkdir(): string;
18
25
  getAcpPermissionSession(): {
19
26
  sessionId: string;
20
27
  requestPermission(params: AcpPermissionRequestParams): Promise<{
@@ -8,5 +8,14 @@ export declare class CliPathService extends DefaultPathService implements PathSe
8
8
  constructor(mediaPersistence: MediaPersistence, toolBootstrap: ToolBootstrap);
9
9
  getActiveProjectRoot(): string;
10
10
  setActiveWorkdir(dir: string): void;
11
+ /**
12
+ * The exact directory the file/exec tools resolve relative paths against:
13
+ * setActiveWorkdir pushes this same value into toolBootstrap.setWorkdir
14
+ * (tool-bootstrap `workdir` + exec initCwd). The permission pipeline must
15
+ * classify cross-workspace ops against THIS, not getActiveProjectRoot()
16
+ * (which is also used for memory/sessions and may report a stale projectStore
17
+ * dir). Falls back to getActiveProjectRoot() before the first setActiveWorkdir.
18
+ */
19
+ getActiveWorkdir(): string;
11
20
  resolveProjectDir(projectId?: string): string | undefined;
12
21
  }
@@ -1,4 +1,5 @@
1
1
  import type { PortableTool } from "../skills/portable-tool.js";
2
+ import type { PermissionMode } from "../runtime/ports/permission-contracts.js";
2
3
  import { type TaskToolHooks } from "../skills/tools/task-tool.js";
3
4
  import { type ExecProgress } from "../skills/tools/exec-tool.js";
4
5
  import type { AgentLogger } from "../agent/types.js";
@@ -7,6 +8,15 @@ import type { PathService, ToolCatalog } from "../runtime/ports/index.js";
7
8
  export { setMediaClientConfig, setProviderToolAPI } from "./media-runtime-facade.js";
8
9
  /** Enable or disable group security mode (blocks sensitive file access). */
9
10
  export declare function setGroupSecurityMode(enabled: boolean): void;
11
+ export interface SandboxPermissionSnapshot {
12
+ mode: PermissionMode;
13
+ workdir: string;
14
+ allowedDirs: string[];
15
+ /** cut7c: current turn is an untrusted community-skill turn → tighten the sandbox. */
16
+ communityTurn: boolean;
17
+ }
18
+ /** Wire the OS-sandbox permission source (live rule-engine snapshot), or null to clear. */
19
+ export declare function setSandboxPermissionSource(source: (() => SandboxPermissionSnapshot | undefined) | null): void;
10
20
  /** Set callback invoked after LLM-driven project switch. */
11
21
  export declare function setProjectSwitchCallback(cb: ((project: {
12
22
  id: string;
@@ -24,9 +34,30 @@ export declare function setTaskToolHooks(hooks: TaskToolHooks | undefined): void
24
34
  * If null, ask_user returns "user declined" to the LLM.
25
35
  */
26
36
  export declare function setAskUserCallback(callback: ((questions: AskUserQuestion[]) => Promise<Record<string, string> | null>) | null): void;
27
- /** Enable full-access mode for trusted sessions that may operate outside the workspace. */
28
- export declare function setBypassWorkspaceBoundary(bypass: boolean): void;
29
37
  export declare function setBootstrapWorkdir(newWorkdir: string): void;
38
+ /**
39
+ * Enforce absolute, mode-independent floors. Returns an error message only for
40
+ * dangers that are NEVER allowed regardless of permission mode, null otherwise.
41
+ *
42
+ * Workspace membership is NO LONGER decided here: cross-workspace access is owned
43
+ * by the permission pipeline (the `tool.before_invoke` hook), which prompts in
44
+ * `default` mode and allows in `auto`/`full_access`. This function only blocks
45
+ * device/system paths that no mode should ever touch via file tools.
46
+ */
47
+ export declare function enforceAbsoluteFloors(resolved: string): string | null;
48
+ /**
49
+ * Validate a shell command string before execution.
50
+ *
51
+ * Cross-workspace access is NO LONGER blocked here — that decision is now owned
52
+ * by the permission pipeline (classifyOperation + hook-runner), which prompts in
53
+ * `default` mode and allows in `auto`/`full_access`.
54
+ *
55
+ * This function only enforces the absolute, mode-independent floor: device and
56
+ * system paths (/dev/* except /dev/null, /proc/*, /sys/*) that must never be
57
+ * accessible regardless of mode. Returns an error string on violation, null on
58
+ * pass.
59
+ */
60
+ export declare function validateExecCommand(command: string, _workdir: string): string | null;
30
61
  export interface BootstrapConfig {
31
62
  workdir?: string;
32
63
  toolCatalog?: ToolCatalog;
@@ -1,7 +1,6 @@
1
- import type { PermissionBehavior, PermissionEngineConfig, PermissionMode, PermissionRuleEntry } from "../runtime/ports/index.js";
1
+ import type { PermissionEngineConfig, PermissionMode, PermissionRuleEntry } from "../runtime/ports/index.js";
2
2
  export interface TurnPermissionRuleEngine {
3
3
  setMode(mode: PermissionMode): void;
4
4
  replaceRules(rules: PermissionRuleEntry[]): void;
5
- setDefaultBehavior(behavior: PermissionBehavior): void;
6
5
  }
7
6
  export declare function syncTurnPermissionConfig(ruleEngine: TurnPermissionRuleEngine, rawPermissions: unknown): PermissionEngineConfig;
@@ -298,6 +298,7 @@ export interface AcpPermissionRequestParams {
298
298
  rawInput: Record<string, unknown>;
299
299
  };
300
300
  message?: string;
301
+ category?: string;
301
302
  options: AcpPermissionOption[];
302
303
  }
303
304
  export interface AcpPermissionOption {
@@ -96,5 +96,11 @@ export interface ToolDefinition {
96
96
  isReadOnly?: boolean;
97
97
  /** Tool can cause irreversible side-effects — force approval in default mode. */
98
98
  isDangerous?: boolean;
99
+ /** Tool deletes data (files, records, cloud resources). */
100
+ isDelete?: boolean;
101
+ /** Tool sends data or requests to an external service. */
102
+ isEgress?: boolean;
103
+ /** Egress carries user data (true) vs. read-only external query (false). */
104
+ egressCarriesData?: boolean;
99
105
  };
100
106
  }
@@ -271,6 +271,18 @@ export interface ToolApprovalRequestNotification {
271
271
  arguments: string;
272
272
  message?: string;
273
273
  suggestions?: string[];
274
+ category?: "cross_workspace" | "high_risk_delete" | "high_risk_egress" | "destructive";
275
+ reason?: string;
276
+ options?: Array<{
277
+ optionId: string;
278
+ name: string;
279
+ kind: string;
280
+ scope?: string;
281
+ }>;
282
+ rememberScope?: {
283
+ kind: "directory";
284
+ path: string;
285
+ };
274
286
  }
275
287
  /** Skill instruction directive. */
276
288
  export interface TurnSkillInstructionNotification {
@@ -6,9 +6,12 @@ export interface DefaultPathServiceOptions {
6
6
  }
7
7
  export declare class DefaultPathService implements PathService {
8
8
  private readonly options;
9
+ /** Last directory set via setActiveWorkdir — the dir tool paths resolve against. */
10
+ private lastActiveWorkdir;
9
11
  constructor(options?: DefaultPathServiceOptions);
10
12
  getActiveProjectRoot(): string;
11
13
  setActiveWorkdir(dir: string): void;
14
+ getActiveWorkdir(): string;
12
15
  resolveProjectDir(projectId?: string): string | undefined;
13
16
  resolveActiveOwnerUserId(): string;
14
17
  getUserAgentHome(): string;
@@ -1,3 +1,4 @@
1
+ import { type ReasoningMode } from "./provider-catalog-adapter.js";
1
2
  import { type KeyConfig, type KeyHandle, type LoadBalanceStrategy, type PoolStatus, type ProviderPoolConfig } from "./key-pool.js";
2
3
  export type RegistryChangeCallback = () => void;
3
4
  export type ModelPurpose = "textGeneration" | "smallModel" | "stt" | "tts" | "imageGeneration" | "imageUnderstanding" | "videoGeneration" | "videoUnderstanding" | "threeDGeneration" | "embedding" | "voiceClone" | "musicGeneration" | "realtimeAudio" | "realtimeVideo";
@@ -34,6 +35,8 @@ export interface ModelEntry {
34
35
  streamRequired?: boolean;
35
36
  capabilities?: string[];
36
37
  pricing?: Record<string, unknown>;
38
+ /** Thinking-strength control class for the UI (see ReasoningMode). */
39
+ reasoningMode?: ReasoningMode;
37
40
  }
38
41
  export type PurposeBindings = Partial<Record<ModelPurpose, string>>;
39
42
  export interface ModelRegistryConfig {
@@ -1,5 +1,17 @@
1
1
  import { type ProviderVariantCapability } from "@xiaozhiclaw/provider-core";
2
2
  export type RuntimeProviderVariantCapability = ProviderVariantCapability;
3
+ /**
4
+ * How a model's thinking depth can be controlled, derived from provider-core
5
+ * capabilities + quirks. The UI maps each mode to a different effort option set:
6
+ * - none : model has no reasoning/thinking → hide the strength control
7
+ * - adaptive : model self-regulates depth natively (Anthropic Opus/Sonnet
8
+ * type:"adaptive") → only "智能"
9
+ * - collapsed : provider collapses effort levels (DeepSeek: low/medium→high,
10
+ * high/xhigh→max) → "标准 / 最大" only
11
+ * - effort : full granular control honored (o-series/Kimi reasoning_effort,
12
+ * Anthropic budget thinking) → full 5 options
13
+ */
14
+ export type ReasoningMode = "none" | "adaptive" | "collapsed" | "effort";
3
15
  export interface RuntimeProviderCatalogProvider {
4
16
  id: string;
5
17
  name: string;
@@ -24,6 +36,8 @@ export interface RuntimeProviderCatalogModel {
24
36
  costCacheRead?: number;
25
37
  costCacheWrite?: number;
26
38
  pricing?: Record<string, unknown>;
39
+ /** Derived control class for thinking strength (see ReasoningMode). */
40
+ reasoningMode?: ReasoningMode;
27
41
  }
28
42
  export interface RuntimeProviderVariantResolutionInput {
29
43
  publicModel: string;
@@ -0,0 +1,19 @@
1
+ import type { PermissionMode, PermissionPromptCategory } from "./ports/permission-contracts.js";
2
+ export interface PermissionPromptOption {
3
+ optionId: string;
4
+ name: string;
5
+ kind: "allow_once" | "allow_always" | "reject_once" | "reject_always";
6
+ scope?: "once" | "directory";
7
+ }
8
+ export interface PermissionModeDescriptor {
9
+ id: PermissionMode;
10
+ label: string;
11
+ summary: string;
12
+ description: string;
13
+ }
14
+ export interface PermissionModelContract {
15
+ modes: PermissionModeDescriptor[];
16
+ promptOptions: Record<PermissionPromptCategory, PermissionPromptOption[]>;
17
+ }
18
+ export declare function promptOptionsForCategory(category: PermissionPromptCategory): PermissionPromptOption[];
19
+ export declare function buildPermissionModel(): PermissionModelContract;
@@ -1,6 +1,16 @@
1
1
  export interface PathService {
2
2
  getActiveProjectRoot(): string;
3
3
  setActiveWorkdir(dir: string): void;
4
+ /**
5
+ * The directory the file/exec tools actually operate in (where relative tool
6
+ * paths resolve and the shell session's cwd lives). This is the value last set
7
+ * via setActiveWorkdir — it is the authoritative "active workdir" used by the
8
+ * permission pipeline to classify cross-workspace operations. It can differ
9
+ * from getActiveProjectRoot() (which feeds memory/sessions/project-agent-dir):
10
+ * getActiveProjectRoot may be a captured snapshot, while this always tracks the
11
+ * live tool workdir. Falls back to getActiveProjectRoot() if never set.
12
+ */
13
+ getActiveWorkdir(): string;
4
14
  resolveProjectDir(projectId?: string): string | undefined;
5
15
  resolveActiveOwnerUserId(): string;
6
16
  getUserAgentHome(): string;
@@ -1,12 +1,20 @@
1
1
  export type PermissionMode = "default" | "auto" | "full_access";
2
2
  export declare const PERMISSION_MODES: readonly PermissionMode[];
3
3
  export type PermissionBehavior = "allow" | "ask" | "deny";
4
+ export type PermissionPromptCategory = "cross_workspace" | "high_risk_delete" | "high_risk_egress" | "destructive";
4
5
  export type PermissionRiskLevel = "read" | "write" | "system" | "external_egress";
5
6
  export interface PermissionRuleEntry {
6
7
  pattern: string;
8
+ pathPrefix?: string;
7
9
  behavior: PermissionBehavior;
8
10
  reason?: string;
9
11
  source?: string;
12
+ /**
13
+ * When set, the rule only matches if this operation-classification flag is true
14
+ * (in addition to pattern/pathPrefix). Lets a rule target an operation property
15
+ * (e.g. a shell command that performs network egress) rather than just a tool name.
16
+ */
17
+ requireOp?: "commandNetworkEgress";
10
18
  }
11
19
  export type PermissionDecisionReason = {
12
20
  type: "rule";
@@ -45,6 +53,12 @@ export interface PermissionAskResult {
45
53
  message: string;
46
54
  toolName: string;
47
55
  input?: Record<string, unknown>;
56
+ category?: PermissionPromptCategory;
57
+ semanticCheck?: "delete" | "egress";
58
+ rememberScope?: {
59
+ kind: "directory";
60
+ path: string;
61
+ };
48
62
  decisionReason?: PermissionDecisionReason;
49
63
  }
50
64
  export interface PermissionDenyResult {
@@ -58,7 +72,6 @@ export interface PermissionConfig {
58
72
  }
59
73
  export interface PermissionEngineConfig extends PermissionConfig {
60
74
  rules: PermissionRuleEntry[];
61
- defaultBehavior: PermissionBehavior;
62
75
  }
63
76
  export interface ToolPermissionCheckInput {
64
77
  toolName: string;
@@ -69,6 +82,9 @@ export interface ToolPermissionCheckInput {
69
82
  isDangerous?: boolean;
70
83
  requiresApproval?: boolean;
71
84
  parallelSafe?: boolean;
85
+ isDelete?: boolean;
86
+ isEgress?: boolean;
87
+ egressCarriesData?: boolean;
72
88
  };
73
89
  }
74
90
  export interface ApprovalRequest {
@@ -77,19 +93,26 @@ export interface ApprovalRequest {
77
93
  toolName: string;
78
94
  arguments?: Record<string, unknown>;
79
95
  message: string;
96
+ /** Prompt category carried through from the decision pipeline (UI hint). */
97
+ category?: PermissionPromptCategory;
98
+ /** When set, the host may offer a "remember this directory" scope option. */
99
+ rememberScope?: {
100
+ kind: "directory";
101
+ path: string;
102
+ };
80
103
  }
81
104
  export interface ApprovalResponse {
82
105
  approvalId: string;
83
106
  decision: "allow" | "deny";
84
107
  updatedInput?: Record<string, unknown>;
108
+ /** Approval scope chosen by the user: just this call, or the whole directory. */
109
+ scope?: "once" | "directory";
85
110
  }
86
111
  export interface PermissionRuleEnginePort {
87
112
  getMode(): PermissionMode;
88
113
  setMode(mode: PermissionMode): void;
89
114
  getRules(): readonly PermissionRuleEntry[];
90
115
  replaceRules(rules: PermissionRuleEntry[]): void;
91
- getDefaultBehavior(): PermissionBehavior;
92
- setDefaultBehavior(behavior: PermissionBehavior): void;
93
116
  }
94
117
  export interface PermissionApprovalResolver {
95
118
  resolveApproval(response: ApprovalResponse): void;
@@ -21,6 +21,9 @@ export interface RuntimeToolContract {
21
21
  isConcurrencySafe?: boolean;
22
22
  isReadOnly?: boolean;
23
23
  riskLevel?: PermissionRiskLevel;
24
+ isDelete?: boolean;
25
+ isEgress?: boolean;
26
+ egressCarriesData?: boolean;
24
27
  isEnabled?: () => boolean;
25
28
  shouldDefer?: boolean;
26
29
  execute?(toolCallId: string, params: any, signal?: AbortSignal): Promise<{
@@ -0,0 +1,7 @@
1
+ import type { PermissionRiskLevel, ToolPermissionCheckInput } from "./permission-contracts.js";
2
+ /**
3
+ * 唯一风险分级核心。优先级:显式 riskLevel > isDangerous("system") > isReadOnly("read") > isEgress("external_egress") > 默认 "write"。
4
+ * isDangerous 优先于 isReadOnly:工具不应同时为二者,冲突时取更危险的分级。
5
+ * 调用点(tools.ts 的 manifest 构建、tool-eligibility 的资格判定)各自在其上叠加专属逻辑(category 推断 / name-pattern 升级)。
6
+ */
7
+ export declare function resolveToolRisk(meta: ToolPermissionCheckInput["meta"]): PermissionRiskLevel;
@@ -1,3 +1,3 @@
1
1
  import type { PermissionRuleEntry } from "./types.js";
2
2
  export type CommunitySandboxRuleId = "community-l1-shell" | "community-l1-file-read" | "community-l1-file-write" | "community-l1-network" | "community-l1-mcp" | "community-l1-host-control" | "community-l2-host-side-effect" | "community-l2-data-egress" | "community-l2-provider-prompt-egress" | "community-l2-provider-media-egress";
3
- export declare function createCommunityL1SandboxRules(): PermissionRuleEntry[];
3
+ export declare function createCommunitySandboxScopedRules(): PermissionRuleEntry[];
@@ -9,6 +9,8 @@ export type PermissionRole = "interactive" | "coordinator" | "worker";
9
9
  export interface PermissionCheckerDeps {
10
10
  ruleEngine: PermissionRuleEngine;
11
11
  hookRegistry: HookRegistry;
12
+ /** Returns the current workspace root for operation classification. */
13
+ getWorkspaceRoot(): string;
12
14
  /**
13
15
  * Callback to send an approval request to the host (Gateway/Electron).
14
16
  * Must return a promise that resolves when the user responds.
@@ -52,6 +54,7 @@ export interface PermissionCheckerDeps {
52
54
  export declare class PermissionChecker {
53
55
  private readonly ruleEngine;
54
56
  private readonly hookRegistry;
57
+ private readonly getWorkspaceRoot;
55
58
  private readonly onRequestApproval;
56
59
  private readonly onDenied;
57
60
  private readonly classifierLLMCall;
@@ -63,7 +66,13 @@ export declare class PermissionChecker {
63
66
  private readonly getTurnId;
64
67
  private readonly communityTelemetryRecorder;
65
68
  private readonly communitySandboxTurnIds;
66
- private readonly communitySandboxRuleEngine;
69
+ /**
70
+ * cut7c — community-skill (untrusted) scoped deny/ask rules, passed per-call as
71
+ * `ruleEngine.check(..., { extraRules })` for community turns. NOT a second engine:
72
+ * the single rule engine evaluates these alongside the base rules. FS/exec tools are
73
+ * absent here → they flow through the unified pipeline (workspace + prompt + OS sandbox).
74
+ */
75
+ private readonly communityScopedRules;
67
76
  private unregisterHook;
68
77
  /** Tool meta cache — populated from ToolDefinition[] at agent creation */
69
78
  private toolMetaCache;
@@ -86,7 +95,12 @@ export declare class PermissionChecker {
86
95
  */
87
96
  setToolMeta(tools: ToolDefinition[]): void;
88
97
  get ruleEngineRef(): PermissionRuleEngine;
89
- private checkCommunitySandboxThenBase;
98
+ /**
99
+ * cut7c — whether the CURRENT turn is a community-skill (untrusted) turn. Consumed by
100
+ * the OS-sandbox builder to tighten community execs (netPolicy=deny: no outbound network
101
+ * from the sandboxed child) as defense-in-depth on POSIX.
102
+ */
103
+ isCommunitySandboxTurn(): boolean;
90
104
  /** Fire permission.denied hook + onDenied callback + audit log */
91
105
  private fireDenied;
92
106
  private handleResult;
@@ -0,0 +1,43 @@
1
+ import type { ToolPermissionCheckInput } from "../../runtime/ports/permission-contracts.js";
2
+ /**
3
+ * 确定性操作归类。注意两个危险信号是分工的、互不包含:
4
+ * - `isDestructive`:shell 破坏性操作(rm -rf、dd、管道下载执行等)+ 递归删除 → 下游走"无条件保底必弹"。
5
+ * - `isDelete`:任意删除(含 meta 标注的删除工具)→ 下游单独走"该文件是否有用"的语义复核,不并入 isDestructive。
6
+ * 消费方(决策流水线)分别独立检查 isDestructive 与 isDelete。
7
+ */
8
+ export interface OperationClassification {
9
+ targetPaths: string[];
10
+ inWorkspace: boolean;
11
+ crossWorkspace: boolean;
12
+ isDelete: boolean;
13
+ isRecursiveDelete: boolean;
14
+ isEgress: boolean;
15
+ egressCarriesData: boolean;
16
+ isDestructive: boolean;
17
+ /**
18
+ * Shell command performs network egress (curl/wget/nc/scp/...). Used to gate
19
+ * untrusted community-skill shell commands (which would otherwise bypass the
20
+ * web_fetch/web_search deny via raw shell). Best-effort detection; the OS sandbox
21
+ * (netPolicy=deny on community turns) is the robust second layer on POSIX.
22
+ */
23
+ commandNetworkEgress: boolean;
24
+ }
25
+ export interface ClassifyContext {
26
+ workspaceRoot: string;
27
+ }
28
+ /**
29
+ * Canonical set of shell-command tool names. This is the single source of truth,
30
+ * imported and used by BOTH `classifyOperation` (below) AND hook-runner's bash
31
+ * pre-filter, so the two stay aligned. Includes the `Bash` case-variant.
32
+ */
33
+ export declare const SHELL_TOOL_NAMES: Set<string>;
34
+ export declare function isWithinWorkspace(resolved: string, root: string): boolean;
35
+ /**
36
+ * 提取 shell 命令引用的路径(重定向目标 + Unix/Windows/相对路径),解析为绝对路径。
37
+ * 移植自 exec validateCommand;win32 下把 /c/Users → C:\Users;跳过单字母 flag(/c /s …);
38
+ * /dev/null 不计入(由绝对底线另处理)。供 classifyOperation 与 exec validateCommand 共用(单一源)。
39
+ * 扫描层用聚焦 shell tokenizer(替代正则):正确处理带空格的引号路径、管道/&& 后的路径、
40
+ * 以及单/双引号内的字面 `>`(不当作重定向)。push() 的过滤/解析逻辑保持不变。
41
+ */
42
+ export declare function extractCommandPaths(command: string, root: string): string[];
43
+ export declare function classifyOperation(input: ToolPermissionCheckInput, ctx: ClassifyContext): OperationClassification;
@@ -1,34 +1,48 @@
1
+ import type { OperationClassification } from "./operation-classifier.js";
1
2
  import type { PermissionMode, PermissionBehavior, PermissionResult, PermissionRuleEntry, PermissionEngineConfig, ToolPermissionCheckInput } from "./types.js";
2
3
  export declare class PermissionRuleEngine {
3
4
  private mode;
4
5
  private rules;
5
- private defaultBehavior;
6
+ private sessionRules;
6
7
  private compiledPatterns;
7
8
  constructor(config: PermissionEngineConfig);
8
9
  getMode(): PermissionMode;
9
10
  setMode(mode: PermissionMode): void;
10
11
  getRules(): readonly PermissionRuleEntry[];
11
- getDefaultBehavior(): PermissionBehavior;
12
- /** Replace all rules at once (for settings hot-reload). */
12
+ /** Replace all persistent rules at once (for settings hot-reload). Session rules are preserved. */
13
13
  replaceRules(rules: PermissionRuleEntry[]): void;
14
- /** Update the default behavior (for settings hot-reload). */
15
- setDefaultBehavior(behavior: PermissionBehavior): void;
16
14
  addRule(rule: PermissionRuleEntry): void;
15
+ /** Add a session-scoped rule (e.g. "remember directory" approvals). */
16
+ addSessionRule(rule: {
17
+ pathPrefix?: string;
18
+ pattern?: string;
19
+ behavior?: PermissionBehavior;
20
+ }): void;
21
+ /** sandbox 可写根:rules + sessionRules 中 allow 且带 pathPrefix 的目录(去重)。 */
22
+ getAllowedWriteDirectories(): string[];
17
23
  /**
18
- * 4-layer permission check (cc hasPermissionsToUseTool parity).
24
+ * Workspace + danger decision pipeline.
19
25
  *
20
- * Layer 0: Mode override
21
- * Layer 1: Explicit rules (first match wins)
22
- * Layer 2: Unified tool risk metadata
23
- * Layer 3: Default behavior
26
+ * `op` is optional and defaults to an all-false classification so callers
27
+ * that have not yet been wired to the classifier keep compiling.
24
28
  */
25
- check(input: ToolPermissionCheckInput): PermissionResult;
26
- private compileRules;
29
+ check(input: ToolPermissionCheckInput, op?: OperationClassification, ctx?: {
30
+ extraRules?: readonly PermissionRuleEntry[];
31
+ }): PermissionResult;
32
+ /** Rebuild compiledPatterns: [persistent-deny] → [session] → [persistent non-deny]. */
33
+ private recompile;
34
+ /**
35
+ * Match a caller-supplied rule set (deny-first) against this call, using the SAME
36
+ * matching semantics as the base rules (pathPrefix prefix-match, else toolName glob).
37
+ * Returns the first hit's result, or null if none match. Used for per-call scoped
38
+ * rules (community-skill turns) so trust-level restrictions stay in the one engine.
39
+ */
40
+ private matchRuleSet;
27
41
  }
28
42
  /**
29
43
  * Parse permission config from Gateway-injected or local settings.
30
44
  *
31
- * Accepts only the user-facing permission mode. Historical rules/defaultBehavior
32
- * fields are ignored so old config cannot create a fourth user permission mode.
45
+ * Accepts the user-facing permission mode plus the persistent `rules` channel.
46
+ * Legacy top-level alias keys (allow/deny/ask arrays, default) are still rejected.
33
47
  */
34
48
  export declare function parsePermissionConfig(raw: unknown): PermissionEngineConfig;
@@ -104,6 +104,24 @@ export interface PortableTool<TParams = Record<string, unknown>> {
104
104
  * Used by permission system to require confirmation.
105
105
  */
106
106
  isDestructive?: boolean;
107
+ /**
108
+ * Whether this tool deletes data (files, records, resources).
109
+ * Distinct from isDestructive (shell-level) — this is a semantic annotation
110
+ * on tools that explicitly delete. Downstream uses separate delete-specific
111
+ * confirmation logic from the general destructive-shell path.
112
+ */
113
+ isDelete?: boolean;
114
+ /**
115
+ * Whether this tool sends data to an external service (egress).
116
+ * When true, the tool is classified as external_egress risk.
117
+ */
118
+ isEgress?: boolean;
119
+ /**
120
+ * Whether egress carries user data (as opposed to read-only queries).
121
+ * Only meaningful when isEgress is true.
122
+ * Examples: send_message (true), web_search (false).
123
+ */
124
+ egressCarriesData?: boolean;
107
125
  /**
108
126
  * Tool-specific permission check before execution.
109
127
  * CC parity: Tool.checkPermissions — returns allow/deny with reason.
@@ -1,5 +1,6 @@
1
1
  import type { PortableTool } from "../portable-tool.js";
2
2
  import type { CommandClassification } from "./shell/command-classification.js";
3
+ import type { SandboxConfig } from "./shell/sandbox/sandbox-types.js";
3
4
  export declare const EXEC_TOOL_NAME: "exec";
4
5
  export interface ExecToolParams {
5
6
  command: string;
@@ -72,6 +73,12 @@ export interface ExecToolHost {
72
73
  * Returns null/undefined if allowed, or an error message if blocked.
73
74
  */
74
75
  validateCommand?(command: string): Promise<string | null | undefined>;
76
+ /**
77
+ * Resolve the OS-sandbox context for this exec (snapshot of the permission
78
+ * single-source: mode + active workdir + approved dirs + helper availability).
79
+ * Returns undefined to disable the OS sandbox (degrade to prompt-only).
80
+ */
81
+ resolveSandbox?(): SandboxConfig | undefined;
75
82
  /**
76
83
  * Interpret a non-zero exit code.
77
84
  * E.g. 127 → "command not found", 137 → "killed by SIGKILL".
@@ -41,5 +41,12 @@ interface MatchResult {
41
41
  strategy: MatchStrategy;
42
42
  }
43
43
  declare function fuzzyFind(content: string, search: string, replaceAll: boolean): MatchResult[];
44
+ export interface PatchTargets {
45
+ /** Every file path the patch adds, updates, deletes, or moves (source + destination). */
46
+ paths: string[];
47
+ /** Paths removed via a *** Delete File: directive. */
48
+ deletes: string[];
49
+ }
50
+ export declare function extractPatchTargets(input: string): PatchTargets;
44
51
  export declare function createPatchTool(deps: PatchToolDeps): PortableTool<PatchToolParams>;
45
52
  export { fuzzyFind, type MatchResult };
@@ -0,0 +1,7 @@
1
+ interface ResolveOpts {
2
+ env?: string;
3
+ exists?: (p: string) => boolean;
4
+ }
5
+ /** 定位 landlock-exec:env 覆盖 → dist/native 默认 → null(降级)。 */
6
+ export declare function resolveLandlockHelper(opts?: ResolveOpts): string | null;
7
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { NetPolicy } from "./sandbox-types.js";
2
+ /**
3
+ * 构造 landlock helper 调用:`<helper> --allow-write R ... [--deny-net] -- <bin> <args>`。
4
+ * helper 在真实路径上加 landlock 写限制(读不 handle=放开),restrict_self 后 execve 原命令。
5
+ */
6
+ export declare function buildLandlockArgv(helperPath: string, writableRoots: string[], netPolicy: NetPolicy, bin: string, args: string[]): string[];
@@ -0,0 +1,3 @@
1
+ export type SandboxPlatform = "win32" | "darwin" | "linux" | "other";
2
+ /** 集中平台判定(仅 sandbox 模块用,不全局重构)。 */
3
+ export declare function currentPlatform(): SandboxPlatform;
@@ -0,0 +1,15 @@
1
+ import type { DecideSandboxInput, SandboxConfig, SandboxDecision } from "./sandbox-types.js";
2
+ import type { SandboxPlatform } from "./platform.js";
3
+ /**
4
+ * 纯决策:平台 + 模式 + 可用性 → 包裹或降级。
5
+ * 降级永不抛、永不拒执行(spec「降级安全」)。
6
+ */
7
+ export declare function decideSandbox(input: DecideSandboxInput): SandboxDecision;
8
+ /** shell-exec 单次入口:派生 scope → 决策 → 返回 spawn 应用的 bin/args + useSandbox。 */
9
+ export declare function applySandboxToSpawn(platform: SandboxPlatform, config: SandboxConfig, bin: string, args: string[]): {
10
+ bin: string;
11
+ args: string[];
12
+ useSandbox: boolean;
13
+ sandboxTmpDir?: string;
14
+ reason: string;
15
+ };
@@ -0,0 +1,6 @@
1
+ import type { SandboxConfig } from "./sandbox-types.js";
2
+ /**
3
+ * 沙箱可写根集 = active workdir + 批准/持久 allow 目录 + 工具记账目录 + 沙箱临时目录。
4
+ * 读全放开,故无读根。resolve + 去重。
5
+ */
6
+ export declare function deriveWritableRoots(config: SandboxConfig): string[];
@@ -0,0 +1,47 @@
1
+ import type { PermissionMode } from "../../../../runtime/ports/permission-contracts.js";
2
+ import type { SandboxPlatform } from "./platform.js";
3
+ /** 网络出站策略:allow=放开;deny=禁非回环出站(细粒度留后)。 */
4
+ export type NetPolicy = "allow" | "deny";
5
+ /**
6
+ * Host 在每次 exec 前注入的沙箱上下文(权限单一真相的快照)。
7
+ * undefined 表示 host 不启用沙箱(由调用方决定)。
8
+ */
9
+ export interface SandboxConfig {
10
+ mode: PermissionMode;
11
+ /** active workdir(pathService.getActiveWorkdir()),恒为可写根。 */
12
+ workdir: string;
13
+ /** 已批准/持久 allow 的目录(rule-engine.getAllowedWriteDirectories())。 */
14
+ allowedDirs: string[];
15
+ /** 工具记账可写目录(taskOutputDir + snapshot 目录),恒可写。 */
16
+ bookkeepingDirs: string[];
17
+ /** 沙箱内 cwd/snapshot 临时目录(可写;= sandboxTmpDir)。 */
18
+ tmpDir: string;
19
+ /** Linux landlock helper 路径(null=不可用→降级)。 */
20
+ helperPath: string | null;
21
+ /** sandbox-exec 是否可用(macOS;由 host 探测)。 */
22
+ sandboxExecAvailable: boolean;
23
+ netPolicy: NetPolicy;
24
+ }
25
+ /** decideSandbox 的输出:是否包裹 + 最终 spawn 的 bin/args。 */
26
+ export interface SandboxDecision {
27
+ /** 是否实际包裹(false=降级,直接用原 bin/args)。 */
28
+ wrapped: boolean;
29
+ bin: string;
30
+ args: string[];
31
+ /** 传给 provider.buildExecCommand 的 useSandbox。 */
32
+ useSandbox: boolean;
33
+ /** 包裹时的可写临时目录(cwdFile 落点);未包裹=undefined。 */
34
+ sandboxTmpDir?: string;
35
+ /** 决策原因(日志/可观测;降级时写 warn)。 */
36
+ reason: string;
37
+ }
38
+ /** decideSandbox 的纯输入(平台/可用性显式传入以便跨平台单测)。 */
39
+ export interface DecideSandboxInput {
40
+ platform: SandboxPlatform;
41
+ config: SandboxConfig;
42
+ /** 原始 spawn 目标。 */
43
+ bin: string;
44
+ args: string[];
45
+ /** 派生好的可写根(deriveWritableRoots 结果)。 */
46
+ writableRoots: string[];
47
+ }