aegis-bridge 2.15.2 → 2.15.3
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 +9 -0
- package/dist/consensus.d.ts +16 -0
- package/dist/consensus.js +19 -0
- package/dist/hooks.js +32 -0
- package/dist/permission-evaluator.d.ts +10 -0
- package/dist/permission-evaluator.js +48 -0
- package/dist/server.js +68 -1
- package/dist/session.d.ts +2 -1
- package/dist/validation.d.ts +63 -0
- package/dist/validation.js +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<img src="https://img.shields.io/github/actions/workflow/status/OneStepAt4time/aegis/ci.yml?branch=main" alt="CI" />
|
|
8
8
|
<img src="https://img.shields.io/npm/l/aegis-bridge.svg" alt="license" />
|
|
9
9
|
<img src="https://img.shields.io/badge/node-%3E%3D20.0.0-blue.svg" alt="node" />
|
|
10
|
+
<img src="https://img.shields.io/badge/MCP-ready-green.svg" alt="MCP ready" />
|
|
10
11
|
</p>
|
|
11
12
|
|
|
12
13
|
<p align="center">
|
|
@@ -96,6 +97,14 @@ Or via `.mcp.json`:
|
|
|
96
97
|
|
|
97
98
|
**3 prompts** — `implement_issue`, `review_pr`, `debug_session`
|
|
98
99
|
|
|
100
|
+
## Ecosystem Integrations
|
|
101
|
+
|
|
102
|
+
Aegis works beyond Claude Code anywhere an MCP host can launch a local stdio server.
|
|
103
|
+
|
|
104
|
+
- [Cursor integration](docs/integrations/cursor.md)
|
|
105
|
+
- [Windsurf integration](docs/integrations/windsurf.md)
|
|
106
|
+
- [MCP Registry preparation](docs/integrations/mcp-registry.md)
|
|
107
|
+
|
|
99
108
|
---
|
|
100
109
|
|
|
101
110
|
## REST API
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ConsensusFocusArea = 'correctness' | 'security' | 'performance';
|
|
2
|
+
export interface ConsensusRequest {
|
|
3
|
+
id: string;
|
|
4
|
+
targetSessionId: string;
|
|
5
|
+
reviewerIds: string[];
|
|
6
|
+
focusAreas: ConsensusFocusArea[];
|
|
7
|
+
status: 'running' | 'completed' | 'failed';
|
|
8
|
+
createdAt: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ConsensusReview {
|
|
11
|
+
reviewerId: string;
|
|
12
|
+
focusArea: ConsensusFocusArea;
|
|
13
|
+
findings: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function buildConsensusPrompt(targetSessionId: string, focusArea: ConsensusFocusArea): string;
|
|
16
|
+
export declare function mergeConsensusFindings(reviews: ConsensusReview[]): string[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function buildConsensusPrompt(targetSessionId, focusArea) {
|
|
2
|
+
return [
|
|
3
|
+
`Review Aegis session ${targetSessionId}.`,
|
|
4
|
+
`Focus area: ${focusArea}.`,
|
|
5
|
+
'Return concise findings ordered by severity.',
|
|
6
|
+
'Prefer concrete regressions, risks, and missing verification.',
|
|
7
|
+
].join(' ');
|
|
8
|
+
}
|
|
9
|
+
export function mergeConsensusFindings(reviews) {
|
|
10
|
+
const merged = new Set();
|
|
11
|
+
for (const review of reviews) {
|
|
12
|
+
for (const finding of review.findings) {
|
|
13
|
+
const normalized = finding.trim();
|
|
14
|
+
if (normalized)
|
|
15
|
+
merged.add(normalized);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return Array.from(merged.values());
|
|
19
|
+
}
|
package/dist/hooks.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* Issue #169: Phase 3 — Hook-driven status detection.
|
|
16
16
|
*/
|
|
17
17
|
import { isValidUUID, hookBodySchema, parseIntSafe } from './validation.js';
|
|
18
|
+
import { evaluatePermissionProfile } from './permission-evaluator.js';
|
|
18
19
|
/** CC hook events that require a decision response. */
|
|
19
20
|
const DECISION_EVENTS = new Set(['PreToolUse', 'PermissionRequest']);
|
|
20
21
|
/** Permission modes that should be auto-approved via hook response. */
|
|
@@ -289,6 +290,37 @@ export function registerHookRoutes(app, deps) {
|
|
|
289
290
|
// Timeout: allow without answer (CC shows question to user in terminal)
|
|
290
291
|
console.log(`Hooks: AskUserQuestion timeout for session ${sessionId} — allowing without answer`);
|
|
291
292
|
}
|
|
293
|
+
if (session.permissionProfile) {
|
|
294
|
+
const evaluation = evaluatePermissionProfile(session.permissionProfile, {
|
|
295
|
+
toolName,
|
|
296
|
+
toolInput: hookBody.tool_input,
|
|
297
|
+
});
|
|
298
|
+
if (evaluation.behavior === 'deny') {
|
|
299
|
+
deps.eventBus.emit(sessionId, {
|
|
300
|
+
event: 'permission_denied',
|
|
301
|
+
sessionId,
|
|
302
|
+
timestamp: new Date().toISOString(),
|
|
303
|
+
data: { toolName, reason: evaluation.reason },
|
|
304
|
+
});
|
|
305
|
+
return reply.status(200).send({
|
|
306
|
+
hookSpecificOutput: {
|
|
307
|
+
hookEventName: 'PreToolUse',
|
|
308
|
+
permissionDecision: 'deny',
|
|
309
|
+
reason: evaluation.reason,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
if (evaluation.behavior === 'ask') {
|
|
314
|
+
deps.eventBus.emitApproval(sessionId, `Permission profile requires approval for ${toolName}`);
|
|
315
|
+
const decision = await deps.sessions.waitForPermissionDecision(sessionId, PERMISSION_TIMEOUT_MS, toolName, evaluation.reason);
|
|
316
|
+
return reply.status(200).send({
|
|
317
|
+
hookSpecificOutput: {
|
|
318
|
+
hookEventName: 'PreToolUse',
|
|
319
|
+
permissionDecision: decision,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
292
324
|
// Default: allow without modification
|
|
293
325
|
return reply.status(200).send({
|
|
294
326
|
hookSpecificOutput: {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PermissionProfile } from './validation.js';
|
|
2
|
+
export interface PermissionEvaluationInput {
|
|
3
|
+
toolName: string;
|
|
4
|
+
toolInput?: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface PermissionEvaluationResult {
|
|
7
|
+
behavior: 'allow' | 'deny' | 'ask';
|
|
8
|
+
reason: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function evaluatePermissionProfile(profile: PermissionProfile, input: PermissionEvaluationInput): PermissionEvaluationResult;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
function globToRegExp(pattern) {
|
|
2
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
|
3
|
+
return new RegExp(`^${escaped}$`, 'i');
|
|
4
|
+
}
|
|
5
|
+
function extractCandidatePaths(toolInput) {
|
|
6
|
+
if (!toolInput)
|
|
7
|
+
return [];
|
|
8
|
+
const values = [toolInput.path, toolInput.file_path, toolInput.target, ...(Array.isArray(toolInput.paths) ? toolInput.paths : [])];
|
|
9
|
+
return values.filter((v) => typeof v === 'string');
|
|
10
|
+
}
|
|
11
|
+
function extractContentSize(toolInput) {
|
|
12
|
+
const content = toolInput?.content;
|
|
13
|
+
return typeof content === 'string' ? content.length : null;
|
|
14
|
+
}
|
|
15
|
+
function isLikelyWriteTool(toolName) {
|
|
16
|
+
return /write|edit|delete|rename|move|create/i.test(toolName);
|
|
17
|
+
}
|
|
18
|
+
export function evaluatePermissionProfile(profile, input) {
|
|
19
|
+
for (const rule of profile.rules) {
|
|
20
|
+
if (rule.tool !== input.toolName)
|
|
21
|
+
continue;
|
|
22
|
+
if (rule.pattern) {
|
|
23
|
+
const candidate = typeof input.toolInput?.command === 'string'
|
|
24
|
+
? input.toolInput.command
|
|
25
|
+
: JSON.stringify(input.toolInput ?? {});
|
|
26
|
+
if (!globToRegExp(rule.pattern).test(candidate))
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (rule.constraints?.readOnly && isLikelyWriteTool(input.toolName)) {
|
|
30
|
+
return { behavior: 'deny', reason: `Denied by readOnly constraint for ${input.toolName}` };
|
|
31
|
+
}
|
|
32
|
+
if (rule.constraints?.paths && rule.constraints.paths.length > 0) {
|
|
33
|
+
const paths = extractCandidatePaths(input.toolInput);
|
|
34
|
+
const allowed = paths.every((candidate) => rule.constraints.paths.some((prefix) => candidate.startsWith(prefix)));
|
|
35
|
+
if (!allowed) {
|
|
36
|
+
return { behavior: 'deny', reason: `Denied by path constraint for ${input.toolName}` };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (rule.constraints?.maxFileSize) {
|
|
40
|
+
const size = extractContentSize(input.toolInput);
|
|
41
|
+
if (size !== null && size > rule.constraints.maxFileSize) {
|
|
42
|
+
return { behavior: 'deny', reason: `Denied by maxFileSize constraint for ${input.toolName}` };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { behavior: rule.behavior, reason: `Matched rule for ${input.toolName}` };
|
|
46
|
+
}
|
|
47
|
+
return { behavior: profile.defaultBehavior, reason: 'No matching permission rule' };
|
|
48
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -37,6 +37,7 @@ import { registerHookRoutes } from './hooks.js';
|
|
|
37
37
|
import { registerWsTerminalRoute } from './ws-terminal.js';
|
|
38
38
|
import { registerMemoryRoutes } from './memory-routes.js';
|
|
39
39
|
import { registerModelRouterRoutes } from './model-router.js';
|
|
40
|
+
import { buildConsensusPrompt } from './consensus.js';
|
|
40
41
|
import * as templateStore from './template-store.js';
|
|
41
42
|
import { SwarmMonitor } from './swarm-monitor.js';
|
|
42
43
|
import { killAllSessions } from './signal-cleanup-helper.js';
|
|
@@ -48,9 +49,10 @@ import { MemoryBridge } from './memory-bridge.js';
|
|
|
48
49
|
import { cleanupTerminatedSessionState } from './session-cleanup.js';
|
|
49
50
|
import { normalizeApiErrorPayload } from './api-error-envelope.js';
|
|
50
51
|
import { listenWithRetry, removePidFile, writePidFile } from './startup.js';
|
|
51
|
-
import { authKeySchema, sendMessageSchema, commandSchema, bashSchema, screenshotSchema, permissionHookSchema, stopHookSchema, batchSessionSchema, pipelineSchema, handshakeRequestSchema, parseIntSafe, isValidUUID, compareSemver, extractCCVersion, MIN_CC_VERSION, } from './validation.js';
|
|
52
|
+
import { authKeySchema, sendMessageSchema, commandSchema, bashSchema, screenshotSchema, permissionHookSchema, stopHookSchema, batchSessionSchema, pipelineSchema, handshakeRequestSchema, parseIntSafe, isValidUUID, compareSemver, extractCCVersion, MIN_CC_VERSION, permissionProfileSchema, } from './validation.js';
|
|
52
53
|
const __filename = fileURLToPath(import.meta.url);
|
|
53
54
|
const __dirname = path.dirname(__filename);
|
|
55
|
+
const consensusRequests = new Map();
|
|
54
56
|
// ── Configuration ────────────────────────────────────────────────────
|
|
55
57
|
// Issue #349: CSP policy for dashboard responses (shared between static and SPA fallback)
|
|
56
58
|
const DASHBOARD_CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' ws: wss:";
|
|
@@ -904,6 +906,48 @@ async function forkSessionHandler(req, reply) {
|
|
|
904
906
|
}
|
|
905
907
|
app.post('/v1/sessions/:id/fork', forkSessionHandler);
|
|
906
908
|
app.post('/sessions/:id/fork', forkSessionHandler);
|
|
909
|
+
async function createConsensusHandler(req, reply) {
|
|
910
|
+
const targetSessionId = req.params.id;
|
|
911
|
+
const target = sessions.getSession(targetSessionId);
|
|
912
|
+
if (!target)
|
|
913
|
+
return reply.status(404).send({ error: 'Target session not found' });
|
|
914
|
+
const focusAreas = (req.body?.focusAreas && req.body.focusAreas.length > 0)
|
|
915
|
+
? req.body.focusAreas
|
|
916
|
+
: ['correctness', 'security', 'performance'];
|
|
917
|
+
const reviewerCount = Math.min(5, Math.max(1, req.body?.reviewerCount ?? focusAreas.length));
|
|
918
|
+
const selectedFocus = focusAreas.slice(0, reviewerCount);
|
|
919
|
+
const reviewerIds = [];
|
|
920
|
+
for (let i = 0; i < selectedFocus.length; i += 1) {
|
|
921
|
+
const focus = selectedFocus[i];
|
|
922
|
+
const child = await sessions.createSession({
|
|
923
|
+
workDir: target.workDir,
|
|
924
|
+
name: `consensus-${focus}-${targetSessionId.slice(0, 6)}`,
|
|
925
|
+
parentId: targetSessionId,
|
|
926
|
+
permissionMode: target.permissionMode,
|
|
927
|
+
});
|
|
928
|
+
reviewerIds.push(child.id);
|
|
929
|
+
await sessions.sendInitialPrompt(child.id, buildConsensusPrompt(targetSessionId, focus));
|
|
930
|
+
}
|
|
931
|
+
const consensusId = crypto.randomUUID();
|
|
932
|
+
const record = {
|
|
933
|
+
id: consensusId,
|
|
934
|
+
targetSessionId,
|
|
935
|
+
reviewerIds,
|
|
936
|
+
focusAreas: selectedFocus,
|
|
937
|
+
status: 'running',
|
|
938
|
+
createdAt: Date.now(),
|
|
939
|
+
};
|
|
940
|
+
consensusRequests.set(consensusId, record);
|
|
941
|
+
return reply.status(202).send(record);
|
|
942
|
+
}
|
|
943
|
+
function getConsensusHandler(req, reply) {
|
|
944
|
+
const item = consensusRequests.get(req.params.id);
|
|
945
|
+
if (!item)
|
|
946
|
+
return reply.status(404).send({ error: 'Consensus request not found' });
|
|
947
|
+
return item;
|
|
948
|
+
}
|
|
949
|
+
app.post('/v1/sessions/:id/consensus', createConsensusHandler);
|
|
950
|
+
app.get('/v1/consensus/:id', getConsensusHandler);
|
|
907
951
|
async function getPermissionPolicyHandler(req, reply) {
|
|
908
952
|
const sessionId = req.params.id;
|
|
909
953
|
const session = sessions.getSession(sessionId);
|
|
@@ -928,6 +972,29 @@ app.get('/v1/sessions/:id/permissions', getPermissionPolicyHandler);
|
|
|
928
972
|
app.put('/v1/sessions/:id/permissions', updatePermissionPolicyHandler);
|
|
929
973
|
app.get('/sessions/:id/permissions', getPermissionPolicyHandler);
|
|
930
974
|
app.put('/sessions/:id/permissions', updatePermissionPolicyHandler);
|
|
975
|
+
async function getPermissionProfileHandler(req, reply) {
|
|
976
|
+
const sessionId = req.params.id;
|
|
977
|
+
const session = sessions.getSession(sessionId);
|
|
978
|
+
if (!session)
|
|
979
|
+
return reply.status(404).send({ error: 'Session not found' });
|
|
980
|
+
return { permissionProfile: session.permissionProfile ?? null };
|
|
981
|
+
}
|
|
982
|
+
async function updatePermissionProfileHandler(req, reply) {
|
|
983
|
+
const sessionId = req.params.id;
|
|
984
|
+
const session = sessions.getSession(sessionId);
|
|
985
|
+
if (!session)
|
|
986
|
+
return reply.status(404).send({ error: 'Session not found' });
|
|
987
|
+
const parsed = permissionProfileSchema.safeParse(req.body ?? {});
|
|
988
|
+
if (!parsed.success)
|
|
989
|
+
return reply.status(400).send({ error: 'Invalid permission profile', details: parsed.error.issues });
|
|
990
|
+
session.permissionProfile = parsed.data;
|
|
991
|
+
await sessions.save();
|
|
992
|
+
return { permissionProfile: parsed.data };
|
|
993
|
+
}
|
|
994
|
+
app.get('/v1/sessions/:id/permission-profile', getPermissionProfileHandler);
|
|
995
|
+
app.put('/v1/sessions/:id/permission-profile', updatePermissionProfileHandler);
|
|
996
|
+
app.get('/sessions/:id/permission-profile', getPermissionProfileHandler);
|
|
997
|
+
app.put('/sessions/:id/permission-profile', updatePermissionProfileHandler);
|
|
931
998
|
// Read messages
|
|
932
999
|
async function readMessagesHandler(req, reply) {
|
|
933
1000
|
try {
|
package/dist/session.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { TmuxManager } from './tmux.js';
|
|
|
8
8
|
import { type ParsedEntry } from './transcript.js';
|
|
9
9
|
import { type UIState } from './terminal-parser.js';
|
|
10
10
|
import type { Config } from './config.js';
|
|
11
|
-
import { type PermissionPolicy } from './validation.js';
|
|
11
|
+
import { type PermissionPolicy, type PermissionProfile } from './validation.js';
|
|
12
12
|
import { type PermissionDecision } from './permission-request-manager.js';
|
|
13
13
|
export interface SessionInfo {
|
|
14
14
|
id: string;
|
|
@@ -40,6 +40,7 @@ export interface SessionInfo {
|
|
|
40
40
|
parentId?: string;
|
|
41
41
|
children?: string[];
|
|
42
42
|
permissionPolicy?: PermissionPolicy;
|
|
43
|
+
permissionProfile?: PermissionProfile;
|
|
43
44
|
prd?: string;
|
|
44
45
|
}
|
|
45
46
|
export interface SessionState {
|
package/dist/validation.d.ts
CHANGED
|
@@ -141,6 +141,48 @@ export declare const permissionRuleSchema: z.ZodObject<{
|
|
|
141
141
|
commandPattern: z.ZodOptional<z.ZodString>;
|
|
142
142
|
}, z.core.$strip>;
|
|
143
143
|
export type PermissionPolicy = z.infer<typeof permissionRuleSchema>[];
|
|
144
|
+
/** Issue #742: richer per-session permission profile. */
|
|
145
|
+
export declare const permissionConstraintSchema: z.ZodObject<{
|
|
146
|
+
readOnly: z.ZodOptional<z.ZodBoolean>;
|
|
147
|
+
paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
148
|
+
maxFileSize: z.ZodOptional<z.ZodNumber>;
|
|
149
|
+
}, z.core.$strict>;
|
|
150
|
+
export declare const permissionProfileRuleSchema: z.ZodObject<{
|
|
151
|
+
tool: z.ZodString;
|
|
152
|
+
behavior: z.ZodEnum<{
|
|
153
|
+
allow: "allow";
|
|
154
|
+
deny: "deny";
|
|
155
|
+
ask: "ask";
|
|
156
|
+
}>;
|
|
157
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
158
|
+
constraints: z.ZodOptional<z.ZodObject<{
|
|
159
|
+
readOnly: z.ZodOptional<z.ZodBoolean>;
|
|
160
|
+
paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
161
|
+
maxFileSize: z.ZodOptional<z.ZodNumber>;
|
|
162
|
+
}, z.core.$strict>>;
|
|
163
|
+
}, z.core.$strict>;
|
|
164
|
+
export declare const permissionProfileSchema: z.ZodObject<{
|
|
165
|
+
defaultBehavior: z.ZodEnum<{
|
|
166
|
+
allow: "allow";
|
|
167
|
+
deny: "deny";
|
|
168
|
+
ask: "ask";
|
|
169
|
+
}>;
|
|
170
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
171
|
+
tool: z.ZodString;
|
|
172
|
+
behavior: z.ZodEnum<{
|
|
173
|
+
allow: "allow";
|
|
174
|
+
deny: "deny";
|
|
175
|
+
ask: "ask";
|
|
176
|
+
}>;
|
|
177
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
178
|
+
constraints: z.ZodOptional<z.ZodObject<{
|
|
179
|
+
readOnly: z.ZodOptional<z.ZodBoolean>;
|
|
180
|
+
paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
181
|
+
maxFileSize: z.ZodOptional<z.ZodNumber>;
|
|
182
|
+
}, z.core.$strict>>;
|
|
183
|
+
}, z.core.$strict>>;
|
|
184
|
+
}, z.core.$strict>;
|
|
185
|
+
export type PermissionProfile = z.infer<typeof permissionProfileSchema>;
|
|
144
186
|
/** Schema for persisted SessionState (sessions: { [id]: SessionInfo }). */
|
|
145
187
|
export declare const persistedStateSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
146
188
|
id: z.ZodString;
|
|
@@ -206,6 +248,27 @@ export declare const persistedStateSchema: z.ZodRecord<z.ZodString, z.ZodObject<
|
|
|
206
248
|
toolName: z.ZodOptional<z.ZodString>;
|
|
207
249
|
commandPattern: z.ZodOptional<z.ZodString>;
|
|
208
250
|
}, z.core.$strip>>>;
|
|
251
|
+
permissionProfile: z.ZodOptional<z.ZodObject<{
|
|
252
|
+
defaultBehavior: z.ZodEnum<{
|
|
253
|
+
allow: "allow";
|
|
254
|
+
deny: "deny";
|
|
255
|
+
ask: "ask";
|
|
256
|
+
}>;
|
|
257
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
258
|
+
tool: z.ZodString;
|
|
259
|
+
behavior: z.ZodEnum<{
|
|
260
|
+
allow: "allow";
|
|
261
|
+
deny: "deny";
|
|
262
|
+
ask: "ask";
|
|
263
|
+
}>;
|
|
264
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
265
|
+
constraints: z.ZodOptional<z.ZodObject<{
|
|
266
|
+
readOnly: z.ZodOptional<z.ZodBoolean>;
|
|
267
|
+
paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
268
|
+
maxFileSize: z.ZodOptional<z.ZodNumber>;
|
|
269
|
+
}, z.core.$strict>>;
|
|
270
|
+
}, z.core.$strict>>;
|
|
271
|
+
}, z.core.$strict>>;
|
|
209
272
|
}, z.core.$strip>>;
|
|
210
273
|
/** Schema for a single continuation pointer entry in session_map.json (Issue #900). */
|
|
211
274
|
export declare const sessionMapEntrySchema: z.ZodObject<{
|
package/dist/validation.js
CHANGED
|
@@ -138,6 +138,22 @@ export const permissionRuleSchema = z.object({
|
|
|
138
138
|
toolName: z.string().optional(),
|
|
139
139
|
commandPattern: z.string().optional(),
|
|
140
140
|
});
|
|
141
|
+
/** Issue #742: richer per-session permission profile. */
|
|
142
|
+
export const permissionConstraintSchema = z.object({
|
|
143
|
+
readOnly: z.boolean().optional(),
|
|
144
|
+
paths: z.array(z.string().min(1)).max(50).optional(),
|
|
145
|
+
maxFileSize: z.number().int().positive().max(10_000_000).optional(),
|
|
146
|
+
}).strict();
|
|
147
|
+
export const permissionProfileRuleSchema = z.object({
|
|
148
|
+
tool: z.string().min(1),
|
|
149
|
+
behavior: z.enum(['allow', 'deny', 'ask']),
|
|
150
|
+
pattern: z.string().optional(),
|
|
151
|
+
constraints: permissionConstraintSchema.optional(),
|
|
152
|
+
}).strict();
|
|
153
|
+
export const permissionProfileSchema = z.object({
|
|
154
|
+
defaultBehavior: z.enum(['allow', 'deny', 'ask']),
|
|
155
|
+
rules: z.array(permissionProfileRuleSchema).max(100),
|
|
156
|
+
}).strict();
|
|
141
157
|
/** Schema for persisted SessionState (sessions: { [id]: SessionInfo }). */
|
|
142
158
|
export const persistedStateSchema = z.record(z.string(), z.object({
|
|
143
159
|
id: z.string(),
|
|
@@ -173,6 +189,7 @@ export const persistedStateSchema = z.record(z.string(), z.object({
|
|
|
173
189
|
toolName: z.string().optional(),
|
|
174
190
|
commandPattern: z.string().optional(),
|
|
175
191
|
})).optional(),
|
|
192
|
+
permissionProfile: permissionProfileSchema.optional(),
|
|
176
193
|
}));
|
|
177
194
|
/** Schema for a single continuation pointer entry in session_map.json (Issue #900). */
|
|
178
195
|
export const sessionMapEntrySchema = z.object({
|