@x-otto/hooks 0.0.1-alpha.2 → 0.0.1-alpha.4
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/README.md +26 -30
- package/dist/index.d.ts +64 -434
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -8
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -2,19 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Module Purpose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Pluggable interception and transformation engine spanning every stage of the agent lifecycle. Pure hook engine only — the permission policy system, sandbox safety assessment, and failure-signal diagnostics were split out to [`@x-otto/guard`](../guard) (RFC-395).
|
|
6
6
|
|
|
7
7
|
## Core Features
|
|
8
8
|
|
|
9
9
|
| Module | Function |
|
|
10
10
|
| ------------------------ | ------------------------------------------------------------- |
|
|
11
11
|
| HookRegistry | Registration, priority ordering, and execution engine for 25 hook timings |
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
| Built-in
|
|
16
|
-
| Presets | 4 permission modes: default / strict / minimal / none |
|
|
17
|
-
| 13 Built-in Hooks | session / tool guard / transform / quality / stream / compaction |
|
|
12
|
+
| defineHook | Type-inferred hook factory (timing literal drives handle signature) |
|
|
13
|
+
| HOOK_NODES | Single source of truth for the 25 timings + compile-time drift guard |
|
|
14
|
+
| Presets | 4 tiers: default / strict / minimal / none |
|
|
15
|
+
| Built-in Hooks | session / tool guard / transform / quality / stream / compaction (infrastructure + agent-hygiene hooks) |
|
|
18
16
|
|
|
19
17
|
## Hook Timings (25 total, single-source HOOK_NODES)
|
|
20
18
|
|
|
@@ -35,44 +33,42 @@ import { createHookRegistry, defineHook } from '@x-otto/hooks'
|
|
|
35
33
|
|
|
36
34
|
const registry = createHookRegistry({ preset: 'default' })
|
|
37
35
|
|
|
38
|
-
// Register a tool-execution
|
|
36
|
+
// Register a tool-execution interceptor
|
|
39
37
|
registry.register(
|
|
40
38
|
defineHook({
|
|
41
|
-
name: '
|
|
39
|
+
name: 'my-tool-guard',
|
|
42
40
|
timing: 'tool.execute.before',
|
|
43
41
|
priority: 0,
|
|
44
42
|
handle: (input, output) => {
|
|
45
|
-
if (input.toolName === 'write' && input.
|
|
46
|
-
output.
|
|
47
|
-
output.
|
|
43
|
+
if (input.toolName === 'write' && String(input.args.path ?? '').includes('/etc/')) {
|
|
44
|
+
output.cancelled = true
|
|
45
|
+
output.cancelReason = 'Writing to /etc/ is blocked'
|
|
48
46
|
}
|
|
49
47
|
},
|
|
50
48
|
}),
|
|
51
49
|
)
|
|
52
50
|
```
|
|
53
51
|
|
|
52
|
+
For permission policies, sandbox safety, and failure diagnostics, see [`@x-otto/guard`](../guard).
|
|
53
|
+
|
|
54
54
|
## Directory Overview
|
|
55
55
|
|
|
56
56
|
```
|
|
57
57
|
src/
|
|
58
|
-
types.ts #
|
|
59
|
-
hook-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
quality/ # 3 quality hooks
|
|
71
|
-
stream/ # 2 stream hooks
|
|
72
|
-
compaction/ # 2 compaction hooks
|
|
73
|
-
background/ # 2 background hooks
|
|
58
|
+
types.ts # re-exports @x-otto/hook-contracts timing/payload types
|
|
59
|
+
hook-nodes.ts # HOOK_NODES single source of truth + drift guard
|
|
60
|
+
hook-spec.ts # defineHook factory + HookSpec
|
|
61
|
+
hook-registry.ts # hook registration + execution engine + 4-tier HookPreset
|
|
62
|
+
core/
|
|
63
|
+
hook-state.ts # per-session state container
|
|
64
|
+
session/ # session hooks (first-message-variant)
|
|
65
|
+
tool-guard/ # tool-guard hooks (label/output truncation / error tracking)
|
|
66
|
+
transform/ # transform hooks (thinking-validator / anthropic-effort / token-budget)
|
|
67
|
+
quality/ # quality hooks (comment-checker)
|
|
68
|
+
stream/ # stream hooks (stream-metrics)
|
|
69
|
+
compaction/ # compaction hooks (compaction-logger)
|
|
74
70
|
index.ts
|
|
75
|
-
tests/ # 9 test files
|
|
71
|
+
tests/ # 9 test files (engine + built-in hooks)
|
|
76
72
|
```
|
|
77
73
|
|
|
78
74
|
## Development Commands
|
|
@@ -85,4 +81,4 @@ pnpm --filter @x-otto/hooks clean
|
|
|
85
81
|
|
|
86
82
|
## Related Packages
|
|
87
83
|
|
|
88
|
-
`@x-otto/shared`, `@x-otto/env`
|
|
84
|
+
`@x-otto/hook-contracts` (timing/payload type contracts), `@x-otto/guard` (permission/sandbox/observability — depends on this package), `@x-otto/shared`, `@x-otto/env`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
|
-
import { ChatMessageInput, ChatMessageOutput, ChatParamsInput, ChatParamsOutput, HookEmitInput, HookHandle, HookInput, HookOutput, HookPayloadMap, HookTiming, InterceptorTiming, MessagesTransformInput, MessagesTransformOutput, ObserverTiming,
|
|
1
|
+
import { ChatMessageInput, ChatMessageOutput, ChatParamsInput, ChatParamsOutput, HookAbortSignal, HookAbortSignal as HookAbortSignal$1, HookEmitInput, HookHandle, HookInput, HookOutput, HookPayloadMap, HookTiming, InterceptorTiming, MessagesTransformInput, MessagesTransformOutput, ObserverTiming, SessionEventInput, SystemPromptTransformInput, SystemPromptTransformOutput, ToolExecuteAfterInput, ToolExecuteAfterOutput, ToolExecuteBeforeInput, ToolExecuteBeforeOutput, WATERFALL_TIMINGS, WaterfallTiming, isWaterfallTiming } from "@x-otto/hook-contracts";
|
|
2
2
|
|
|
3
|
+
//#region src/hook-spec.d.ts
|
|
4
|
+
type BaseRuntimeHook = {
|
|
5
|
+
name: string;
|
|
6
|
+
timing: HookTiming;
|
|
7
|
+
priority: number;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
};
|
|
10
|
+
type ObserverRuntimeHook = BaseRuntimeHook & {
|
|
11
|
+
kind: 'observer';
|
|
12
|
+
handle: (input: unknown) => void | Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
type InterceptorRuntimeHook = BaseRuntimeHook & {
|
|
15
|
+
kind: 'interceptor';
|
|
16
|
+
handle: (input: unknown, output: unknown) => void | HookAbortSignal$1 | Promise<void | HookAbortSignal$1>;
|
|
17
|
+
};
|
|
18
|
+
type HookSpec = ObserverRuntimeHook | InterceptorRuntimeHook;
|
|
19
|
+
/**
|
|
20
|
+
* 所有 as 强转集中在此处:HookHandle<T> 因函数参数逆变无法自动赋给 (unknown) => void。
|
|
21
|
+
* factory 函数通过 timing 字面量驱动 handle 的类型推断,调用侧零 cast。
|
|
22
|
+
*/
|
|
23
|
+
declare function defineHook<T extends HookTiming>(config: {
|
|
24
|
+
name: string;
|
|
25
|
+
timing: T;
|
|
26
|
+
handle: HookHandle<T>;
|
|
27
|
+
priority?: number;
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
}): HookSpec;
|
|
30
|
+
//#endregion
|
|
3
31
|
//#region src/core/hook-state.d.ts
|
|
4
32
|
interface StreamMetrics {
|
|
5
33
|
startTime: number;
|
|
@@ -27,33 +55,33 @@ declare class HookState {
|
|
|
27
55
|
clearSession(sessionId: string): void;
|
|
28
56
|
}
|
|
29
57
|
//#endregion
|
|
30
|
-
//#region src/hook-
|
|
31
|
-
type
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
priority: number;
|
|
35
|
-
enabled: boolean;
|
|
36
|
-
};
|
|
37
|
-
type ObserverRuntimeHook = BaseRuntimeHook & {
|
|
38
|
-
kind: 'observer';
|
|
39
|
-
handle: (input: unknown) => void | Promise<void>;
|
|
40
|
-
};
|
|
41
|
-
type InterceptorRuntimeHook = BaseRuntimeHook & {
|
|
42
|
-
kind: 'interceptor';
|
|
43
|
-
handle: (input: unknown, output: unknown) => void | Promise<void>;
|
|
44
|
-
};
|
|
45
|
-
type HookSpec = ObserverRuntimeHook | InterceptorRuntimeHook;
|
|
58
|
+
//#region src/hook-preset.d.ts
|
|
59
|
+
type HookPreset = 'default' | 'strict' | 'minimal' | 'none';
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/scope.d.ts
|
|
46
62
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
63
|
+
* RFC-395 M4:scoped registration 的 disposable 契约。token 由 `HookRegistry.register()`
|
|
64
|
+
* / `registerAll()` 返回,`dispose()` 精确移除该次注册(按 spec 引用),与 `unregister(name)`
|
|
65
|
+
* 的按名移除互补。现有调用方(忽略返回值)行为不变。
|
|
49
66
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
interface Disposable {
|
|
68
|
+
dispose(): void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* RFC-395 M4:具名子 scope——`HookRegistry.createScope(name)` 创建。scope 内的注册
|
|
72
|
+
* 自动带 `${name}:` 前缀(与 RFC-216 插件分桶的 `plugin:${pluginId}:` 命名约定同构),
|
|
73
|
+
* `dispose()` 批量撤销 scope 内全部注册。与插件分桶共存:token 优先,分桶兜底。
|
|
74
|
+
*/
|
|
75
|
+
declare class HookScope implements Disposable {
|
|
76
|
+
private readonly registry;
|
|
77
|
+
private readonly name;
|
|
78
|
+
private readonly tokens;
|
|
79
|
+
constructor(registry: HookRegistry, name: string);
|
|
80
|
+
/** 带 `${name}:` 前缀注册;返回精确 token(dispose 只移除本次注册)。 */
|
|
81
|
+
register(spec: HookSpec): Disposable;
|
|
82
|
+
/** 批量撤销 scope 内全部注册(token 幂等:已被外部 unregister 的 token 是 no-op)。 */
|
|
83
|
+
dispose(): void;
|
|
84
|
+
}
|
|
57
85
|
//#endregion
|
|
58
86
|
//#region src/hook-registry.d.ts
|
|
59
87
|
interface RegisteredHookInfo {
|
|
@@ -62,7 +90,6 @@ interface RegisteredHookInfo {
|
|
|
62
90
|
priority: number;
|
|
63
91
|
enabled: boolean;
|
|
64
92
|
}
|
|
65
|
-
type HookPreset = 'default' | 'strict' | 'minimal' | 'none';
|
|
66
93
|
interface HookRegistryOptions {
|
|
67
94
|
preset?: HookPreset;
|
|
68
95
|
/** RFC-153 D1:interceptor/observer hook 超时时的旁路通知(可选,宿主层注入)。
|
|
@@ -80,8 +107,15 @@ declare class HookRegistry {
|
|
|
80
107
|
private readonly onHookTimeout?;
|
|
81
108
|
readonly state: HookState;
|
|
82
109
|
constructor(options?: HookRegistryOptions);
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
/**
|
|
111
|
+
* RFC-395 M4:返回精确 token(dispose 按 spec 引用移除本次注册,不误伤同名 hook)。
|
|
112
|
+
* 现有调用方忽略返回值即可,行为不变。
|
|
113
|
+
*/
|
|
114
|
+
register(spec: HookSpec): Disposable;
|
|
115
|
+
/** RFC-395 M4:批量注册,返回一一对应的 token 数组(与 register 同语义)。 */
|
|
116
|
+
registerAll(specs: HookSpec[]): Disposable[];
|
|
117
|
+
/** RFC-395 M4:具名子 scope——scope 内注册自动带 `${name}:` 前缀,dispose 批量撤销。 */
|
|
118
|
+
createScope(name: string): HookScope;
|
|
85
119
|
on<T extends HookTiming>(timing: T, name: string, handle: HookHandle<T>, priority?: number): this;
|
|
86
120
|
has(name: string): boolean;
|
|
87
121
|
unregister(name: string): boolean;
|
|
@@ -89,33 +123,6 @@ declare class HookRegistry {
|
|
|
89
123
|
enable(name: string): void;
|
|
90
124
|
getRegistered(timing?: HookTiming): RegisteredHookInfo[];
|
|
91
125
|
execute<T extends InterceptorTiming>(timing: T, input: HookInput<T>, output: HookOutput<T>): Promise<void>;
|
|
92
|
-
/**
|
|
93
|
-
* RFC-153 D1:interceptor hooks 挂起兜底——`execute()` 循环内此前是裸 `await hook.handle(...)`,
|
|
94
|
-
* 挂起(无限循环/无限 fetch/死锁)的 hook 会永久阻塞整个 agent turn。复用 observer 路径
|
|
95
|
-
* (`runObserverHookWithTimeout`)已验证的 `Promise.race` + `unref()` 模式,阈值改用两路径共享的
|
|
96
|
-
* `HOOK_TIMEOUT_MS` 常量(规则 1:超时不等于中止——不承诺真正终止 hook 内部执行)。
|
|
97
|
-
* 超时命中时通过 `onHookTimeout` 回调向上冒泡(规则 7:只携带领域无关字段,`packages/hooks`
|
|
98
|
-
* 不认识"插件"概念),调用方(`packages/coding`)负责解析 hookName 里的 pluginId 前缀。
|
|
99
|
-
*/
|
|
100
|
-
private runInterceptorWithTimeout;
|
|
101
|
-
/**
|
|
102
|
-
* RFC-143 M0:observer hook 挂起兜底。`emit()` 此前的 try/catch 只吞同步抛错和 rejected
|
|
103
|
-
* promise,不吞「handler 挂起」(无限循环/无限 fetch/死锁)——`await hook.handle(input)` 会
|
|
104
|
-
* 无限期阻塞调用方(如用户显式 `removeSession`、task 生命周期判定、流式启动)。
|
|
105
|
-
* 超时阈值使用与 interceptor 路径(`runInterceptorWithTimeout`)共享的模块级常量
|
|
106
|
-
* `HOOK_TIMEOUT_MS`(RFC-153 D1:避免两条路径各自维护一份可能漂移的数字,此前本方法
|
|
107
|
-
* 独立持有的 `EMIT_TIMEOUT_MS` 已废弃合并)。`unref()` 避免这个定时器阻塞进程退出。
|
|
108
|
-
*/
|
|
109
|
-
private runObserverHookWithTimeout;
|
|
110
|
-
/**
|
|
111
|
-
* RFC-143 R2a:`session.error` timing 的 input 契约上携带原始 `Error` 对象
|
|
112
|
-
* (`HookPayloadMap['session.error'].input.error: Error`,见 hook-contracts/timings.ts)。
|
|
113
|
-
* SDK 层(`module-wiring.ts`)已对 definePlugin 消费路径做 `error.message` 转换,但那只是
|
|
114
|
-
* 单一消费方的自觉行为——emit() 是所有 observer hook(含非 SDK 注册路径,如未来的引擎内部
|
|
115
|
-
* 钩子/测试工具)的唯一入口,必须在这里做结构式 sanitize,防止任何消费方读到含栈追踪/敏感
|
|
116
|
-
* 路径信息的原始 Error 对象。sanitize 后的 input 只对 `session.error` 生效,其余 timing 原样传递。
|
|
117
|
-
*/
|
|
118
|
-
private sanitizeEmitInput;
|
|
119
126
|
emit<T extends ObserverTiming>(timing: T, input: HookEmitInput<T>): Promise<void>;
|
|
120
127
|
clear(): void;
|
|
121
128
|
private getSortedInterceptorHooks;
|
|
@@ -166,382 +173,5 @@ declare function createStreamEndMetricsHook(state: HookState): HookSpec;
|
|
|
166
173
|
declare function createCompactionLoggerHook(state: HookState): HookSpec;
|
|
167
174
|
declare function createCompactionAfterHook(state: HookState): HookSpec;
|
|
168
175
|
//#endregion
|
|
169
|
-
|
|
170
|
-
/** 编辑文件记录的内部类型(hooks 层使用,不耦合 tui 的 EditedFileEntry)。 */
|
|
171
|
-
interface EditFileInfo {
|
|
172
|
-
path: string;
|
|
173
|
-
operation: 'edit' | 'write' | 'delete';
|
|
174
|
-
oldContent?: string;
|
|
175
|
-
newContent?: string;
|
|
176
|
-
addedLines: number;
|
|
177
|
-
removedLines: number;
|
|
178
|
-
timestamp: number;
|
|
179
|
-
toolCallId: string;
|
|
180
|
-
}
|
|
181
|
-
/** 文件修改回调(CLI 层负责适配到 TuiHandle.addEditedFile) */
|
|
182
|
-
type EditFileCallback = (info: EditFileInfo) => void;
|
|
183
|
-
interface EditFilesPanelHookOptions {
|
|
184
|
-
onFileEdited?: EditFileCallback;
|
|
185
|
-
/** 延迟获取回调——用于注册时 TUI 尚未就绪的场景(取值时机=emit 时而非 register 时)。优先级高于 onFileEdited。 */
|
|
186
|
-
getOnFileEdited?: () => EditFileCallback | undefined;
|
|
187
|
-
dedupCache?: Set<string> | null;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* 创建 hook spec,注册后在每次 edit/write 工具执行完成后累积回写 TUI。
|
|
191
|
-
*
|
|
192
|
-
* 用法:
|
|
193
|
-
* const hook = createEditFilesPanelHook({
|
|
194
|
-
* onFileEdited: (info) => tuiHandle.addEditedFile({
|
|
195
|
-
* ...info,
|
|
196
|
-
* addedLines: info.addedLines,
|
|
197
|
-
* removedLines: info.removedLines,
|
|
198
|
-
* turnId: currentTurnId,
|
|
199
|
-
* }),
|
|
200
|
-
* })
|
|
201
|
-
* hookRegistry.register(hook)
|
|
202
|
-
*/
|
|
203
|
-
declare function createEditFilesPanelHook(options?: EditFilesPanelHookOptions): HookSpec;
|
|
204
|
-
//#endregion
|
|
205
|
-
//#region src/core/permission/types.d.ts
|
|
206
|
-
interface PolicyScope {
|
|
207
|
-
/** 适用的 agent 名称, '*' 或缺省表示所有 */
|
|
208
|
-
agents?: string[];
|
|
209
|
-
sessions?: string[];
|
|
210
|
-
}
|
|
211
|
-
interface PermissionContext {
|
|
212
|
-
timing: HookTiming;
|
|
213
|
-
toolName: string;
|
|
214
|
-
args: Record<string, unknown>;
|
|
215
|
-
agentName: string;
|
|
216
|
-
sessionId: string;
|
|
217
|
-
filePath?: string;
|
|
218
|
-
metadata: Record<string, unknown>;
|
|
219
|
-
}
|
|
220
|
-
interface RuleMatch {
|
|
221
|
-
tools?: string[];
|
|
222
|
-
paths?: RegExp[];
|
|
223
|
-
/** 必须同步。 */
|
|
224
|
-
condition?: (context: PermissionContext) => boolean;
|
|
225
|
-
}
|
|
226
|
-
interface PermissionRule {
|
|
227
|
-
name: string;
|
|
228
|
-
effect: RuleEffect$1;
|
|
229
|
-
match: RuleMatch;
|
|
230
|
-
denyReason?: string;
|
|
231
|
-
askPrompt?: string;
|
|
232
|
-
/**
|
|
233
|
-
* 兜底规则(C1/RFC-016):true 表示本规则仅在**没有任何非兜底规则命中**时生效
|
|
234
|
-
* (如 strict 的 deny-unmatched、readonly 的 deny-writes 这类 match:{} 默认拒绝)。
|
|
235
|
-
* 在 deny-wins 评估下,兜底 deny 不得盖过显式 allow(否则 strict/readonly 自破)。
|
|
236
|
-
*/
|
|
237
|
-
fallthrough?: boolean;
|
|
238
|
-
}
|
|
239
|
-
interface PermissionPolicy {
|
|
240
|
-
name: string;
|
|
241
|
-
priority: number;
|
|
242
|
-
enabled: boolean;
|
|
243
|
-
scope: PolicyScope;
|
|
244
|
-
rules: PermissionRule[];
|
|
245
|
-
/**
|
|
246
|
-
* 安全护栏标记(C1/RFC-016):true 表示本策略的 ask/deny 是安全护栏
|
|
247
|
-
* (受保护路径、破坏性命令),alwaysAllow 绝不升级其 ask,bypass 也不能盖过。
|
|
248
|
-
* 缺省 false(mode/user 策略的 ask 可被 alwaysAllow 升级)。
|
|
249
|
-
*/
|
|
250
|
-
safety?: boolean;
|
|
251
|
-
}
|
|
252
|
-
interface PermissionDecision {
|
|
253
|
-
effect: RuleEffect$1;
|
|
254
|
-
policyName: string;
|
|
255
|
-
ruleName: string;
|
|
256
|
-
reason?: string;
|
|
257
|
-
timestamp: number;
|
|
258
|
-
evaluatedPolicies: number;
|
|
259
|
-
/**
|
|
260
|
-
* 命中策略的安全护栏位(透传自 PermissionPolicy.safety)——HITL 风险等级单一真源(RFC-077 M3)。
|
|
261
|
-
* 取代 riskFromDecision 靠正则匹配 policyName 子串(destructive|credential|file-guard)反推风险的
|
|
262
|
-
* 字符串考古:自定义安全策略名不含三魔词时不再被误判为 medium。仅 ask/deny(经 pick)携带;
|
|
263
|
-
* allow/默认放行不参与风险评估故可空。
|
|
264
|
-
*/
|
|
265
|
-
safety?: boolean;
|
|
266
|
-
}
|
|
267
|
-
interface PermissionAuditEntry {
|
|
268
|
-
timestamp: number;
|
|
269
|
-
sessionId: string;
|
|
270
|
-
agentName: string;
|
|
271
|
-
toolName: string;
|
|
272
|
-
filePath?: string;
|
|
273
|
-
decision: PermissionDecision;
|
|
274
|
-
}
|
|
275
|
-
//#endregion
|
|
276
|
-
//#region src/core/permission/builtin-policies.d.ts
|
|
277
|
-
interface PermissionToolDescriptor {
|
|
278
|
-
name: string;
|
|
279
|
-
readonly?: boolean;
|
|
280
|
-
metadata?: {
|
|
281
|
-
category?: string;
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
interface PermissionToolSets {
|
|
285
|
-
readonlyTools: Set<string>;
|
|
286
|
-
writeTools: Set<string>;
|
|
287
|
-
fileWriteTools: Set<string>;
|
|
288
|
-
}
|
|
289
|
-
interface PermissionToolSetsInput {
|
|
290
|
-
readonlyTools?: Iterable<string>;
|
|
291
|
-
writeTools?: Iterable<string>;
|
|
292
|
-
fileWriteTools?: Iterable<string>;
|
|
293
|
-
}
|
|
294
|
-
declare function createPermissionToolSetsFromRegistry(tools: Iterable<PermissionToolDescriptor>): PermissionToolSets;
|
|
295
|
-
declare function createFileGuardPolicy(toolSets?: PermissionToolSetsInput): PermissionPolicy;
|
|
296
|
-
declare const DESTRUCTIVE_COMMAND_POLICY: PermissionPolicy;
|
|
297
|
-
declare const CREDENTIAL_READ_POLICY: PermissionPolicy;
|
|
298
|
-
declare function createOttoStateDomainPolicy(toolSets?: PermissionToolSetsInput): PermissionPolicy;
|
|
299
|
-
declare function createEgressAllowlistPolicy(allowedHosts: readonly string[]): PermissionPolicy;
|
|
300
|
-
declare function createDisabledToolsPolicy(disabledTools: string[]): PermissionPolicy;
|
|
301
|
-
declare function createPermissionModePolicy(mode: PermissionMode, toolSets?: PermissionToolSetsInput): PermissionPolicy;
|
|
302
|
-
//#endregion
|
|
303
|
-
//#region src/core/permission/register-builtin.d.ts
|
|
304
|
-
interface RegisterBuiltinPoliciesOptions {
|
|
305
|
-
toolSets?: PermissionToolSetsInput;
|
|
306
|
-
}
|
|
307
|
-
//#endregion
|
|
308
|
-
//#region src/core/permission/policy-registry.d.ts
|
|
309
|
-
declare class PermissionPolicyRegistry {
|
|
310
|
-
private policies;
|
|
311
|
-
/**
|
|
312
|
-
* 项目级「始终允许」的工具名(R8-B base)。语义:把该工具被评估出的 `ask` 升级为 `allow`,
|
|
313
|
-
* 但绝不把 `deny` 改为 allow——安全 deny(受保护路径/破坏性命令)始终优先。
|
|
314
|
-
* 来源 .otto/config.json,对工作区所有会话生效(手动配置的常驻放行基线)。
|
|
315
|
-
*/
|
|
316
|
-
private alwaysAllow;
|
|
317
|
-
/**
|
|
318
|
-
* 会话级「始终允许」(弹窗里点「始终允许」的运行时授权):按 sessionId 隔离,
|
|
319
|
-
* 跟随会话持久化(落盘进 session 记录,--continue 时回灌),互不串味——
|
|
320
|
-
* 一次性的信任决策不再泄漏到项目其它会话/永久污染 config(修旧 R8-B 全局泄漏)。
|
|
321
|
-
*/
|
|
322
|
-
private sessionAlwaysAllow;
|
|
323
|
-
setAlwaysAllow(tools: readonly string[]): void;
|
|
324
|
-
addAlwaysAllow(tool: string): void;
|
|
325
|
-
getAlwaysAllow(): string[];
|
|
326
|
-
/** 整体替换某会话的 always-allow 集合(会话恢复/回灌用)。空集合=移除该会话条目。 */
|
|
327
|
-
setSessionAlwaysAllow(sessionId: string, tools: readonly string[]): void;
|
|
328
|
-
addSessionAlwaysAllow(sessionId: string, tool: string): void;
|
|
329
|
-
/** 不含项目级基线。 */
|
|
330
|
-
getSessionAlwaysAllow(sessionId: string): string[];
|
|
331
|
-
clearSessionAlwaysAllow(sessionId: string): void;
|
|
332
|
-
register(policy: PermissionPolicy): void;
|
|
333
|
-
registerAll(policies: PermissionPolicy[]): void;
|
|
334
|
-
unregister(name: string): boolean;
|
|
335
|
-
has(name: string): boolean;
|
|
336
|
-
/**
|
|
337
|
-
* collect-all / deny-wins(C1/RFC-016,收集全部命中规则后按 effect 优先级裁决):
|
|
338
|
-
* 收集所有命中规则,决策优先级 deny > ask > allow,**与命中/优先级顺序无关**——
|
|
339
|
-
* priority 仅用于在同 effect 内择 reason。这样 `bypass` 的 allow-all 不再能盖过
|
|
340
|
-
* 安全护栏的 deny/ask(first-match 旧语义的倒置缺口)。
|
|
341
|
-
*
|
|
342
|
-
* alwaysAllow(R8-B):仅升级**非安全护栏**的 ask→allow;安全护栏(file-guard/
|
|
343
|
-
* destructive-guard,policy.safety=true)的 ask/deny 绝不被升级或盖过。
|
|
344
|
-
*/
|
|
345
|
-
evaluate(context: PermissionContext): PermissionDecision;
|
|
346
|
-
getEffective(agentName: string): PermissionPolicy[];
|
|
347
|
-
getAll(): PermissionPolicy[];
|
|
348
|
-
clear(): void;
|
|
349
|
-
}
|
|
350
|
-
declare function createPermissionRegistry(options?: RegisterBuiltinPoliciesOptions): PermissionPolicyRegistry;
|
|
351
|
-
declare function compilePolicyFromSetting(setting: PermissionPolicySetting): PermissionPolicy;
|
|
352
|
-
//#endregion
|
|
353
|
-
//#region src/core/permission/audit-log.d.ts
|
|
354
|
-
declare class PermissionAuditLog {
|
|
355
|
-
private entries;
|
|
356
|
-
private readonly maxEntries;
|
|
357
|
-
constructor(maxEntries?: number);
|
|
358
|
-
record(context: PermissionContext, decision: PermissionDecision): void;
|
|
359
|
-
getEntries(filter?: {
|
|
360
|
-
sessionId?: string;
|
|
361
|
-
effect?: RuleEffect;
|
|
362
|
-
}): PermissionAuditEntry[];
|
|
363
|
-
getDenyCount(sessionId?: string): number;
|
|
364
|
-
getAskCount(sessionId?: string): number;
|
|
365
|
-
clear(sessionId?: string): void;
|
|
366
|
-
get size(): number;
|
|
367
|
-
}
|
|
368
|
-
//#endregion
|
|
369
|
-
//#region src/core/permission/permission-guard.d.ts
|
|
370
|
-
declare class PermissionGuardHook {
|
|
371
|
-
readonly name = "permission-guard";
|
|
372
|
-
readonly timing: "tool.execute.before";
|
|
373
|
-
readonly priority = 5;
|
|
374
|
-
enabled: boolean;
|
|
375
|
-
private readonly registry;
|
|
376
|
-
private readonly auditLog?;
|
|
377
|
-
constructor(registry: PermissionPolicyRegistry, auditLog?: PermissionAuditLog);
|
|
378
|
-
handle: (input: ToolExecuteBeforeInput, output: ToolExecuteBeforeOutput) => void;
|
|
379
|
-
}
|
|
380
|
-
declare function createPermissionGuardHook(registry: PermissionPolicyRegistry, auditLog?: PermissionAuditLog): HookSpec;
|
|
381
|
-
//#endregion
|
|
382
|
-
//#region src/core/permission/baseline-provisioning.d.ts
|
|
383
|
-
/**
|
|
384
|
-
* RFC-077 M3-B:bare-runtime(无工具注册表)的基线 `fileWriteTools`——`createFileGuardPolicy`
|
|
385
|
-
* 的 protect-system-paths 规则按 `tools: [...fileWriteTools]` 过滤,无注册表派生集时须给默认,
|
|
386
|
-
* 否则空集 → 该规则匹配不到任何工具 → protected-path 写不被拦。
|
|
387
|
-
*
|
|
388
|
-
* 与 standalone `createFileGuardHook` 的 `WRITE_TOOLS` 同源(canonical otto 文件写工具);bash 非
|
|
389
|
-
* path-based 故不入 fileWriteTools(其命令面由 destructive/credential 命令策略覆盖)。**宿主有工具
|
|
390
|
-
* 注册表时经 `createPermissionToolSetsFromRegistry` 派生集覆盖本默认**(coding 走该路径)。
|
|
391
|
-
*/
|
|
392
|
-
declare const BASELINE_PERMISSION_TOOL_SETS: PermissionToolSetsInput;
|
|
393
|
-
/**
|
|
394
|
-
* RFC-077 M3-B(D6 §4.11「安全护栏先 provision 再删」):把权限策略引擎 + 基线安全策略
|
|
395
|
-
* (file-guard / credential / destructive,经 `createPermissionRegistry` 注册)下沉为 bare-runtime
|
|
396
|
-
* 的**默认提供**——注册 permission-guard hook(`tool.execute.before`),让无宿主 PermissionController
|
|
397
|
-
* 的裸运行时也 safe-by-default(安全是引擎核心能力,非宿主可选项)。
|
|
398
|
-
*
|
|
399
|
-
* **幂等**:宿主(coding)已注册 permission-guard 时直接返回,绝不双注册——bare 分支与宿主
|
|
400
|
-
* provision 二选一(createAgentRuntime 仅在自建 hookRegistry、未被宿主注入时调用本函数)。
|
|
401
|
-
*
|
|
402
|
-
* @returns 新建的基线 registry(宿主已 provision 时返回 undefined)。
|
|
403
|
-
*/
|
|
404
|
-
declare function provisionBaselineSecurity(hookRegistry: HookRegistry): PermissionPolicyRegistry | undefined;
|
|
405
|
-
//#endregion
|
|
406
|
-
//#region src/core/permission/always-allow.d.ts
|
|
407
|
-
/**
|
|
408
|
-
* 条目是否格式良好——供 `/permissions add` 校验,拒绝静默持久化危险/无意义条目。
|
|
409
|
-
* 合法:`tool`(整工具)或 `tool(非空 content)`。
|
|
410
|
-
* 非法:空串、空括号 `tool()`(读法似"无参"实为整工具,塌缩陷阱,RFC-073 P3)、
|
|
411
|
-
* 括号不平衡/含元字符的垃圾(`bash(((`、`a b`)。
|
|
412
|
-
*/
|
|
413
|
-
declare function isWellFormedAllowEntry(raw: string): boolean;
|
|
414
|
-
/**
|
|
415
|
-
* 为「始终允许」按钮派生一个 bash 命令前缀(程序名 + 子命令两段),供 UI 显式呈现 +
|
|
416
|
-
* 持久化为 `bash(<prefix>)`。复合/混淆/env 赋值命令返回 null → 调用方回退整工具放行
|
|
417
|
-
* (此时安全护栏仍 bypass-immune,不会因整工具放行而漏过危险叶子)。
|
|
418
|
-
*
|
|
419
|
-
* 例:`git status -s`→`git status`、`npm run build`→`npm run`、`ls -la`→`ls`、
|
|
420
|
-
* `cd /x && git status`→`git status`、`a && b`/`$()`/`FOO=1 cmd`→null。
|
|
421
|
-
*/
|
|
422
|
-
declare function bashAlwaysAllowPrefix(command: string): string | null;
|
|
423
|
-
//#endregion
|
|
424
|
-
//#region src/core/permission/command-canonicalize.d.ts
|
|
425
|
-
/**
|
|
426
|
-
* 从一条 bash 命令启发式提取**写入目标路径**(M4 Phase 2 前置 / C2 加固)。
|
|
427
|
-
*
|
|
428
|
-
* bash 无 pathParams,沙箱 assess 看不到 shell 写目标 → 默认开会让越界 bash 写硬失败。
|
|
429
|
-
* 本函数解析每个子片段的写向量,喂给 assess 以便升级为 ask 而非硬失败:
|
|
430
|
-
* - 重定向 `> f` `>> f` `2> f` `&> f` `>| f`
|
|
431
|
-
* - `tee [-a] f...`、`cp/mv/install/ln ... dst`、`dd of=f`、`touch/rm/rmdir/mkdir f...`
|
|
432
|
-
* 启发式(非 tree-sitter):覆盖常见形态;复杂引用/变量间接漏网者仍由沙箱兜底(硬失败,
|
|
433
|
-
* 不弱于现状)。返回去重路径(相对/绝对原样,由 assess 解析)。
|
|
434
|
-
*/
|
|
435
|
-
declare function extractWriteTargets(command: string): string[];
|
|
436
|
-
//#endregion
|
|
437
|
-
//#region src/sandbox-safety.d.ts
|
|
438
|
-
type SandboxVerdict = 'auto-allow' | 'ask' | 'deny';
|
|
439
|
-
interface SandboxAssessment {
|
|
440
|
-
verdict: SandboxVerdict;
|
|
441
|
-
reason?: string;
|
|
442
|
-
}
|
|
443
|
-
interface SandboxOp {
|
|
444
|
-
writePaths?: readonly string[];
|
|
445
|
-
network?: boolean;
|
|
446
|
-
}
|
|
447
|
-
interface SandboxAssessContext {
|
|
448
|
-
mode: PermissionMode;
|
|
449
|
-
projectRoot: string;
|
|
450
|
-
writableRoots: readonly string[];
|
|
451
|
-
deniedRoots: readonly string[];
|
|
452
|
-
networkAllowed: boolean;
|
|
453
|
-
/**
|
|
454
|
-
* bash 沙箱越界自动放行开关(opt-in,默认 false,见 SandboxConfig.autoAllowBashIfSandboxed)。
|
|
455
|
-
* 本字段只是透传给 sandbox-guard hook 消费——assessSandboxSafety 本身不读取它(保持纯函数
|
|
456
|
-
* 决策矩阵不变),由 sandbox-guard 在拿到 'ask' verdict 后结合此字段 + 权限层已有裁决
|
|
457
|
-
* (output.decision)判断是否降级为自动放行。
|
|
458
|
-
*/
|
|
459
|
-
autoAllowBashIfSandboxed?: boolean;
|
|
460
|
-
}
|
|
461
|
-
declare function assessSandboxSafety(op: SandboxOp, ctx: SandboxAssessContext): SandboxAssessment;
|
|
462
|
-
declare function defaultDeniedRoots(workspaceDir: string, homeDir: string): string[];
|
|
463
|
-
/**
|
|
464
|
-
* 默认可写根:projectRoot + os.tmpdir() + 字面 `/tmp`。
|
|
465
|
-
*
|
|
466
|
-
* 参照 codex(openai/codex codex-rs/protocol/src/permissions.rs
|
|
467
|
-
* legacy_runtime_file_system_policy_for_cwd):SlashTmp 与 Tmpdir 是两个独立的默认可写
|
|
468
|
-
* special path,同时生效(`exclude_slash_tmp`/`exclude_tmpdir_env_var` 默认均 false)。
|
|
469
|
-
* macOS 上 `os.tmpdir()` 落在 `/var/folders/...`(进程级临时目录),与用户/shell 习惯手写的
|
|
470
|
-
* 字面 `/tmp`(→ 真实路径 `/private/tmp`,全系统共享临时目录)是两棵不同的目录树——只信
|
|
471
|
-
* os.tmpdir() 会让 `mkdir -p /tmp/xxx` 这类零风险操作被误判越界触发审批(见 sandbox-guard
|
|
472
|
-
* 越界升级事故复盘)。underAny() 的 realpath 归一化解决的是"同一路径不同书写形式",本条解决
|
|
473
|
-
* 的是"两个本就不同的物理临时目录都该被信任"。
|
|
474
|
-
*/
|
|
475
|
-
declare function computeSandboxRoots(mode: PermissionMode, projectRoot: string, tmpDir: string, extra?: readonly string[]): string[];
|
|
476
|
-
//#endregion
|
|
477
|
-
//#region src/observability/failure-signals.d.ts
|
|
478
|
-
type FailureSignalKind = 'tool-error' | 'permission-denied';
|
|
479
|
-
interface FailureSignal {
|
|
480
|
-
kind: FailureSignalKind;
|
|
481
|
-
/** 聚合签名(如 toolName)——同签名复发即累加。 */
|
|
482
|
-
signature: string;
|
|
483
|
-
count: number;
|
|
484
|
-
firstSeen: number;
|
|
485
|
-
lastSeen: number;
|
|
486
|
-
/** 最近一次的简短细节(如错误首行,截断)。 */
|
|
487
|
-
detail?: string;
|
|
488
|
-
}
|
|
489
|
-
/** 跨会话失败信号存储:按 (kind,signature) 聚合计数。诊断据此只认硬信号。 */
|
|
490
|
-
declare class FailureSignalStore {
|
|
491
|
-
private readonly signals;
|
|
492
|
-
private readonly clock;
|
|
493
|
-
constructor(clock?: () => number);
|
|
494
|
-
record(kind: FailureSignalKind, signature: string, detail?: string): void;
|
|
495
|
-
/** 复发达阈值(默认 ≥3)的信号,按计数降序——诊断只在这些硬信号上提案。 */
|
|
496
|
-
recurring(minCount?: number): FailureSignal[];
|
|
497
|
-
all(): FailureSignal[];
|
|
498
|
-
clear(): void;
|
|
499
|
-
}
|
|
500
|
-
/** RFC-026 阶段3:tool.execute.after 把工具 isError 记成结构化失败信号。 */
|
|
501
|
-
declare function createFailureSignalHook(store: FailureSignalStore): HookSpec;
|
|
502
|
-
/**
|
|
503
|
-
* RFC-067 M121-04a:把**权限拒绝**记成 `permission-denied` 失败信号(补 FailureSignalKind 的另一半,
|
|
504
|
-
* 此前从无 caller 记录此 kind)。
|
|
505
|
-
*
|
|
506
|
-
* 为何在 tool.execute.before:denied 工具在 tool-executor 早返(errorKind:'permission'),**绕过
|
|
507
|
-
* tool.execute.after**,故 after-hook(createFailureSignalHook)看不到它。权限/沙箱守卫在 before 层
|
|
508
|
-
* 设 `output.decision='deny'`(如 sandbox-guard pri 6),所有 before-hook 跑完才判 deny 裁决——故本
|
|
509
|
-
* recorder 用**晚 priority(90)**确保跑在守卫之后、读到累积的 decision。
|
|
510
|
-
*/
|
|
511
|
-
declare function createPermissionDenyHook(store: FailureSignalStore): HookSpec;
|
|
512
|
-
//#endregion
|
|
513
|
-
//#region src/observability/diagnoser.d.ts
|
|
514
|
-
interface Proposal {
|
|
515
|
-
/** 稳定 id(= kind:signature,便于去重)。 */
|
|
516
|
-
id: string;
|
|
517
|
-
signature: string;
|
|
518
|
-
kind: 'investigate-fix';
|
|
519
|
-
/** 改进指令(L1:用户审后可 /job 执行;诊断器**不**自动跑)。 */
|
|
520
|
-
directive: string;
|
|
521
|
-
/** 来源证据(provenance):哪个信号、复发几次、细节。 */
|
|
522
|
-
evidence: {
|
|
523
|
-
signalKind: FailureSignal['kind'];
|
|
524
|
-
count: number;
|
|
525
|
-
detail?: string;
|
|
526
|
-
};
|
|
527
|
-
createdAt: number;
|
|
528
|
-
}
|
|
529
|
-
interface DiagnoserConfig {
|
|
530
|
-
/** 复发阈值(信号 count ≥ 此才提案;默认 3)。 */
|
|
531
|
-
minCount?: number;
|
|
532
|
-
clock?: () => number;
|
|
533
|
-
}
|
|
534
|
-
declare class Diagnoser {
|
|
535
|
-
private readonly signals;
|
|
536
|
-
private readonly proposed;
|
|
537
|
-
private readonly minCount;
|
|
538
|
-
private readonly clock;
|
|
539
|
-
constructor(signals: FailureSignalStore, config?: DiagnoserConfig);
|
|
540
|
-
/** 扫硬信号(recurring ≥ minCount)→ **新**提案(已提过的签名跳过,防风暴)。 */
|
|
541
|
-
propose(): Proposal[];
|
|
542
|
-
/** 重置去重(用户处理完想重新评估时)。 */
|
|
543
|
-
reset(): void;
|
|
544
|
-
}
|
|
545
|
-
//#endregion
|
|
546
|
-
export { BASELINE_PERMISSION_TOOL_SETS, CREDENTIAL_READ_POLICY, type ChatMessageInput, type ChatMessageOutput, type ChatParamsInput, type ChatParamsOutput, type CompactionStats, DESTRUCTIVE_COMMAND_POLICY, Diagnoser, type DiagnoserConfig, type EditFileCallback, type EditFileInfo, type EditFilesPanelHookOptions, type FailureSignal, type FailureSignalKind, FailureSignalStore, type HookHandle, type HookInput, type HookOutput, type HookPayloadMap, type HookPreset, HookRegistry, type HookRegistryOptions, type HookSpec, HookState, type HookTiming, type InterceptorTiming, type MessagesTransformInput, type MessagesTransformOutput, type ObserverTiming, type PermissionAuditEntry, PermissionAuditLog, type PermissionContext, type PermissionDecision, PermissionGuardHook, type PermissionMode, type PermissionPolicy, PermissionPolicyRegistry, type PermissionPolicySetting, type PermissionRule, type PermissionRuleConfig, type PermissionToolDescriptor, type PermissionToolSets, type PermissionToolSetsInput, type PolicyScope, type Proposal, type RuleEffect, type RuleMatch, type SandboxAssessContext, type SandboxAssessment, type SandboxOp, type SandboxVerdict, type SessionEventInput, type StreamMetrics, type SystemPromptTransformInput, type SystemPromptTransformOutput, type TokenBudgetConfig, type ToolErrorRecord, type ToolErrorTrackerConfig, type ToolExecuteAfterInput, type ToolExecuteAfterOutput, type ToolExecuteBeforeInput, type ToolExecuteBeforeOutput, assessSandboxSafety, bashAlwaysAllowPrefix, compilePolicyFromSetting, computeSandboxRoots, createAnthropicEffortHook, createCommentCheckerHook, createCompactionAfterHook, createCompactionLoggerHook, createDisabledToolsPolicy, createEditFilesPanelHook, createEgressAllowlistPolicy, createFailureSignalHook, createFileGuardPolicy, createFirstMessageVariantHook, createHookRegistry, createLabelTruncatorHook, createOttoStateDomainPolicy, createOutputTruncationHook, createPermissionDenyHook, createPermissionGuardHook, createPermissionModePolicy, createPermissionRegistry, createPermissionToolSetsFromRegistry, createStreamEndMetricsHook, createStreamMetricsHook, createThinkingValidatorHook, createTokenBudgetHook, createToolErrorTrackerHook, defaultDeniedRoots, defineHook, extractWriteTargets, isWellFormedAllowEntry, provisionBaselineSecurity };
|
|
176
|
+
export { type ChatMessageInput, type ChatMessageOutput, type ChatParamsInput, type ChatParamsOutput, type CompactionStats, type Disposable, type HookAbortSignal, type HookHandle, type HookInput, type HookOutput, type HookPayloadMap, type HookPreset, HookRegistry, type HookRegistryOptions, HookScope, type HookSpec, HookState, type HookTiming, type InterceptorTiming, type MessagesTransformInput, type MessagesTransformOutput, type ObserverTiming, type SessionEventInput, type StreamMetrics, type SystemPromptTransformInput, type SystemPromptTransformOutput, type TokenBudgetConfig, type ToolErrorRecord, type ToolErrorTrackerConfig, type ToolExecuteAfterInput, type ToolExecuteAfterOutput, type ToolExecuteBeforeInput, type ToolExecuteBeforeOutput, WATERFALL_TIMINGS, type WaterfallTiming, createAnthropicEffortHook, createCommentCheckerHook, createCompactionAfterHook, createCompactionLoggerHook, createFirstMessageVariantHook, createHookRegistry, createLabelTruncatorHook, createOutputTruncationHook, createStreamEndMetricsHook, createStreamMetricsHook, createThinkingValidatorHook, createTokenBudgetHook, createToolErrorTrackerHook, defineHook, isWaterfallTiming };
|
|
547
177
|
//# sourceMappingURL=index.d.ts.map
|