agentlock-shared 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +34 -0
  3. package/dist/__tests__/content-crypto.test.d.ts +2 -0
  4. package/dist/__tests__/content-crypto.test.d.ts.map +1 -0
  5. package/dist/__tests__/content-crypto.test.js +117 -0
  6. package/dist/__tests__/content-crypto.test.js.map +1 -0
  7. package/dist/__tests__/crypto.test.d.ts +2 -0
  8. package/dist/__tests__/crypto.test.d.ts.map +1 -0
  9. package/dist/__tests__/crypto.test.js +53 -0
  10. package/dist/__tests__/crypto.test.js.map +1 -0
  11. package/dist/__tests__/policy.test.d.ts +2 -0
  12. package/dist/__tests__/policy.test.d.ts.map +1 -0
  13. package/dist/__tests__/policy.test.js +80 -0
  14. package/dist/__tests__/policy.test.js.map +1 -0
  15. package/dist/__tests__/redact.test.d.ts +2 -0
  16. package/dist/__tests__/redact.test.d.ts.map +1 -0
  17. package/dist/__tests__/redact.test.js +39 -0
  18. package/dist/__tests__/redact.test.js.map +1 -0
  19. package/dist/__tests__/signing.test.d.ts +2 -0
  20. package/dist/__tests__/signing.test.d.ts.map +1 -0
  21. package/dist/__tests__/signing.test.js +51 -0
  22. package/dist/__tests__/signing.test.js.map +1 -0
  23. package/dist/content-crypto.d.ts +24 -0
  24. package/dist/content-crypto.d.ts.map +1 -0
  25. package/dist/content-crypto.js +58 -0
  26. package/dist/content-crypto.js.map +1 -0
  27. package/dist/crypto.d.ts +13 -0
  28. package/dist/crypto.d.ts.map +1 -0
  29. package/dist/crypto.js +85 -0
  30. package/dist/crypto.js.map +1 -0
  31. package/dist/index.d.ts +9 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +25 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/mcp-catalog.d.ts +15 -0
  36. package/dist/mcp-catalog.d.ts.map +1 -0
  37. package/dist/mcp-catalog.js +160 -0
  38. package/dist/mcp-catalog.js.map +1 -0
  39. package/dist/plans.d.ts +24 -0
  40. package/dist/plans.d.ts.map +1 -0
  41. package/dist/plans.js +80 -0
  42. package/dist/plans.js.map +1 -0
  43. package/dist/policy.d.ts +10 -0
  44. package/dist/policy.d.ts.map +1 -0
  45. package/dist/policy.js +168 -0
  46. package/dist/policy.js.map +1 -0
  47. package/dist/redact.d.ts +4 -0
  48. package/dist/redact.d.ts.map +1 -0
  49. package/dist/redact.js +115 -0
  50. package/dist/redact.js.map +1 -0
  51. package/dist/schemas.d.ts +128 -0
  52. package/dist/schemas.d.ts.map +1 -0
  53. package/dist/schemas.js +47 -0
  54. package/dist/schemas.js.map +1 -0
  55. package/dist/signing.d.ts +23 -0
  56. package/dist/signing.d.ts.map +1 -0
  57. package/dist/signing.js +96 -0
  58. package/dist/signing.js.map +1 -0
  59. package/dist/types.d.ts +184 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +3 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +29 -0
  64. package/src/__tests__/policy.test.ts +88 -0
  65. package/src/__tests__/redact.test.ts +41 -0
  66. package/src/__tests__/signing.test.ts +55 -0
  67. package/src/crypto.ts +87 -0
  68. package/src/index.ts +8 -0
  69. package/src/mcp-catalog.ts +181 -0
  70. package/src/plans.ts +96 -0
  71. package/src/policy.ts +186 -0
  72. package/src/redact.ts +114 -0
  73. package/src/schemas.ts +53 -0
  74. package/src/signing.ts +120 -0
  75. package/src/types.ts +212 -0
  76. package/test-gateway.mjs +47 -0
  77. package/tsconfig.json +10 -0
  78. package/vitest.config.ts +8 -0
