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.
- package/dist/agent.js +6 -6
- package/dist/cli.js +269 -262
- package/dist/index.js +265 -258
- package/dist/types/cli/permission-bootstrap.d.ts +7 -0
- package/dist/types/cli/session-coordinator.d.ts +9 -0
- package/dist/types/cli/tool-bootstrap.d.ts +33 -2
- package/dist/types/cli/turn-permission-sync.d.ts +1 -2
- package/dist/types/protocol/wire/acp-protocol.d.ts +1 -0
- package/dist/types/protocol/wire/chat-types.d.ts +6 -0
- package/dist/types/protocol/wire/notification-payloads.d.ts +12 -0
- package/dist/types/runtime/infra/default-path-service.d.ts +3 -0
- package/dist/types/runtime/infra/model-registry.d.ts +3 -0
- package/dist/types/runtime/infra/provider-catalog-adapter.d.ts +14 -0
- package/dist/types/runtime/permission-model.d.ts +19 -0
- package/dist/types/runtime/ports/path-service.d.ts +10 -0
- package/dist/types/runtime/ports/permission-contracts.d.ts +26 -3
- package/dist/types/runtime/ports/tool-contracts.d.ts +3 -0
- package/dist/types/runtime/ports/tool-risk.d.ts +7 -0
- package/dist/types/skills/permissions/community-sandbox-policy.d.ts +1 -1
- package/dist/types/skills/permissions/hook-runner.d.ts +16 -2
- package/dist/types/skills/permissions/operation-classifier.d.ts +43 -0
- package/dist/types/skills/permissions/rule-engine.d.ts +28 -14
- package/dist/types/skills/portable-tool.d.ts +18 -0
- package/dist/types/skills/tools/exec-tool.d.ts +7 -0
- package/dist/types/skills/tools/patch-tool.d.ts +7 -0
- package/dist/types/skills/tools/shell/sandbox/helper-resolver.d.ts +7 -0
- package/dist/types/skills/tools/shell/sandbox/landlock-argv.d.ts +6 -0
- package/dist/types/skills/tools/shell/sandbox/platform.d.ts +3 -0
- package/dist/types/skills/tools/shell/sandbox/sandbox-launcher.d.ts +15 -0
- package/dist/types/skills/tools/shell/sandbox/sandbox-scope.d.ts +6 -0
- package/dist/types/skills/tools/shell/sandbox/sandbox-types.d.ts +47 -0
- package/dist/types/skills/tools/shell/sandbox/seatbelt-profile.d.ts +6 -0
- package/dist/types/skills/tools/shell/shell-exec.d.ts +3 -0
- 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 {
|
|
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;
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
24
|
+
* Workspace + danger decision pipeline.
|
|
19
25
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
|
26
|
-
|
|
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
|
|
32
|
-
*
|
|
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,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,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,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
|
+
}
|