@guilz-dev/belay 0.9.2 → 0.9.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 +36 -2
- package/dist/adapters/codex/hooks.d.ts +1 -0
- package/dist/adapters/codex/hooks.js +3 -0
- package/dist/adapters/codex/runtime-entry.d.ts +3 -0
- package/dist/adapters/codex/runtime-entry.js +27 -4
- package/dist/adapters/cursor/cwd-resolution.d.ts +10 -0
- package/dist/adapters/cursor/cwd-resolution.js +58 -0
- package/dist/adapters/cursor/dispatcher-generation.d.ts +3 -0
- package/dist/adapters/cursor/dispatcher-generation.js +5 -0
- package/dist/adapters/cursor/hook-dispatch-entry.d.ts +7 -0
- package/dist/adapters/cursor/hook-dispatch-entry.js +126 -0
- package/dist/adapters/cursor/hook-router.d.ts +23 -0
- package/dist/adapters/cursor/hook-router.js +266 -0
- package/dist/adapters/cursor/hooks.d.ts +5 -0
- package/dist/adapters/cursor/hooks.js +115 -3
- package/dist/adapters/cursor/routing-layout.d.ts +5 -0
- package/dist/adapters/cursor/routing-layout.js +29 -0
- package/dist/adapters/cursor/runtime-entry.d.ts +18 -0
- package/dist/adapters/cursor/runtime-entry.js +135 -34
- package/dist/adapters/layouts/scope.d.ts +5 -0
- package/dist/adapters/layouts/scope.js +71 -0
- package/dist/adapters/shared/gate-runtime.js +27 -3
- package/dist/adapters/shared/repo-root.js +20 -1
- package/dist/bundle/claude-runtime.mjs +2660 -1951
- package/dist/bundle/codex-runtime.mjs +2680 -1953
- package/dist/bundle/cursor-dispatcher.mjs +443 -0
- package/dist/bundle/cursor-runtime.mjs +5638 -4697
- package/dist/cli.js +33 -3
- package/dist/commands/doctor.js +215 -16
- package/dist/commands/health-snapshot.d.ts +3 -0
- package/dist/commands/health-snapshot.js +61 -0
- package/dist/commands/recovery-checkpoints.d.ts +3 -3
- package/dist/commands/report.js +14 -0
- package/dist/commands/status.js +15 -0
- package/dist/commands/where.d.ts +4 -0
- package/dist/commands/where.js +52 -0
- package/dist/config-io.js +11 -2
- package/dist/core/approval-repo-lookup.d.ts +16 -0
- package/dist/core/approval-repo-lookup.js +48 -0
- package/dist/core/audit-io.d.ts +1 -1
- package/dist/core/audit-io.js +1 -1
- package/dist/core/audit-legacy-archive.js +43 -25
- package/dist/core/audit-query.d.ts +1 -0
- package/dist/core/audit-query.js +7 -0
- package/dist/core/audit-serialize.d.ts +3 -0
- package/dist/core/audit-serialize.js +39 -3
- package/dist/core/audit-summary.d.ts +9 -0
- package/dist/core/audit-summary.js +58 -1
- package/dist/core/audit-types.d.ts +4 -0
- package/dist/core/classify-tool.js +16 -8
- package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
- package/dist/core/effect-ir/argv-delegate.js +42 -0
- package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
- package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
- package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
- package/dist/core/effect-ir/shell-lower/augment.js +97 -0
- package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
- package/dist/core/effect-ir/shell-lower/context.js +1 -0
- package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
- package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.js +29 -0
- package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
- package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
- package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
- package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
- package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
- package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
- package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
- package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
- package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
- package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
- package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
- package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
- package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
- package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
- package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
- package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
- package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
- package/dist/core/effect-ir/shell-lower/segment.js +82 -0
- package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
- package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
- package/dist/core/effect-ir/shell-lower.d.ts +2 -7
- package/dist/core/effect-ir/shell-lower.js +122 -1511
- package/dist/core/glob.js +32 -16
- package/dist/core/integrity.d.ts +2 -2
- package/dist/core/integrity.js +52 -12
- package/dist/core/replay-scrub.d.ts +3 -0
- package/dist/core/replay-scrub.js +30 -3
- package/dist/core/shell-substitution.js +2 -2
- package/dist/core/shell-tokenizer.d.ts +28 -0
- package/dist/core/shell-tokenizer.js +111 -29
- package/dist/core/shell-unparseable.js +3 -1
- package/dist/core/verdict/docker-compose-run.d.ts +18 -0
- package/dist/core/verdict/docker-compose-run.js +136 -0
- package/dist/core/verdict/launcher-resolve.js +28 -28
- package/dist/core/verdict/parser.d.ts +3 -0
- package/dist/core/verdict/parser.js +23 -40
- package/dist/core/verdict/recursive-invocation.d.ts +20 -0
- package/dist/core/verdict/recursive-invocation.js +224 -0
- package/dist/defaults.d.ts +7 -3
- package/dist/defaults.js +35 -21
- package/dist/installer/bootstrap.d.ts +1 -0
- package/dist/installer/bootstrap.js +2 -2
- package/dist/installer/runtime-artifacts.js +43 -13
- package/dist/installer/scope-config.d.ts +2 -2
- package/dist/installer.d.ts +10 -1
- package/dist/installer.js +83 -7
- package/dist/node-resolution.d.ts +1 -0
- package/dist/node-resolution.js +61 -0
- package/dist/templates.d.ts +7 -4
- package/dist/templates.js +42 -4
- package/dist/types.d.ts +35 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -2
- package/skills/belay/SKILL.md +5 -0
- package/skills/belay/belay-report.md +4 -1
|
@@ -1,10 +1,34 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
1
2
|
import { getManagedHookEntries } from '../../defaults.js';
|
|
2
3
|
function entryMatches(existing, expected) {
|
|
3
4
|
return existing.command === expected.command && existing.matcher === expected.matcher;
|
|
4
5
|
}
|
|
5
|
-
function
|
|
6
|
+
function legacyManagedEntries(platform, hooksDir, repoRoot) {
|
|
7
|
+
const entries = [
|
|
8
|
+
...getManagedHookEntries(platform, hooksDir, repoRoot, 'legacy-relative'),
|
|
9
|
+
...getManagedHookEntries(platform, hooksDir, repoRoot, 'legacy-absolute'),
|
|
10
|
+
...getManagedHookEntries(platform, hooksDir, repoRoot, 'legacy-quoted-absolute'),
|
|
11
|
+
...getManagedHookEntries(platform, hooksDir, repoRoot, 'legacy-bare-powershell-absolute'),
|
|
12
|
+
];
|
|
13
|
+
if (existsSync(hooksDir)) {
|
|
14
|
+
const canonicalHooksDir = realpathSync(hooksDir);
|
|
15
|
+
if (canonicalHooksDir !== hooksDir) {
|
|
16
|
+
entries.push(...getManagedHookEntries(platform, canonicalHooksDir, repoRoot, 'legacy-absolute'));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return entries;
|
|
20
|
+
}
|
|
21
|
+
function variantsForDefinition(legacyEntries, event, definition) {
|
|
22
|
+
return [
|
|
23
|
+
definition,
|
|
24
|
+
...legacyEntries
|
|
25
|
+
.filter((entry) => entry.event === event && entry.definition.matcher === definition.matcher)
|
|
26
|
+
.map((entry) => entry.definition),
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
function mergeHookEntry(current, expected, managedVariants, placement) {
|
|
6
30
|
const entries = Array.isArray(current) ? [...current] : [];
|
|
7
|
-
const filtered = entries.filter((entry) => !entryMatches(entry,
|
|
31
|
+
const filtered = entries.filter((entry) => !managedVariants.some((variant) => entryMatches(entry, variant)));
|
|
8
32
|
if (placement === 'prepend') {
|
|
9
33
|
return [expected, ...filtered];
|
|
10
34
|
}
|
|
@@ -20,24 +44,105 @@ export function managedShellPreToolUseEntry(platform, hooksDir, repoRoot) {
|
|
|
20
44
|
return {
|
|
21
45
|
command: referencePreToolUse.command,
|
|
22
46
|
matcher: 'Shell',
|
|
47
|
+
failClosed: true,
|
|
23
48
|
};
|
|
24
49
|
}
|
|
25
50
|
/** @deprecated Use {@link managedShellPreToolUseEntry}. */
|
|
26
51
|
export const legacyManagedShellPreToolUseEntry = managedShellPreToolUseEntry;
|
|
52
|
+
export function stripCursorHooksFile(current, platform, hooksDir, repoRoot) {
|
|
53
|
+
const next = {
|
|
54
|
+
version: current.version || 1,
|
|
55
|
+
hooks: { ...current.hooks },
|
|
56
|
+
};
|
|
57
|
+
const managedEntries = getManagedHookEntries(platform, hooksDir, repoRoot);
|
|
58
|
+
const legacyEntries = legacyManagedEntries(platform, hooksDir, repoRoot);
|
|
59
|
+
for (const { event, definition } of managedEntries) {
|
|
60
|
+
const entries = next.hooks[event];
|
|
61
|
+
if (!Array.isArray(entries)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const variants = variantsForDefinition(legacyEntries, event, definition);
|
|
65
|
+
next.hooks[event] = entries.filter((entry) => !variants.some((variant) => entryMatches(entry, variant)));
|
|
66
|
+
if (next.hooks[event].length === 0) {
|
|
67
|
+
delete next.hooks[event];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return stripLegacyManagedShellPreToolUseGates(next, platform, hooksDir, repoRoot);
|
|
71
|
+
}
|
|
27
72
|
export function mergeCursorHooksFile(current, platform, hooksDir, repoRoot) {
|
|
28
73
|
const next = {
|
|
29
74
|
version: current.version || 1,
|
|
30
75
|
hooks: { ...current.hooks },
|
|
31
76
|
};
|
|
32
77
|
const managedEntries = getManagedHookEntries(platform, hooksDir, repoRoot);
|
|
78
|
+
const legacyEntries = legacyManagedEntries(platform, hooksDir, repoRoot);
|
|
33
79
|
for (const { event, definition } of managedEntries) {
|
|
80
|
+
const variants = variantsForDefinition(legacyEntries, event, definition);
|
|
34
81
|
next.hooks[event] = mergeHookEntry(next.hooks[event], {
|
|
35
82
|
command: definition.command,
|
|
36
83
|
matcher: definition.matcher,
|
|
37
|
-
|
|
84
|
+
failClosed: definition.failClosed,
|
|
85
|
+
}, variants, definition.placement);
|
|
86
|
+
}
|
|
87
|
+
return stripLegacyManagedShellPreToolUseGates(next, platform, hooksDir, repoRoot);
|
|
88
|
+
}
|
|
89
|
+
export function legacyManagedShellPreToolUseVariants(platform, hooksDir, repoRoot) {
|
|
90
|
+
const shellEntry = managedShellPreToolUseEntry(platform, hooksDir, repoRoot);
|
|
91
|
+
const legacyEntries = legacyManagedEntries(platform, hooksDir, repoRoot);
|
|
92
|
+
const commands = new Set([shellEntry.command]);
|
|
93
|
+
for (const entry of legacyEntries) {
|
|
94
|
+
if (entry.event === 'preToolUse' && entry.definition.matcher === 'Write') {
|
|
95
|
+
commands.add(entry.definition.command);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return [...commands].map((command) => ({
|
|
99
|
+
command,
|
|
100
|
+
matcher: 'Shell',
|
|
101
|
+
failClosed: true,
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
function stripLegacyManagedShellPreToolUseGates(hooks, platform, hooksDir, repoRoot) {
|
|
105
|
+
const variants = legacyManagedShellPreToolUseVariants(platform, hooksDir, repoRoot);
|
|
106
|
+
const preToolUse = hooks.hooks.preToolUse;
|
|
107
|
+
if (!Array.isArray(preToolUse) || variants.length === 0) {
|
|
108
|
+
return hooks;
|
|
109
|
+
}
|
|
110
|
+
const filtered = preToolUse.filter((entry) => !variants.some((variant) => entryMatches(entry, variant)));
|
|
111
|
+
if (filtered.length === preToolUse.length) {
|
|
112
|
+
return hooks;
|
|
113
|
+
}
|
|
114
|
+
const next = {
|
|
115
|
+
version: hooks.version || 1,
|
|
116
|
+
hooks: { ...hooks.hooks },
|
|
117
|
+
};
|
|
118
|
+
if (filtered.length === 0) {
|
|
119
|
+
delete next.hooks.preToolUse;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
next.hooks.preToolUse = filtered;
|
|
38
123
|
}
|
|
39
124
|
return next;
|
|
40
125
|
}
|
|
126
|
+
export function hasLegacyCursorDoubleShellGates(hooks, platform, hooksDir, repoRoot) {
|
|
127
|
+
const shellVariants = legacyManagedShellPreToolUseVariants(platform, hooksDir, repoRoot);
|
|
128
|
+
const preToolUse = hooks.hooks.preToolUse;
|
|
129
|
+
const beforeShell = hooks.hooks.beforeShellExecution;
|
|
130
|
+
if (!Array.isArray(preToolUse) || !Array.isArray(beforeShell)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const hasLegacyShellPreToolUse = preToolUse.some((entry) => shellVariants.some((variant) => entryMatches(entry, variant)));
|
|
134
|
+
if (!hasLegacyShellPreToolUse) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const managedEntries = getManagedHookEntries(platform, hooksDir, repoRoot);
|
|
138
|
+
const shellManaged = managedEntries.find((entry) => entry.event === 'beforeShellExecution');
|
|
139
|
+
if (!shellManaged) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const shellVariantsForBefore = variantsForDefinition(legacyManagedEntries(platform, hooksDir, repoRoot), 'beforeShellExecution', shellManaged.definition);
|
|
143
|
+
return beforeShell.some((entry) => shellVariantsForBefore.some((variant) => entryMatches(entry, variant)));
|
|
144
|
+
}
|
|
145
|
+
/** @deprecated Use {@link hasLegacyCursorDoubleShellGates}. */
|
|
41
146
|
export function hasDuplicateCursorShellGates(hooks, platform, hooksDir, repoRoot) {
|
|
42
147
|
const shellEntry = managedShellPreToolUseEntry(platform, hooksDir, repoRoot);
|
|
43
148
|
const preToolUse = hooks.hooks.preToolUse;
|
|
@@ -47,3 +152,10 @@ export function hasDuplicateCursorShellGates(hooks, platform, hooksDir, repoRoot
|
|
|
47
152
|
const shellMatches = preToolUse.filter((entry) => entryMatches(entry, shellEntry));
|
|
48
153
|
return shellMatches.length > 1;
|
|
49
154
|
}
|
|
155
|
+
export function hasManagedCursorHookEntries(hooks, platform, hooksDir, repoRoot) {
|
|
156
|
+
const managedEntries = [
|
|
157
|
+
...getManagedHookEntries(platform, hooksDir, repoRoot),
|
|
158
|
+
...legacyManagedEntries(platform, hooksDir, repoRoot),
|
|
159
|
+
];
|
|
160
|
+
return managedEntries.some(({ event, definition }) => hooks.hooks[event]?.some((entry) => entryMatches(entry, definition)));
|
|
161
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function cursorRoutingConfigPath(repoRoot: string): string;
|
|
2
|
+
export declare function cursorRoutingHooksDir(repoRoot: string): string;
|
|
3
|
+
export declare function cursorRoutingHooksSettingsPath(repoRoot: string): string;
|
|
4
|
+
export declare function cursorRoutingRuntimeDir(repoRoot: string): string;
|
|
5
|
+
export declare function findCursorRoutingRepoRoot(startPath: string): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function cursorRoutingConfigPath(repoRoot) {
|
|
4
|
+
return path.join(repoRoot, '.cursor', 'belay.config.json');
|
|
5
|
+
}
|
|
6
|
+
export function cursorRoutingHooksDir(repoRoot) {
|
|
7
|
+
return path.join(repoRoot, '.cursor', 'hooks');
|
|
8
|
+
}
|
|
9
|
+
export function cursorRoutingHooksSettingsPath(repoRoot) {
|
|
10
|
+
return path.join(repoRoot, '.cursor', 'hooks.json');
|
|
11
|
+
}
|
|
12
|
+
export function cursorRoutingRuntimeDir(repoRoot) {
|
|
13
|
+
return path.join(repoRoot, '.cursor', 'belay', 'runtime');
|
|
14
|
+
}
|
|
15
|
+
export function findCursorRoutingRepoRoot(startPath) {
|
|
16
|
+
const resolvedStart = path.resolve(startPath);
|
|
17
|
+
let current = resolvedStart;
|
|
18
|
+
while (true) {
|
|
19
|
+
if (existsSync(path.join(current, '.git')) ||
|
|
20
|
+
(existsSync(path.join(current, '.cursor')) && existsSync(cursorRoutingConfigPath(current)))) {
|
|
21
|
+
return current;
|
|
22
|
+
}
|
|
23
|
+
const parent = path.dirname(current);
|
|
24
|
+
if (parent === current) {
|
|
25
|
+
return resolvedStart;
|
|
26
|
+
}
|
|
27
|
+
current = parent;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
+
export declare function resolveCursorActionCwd(payload: Record<string, unknown>, fallback?: string): string;
|
|
2
|
+
export declare function handleBeforeSubmitPromptHook(payload: Record<string, unknown>): Promise<{
|
|
3
|
+
replay?: import("../../core/approval-replay.js").ApprovalReplayHint | undefined;
|
|
4
|
+
user_message?: string | undefined;
|
|
5
|
+
continue: boolean;
|
|
6
|
+
}>;
|
|
1
7
|
export declare function runBeforeSubmitPromptHook(): Promise<void>;
|
|
8
|
+
export declare function handleShellGateHook(payload: Record<string, unknown>): Promise<import("../../core/gate-contract.js").GatePermissionResponse | {
|
|
9
|
+
permission: string;
|
|
10
|
+
user_message: string;
|
|
11
|
+
}>;
|
|
2
12
|
export declare function runShellGateHook(): Promise<void>;
|
|
13
|
+
export declare function handleToolGateHook(eventName: string, payload: Record<string, unknown>): Promise<import("../../core/gate-contract.js").GatePermissionResponse | {
|
|
14
|
+
permission: string;
|
|
15
|
+
user_message: string;
|
|
16
|
+
} | {
|
|
17
|
+
permission: string;
|
|
18
|
+
user_message?: undefined;
|
|
19
|
+
}>;
|
|
3
20
|
export declare function runToolGateHook(eventName: string): Promise<void>;
|
|
21
|
+
export declare function handleAuditHook(eventName: string, payload: Record<string, unknown>): Promise<{}>;
|
|
4
22
|
export declare function runAuditHook(eventName: string): Promise<void>;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import process from 'node:process';
|
|
3
|
+
import { approvalCommandMatch } from '../../core/approval.js';
|
|
4
|
+
import { findApprovalRepoRoots, formatAmbiguousApprovalRepoMessage, } from '../../core/approval-repo-lookup.js';
|
|
5
|
+
import { DEFAULT_CONFIG_V4 } from '../../core/config.js';
|
|
2
6
|
import { cursorLayout } from '../layouts/cursor.js';
|
|
3
7
|
import { appendObservedAudit, createDefaultGateRuntimeDeps, evaluateGatedAction, gateVerdictToCursorResponse, processApprovalPrompt, resolveGateConfig, } from '../shared/gate-runtime.js';
|
|
4
8
|
import { findRepoRoot } from '../shared/repo-root.js';
|
|
9
|
+
import { GLOBAL_HOOK_WORKSPACE_MISSING_MESSAGE, isUnsafeGlobalHookFallback, resolveCursorActionCwdDetails, } from './cwd-resolution.js';
|
|
5
10
|
async function readStdinJson() {
|
|
6
11
|
const chunks = [];
|
|
7
12
|
for await (const chunk of process.stdin) {
|
|
@@ -21,6 +26,38 @@ async function readStdinJson() {
|
|
|
21
26
|
function jsonResponse(value) {
|
|
22
27
|
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
23
28
|
}
|
|
29
|
+
export function resolveCursorActionCwd(payload, fallback = process.cwd()) {
|
|
30
|
+
return resolveCursorActionCwdDetails(payload, fallback).cwd;
|
|
31
|
+
}
|
|
32
|
+
function resolveCursorToolActionCwdDetails(payload, fallback, eventName, toolName) {
|
|
33
|
+
if ((eventName === 'preToolUse' || eventName === 'PreToolUse') && toolName === 'Shell') {
|
|
34
|
+
return resolveCursorActionCwdDetails(payload, fallback);
|
|
35
|
+
}
|
|
36
|
+
const toolInput = payload.tool_input;
|
|
37
|
+
if (toolInput === null || typeof toolInput !== 'object' || Array.isArray(toolInput)) {
|
|
38
|
+
return resolveCursorActionCwdDetails(payload, fallback);
|
|
39
|
+
}
|
|
40
|
+
const { working_directory: _workingDirectory, ...withoutWorkingDirectory } = toolInput;
|
|
41
|
+
return resolveCursorActionCwdDetails({ ...payload, tool_input: withoutWorkingDirectory }, fallback);
|
|
42
|
+
}
|
|
43
|
+
function collectCandidateRepoRoots(payload, resolution) {
|
|
44
|
+
const roots = [];
|
|
45
|
+
const add = (value) => {
|
|
46
|
+
const resolved = path.resolve(value);
|
|
47
|
+
if (!roots.includes(resolved)) {
|
|
48
|
+
roots.push(resolved);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
if (Array.isArray(payload.workspace_roots)) {
|
|
52
|
+
for (const root of payload.workspace_roots) {
|
|
53
|
+
if (typeof root === 'string' && root.trim()) {
|
|
54
|
+
add(root);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
add(resolution.cwd);
|
|
59
|
+
return roots;
|
|
60
|
+
}
|
|
24
61
|
async function loadRuntimeContext(cwd) {
|
|
25
62
|
const repoRoot = findRepoRoot(cwd, cursorLayout);
|
|
26
63
|
const configPath = cursorLayout.configPath(repoRoot);
|
|
@@ -28,61 +65,119 @@ async function loadRuntimeContext(cwd) {
|
|
|
28
65
|
const config = await resolveGateConfig({ layout: cursorLayout, repoRoot, configPath }, deps);
|
|
29
66
|
return { layout: cursorLayout, repoRoot, config, configPath };
|
|
30
67
|
}
|
|
68
|
+
async function processApprovalPromptWithRepoFallback(ctx, deps, prompt, payload, resolution) {
|
|
69
|
+
const tokenPrefix = ctx.config.tokenPrefix || DEFAULT_CONFIG_V4.tokenPrefix;
|
|
70
|
+
const approvalId = approvalCommandMatch(prompt, tokenPrefix);
|
|
71
|
+
const result = await processApprovalPrompt(ctx, deps, prompt);
|
|
72
|
+
if (result.continue || !approvalId) {
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
const notFound = result.user_message?.includes('not found') ||
|
|
76
|
+
result.user_message?.includes('Belay approval not found');
|
|
77
|
+
if (!notFound) {
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
const candidates = collectCandidateRepoRoots(payload, resolution).filter((root) => root !== ctx.repoRoot);
|
|
81
|
+
if (candidates.length === 0) {
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
const lookup = await findApprovalRepoRoots({
|
|
85
|
+
approvalId,
|
|
86
|
+
candidateRepoRoots: candidates,
|
|
87
|
+
adapter: 'cursor',
|
|
88
|
+
});
|
|
89
|
+
if (lookup.status === 'ambiguous') {
|
|
90
|
+
return {
|
|
91
|
+
continue: false,
|
|
92
|
+
user_message: formatAmbiguousApprovalRepoMessage(lookup.repoRoots),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (lookup.status !== 'found') {
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
const retryCtx = await loadRuntimeContext(lookup.repoRoot);
|
|
99
|
+
return processApprovalPrompt(retryCtx, deps, prompt);
|
|
100
|
+
}
|
|
31
101
|
function isSubagentEvent(payload, eventName) {
|
|
32
102
|
return eventName === 'subagentStart' || payload.subagent_type !== undefined;
|
|
33
103
|
}
|
|
34
104
|
function isFileMutationTool(toolName) {
|
|
35
105
|
return toolName === 'Write' || toolName === 'StrReplace' || toolName === 'Delete';
|
|
36
106
|
}
|
|
37
|
-
export async function
|
|
107
|
+
export async function handleBeforeSubmitPromptHook(payload) {
|
|
38
108
|
try {
|
|
39
|
-
const payload = await readStdinJson();
|
|
40
109
|
const prompt = String(payload.prompt ?? '');
|
|
41
|
-
const
|
|
110
|
+
const resolution = resolveCursorActionCwdDetails(payload);
|
|
111
|
+
if (isUnsafeGlobalHookFallback(resolution)) {
|
|
112
|
+
return {
|
|
113
|
+
continue: false,
|
|
114
|
+
user_message: GLOBAL_HOOK_WORKSPACE_MISSING_MESSAGE,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const ctx = await loadRuntimeContext(resolution.cwd);
|
|
42
118
|
const deps = createDefaultGateRuntimeDeps();
|
|
43
|
-
const result = await
|
|
44
|
-
|
|
119
|
+
const result = await processApprovalPromptWithRepoFallback(ctx, deps, prompt, payload, resolution);
|
|
120
|
+
return {
|
|
45
121
|
continue: result.continue,
|
|
46
122
|
...(result.user_message ? { user_message: result.user_message } : {}),
|
|
47
123
|
...(result.replay ? { replay: result.replay } : {}),
|
|
48
|
-
}
|
|
124
|
+
};
|
|
49
125
|
}
|
|
50
126
|
catch {
|
|
51
|
-
|
|
127
|
+
return {
|
|
52
128
|
continue: false,
|
|
53
129
|
user_message: 'belay failed while processing approval state. Run belay doctor, then retry.',
|
|
54
|
-
}
|
|
130
|
+
};
|
|
55
131
|
}
|
|
56
132
|
}
|
|
57
|
-
export async function
|
|
133
|
+
export async function runBeforeSubmitPromptHook() {
|
|
134
|
+
jsonResponse(await handleBeforeSubmitPromptHook(await readStdinJson()));
|
|
135
|
+
}
|
|
136
|
+
export async function handleShellGateHook(payload) {
|
|
58
137
|
try {
|
|
59
|
-
const payload = await readStdinJson();
|
|
60
138
|
const command = String(payload.command ?? '').trim();
|
|
61
|
-
const
|
|
139
|
+
const resolution = resolveCursorActionCwdDetails(payload);
|
|
140
|
+
if (isUnsafeGlobalHookFallback(resolution)) {
|
|
141
|
+
return {
|
|
142
|
+
permission: 'deny',
|
|
143
|
+
user_message: GLOBAL_HOOK_WORKSPACE_MISSING_MESSAGE,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const cwd = resolution.cwd;
|
|
62
147
|
const ctx = await loadRuntimeContext(cwd);
|
|
63
148
|
const deps = createDefaultGateRuntimeDeps();
|
|
64
149
|
const verdict = await evaluateGatedAction(ctx, deps, {
|
|
65
150
|
kind: 'shell',
|
|
66
151
|
cwd,
|
|
67
152
|
command,
|
|
153
|
+
payload,
|
|
68
154
|
sourceEvent: 'beforeShellExecution',
|
|
69
155
|
});
|
|
70
|
-
|
|
156
|
+
return gateVerdictToCursorResponse(verdict);
|
|
71
157
|
}
|
|
72
158
|
catch {
|
|
73
|
-
|
|
159
|
+
return {
|
|
74
160
|
permission: 'deny',
|
|
75
161
|
user_message: 'belay failed while classifying this shell command. Run belay doctor, then retry.',
|
|
76
|
-
}
|
|
162
|
+
};
|
|
77
163
|
}
|
|
78
164
|
}
|
|
79
|
-
export async function
|
|
165
|
+
export async function runShellGateHook() {
|
|
166
|
+
jsonResponse(await handleShellGateHook(await readStdinJson()));
|
|
167
|
+
}
|
|
168
|
+
export async function handleToolGateHook(eventName, payload) {
|
|
80
169
|
try {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
170
|
+
const toolName = String(payload.tool_name ?? '');
|
|
171
|
+
const resolution = resolveCursorToolActionCwdDetails(payload, process.cwd(), eventName, toolName);
|
|
172
|
+
if (isUnsafeGlobalHookFallback(resolution)) {
|
|
173
|
+
return {
|
|
174
|
+
permission: 'deny',
|
|
175
|
+
user_message: GLOBAL_HOOK_WORKSPACE_MISSING_MESSAGE,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const cwd = resolution.cwd;
|
|
83
179
|
const ctx = await loadRuntimeContext(cwd);
|
|
84
180
|
const deps = createDefaultGateRuntimeDeps();
|
|
85
|
-
const toolName = String(payload.tool_name ?? '');
|
|
86
181
|
if (isSubagentEvent(payload, eventName)) {
|
|
87
182
|
const verdict = await evaluateGatedAction(ctx, deps, {
|
|
88
183
|
kind: 'subagent',
|
|
@@ -90,8 +185,7 @@ export async function runToolGateHook(eventName) {
|
|
|
90
185
|
payload,
|
|
91
186
|
sourceEvent: eventName,
|
|
92
187
|
});
|
|
93
|
-
|
|
94
|
-
return;
|
|
188
|
+
return gateVerdictToCursorResponse(verdict);
|
|
95
189
|
}
|
|
96
190
|
if (toolName === 'Shell') {
|
|
97
191
|
const verdict = await evaluateGatedAction(ctx, deps, {
|
|
@@ -101,8 +195,7 @@ export async function runToolGateHook(eventName) {
|
|
|
101
195
|
toolName,
|
|
102
196
|
sourceEvent: eventName,
|
|
103
197
|
});
|
|
104
|
-
|
|
105
|
-
return;
|
|
198
|
+
return gateVerdictToCursorResponse(verdict);
|
|
106
199
|
}
|
|
107
200
|
if (isFileMutationTool(toolName)) {
|
|
108
201
|
const verdict = await evaluateGatedAction(ctx, deps, {
|
|
@@ -112,8 +205,7 @@ export async function runToolGateHook(eventName) {
|
|
|
112
205
|
toolName,
|
|
113
206
|
sourceEvent: eventName,
|
|
114
207
|
});
|
|
115
|
-
|
|
116
|
-
return;
|
|
208
|
+
return gateVerdictToCursorResponse(verdict);
|
|
117
209
|
}
|
|
118
210
|
if (payload.tool_name === 'Task') {
|
|
119
211
|
const verdict = await evaluateGatedAction(ctx, deps, {
|
|
@@ -122,28 +214,37 @@ export async function runToolGateHook(eventName) {
|
|
|
122
214
|
payload,
|
|
123
215
|
sourceEvent: eventName,
|
|
124
216
|
});
|
|
125
|
-
|
|
126
|
-
return;
|
|
217
|
+
return gateVerdictToCursorResponse(verdict);
|
|
127
218
|
}
|
|
128
|
-
|
|
219
|
+
return { permission: 'allow' };
|
|
129
220
|
}
|
|
130
221
|
catch {
|
|
131
|
-
|
|
222
|
+
return {
|
|
132
223
|
permission: 'deny',
|
|
133
224
|
user_message: 'belay failed while classifying this tool action. Run belay doctor, then retry.',
|
|
134
|
-
}
|
|
225
|
+
};
|
|
135
226
|
}
|
|
136
227
|
}
|
|
137
|
-
export async function
|
|
228
|
+
export async function runToolGateHook(eventName) {
|
|
229
|
+
jsonResponse(await handleToolGateHook(eventName, await readStdinJson()));
|
|
230
|
+
}
|
|
231
|
+
export async function handleAuditHook(eventName, payload) {
|
|
138
232
|
try {
|
|
139
|
-
const
|
|
140
|
-
const
|
|
233
|
+
const toolName = String(payload.tool_name ?? '');
|
|
234
|
+
const resolution = resolveCursorToolActionCwdDetails(payload, process.cwd(), eventName, toolName);
|
|
235
|
+
if (isUnsafeGlobalHookFallback(resolution)) {
|
|
236
|
+
return {};
|
|
237
|
+
}
|
|
238
|
+
const ctx = await loadRuntimeContext(resolution.cwd);
|
|
141
239
|
const deps = createDefaultGateRuntimeDeps();
|
|
142
240
|
await appendObservedAudit(ctx, deps, eventName, payload);
|
|
143
|
-
|
|
241
|
+
return {};
|
|
144
242
|
}
|
|
145
243
|
catch (error) {
|
|
146
244
|
console.error('belay audit hook failed:', error instanceof Error ? error.message : String(error));
|
|
147
|
-
|
|
245
|
+
return {};
|
|
148
246
|
}
|
|
149
247
|
}
|
|
248
|
+
export async function runAuditHook(eventName) {
|
|
249
|
+
jsonResponse(await handleAuditHook(eventName, await readStdinJson()));
|
|
250
|
+
}
|
|
@@ -11,8 +11,13 @@ export interface ScopedPaths {
|
|
|
11
11
|
skillsDir: string;
|
|
12
12
|
commandsDir?: string;
|
|
13
13
|
}
|
|
14
|
+
export declare function resolveTrustedWindowsPowerShellPath(systemRoot?: string | undefined): string;
|
|
14
15
|
export declare function isPathInside(child: string, parent: string): boolean;
|
|
15
16
|
export declare function buildRunnerInvocation(platform: NodeJS.Platform, hooksDir: string, repoRoot: string, hookScript: string, ...args: string[]): string;
|
|
17
|
+
export declare function buildAbsoluteRunnerInvocation(platform: NodeJS.Platform, hooksDir: string, hookScript: string, ...args: string[]): string;
|
|
18
|
+
export declare function buildLegacyBarePowerShellRunnerInvocation(platform: NodeJS.Platform, hooksDir: string, hookScript: string, ...args: string[]): string;
|
|
19
|
+
export declare function buildLegacyQuotedAbsoluteRunnerInvocation(platform: NodeJS.Platform, hooksDir: string, hookScript: string, ...args: string[]): string;
|
|
20
|
+
export declare function buildLegacyAbsoluteRunnerInvocation(platform: NodeJS.Platform, hooksDir: string, hookScript: string, ...args: string[]): string;
|
|
16
21
|
export declare function resolveScopedPaths(layout: AdapterLayout, scope: InstallScope, repoRoot: string): ScopedPaths;
|
|
17
22
|
export declare function resolveInstallScope(options: {
|
|
18
23
|
scope?: InstallScope;
|
|
@@ -1,6 +1,50 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
1
2
|
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { isPathOutsideRoot } from '../../core/path-utils.js';
|
|
5
|
+
function canonicalizePotentialPath(inputPath) {
|
|
6
|
+
const unresolvedSegments = [];
|
|
7
|
+
let existingAncestor = path.resolve(inputPath);
|
|
8
|
+
while (!existsSync(existingAncestor)) {
|
|
9
|
+
const parent = path.dirname(existingAncestor);
|
|
10
|
+
if (parent === existingAncestor) {
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
unresolvedSegments.unshift(path.basename(existingAncestor));
|
|
14
|
+
existingAncestor = parent;
|
|
15
|
+
}
|
|
16
|
+
return existsSync(existingAncestor)
|
|
17
|
+
? path.join(realpathSync(existingAncestor), ...unresolvedSegments)
|
|
18
|
+
: path.resolve(inputPath);
|
|
19
|
+
}
|
|
20
|
+
function quotePowerShellLiteral(value) {
|
|
21
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
22
|
+
}
|
|
23
|
+
export function resolveTrustedWindowsPowerShellPath(systemRoot = process.env.SystemRoot || process.env.WINDIR) {
|
|
24
|
+
if (!systemRoot || !path.win32.isAbsolute(systemRoot)) {
|
|
25
|
+
throw new Error('A trusted Windows system root must be an absolute drive path.');
|
|
26
|
+
}
|
|
27
|
+
const hasControlCharacter = [...systemRoot].some((character) => character.charCodeAt(0) < 32);
|
|
28
|
+
if (systemRoot !== systemRoot.trim() || hasControlCharacter || /[%!?*"&|<>^]/.test(systemRoot)) {
|
|
29
|
+
throw new Error('The trusted Windows system root contains unsafe path or cmd.exe expansion characters.');
|
|
30
|
+
}
|
|
31
|
+
if (systemRoot.slice(2).includes(':')) {
|
|
32
|
+
throw new Error('The trusted Windows system root contains an invalid drive path.');
|
|
33
|
+
}
|
|
34
|
+
const normalizedRoot = path.win32.normalize(systemRoot);
|
|
35
|
+
if (!/^[A-Za-z]:\\$/.test(path.win32.parse(normalizedRoot).root)) {
|
|
36
|
+
throw new Error('A trusted Windows system root must be an absolute drive path.');
|
|
37
|
+
}
|
|
38
|
+
return path.win32.join(normalizedRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
39
|
+
}
|
|
40
|
+
function buildEncodedPowerShellRunnerInvocation(executable, runnerPath, hookScript, args) {
|
|
41
|
+
const encodedCommand = Buffer.from([
|
|
42
|
+
'&',
|
|
43
|
+
quotePowerShellLiteral(runnerPath),
|
|
44
|
+
...[hookScript, ...args].map(quotePowerShellLiteral),
|
|
45
|
+
].join(' '), 'utf16le').toString('base64');
|
|
46
|
+
return `${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encodedCommand}`;
|
|
47
|
+
}
|
|
4
48
|
function agentHomeDir(adapter) {
|
|
5
49
|
const home = os.homedir();
|
|
6
50
|
if (adapter === 'cursor') {
|
|
@@ -38,6 +82,33 @@ export function buildRunnerInvocation(platform, hooksDir, repoRoot, hookScript,
|
|
|
38
82
|
: runnerAbs;
|
|
39
83
|
return [runnerRef, hookScript, ...args].join(' ');
|
|
40
84
|
}
|
|
85
|
+
export function buildAbsoluteRunnerInvocation(platform, hooksDir, hookScript, ...args) {
|
|
86
|
+
const runnerFile = platform === 'win32' ? 'belay-runner.ps1' : 'belay-runner';
|
|
87
|
+
const canonicalHooksDir = canonicalizePotentialPath(hooksDir);
|
|
88
|
+
const runnerPath = path.join(canonicalHooksDir, runnerFile);
|
|
89
|
+
if (platform === 'win32') {
|
|
90
|
+
const powerShellPath = resolveTrustedWindowsPowerShellPath();
|
|
91
|
+
return buildEncodedPowerShellRunnerInvocation(`"${powerShellPath}"`, runnerPath, hookScript, args);
|
|
92
|
+
}
|
|
93
|
+
return [`'${runnerPath.replaceAll("'", "'\\''")}'`, hookScript, ...args].join(' ');
|
|
94
|
+
}
|
|
95
|
+
export function buildLegacyBarePowerShellRunnerInvocation(platform, hooksDir, hookScript, ...args) {
|
|
96
|
+
if (platform !== 'win32') {
|
|
97
|
+
return buildAbsoluteRunnerInvocation(platform, hooksDir, hookScript, ...args);
|
|
98
|
+
}
|
|
99
|
+
const runnerPath = path.join(canonicalizePotentialPath(hooksDir), 'belay-runner.ps1');
|
|
100
|
+
return buildEncodedPowerShellRunnerInvocation('powershell.exe', runnerPath, hookScript, args);
|
|
101
|
+
}
|
|
102
|
+
export function buildLegacyQuotedAbsoluteRunnerInvocation(platform, hooksDir, hookScript, ...args) {
|
|
103
|
+
const runnerFile = platform === 'win32' ? 'belay-runner.cmd' : 'belay-runner';
|
|
104
|
+
const runnerPath = path.join(canonicalizePotentialPath(hooksDir), runnerFile);
|
|
105
|
+
const quotedRunnerPath = platform === 'win32' ? `"${runnerPath}"` : `'${runnerPath.replaceAll("'", "'\\''")}'`;
|
|
106
|
+
return [quotedRunnerPath, hookScript, ...args].join(' ');
|
|
107
|
+
}
|
|
108
|
+
export function buildLegacyAbsoluteRunnerInvocation(platform, hooksDir, hookScript, ...args) {
|
|
109
|
+
const runnerFile = platform === 'win32' ? 'belay-runner.cmd' : 'belay-runner';
|
|
110
|
+
return [path.resolve(hooksDir, runnerFile), hookScript, ...args].join(' ');
|
|
111
|
+
}
|
|
41
112
|
export function resolveScopedPaths(layout, scope, repoRoot) {
|
|
42
113
|
const resolvedRepo = path.resolve(repoRoot);
|
|
43
114
|
const adapter = layout.name;
|
|
@@ -7,7 +7,7 @@ import { buildReplayHint, buildRetryInstructionForConfig, canAutoReplay, getExec
|
|
|
7
7
|
import { claimApprovedForReplay, gateApprovalStoreFromDeps, recordApproval, } from '../../core/approval-service.js';
|
|
8
8
|
import { issueApprovalToken } from '../../core/approval-token.js';
|
|
9
9
|
import { buildAuditActionSnapshot, buildAuditReplayContext, } from '../../core/audit-replay-context.js';
|
|
10
|
-
import { approvalCorrelationId, serializeAuditRecordV3 } from '../../core/audit-serialize.js';
|
|
10
|
+
import { approvalCorrelationId, serializeAuditRecordV3, toolInvocationCorrelationId, } from '../../core/audit-serialize.js';
|
|
11
11
|
import { boundedUtf8Tail } from '../../core/bounded-output.js';
|
|
12
12
|
import { mutateApprovalStateWithRetry } from '../../core/capability/approval-state-mutation.js';
|
|
13
13
|
import { APPROVAL_STATE_VERSION_V3 } from '../../core/capability/approval-v3.js';
|
|
@@ -36,7 +36,7 @@ import { extractJudgeFallbackReason, formatJudgeInfrastructureDenyMessage, infer
|
|
|
36
36
|
import { notifyDeny } from '../../core/notify.js';
|
|
37
37
|
import { canonicalPath } from '../../core/path-utils.js';
|
|
38
38
|
import { recoveryFailClosedResult, recoveryFailReasonFromSkip, } from '../../core/recovery/fail-closed.js';
|
|
39
|
-
import { fingerprintReplayPayload } from '../../core/replay-scrub.js';
|
|
39
|
+
import { fingerprintReplayPayload, redactToolInvocationId } from '../../core/replay-scrub.js';
|
|
40
40
|
import { FILE_CHECKPOINT_ISOLATION_UNAVAILABLE, isTransactionalEligible, runTransactionalExecution, TRANSACTIONAL_ALREADY_APPLIED, TRANSACTIONAL_APPROVAL_BYPASS_REASONS, } from '../../core/transactional/index.js';
|
|
41
41
|
import { buildAuditProvenanceFields } from '../../runtime-provenance.js';
|
|
42
42
|
import { egressStatus } from '../../services/egress-service.js';
|
|
@@ -616,6 +616,9 @@ export async function evaluateGatedAction(ctx, deps, params) {
|
|
|
616
616
|
event: resolveGateAuditEvent(sourceEvent, params.kind),
|
|
617
617
|
sourceEvent,
|
|
618
618
|
kind: params.kind,
|
|
619
|
+
...(typeof params.payload?.tool_use_id === 'string'
|
|
620
|
+
? { toolInvocationCorrelationId: toolInvocationCorrelationId(params.payload.tool_use_id) }
|
|
621
|
+
: {}),
|
|
619
622
|
fingerprint: verdict.fingerprint,
|
|
620
623
|
verdict: verdict.verdict,
|
|
621
624
|
reason: verdict.reason,
|
|
@@ -779,6 +782,9 @@ export async function evaluateGatedAction(ctx, deps, params) {
|
|
|
779
782
|
const scrubbedPayload = fingerprintReplayPayload(params.kind, params.payload, scrubOpts);
|
|
780
783
|
return gateDecisionToVerdict(ctx, deps, params.kind, result, {
|
|
781
784
|
sourceEvent: params.sourceEvent,
|
|
785
|
+
toolInvocationCorrelationId: typeof params.payload?.tool_use_id === 'string'
|
|
786
|
+
? toolInvocationCorrelationId(params.payload.tool_use_id)
|
|
787
|
+
: undefined,
|
|
782
788
|
predictedAssessment,
|
|
783
789
|
observedAssessment,
|
|
784
790
|
transactionalLayer,
|
|
@@ -904,6 +910,10 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
904
910
|
event: auditEvent,
|
|
905
911
|
sourceEvent,
|
|
906
912
|
kind,
|
|
913
|
+
repoLabel: path.basename(ctx.repoRoot),
|
|
914
|
+
...(auditExtras.toolInvocationCorrelationId
|
|
915
|
+
? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId }
|
|
916
|
+
: {}),
|
|
907
917
|
fingerprint: result.fingerprint,
|
|
908
918
|
summary: result.normalizedCommand ?? result.summary ?? '',
|
|
909
919
|
assessment: result.assessment,
|
|
@@ -1398,12 +1408,26 @@ export function gateVerdictToCodexUserPromptResponse(verdict) {
|
|
|
1398
1408
|
};
|
|
1399
1409
|
}
|
|
1400
1410
|
export async function appendObservedAudit(ctx, deps, eventName, payload) {
|
|
1411
|
+
const rawToolUseId = typeof payload.tool_use_id === 'string' ? payload.tool_use_id : undefined;
|
|
1412
|
+
const summaryPayload = redactToolInvocationId(payload, rawToolUseId);
|
|
1401
1413
|
await deps.appendAudit(ctx, {
|
|
1402
1414
|
event: eventName,
|
|
1403
1415
|
kind: 'audit',
|
|
1404
1416
|
verdict: 'allow',
|
|
1405
1417
|
reason: 'observed',
|
|
1406
|
-
|
|
1418
|
+
...(rawToolUseId
|
|
1419
|
+
? { toolInvocationCorrelationId: toolInvocationCorrelationId(rawToolUseId) }
|
|
1420
|
+
: {}),
|
|
1421
|
+
...(typeof summaryPayload.tool_name === 'string' ? { toolName: summaryPayload.tool_name } : {}),
|
|
1422
|
+
...(typeof summaryPayload.failure_type === 'string'
|
|
1423
|
+
? { failureType: summaryPayload.failure_type }
|
|
1424
|
+
: {}),
|
|
1425
|
+
...(typeof summaryPayload.error_message === 'string'
|
|
1426
|
+
? { errorMessage: summaryPayload.error_message }
|
|
1427
|
+
: {}),
|
|
1428
|
+
...(typeof payload.duration === 'number' ? { durationMs: payload.duration } : {}),
|
|
1429
|
+
...(typeof payload.is_interrupt === 'boolean' ? { isInterrupt: payload.is_interrupt } : {}),
|
|
1430
|
+
summary: canonicalStringify(summaryPayload),
|
|
1407
1431
|
});
|
|
1408
1432
|
}
|
|
1409
1433
|
export { GateNormalizationError };
|