package/src/policy.ts ADDED
@@ -0,0 +1,186 @@
1
+ import type {
2
+ PolicyRules,
3
+ AgentActionRequest,
4
+ PolicyEvaluationResult,
5
+ RiskLevel,
6
+ PolicyDecision,
7
+ } from './types.js';
8
+ import { redact } from './redact.js';
9
+
10
+ const RISK_MAP: Record<string, RiskLevel> = {
11
+ read: 'low',
12
+ write: 'medium',
13
+ financial: 'high',
14
+ admin: 'critical',
15
+ };
16
+
17
+ export const DEFAULT_POLICY_RULES: PolicyRules = {
18
+ defaultMode: 'require_approval',
19
+ rules: [
20
+ { action_type: 'read', decision: 'ALLOW' },
21
+ { action_type: 'write', decision: 'REQUIRE_APPROVAL' },
22
+ { action_type: 'financial', decision: 'REQUIRE_APPROVAL' },
23
+ { action_type: 'admin', decision: 'BLOCK' },
24
+ ],
25
+ http: {
26
+ allowedDomains: [],
27
+ allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
28
+ blockList: [],
29
+ },
30
+ limits: {
31
+ maxActionsPerHour: 100,
32
+ },
33
+ };
34
+
35
+ export function evaluatePolicy(
36
+ action: AgentActionRequest,
37
+ rules: PolicyRules
38
+ ): PolicyEvaluationResult {
39
+ const risk_level = RISK_MAP[action.action_type] ?? 'medium';
40
+
41
+ // Browser tools: browser.open always requires approval
42
+ if (action.tool.startsWith('browser.')) {
43
+ if (action.tool === 'browser.open') {
44
+ return {
45
+ decision: 'REQUIRE_APPROVAL',
46
+ risk_level: 'medium',
47
+ reason: 'Browser sessions always require approval to start',
48
+ };
49
+ }
50
+ // Other browser.* actions with a valid session are handled at the gateway
51
+ // level (auto-approved). If they reach the policy engine without a session,
52
+ // they should be blocked.
53
+ return {
54
+ decision: 'BLOCK',
55
+ risk_level: 'medium',
56
+ reason: 'Browser actions require an active session (use browser.open first)',
57
+ };
58
+ }
59
+
60
+ // MCP tools: list_tools is a read (low risk), call_tool defers to action_type rules
61
+ if (action.tool === 'mcp.list_tools') {
62
+ return {
63
+ decision: 'ALLOW',
64
+ risk_level: 'low',
65
+ reason: 'MCP tool discovery is read-only',
66
+ };
67
+ }
68
+
69
+ if (action.tool.split('.')[0] === 'http' && rules.http) {
70
+ const url = action.payload.url as string | undefined;
71
+ if (url) {
72
+ try {
73
+ const domain = new URL(url).hostname;
74
+ // Use exact match or proper subdomain match (preceded by a dot)
75
+ // to prevent "not-trusted.com" from matching allowlist entry "trusted.com"
76
+ const matchesDomain = (d: string, pattern: string) =>
77
+ d === pattern || d.endsWith('.' + pattern);
78
+ if (rules.http.blockList.some((b) => matchesDomain(domain, b))) {
79
+ return { decision: 'BLOCK', risk_level: 'critical', reason: `Domain ${domain} is in block list` };
80
+ }
81
+ if (rules.http.allowedDomains.length === 0) {
82
+ // No allowlist configured: safe default is REQUIRE_APPROVAL, not ALLOW.
83
+ // This prevents agents from exfiltrating data to arbitrary domains.
84
+ return {
85
+ decision: 'REQUIRE_APPROVAL',
86
+ risk_level,
87
+ reason: 'HTTP allowlist not configured — approval required for all HTTP calls',
88
+ };
89
+ }
90
+ if (!rules.http.allowedDomains.some((d) => matchesDomain(domain, d))) {
91
+ return { decision: 'BLOCK', risk_level, reason: `Domain ${domain} not in allowed list` };
92
+ }
93
+ } catch {
94
+ return { decision: 'BLOCK', risk_level: 'critical', reason: 'Invalid URL' };
95
+ }
96
+ }
97
+ const method = (action.payload.method as string | undefined)?.toUpperCase();
98
+ if (method && !rules.http.allowedMethods.includes(method)) {
99
+ return { decision: 'BLOCK', risk_level, reason: `HTTP method ${method} not allowed` };
100
+ }
101
+ }
102
+
103
+ if (
104
+ rules.limits?.maxCostPerAction !== undefined &&
105
+ action.cost_estimate !== undefined &&
106
+ action.cost_estimate > rules.limits.maxCostPerAction
107
+ ) {
108
+ return {
109
+ decision: 'BLOCK',
110
+ risk_level: 'high',
111
+ reason: `Cost estimate ${action.cost_estimate} exceeds limit ${rules.limits.maxCostPerAction}`,
112
+ };
113
+ }
114
+
115
+ // Most specific: tool-specific rule
116
+ let matched = rules.rules.find((r) => r.tool === action.tool);
117
+ // Then action-type rule
118
+ if (!matched) matched = rules.rules.find((r) => r.action_type === action.action_type);
119
+
120
+ if (matched) {
121
+ return {
122
+ decision: matched.decision,
123
+ risk_level,
124
+ reason: `Matched rule: ${matched.action_type ?? matched.tool}`,
125
+ matched_rule: matched,
126
+ };
127
+ }
128
+
129
+ const defaultDecision: PolicyDecision =
130
+ rules.defaultMode === 'allow' ? 'ALLOW' : rules.defaultMode === 'block' ? 'BLOCK' : 'REQUIRE_APPROVAL';
131
+
132
+ return { decision: defaultDecision, risk_level, reason: 'Default policy' };
133
+ }
134
+
135
+ export function buildActionPreview(action: AgentActionRequest): {
136
+ summary: string;
137
+ target?: string;
138
+ impact?: string;
139
+ cost_estimate?: number;
140
+ } {
141
+ let summary = `${action.action_type.toUpperCase()} via ${action.tool}`;
142
+ let target: string | undefined;
143
+
144
+ if (action.tool.split('.')[0] === 'http') {
145
+ const url = action.payload.url as string | undefined;
146
+ const method = action.payload.method as string | undefined;
147
+ if (url) {
148
+ try {
149
+ target = new URL(url).hostname;
150
+ } catch {
151
+ target = url;
152
+ }
153
+ summary = `${method?.toUpperCase() ?? 'HTTP'} request to ${target}`;
154
+ }
155
+ } else if (action.tool === 'browser.open') {
156
+ const url = action.payload.url as string | undefined;
157
+ if (url) {
158
+ try {
159
+ target = new URL(url).hostname;
160
+ } catch {
161
+ target = url;
162
+ }
163
+ summary = `Open browser session to ${target}`;
164
+ } else {
165
+ summary = 'Open browser session';
166
+ }
167
+ } else if (action.tool === 'mcp.list_tools') {
168
+ const server = action.payload.server as string | undefined;
169
+ target = server;
170
+ summary = `List available tools on MCP server "${server ?? 'unknown'}"`;
171
+ } else if (action.tool === 'mcp.call_tool') {
172
+ const server = action.payload.server as string | undefined;
173
+ const method = action.payload.method as string | undefined;
174
+ target = server;
175
+ summary = `Call "${method ?? 'unknown'}" on MCP server "${server ?? 'unknown'}"`;
176
+ } else if (action.tool === 'demo') {
177
+ summary = `Write to demo table: ${JSON.stringify(redact(action.payload)).slice(0, 80)}`;
178
+ }
179
+
180
+ return {
181
+ summary,
182
+ target,
183
+ impact: action.action_type === 'write' ? 'Data will be modified' : undefined,
184
+ cost_estimate: action.cost_estimate,
185
+ };
186
+ }
package/src/redact.ts ADDED
@@ -0,0 +1,114 @@
1
+ // Exact-match field names (checked after lowercasing)
2
+ const SECRET_FIELDS = new Set([
3
+ 'authorization',
4
+ 'api_key',
5
+ 'apikey',
6
+ 'api-key',
7
+ 'token',
8
+ 'secret',
9
+ 'password',
10
+ 'passwd',
11
+ 'private_key',
12
+ 'privatekey',
13
+ 'access_token',
14
+ 'refresh_token',
15
+ 'client_secret',
16
+ 'x-api-key',
17
+ 'x-auth-token',
18
+ 'credentials',
19
+ 'bearer',
20
+ 'session_token',
21
+ 'session_key',
22
+ 'cookie',
23
+ 'set-cookie',
24
+ 'aws_secret_access_key',
25
+ 'aws_session_token',
26
+ 'database_url',
27
+ 'connection_string',
28
+ 'private-key',
29
+ 'master_key',
30
+ 'encryption_key',
31
+ 'signing_key',
32
+ 'service_role_key',
33
+ 'supabase_service_role_key',
34
+ ]);
35
+
36
+ // Substring patterns: if the lowercased key contains any of these, redact
37
+ const SECRET_SUBSTRINGS = [
38
+ 'secret',
39
+ 'password',
40
+ 'passwd',
41
+ 'token',
42
+ 'api_key',
43
+ 'apikey',
44
+ 'private_key',
45
+ 'privatekey',
46
+ 'credential',
47
+ 'authorization',
48
+ 'auth_key',
49
+ 'master_key',
50
+ 'encryption_key',
51
+ 'signing_key',
52
+ 'connection_string',
53
+ 'database_url',
54
+ 'access_key',
55
+ 'session_id',
56
+ ];
57
+
58
+ const REDACTED = '[REDACTED]';
59
+
60
+ // Value-based patterns to detect secrets regardless of field name
61
+ const SECRET_VALUE_PATTERNS = [
62
+ /^(sk|pk|rk)_(live|test)_[a-zA-Z0-9]{10,}$/, // Stripe keys
63
+ /^r[us]_[a-zA-Z0-9]{20,}$/, // Stripe restricted keys
64
+ /^ghp_[a-zA-Z0-9]{36}$/, // GitHub PATs
65
+ /^github_pat_[a-zA-Z0-9_]{20,}$/, // GitHub fine-grained PATs
66
+ /^gho_[a-zA-Z0-9]{36}$/, // GitHub OAuth tokens
67
+ /^AKIA[A-Z0-9]{16}$/, // AWS access key IDs
68
+ /^eyJ[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}/, // JWTs (eyJ prefix)
69
+ /^xox[bpras]-[a-zA-Z0-9-]{10,}$/, // Slack tokens
70
+ /^Bearer\s+[a-zA-Z0-9._\-]{20,}$/, // Bearer tokens
71
+ /^AIza[a-zA-Z0-9_-]{35}$/, // Google API keys
72
+ /^sk-[a-zA-Z0-9]{20,}$/, // OpenAI API keys
73
+ /^sk-ant-[a-zA-Z0-9_-]{20,}$/, // Anthropic API keys
74
+ /^SG\.[a-zA-Z0-9_-]{20,}$/, // SendGrid API keys
75
+ /^SK[a-f0-9]{32}$/, // Twilio API keys
76
+ ];
77
+
78
+ function isSecretField(key: string): boolean {
79
+ const lower = key.toLowerCase();
80
+ if (SECRET_FIELDS.has(lower)) return true;
81
+ return SECRET_SUBSTRINGS.some((sub) => lower.includes(sub));
82
+ }
83
+
84
+ function isSecretValue(value: string): boolean {
85
+ return SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value));
86
+ }
87
+
88
+ export function redact(obj: unknown, depth = 0): unknown {
89
+ if (depth > 10) return obj;
90
+ if (obj === null || obj === undefined) return obj;
91
+ if (typeof obj === 'string') return isSecretValue(obj) ? REDACTED : obj;
92
+ if (typeof obj !== 'object') return obj;
93
+ if (Array.isArray(obj)) return obj.map((item) => redact(item, depth + 1));
94
+
95
+ const result: Record<string, unknown> = {};
96
+ for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
97
+ result[key] = isSecretField(key) ? REDACTED : redact(value, depth + 1);
98
+ }
99
+ return result;
100
+ }
101
+
102
+ export function redactHeaders(headers: Record<string, string>): Record<string, string> {
103
+ const result: Record<string, string> = {};
104
+ for (const [key, value] of Object.entries(headers)) {
105
+ result[key] = isSecretField(key) ? REDACTED : value;
106
+ }
107
+ return result;
108
+ }
109
+
110
+ export function sanitizeActionRequest(
111
+ request: Record<string, unknown>
112
+ ): Record<string, unknown> {
113
+ return redact(request) as Record<string, unknown>;
114
+ }
package/src/schemas.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Max payload size: 64KB when serialized */
4
+ const MAX_PAYLOAD_SIZE = 65_536;
5
+
6
+ export const AgentActionRequestSchema = z.object({
7
+ action_type: z.enum(['read', 'write', 'financial', 'admin']),
8
+ tool: z.string().min(1).max(100).regex(/^[a-zA-Z0-9._\-:]+$/, 'Tool name must be alphanumeric with dots, dashes, underscores, or colons'),
9
+ payload: z.record(z.unknown()).refine(
10
+ (val) => JSON.stringify(val).length <= MAX_PAYLOAD_SIZE,
11
+ { message: `Payload exceeds maximum size of ${MAX_PAYLOAD_SIZE} bytes` }
12
+ ),
13
+ idempotency_key: z.string().max(128).optional(),
14
+ cost_estimate: z.number().optional(),
15
+ });
16
+
17
+ export const RegisterAgentSchema = z.object({
18
+ name: z.string().min(1).max(100),
19
+ environment: z.enum(['development', 'staging', 'production']).default('production'),
20
+ public_key: z.string().min(40),
21
+ allowed_tools: z.array(z.string()).default([]),
22
+ });
23
+
24
+ export const PolicyRulesSchema = z.object({
25
+ defaultMode: z.enum(['allow', 'require_approval', 'block']),
26
+ rules: z.array(
27
+ z.object({
28
+ action_type: z.enum(['read', 'write', 'financial', 'admin']).optional(),
29
+ tool: z.string().optional(),
30
+ domain: z.string().optional(),
31
+ decision: z.enum(['ALLOW', 'REQUIRE_APPROVAL', 'BLOCK']),
32
+ require_two_approvals: z.boolean().optional(),
33
+ })
34
+ ),
35
+ http: z
36
+ .object({
37
+ allowedDomains: z.array(z.string()),
38
+ allowedMethods: z.array(z.string()),
39
+ blockList: z.array(z.string()),
40
+ })
41
+ .optional(),
42
+ limits: z
43
+ .object({
44
+ maxCostPerAction: z.number().optional(),
45
+ maxActionsPerHour: z.number().optional(),
46
+ })
47
+ .optional(),
48
+ });
49
+
50
+ export const ApproveRequestSchema = z.object({
51
+ action: z.enum(['approve', 'deny']),
52
+ reason: z.string().max(1000).optional(),
53
+ });
package/src/signing.ts ADDED
@@ -0,0 +1,120 @@
1
+ import nacl from 'tweetnacl';
2
+ import { encodeBase64, decodeBase64, decodeUTF8 } from 'tweetnacl-util';
3
+
4
+ export interface SignedHeaders {
5
+ 'x-agent-id': string;
6
+ 'x-timestamp': string;
7
+ 'x-signature': string;
8
+ 'x-nonce'?: string;
9
+ }
10
+
11
+ export interface KeyPair {
12
+ publicKey: string;
13
+ privateKey: string;
14
+ }
15
+
16
+ export function generateKeypair(): KeyPair {
17
+ const pair = nacl.sign.keyPair();
18
+ return {
19
+ publicKey: encodeBase64(pair.publicKey),
20
+ privateKey: encodeBase64(pair.secretKey),
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Recursively stable-stringify: sorts object keys at every nesting level.
26
+ * This is critical for signature verification — every language SDK must produce
27
+ * the exact same canonical string for the same payload.
28
+ *
29
+ * Bug fixed: JSON.stringify(obj, replacerArray) only serializes keys named in the
30
+ * replacer at every nesting level, so nested objects would be serialized as {}.
31
+ */
32
+ function stableStringify(val: unknown): string | undefined {
33
+ if (val === undefined) return undefined;
34
+ if (val === null) return 'null';
35
+ if (typeof val === 'number') {
36
+ // NaN, Infinity, -Infinity serialize to null per JSON spec
37
+ if (!Number.isFinite(val)) return 'null';
38
+ return JSON.stringify(val);
39
+ }
40
+ if (typeof val === 'boolean' || typeof val === 'string') return JSON.stringify(val);
41
+ if (Array.isArray(val)) {
42
+ return `[${val.map((v) => stableStringify(v) ?? 'null').join(',')}]`;
43
+ }
44
+ if (typeof val === 'object') {
45
+ const sorted = Object.keys(val as object).sort();
46
+ const pairs: string[] = [];
47
+ for (const k of sorted) {
48
+ const v = stableStringify((val as Record<string, unknown>)[k]);
49
+ if (v !== undefined) {
50
+ pairs.push(`${JSON.stringify(k)}:${v}`);
51
+ }
52
+ }
53
+ return `{${pairs.join(',')}}`;
54
+ }
55
+ return JSON.stringify(val);
56
+ }
57
+
58
+ export function canonicalStringify(obj: Record<string, unknown>): string {
59
+ return stableStringify(obj) ?? '{}';
60
+ }
61
+
62
+ export function signRequest(
63
+ body: Record<string, unknown>,
64
+ agentId: string,
65
+ privateKeyBase64: string
66
+ ): SignedHeaders {
67
+ const timestamp = Date.now().toString();
68
+ const nonce = encodeBase64(nacl.randomBytes(16));
69
+ const canonical = canonicalStringify(body);
70
+ const message = decodeUTF8(`${canonical}:${timestamp}:${nonce}`);
71
+
72
+ const privateKey = decodeBase64(privateKeyBase64);
73
+ const signature = nacl.sign.detached(message, privateKey);
74
+
75
+ return {
76
+ 'x-agent-id': agentId,
77
+ 'x-timestamp': timestamp,
78
+ 'x-signature': encodeBase64(signature),
79
+ 'x-nonce': nonce,
80
+ };
81
+ }
82
+
83
+ export function verifyRequest(
84
+ body: Record<string, unknown>,
85
+ headers: {
86
+ 'x-agent-id'?: string;
87
+ 'x-timestamp'?: string;
88
+ 'x-signature'?: string;
89
+ 'x-nonce'?: string;
90
+ },
91
+ publicKeyBase64: string,
92
+ maxSkewMs = 5 * 60 * 1000
93
+ ): { agentId: string; nonce: string } {
94
+ const agentId = headers['x-agent-id'];
95
+ const timestamp = headers['x-timestamp'];
96
+ const signatureB64 = headers['x-signature'];
97
+ const nonce = headers['x-nonce'];
98
+
99
+ if (!agentId || !timestamp || !signatureB64 || !nonce) {
100
+ throw new Error('Missing required signature headers');
101
+ }
102
+
103
+ const ts = parseInt(timestamp, 10);
104
+ const now = Date.now();
105
+ if (Math.abs(now - ts) > maxSkewMs) {
106
+ throw new Error(`Timestamp skew too large: ${Math.abs(now - ts)}ms`);
107
+ }
108
+
109
+ const canonical = canonicalStringify(body);
110
+ const message = decodeUTF8(`${canonical}:${timestamp}:${nonce}`);
111
+ const signature = decodeBase64(signatureB64);
112
+ const publicKey = decodeBase64(publicKeyBase64);
113
+
114
+ const valid = nacl.sign.detached.verify(message, signature, publicKey);
115
+ if (!valid) {
116
+ throw new Error('Invalid signature');
117
+ }
118
+
119
+ return { agentId, nonce };
120
+ }
package/src/types.ts ADDED
@@ -0,0 +1,212 @@
1
+ export type WorkspaceRole = 'owner' | 'admin' | 'approver' | 'member';
2
+ export type AgentStatus = 'active' | 'revoked' | 'suspended';
3
+ export type AgentEnvironment = 'development' | 'staging' | 'production';
4
+ export type ApprovalStatus = 'PENDING' | 'NEEDS_SECOND_APPROVAL' | 'APPROVED' | 'DENIED' | 'EXPIRED' | 'CANCELLED';
5
+ export type ExecutionStatus = 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'UNDONE';
6
+ export type ActionType = 'read' | 'write' | 'financial' | 'admin';
7
+ export type PolicyDecision = 'ALLOW' | 'REQUIRE_APPROVAL' | 'BLOCK';
8
+ export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
9
+
10
+ export interface Workspace {
11
+ id: string;
12
+ name: string;
13
+ slug: string;
14
+ safe_mode: boolean;
15
+ safe_mode_enabled_at?: string;
16
+ safe_mode_enabled_by?: string;
17
+ timeline_enabled: boolean;
18
+ audit_log_enabled: boolean;
19
+ retention_days?: number | null;
20
+ created_at: string;
21
+ updated_at: string;
22
+ }
23
+
24
+ export interface WorkspaceMember {
25
+ id: string;
26
+ workspace_id: string;
27
+ user_id: string;
28
+ role: WorkspaceRole;
29
+ created_at: string;
30
+ }
31
+
32
+ export interface Agent {
33
+ id: string;
34
+ workspace_id: string;
35
+ name: string;
36
+ environment: AgentEnvironment;
37
+ public_key: string;
38
+ allowed_tools: string[];
39
+ status: AgentStatus;
40
+ created_by?: string;
41
+ created_at: string;
42
+ updated_at: string;
43
+ last_seen_at?: string;
44
+ }
45
+
46
+ export interface ApiCredential {
47
+ id: string;
48
+ workspace_id: string;
49
+ name: string;
50
+ connector_type: string;
51
+ last_four?: string;
52
+ created_by?: string;
53
+ created_at: string;
54
+ updated_at: string;
55
+ }
56
+
57
+ export interface Policy {
58
+ id: string;
59
+ workspace_id: string;
60
+ name: string;
61
+ is_default: boolean;
62
+ rules: PolicyRules;
63
+ created_at: string;
64
+ updated_at: string;
65
+ }
66
+
67
+ export interface PolicyRules {
68
+ defaultMode: 'allow' | 'require_approval' | 'block';
69
+ rules: PolicyRule[];
70
+ http?: {
71
+ allowedDomains: string[];
72
+ allowedMethods: string[];
73
+ blockList: string[];
74
+ };
75
+ limits?: {
76
+ maxCostPerAction?: number;
77
+ maxActionsPerHour?: number;
78
+ };
79
+ }
80
+
81
+ export interface PolicyRule {
82
+ action_type?: ActionType;
83
+ tool?: string;
84
+ domain?: string;
85
+ decision: PolicyDecision;
86
+ require_two_approvals?: boolean;
87
+ }
88
+
89
+ export interface PolicyEvaluationResult {
90
+ decision: PolicyDecision;
91
+ risk_level: RiskLevel;
92
+ reason: string;
93
+ matched_rule?: PolicyRule;
94
+ }
95
+
96
+ export interface ApprovalRequest {
97
+ id: string;
98
+ workspace_id: string;
99
+ agent_id: string;
100
+ status: ApprovalStatus;
101
+ action_type: ActionType;
102
+ tool: string;
103
+ preview: ActionPreview;
104
+ risk_level: RiskLevel;
105
+ policy_decision: string;
106
+ policy_reason?: string;
107
+ expires_at: string;
108
+ requires_two_approvals: boolean;
109
+ approved_by?: string;
110
+ denied_by?: string;
111
+ decided_at?: string;
112
+ second_approved_by?: string;
113
+ second_decided_at?: string;
114
+ request_hash: string;
115
+ request_body: Record<string, unknown>;
116
+ created_at: string;
117
+ updated_at: string;
118
+ }
119
+
120
+ export interface ActionPreview {
121
+ summary: string;
122
+ target?: string;
123
+ impact?: string;
124
+ cost_estimate?: number;
125
+ raw_action?: Record<string, unknown>;
126
+ }
127
+
128
+ export interface ActionExecution {
129
+ id: string;
130
+ workspace_id: string;
131
+ approval_request_id?: string;
132
+ agent_id: string;
133
+ connector: string;
134
+ action_type: ActionType;
135
+ status: ExecutionStatus;
136
+ sanitized_request: Record<string, unknown>;
137
+ sanitized_response?: Record<string, unknown>;
138
+ undo_supported: boolean;
139
+ error_message?: string;
140
+ executed_at?: string;
141
+ completed_at?: string;
142
+ undone_at?: string;
143
+ created_at: string;
144
+ updated_at: string;
145
+ }
146
+
147
+ export interface AuditEvent {
148
+ id: string;
149
+ workspace_id: string;
150
+ event_type: string;
151
+ actor_id?: string;
152
+ actor_type: 'user' | 'agent' | 'system';
153
+ agent_id?: string;
154
+ resource_type?: string;
155
+ resource_id?: string;
156
+ metadata: Record<string, unknown>;
157
+ created_at: string;
158
+ }
159
+
160
+ export type BrowserSessionStatus = 'active' | 'closed' | 'expired';
161
+
162
+ export type BrowserTool =
163
+ | 'browser.open'
164
+ | 'browser.click'
165
+ | 'browser.type'
166
+ | 'browser.fill_credentials'
167
+ | 'browser.navigate'
168
+ | 'browser.snapshot'
169
+ | 'browser.screenshot'
170
+ | 'browser.press_key'
171
+ | 'browser.select'
172
+ | 'browser.scroll'
173
+ | 'browser.close';
174
+
175
+ export interface BrowserSession {
176
+ id: string;
177
+ workspace_id: string;
178
+ agent_id: string;
179
+ approval_request_id: string;
180
+ status: BrowserSessionStatus;
181
+ allowed_domains: string[];
182
+ action_count: number;
183
+ created_at: string;
184
+ last_activity_at: string;
185
+ expires_at: string;
186
+ closed_at?: string;
187
+ }
188
+
189
+ export interface BrowserActionResult {
190
+ session_id: string;
191
+ snapshot: string;
192
+ page_url: string;
193
+ page_title: string;
194
+ action_performed: string;
195
+ screenshot?: string;
196
+ }
197
+
198
+ export interface AgentActionRequest {
199
+ action_type: ActionType;
200
+ tool: string;
201
+ payload: Record<string, unknown>;
202
+ idempotency_key?: string;
203
+ cost_estimate?: number;
204
+ }
205
+
206
+ export interface GatewayRequestResult {
207
+ request_id: string;
208
+ decision: PolicyDecision;
209
+ status: ApprovalStatus | 'ALLOWED' | 'BLOCKED';
210
+ message?: string;
211
+ expires_at?: string;
212
+ }