@walkhi/code-relax 0.1.0-beta.50 → 0.1.0-beta.59

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 (101) hide show
  1. package/README.md +105 -105
  2. package/dist/bin/cleanup-accounts.mjs +63 -63
  3. package/dist/bin/codex-remote.mjs +403 -403
  4. package/dist/bin/publish-android-release.mjs +63 -63
  5. package/dist/bin/release-upload-server.mjs +190 -190
  6. package/dist/bin/set-relay-admin-password.mjs +11 -0
  7. package/dist/build-platform.json +1 -1
  8. package/dist/get-shared-service-url.ps1 +14 -14
  9. package/dist/shared/code-browser.cjs +159 -159
  10. package/dist/shared/file-changes.cjs +31 -31
  11. package/dist/shared/file-citations.cjs +117 -117
  12. package/dist/shared/file-mentions.cjs +92 -92
  13. package/dist/shared/message-delivery.cjs +115 -115
  14. package/dist/shared/p2p-file-channel.cjs +87 -87
  15. package/dist/shared/release-version.cjs +31 -0
  16. package/dist/shared/server-requests.cjs +311 -311
  17. package/dist/shared/timeline-sync.cjs +173 -173
  18. package/dist/skills/code-relax/SKILL.md +22 -22
  19. package/dist/src/bounded-process-log.mjs +63 -63
  20. package/dist/src/celsius-client.mjs +107 -107
  21. package/dist/src/dots-approval-timeline.mjs +99 -99
  22. package/dist/src/dots-avatar.mjs +42 -42
  23. package/dist/src/dots-chat-projection.mjs +50 -50
  24. package/dist/src/dots-chat-server.mjs +483 -483
  25. package/dist/src/dots-message-images.mjs +319 -319
  26. package/dist/src/dots-profile.mjs +77 -77
  27. package/dist/src/dots-room-activity.mjs +172 -172
  28. package/dist/src/dots-room-client.mjs +137 -135
  29. package/dist/src/dots-room-projection.mjs +94 -94
  30. package/dist/src/dots-schedules.mjs +66 -66
  31. package/dist/src/dots-summary-tasks.mjs +32 -32
  32. package/dist/src/dots-task-catalog.mjs +430 -430
  33. package/dist/src/file-transfer-range.mjs +22 -22
  34. package/dist/src/file-upload-session.mjs +146 -130
  35. package/dist/src/hosted-history-app-server.mjs +103 -103
  36. package/dist/src/hosted-history-cache.mjs +113 -113
  37. package/dist/src/lan-socket-server.mjs +338 -266
  38. package/dist/src/managed-app-server.mjs +373 -373
  39. package/dist/src/managed-endpoint.mjs +25 -25
  40. package/dist/src/message-upload-files.mjs +109 -109
  41. package/dist/src/onboarding.mjs +12 -12
  42. package/dist/src/platform/README.md +1 -1
  43. package/dist/src/platform/codex-protocol.mjs +46 -46
  44. package/dist/src/platform/desktop-project-store.mjs +219 -208
  45. package/dist/src/platform/linux/desktop-host.mjs +87 -87
  46. package/dist/src/platform/linux/process.mjs +53 -53
  47. package/dist/src/platform/linux/system.mjs +50 -50
  48. package/dist/src/platform/linux/user-environment.mjs +9 -9
  49. package/dist/src/platform/macos/codex-runtime.mjs +125 -125
  50. package/dist/src/platform/macos/desktop-host.mjs +86 -86
  51. package/dist/src/platform/macos/user-environment.mjs +9 -9
  52. package/dist/src/platform/native-package.mjs +58 -58
  53. package/dist/src/platform/windows/activate-desktop.ps1 +49 -49
  54. package/dist/src/platform/windows/codex-runtime.mjs +215 -215
  55. package/dist/src/platform/windows/credential-store.mjs +27 -27
  56. package/dist/src/platform/windows/credential-store.ps1 +49 -49
  57. package/dist/src/platform/windows/desktop-host.mjs +65 -65
  58. package/dist/src/platform/windows/desktop-info.ps1 +38 -38
  59. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
  60. package/dist/src/platform/windows/desktop-shortcuts.ps1 +14 -14
  61. package/dist/src/platform/windows/dots-profile.mjs +49 -49
  62. package/dist/src/platform/windows/launch-worker.mjs +41 -41
  63. package/dist/src/platform/windows/project-locations.mjs +16 -16
  64. package/dist/src/platform/windows/service-host.mjs +35 -35
  65. package/dist/src/platform/windows/user-environment.mjs +52 -52
  66. package/dist/src/project-management.mjs +58 -58
  67. package/dist/src/runtime-discovery.mjs +47 -47
  68. package/dist/src/self-relay/account-cleanup.mjs +30 -30
  69. package/dist/src/self-relay/admin-auth.mjs +59 -0
  70. package/dist/src/self-relay/admin-login-page.mjs +7 -0
  71. package/dist/src/self-relay/admin-page.mjs +87 -80
  72. package/dist/src/self-relay/admin-server.mjs +49 -18
  73. package/dist/src/self-relay/admin-style.mjs +58 -14
  74. package/dist/src/self-relay/admin-workstations-page.mjs +95 -0
  75. package/dist/src/self-relay/admin-workstations.mjs +143 -0
  76. package/dist/src/self-relay/connection-reporting.mjs +38 -0
  77. package/dist/src/self-relay/connector.mjs +444 -439
  78. package/dist/src/self-relay/demo.mjs +31 -31
  79. package/dist/src/self-relay/diagnostics.mjs +54 -54
  80. package/dist/src/self-relay/email-accounts.mjs +183 -183
  81. package/dist/src/self-relay/file-wire.mjs +13 -13
  82. package/dist/src/self-relay/lifecycle.mjs +11 -0
  83. package/dist/src/self-relay/p2p-probe.mjs +168 -168
  84. package/dist/src/self-relay/phone-connections.mjs +55 -0
  85. package/dist/src/self-relay/privacy-page.mjs +26 -26
  86. package/dist/src/self-relay/public-releases.mjs +67 -67
  87. package/dist/src/self-relay/rtc-file-bridge.mjs +46 -41
  88. package/dist/src/self-relay/server.mjs +63 -3
  89. package/dist/src/self-relay/usage-meter.mjs +141 -141
  90. package/dist/src/server.mjs +23 -9
  91. package/dist/src/service-doctor.mjs +148 -148
  92. package/dist/src/service-install.mjs +97 -97
  93. package/dist/src/service-lifecycle.mjs +139 -139
  94. package/dist/src/service-update.mjs +6 -3
  95. package/dist/src/shared-app-server.mjs +683 -666
  96. package/dist/src/shared-catalog.mjs +132 -132
  97. package/dist/src/shared-thread-stream.mjs +165 -165
  98. package/dist/src/usage-reset.mjs +17 -17
  99. package/dist/src/workstation-downloads.mjs +120 -120
  100. package/package.json +4 -4
  101. package/tools/postinstall.mjs +186 -186
@@ -1,311 +1,311 @@
1
- (function (root, factory) {
2
- if (typeof module === 'object' && module.exports) module.exports = factory();
3
- else root.CodexServerRequests = factory();
4
- })(globalThis, function () {
5
- const command = 'item/commandExecution/requestApproval';
6
- const file = 'item/fileChange/requestApproval';
7
- const input = 'item/tool/requestUserInput';
8
- const elicitation = 'mcpServer/elicitation/request';
9
- const permissions = 'item/permissions/requestApproval';
10
- const equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
11
- const approvalId = request => 'approval:' + (request.params.turnId || '') + ':' + typeof request.id + ':' + request.id;
12
- function confirmation(request) {
13
- const p = request.params || {}, value = p._meta?.['openai/confirmation'], schema = p.requestedSchema;
14
- if (request.method !== elicitation || p.mode !== 'form' || value?.version !== 1
15
- || typeof value.field !== 'string' || schema?.type !== 'object'
16
- || Object.keys(schema.properties || {}).length !== 1 || schema.properties[value.field]?.type !== 'boolean'
17
- || schema.required?.length !== 1 || schema.required[0] !== value.field) return null;
18
- return value;
19
- }
20
- function confirmationResult(request, decision) {
21
- return { action: decision, content: decision === 'accept' ? { [confirmation(request).field]: true } : null, _meta: null };
22
- }
23
- function browserAccess(request) {
24
- const p = request.params || {}, meta = p._meta || {};
25
- if (request.method !== elicitation || p.mode !== 'form' || meta.tool_name !== 'access_browser_origin'
26
- || meta.codex_approval_kind !== 'mcp_tool_call' || meta.codex_request_type !== 'approval_request'
27
- || meta.codex_sensitive_action !== true || p.requestedSchema?.type !== 'object'
28
- || Object.keys(p.requestedSchema.properties || {}).length) return null;
29
- try {
30
- const origin = new URL(meta.origin);
31
- if (!['http:', 'https:'].includes(origin.protocol) || origin.origin !== meta.origin) return null;
32
- return { origin: meta.origin, persistent: meta.persist === 'always' };
33
- } catch { return null; }
34
- }
35
- function browserAction(request) {
36
- const p = request.params || {}, meta = p._meta || {};
37
- if (browserAccess(request) || request.method !== elicitation || p.mode !== 'form'
38
- || p.requestedSchema?.type !== 'object' || Object.keys(p.requestedSchema.properties || {}).length
39
- || p.requestedSchema.required?.length || typeof p.message !== 'string' || !p.message.trim()) return null;
40
- const tool = meta.codex_approval_kind === 'mcp_tool_call' && meta.connector_id === 'browser-use';
41
- if (!tool && !['browser', 'browser-use'].includes(p.serverName)) return null;
42
- if (meta.codex_approval_kind && meta.codex_approval_kind !== 'mcp_tool_call') return null;
43
- const modes = meta.persist == null ? [] : Array.isArray(meta.persist) ? meta.persist : [meta.persist];
44
- if (modes.some(mode => !['session', 'always'].includes(mode))) return null;
45
- if (!tool && !modes.length) return null;
46
- try {
47
- const origin = new URL(meta.origin);
48
- if (!['http:', 'https:'].includes(origin.protocol) || origin.origin !== meta.origin) return null;
49
- return { origin: meta.origin, session: modes.includes('session'), persistent: modes.includes('always') };
50
- } catch { return null; }
51
- }
52
- function browserActionResult(decision) {
53
- return { action: decision === 'cancel' ? 'cancel' : 'accept', content: decision === 'cancel' ? null : {},
54
- _meta: decision === 'allowAlways' ? { persist: 'always' }
55
- : decision === 'allowSession' ? { persist: 'session' } : null };
56
- }
57
- function computerAppAccess(request) {
58
- const p = request.params || {}, meta = p._meta || {};
59
- if (request.method !== elicitation || p.mode !== 'form' || meta.codex_approval_kind !== 'mcp_tool_call'
60
- || (meta.codex_request_type != null && meta.codex_request_type !== 'approval_request')
61
- || typeof meta.connector_id !== 'string' || p.requestedSchema?.type !== 'object'
62
- || Object.keys(p.requestedSchema.properties || {}).length || p.requestedSchema.required?.length) return null;
63
- const connector = meta.connector_id.trim().toLowerCase().split(/[^a-z0-9]+/g).filter(Boolean).join('-');
64
- if (connector !== 'computer-use' && !connector.startsWith('computer-use-')) return null;
65
- const modes = Array.isArray(meta.persist) ? meta.persist : [meta.persist];
66
- if (!modes.length || modes.some(mode => !['session', 'always'].includes(mode))) return null;
67
- const display = Array.isArray(meta.tool_params_display) ? meta.tool_params_display.find(entry => entry?.name === 'app')?.value : null;
68
- const app = typeof display === 'string' && display.trim() ? display : meta.tool_params?.app;
69
- if (typeof app !== 'string' || !app.trim()) return null;
70
- return { app: app.trim(), session: modes.includes('session'), persistent: modes.includes('always'),
71
- subtitle: typeof meta.subtitle === 'string' ? meta.subtitle : '', highRisk: meta.riskLevel === 'high' };
72
- }
73
- function computerAppResult(request, decision) {
74
- const app = computerAppAccess(request);
75
- return { action: decision === 'decline' ? 'decline' : 'accept', content: decision === 'decline' ? null : {},
76
- _meta: decision === 'allowAlways' ? { persist: 'always' }
77
- : decision === 'allowConversation' && app?.session ? { persist: 'session' } : null };
78
- }
79
- function mcpToolApproval(request) {
80
- const p = request.params || {}, meta = p._meta || {};
81
- if (request.method !== elicitation || p.mode !== 'form' || meta.codex_approval_kind !== 'mcp_tool_call'
82
- || (meta.codex_request_type != null && meta.codex_request_type !== 'approval_request')
83
- || p.requestedSchema?.type !== 'object' || Object.keys(p.requestedSchema.properties || {}).length
84
- || p.requestedSchema.required?.length || typeof p.message !== 'string' || !p.message.trim()
85
- || typeof meta.connector_id !== 'string' || !meta.connector_id.trim()
86
- || !meta.tool_params || typeof meta.tool_params !== 'object' || Array.isArray(meta.tool_params)) return null;
87
- const connector = meta.connector_id.trim().toLowerCase().split(/[^a-z0-9]+/g).filter(Boolean).join('-');
88
- // These requests have dedicated approval semantics in Desktop.
89
- if (connector === 'browser-use' || connector === 'computer-use' || connector.startsWith('computer-use-')
90
- || meta.tool_name === 'access_browser_origin' || meta.tool_name === 'access_browser_origin_with_raw_cdp'
91
- || meta.connector_id === 'connector_20205bf7d4e99a89d7154bb849718324') return null;
92
- const modes = meta.persist === undefined ? [] : Array.isArray(meta.persist) ? meta.persist : [meta.persist];
93
- if ((Array.isArray(meta.persist) && (!modes.length || modes.length > 2))
94
- || modes.some(mode => !['session', 'always'].includes(mode))) return null;
95
- return { session: modes.includes('session'), persistent: modes.includes('always') };
96
- }
97
- function mcpToolResult(decision) {
98
- return { action: decision === 'decline' ? 'decline' : 'accept', content: decision === 'decline' ? null : {},
99
- _meta: decision === 'allowAlways' ? { persist: 'always' }
100
- : decision === 'allowSession' ? { persist: 'session' } : null };
101
- }
102
- function externalAction(request) {
103
- const p = request.params || {};
104
- if (request.method !== elicitation || p.mode !== 'url') return null;
105
- try {
106
- const url = new URL(p.url);
107
- if (url.protocol !== 'https:' || url.username || url.password) return null;
108
- if (p.serverName !== 'codex_apps') return { url: url.href, connector: null };
109
- const connector = p._meta?._codex_apps?.connector_auth_failure;
110
- const trusted = host => ['chatgpt.com', 'chatgpt-staging.com'].some(domain => host === domain || host.endsWith('.' + domain));
111
- if (!trusted(url.hostname) || connector?.is_auth_failure !== true
112
- || typeof connector.connector_id !== 'string' || typeof connector.connector_name !== 'string'
113
- || typeof connector.install_url !== 'string' || connector.connection_consent != null) return null;
114
- return { url: url.href, connector };
115
- } catch { return null; }
116
- }
117
- function elicitationResult(action, content = null) { return { action, content: action === 'accept' ? content : null, _meta: null }; }
118
- function fieldOptions(field) {
119
- const schema = field.type === 'array' ? field.items : field;
120
- if (Array.isArray(schema?.enum)) return schema.enum.map((value, index) => ({ value, label: schema.enumNames?.[index] || value }));
121
- const options = schema?.oneOf || schema?.anyOf;
122
- return Array.isArray(options) ? options.map(option => ({ value: option.const, label: option.title || option.const })) : null;
123
- }
124
- function formSchema(request) {
125
- const p = request.params || {}, meta = p._meta || {}, schema = p.requestedSchema;
126
- // Specialized approvals, credentials and OpenAI forms have their own response contracts.
127
- if (request.method !== elicitation || p.mode !== 'form' || meta.codex_approval_kind
128
- || meta['openai/elicitation'] || meta['openai/confirmation'] || schema?.type !== 'object'
129
- || ['browser', 'browser-use'].includes(p.serverName)
130
- || !schema.properties || typeof schema.properties !== 'object' || Array.isArray(schema.properties)
131
- || Object.keys(schema).some(key => !['$schema', 'type', 'properties', 'required'].includes(key))) return null;
132
- const known = ['type', 'title', 'description', 'default', 'minLength', 'maxLength', 'format', 'minimum', 'maximum', 'enum', 'enumNames', 'oneOf', 'items', 'minItems', 'maxItems'];
133
- for (const field of Object.values(schema.properties)) {
134
- if (!field || Object.keys(field).some(key => !known.includes(key))
135
- || !['string', 'number', 'integer', 'boolean', 'array'].includes(field.type)) return null;
136
- const options = fieldOptions(field);
137
- if ((field.type === 'array' || field.enum != null || field.oneOf != null) && !options
138
- || options && (!options.length || options.some(option => typeof option.value !== 'string' || typeof option.label !== 'string'))) return null;
139
- if (field.format && !['email', 'uri', 'date', 'date-time'].includes(field.format)) return null;
140
- }
141
- if (schema.required != null && (!Array.isArray(schema.required) || schema.required.some(key => !Object.hasOwn(schema.properties, key)))) return null;
142
- return schema;
143
- }
144
- function validateForm(schema, values) {
145
- if (!values || typeof values !== 'object' || Array.isArray(values)
146
- || Object.keys(values).some(key => !Object.hasOwn(schema.properties, key))) throw new Error('表单内容无效');
147
- for (const [key, field] of Object.entries(schema.properties)) {
148
- const value = values[key], label = field.title || key;
149
- if (value === undefined) { if (schema.required?.includes(key)) throw new Error(`请填写${label}`); continue; }
150
- let valid = field.type === 'array' ? Array.isArray(value) : typeof value === (field.type === 'integer' ? 'number' : field.type);
151
- if (typeof value === 'number') valid &&= Number.isFinite(value) && (field.type !== 'integer' || Number.isInteger(value))
152
- && (field.minimum == null || value >= field.minimum) && (field.maximum == null || value <= field.maximum);
153
- if (typeof value === 'string') {
154
- valid &&= (field.minLength == null || [...value].length >= field.minLength) && (field.maxLength == null || [...value].length <= field.maxLength);
155
- if (field.format === 'email') valid &&= /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
156
- if (field.format === 'uri') { try { new URL(value); } catch { valid = false; } }
157
- if (field.format === 'date') valid &&= /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(value));
158
- if (field.format === 'date-time') valid &&= /^\d{4}-\d{2}-\d{2}T.+(?:Z|[+-]\d{2}:\d{2})$/.test(value) && !Number.isNaN(Date.parse(value));
159
- }
160
- const options = fieldOptions(field);
161
- if (Array.isArray(value)) valid &&= new Set(value).size === value.length && (field.minItems == null || value.length >= field.minItems)
162
- && (field.maxItems == null || value.length <= field.maxItems) && value.every(item => options?.some(option => option.value === item));
163
- else if (options) valid &&= options.some(option => option.value === value);
164
- if (!valid) throw new Error(`${label}的内容不符合请求要求`);
165
- }
166
- }
167
- function permissionResult(request, decision) {
168
- return { permissions: decision === 'decline' ? {} : request.params.permissions, scope: decision === 'acceptForSession' ? 'session' : 'turn' };
169
- }
170
- function blocksComposer(request) {
171
- if (externalAction(request)) return false;
172
- return request.method === input || Boolean(formSchema(request))
173
- || request.method !== elicitation && decisions(request).length > 0
174
- || request.method === elicitation && decisions(request).some(value => value !== 'cancel');
175
- }
176
- function decisions(request) {
177
- if (confirmation(request)) return ['decline', 'accept'];
178
- if (request.method === command) return request.params.availableDecisions ?? ['accept', 'acceptForSession', 'decline', 'cancel'];
179
- if (request.method === file) return ['accept', 'acceptForSession', 'decline', 'cancel'];
180
- if (request.method === permissions && request.params.permissions) return ['accept', 'acceptForSession', 'decline'];
181
- if (externalAction(request)) return ['decline', 'accept'];
182
- const action = browserAction(request);
183
- if (action) return ['allowOnce', ...(action.session ? ['allowSession'] : []), ...(action.persistent ? ['allowAlways'] : []), 'cancel'];
184
- const app = computerAppAccess(request);
185
- if (app) return ['decline', 'allowConversation', ...(app.persistent ? ['allowAlways'] : [])];
186
- const browser = browserAccess(request);
187
- if (browser) return browser.persistent ? ['allowOnce', 'allowSite', 'allowAllSites', 'decline'] : ['allowOnce', 'decline'];
188
- const tool = mcpToolApproval(request);
189
- if (tool) return ['decline', 'allowOnce', ...(tool.session ? ['allowSession'] : []), ...(tool.persistent ? ['allowAlways'] : [])];
190
- return request.method === elicitation ? ['cancel'] : [];
191
- }
192
- function validate(request, result) {
193
- if (request.method === elicitation && equal(result, elicitationResult('cancel'))) return;
194
- if (externalAction(request)) {
195
- if (!['accept', 'decline'].some(action => equal(result, elicitationResult(action)))) throw new Error('外部操作结果无效');
196
- return;
197
- }
198
- const schema = formSchema(request);
199
- if (schema) {
200
- if (equal(result, elicitationResult('decline'))) return;
201
- if (result?.action !== 'accept' || result._meta !== null || Object.keys(result).length !== 3) throw new Error('表单响应格式无效');
202
- validateForm(schema, result.content); return;
203
- }
204
- if (request.method === permissions) {
205
- if (!decisions(request).some(decision => equal(result, permissionResult(request, decision)))) throw new Error('权限决定不属于当前请求');
206
- return;
207
- }
208
- if (confirmation(request)) {
209
- if (!decisions(request).some(decision => equal(confirmationResult(request, decision), result))) throw new Error('确认结果不属于当前请求');
210
- return;
211
- }
212
- if ([command, file].includes(request.method)) {
213
- if (!result || Object.keys(result).length !== 1 || !decisions(request).some(value => equal(value, result.decision))) throw new Error('审批选项不属于当前请求');
214
- return;
215
- }
216
- if (request.method === input) {
217
- const questions = request.params.questions || [];
218
- if (!result?.answers || Object.keys(result).length !== 1 || Object.keys(result.answers).length !== questions.length
219
- || questions.some(q => !Array.isArray(result.answers[q.id]?.answers) || result.answers[q.id].answers.some(a => typeof a !== 'string'))) throw new Error('回答格式无效');
220
- return;
221
- }
222
- if (browserAction(request)) {
223
- if (!decisions(request).some(decision => equal(browserActionResult(decision), result))) throw new Error('浏览器操作决定不属于当前请求');
224
- return;
225
- }
226
- if (computerAppAccess(request)) {
227
- if (!decisions(request).some(decision => equal(computerAppResult(request, decision), result))) throw new Error('应用访问决定不属于当前请求');
228
- return;
229
- }
230
- const browser = browserAccess(request);
231
- if (browser) {
232
- const allowed = [
233
- { action: 'accept', content: {}, _meta: null },
234
- { action: 'decline', content: null, _meta: null },
235
- ];
236
- if (browser.persistent) allowed.push(
237
- { action: 'accept', content: {}, _meta: { persist: 'always' } },
238
- { action: 'accept', content: {}, _meta: { persist: 'always', browser_use_persistent_approval_scope: 'all-sites' } },
239
- );
240
- if (!allowed.some(choice => equal(choice, result))) throw new Error('网站访问决定不属于当前请求');
241
- return;
242
- }
243
- if (mcpToolApproval(request)) {
244
- if (!decisions(request).some(decision => equal(mcpToolResult(decision), result))) throw new Error('工具授权决定不属于当前请求');
245
- return;
246
- }
247
- throw new Error('此类请求需在工作站处理');
248
- }
249
- class Store {
250
- constructor(epoch, changed = () => {}) {
251
- this.epoch = epoch; this.changed = changed; this.requests = new Map(); this.approvalHistory = new Map();
252
- }
253
- list(threadId) { return [...this.requests.values()].filter(r => r.params.threadId === threadId).map(r => ({ ...r })); }
254
- history(threadId) { return [...this.approvalHistory.values()].filter(r => r.params.threadId === threadId).map(r => ({ ...r })); }
255
- complete(request, status) {
256
- const approval = this.approvalHistory.get(approvalId(request));
257
- if (approval) { approval.status = status; approval.completedAtMs = Date.now(); }
258
- }
259
- receive(message) {
260
- if (!['string', 'number'].includes(typeof message.id) || !message.params?.threadId || this.requests.has(message.id)) return;
261
- const request = { id: message.id, method: message.method, params: message.params, epoch: this.epoch, status: 'pending' };
262
- if (request.method !== input) this.approvalHistory.set(approvalId(request), {
263
- type: 'approval', id: approvalId(request), requestId: request.id, method: request.method,
264
- params: request.params, turnId: request.params.turnId, status: 'pending', receivedAtMs: Date.now(),
265
- });
266
- this.requests.set(message.id, request); this.changed(request.params.threadId);
267
- }
268
- notification(method, params) {
269
- if (method === 'serverRequest/resolved') {
270
- const request = this.requests.get(params.requestId);
271
- if (request?.params.threadId === params.threadId) {
272
- this.complete(request, 'resolved'); this.requests.delete(params.requestId); this.changed(params.threadId);
273
- }
274
- } else if (method === 'turn/completed' || method === 'thread/closed') {
275
- for (const [id, r] of this.requests) if (r.params.threadId === params.threadId && (method === 'thread/closed' || r.params.turnId === params.turn?.id)) {
276
- this.complete(r, 'expired'); this.requests.delete(id);
277
- }
278
- this.changed(params.threadId);
279
- }
280
- }
281
- forget(threadId) {
282
- for (const [id, request] of this.requests) if (request.params.threadId === threadId) this.requests.delete(id);
283
- for (const [id, approval] of this.approvalHistory) if (approval.params.threadId === threadId) this.approvalHistory.delete(id);
284
- this.changed(threadId);
285
- }
286
- reset(epoch) {
287
- const threads = new Set([...this.requests.values()].map(r => r.params.threadId));
288
- for (const request of this.requests.values()) this.complete(request, 'expired');
289
- this.requests.clear(); this.epoch = epoch; for (const id of threads) this.changed(id);
290
- this.approvalHistory.clear();
291
- }
292
- async respond(threadId, { id, epoch, result }, send) {
293
- const request = this.requests.get(id);
294
- if (!request || epoch !== this.epoch || request.params.threadId !== threadId) throw new Error('审批已处理或连接已变化,请等待同步');
295
- if (request.status !== 'pending') throw new Error('决定已提交,正在等待工作站确认,请勿重复提交');
296
- validate(request, result);
297
- const approval = this.approvalHistory.get(approvalId(request));
298
- if (approval) { approval.status = 'submitted'; approval.result = result; }
299
- request.status = 'submitted'; this.changed(threadId);
300
- try { await send({ id: request.id, result }); }
301
- catch (error) {
302
- if (this.requests.get(id) === request) {
303
- request.status = 'unknown'; if (approval) approval.status = 'unknown'; this.changed(threadId);
304
- }
305
- throw new Error('审批发送结果未确认,请检查工作站;不会自动重发。' + error.message);
306
- }
307
- return { submitted: true };
308
- }
309
- }
310
- return { Store, decisions, validate, command, file, input, elicitation, permissions, externalAction, elicitationResult, formSchema, fieldOptions, validateForm, permissionResult, blocksComposer, browserAccess, browserAction, browserActionResult, computerAppAccess, computerAppResult, mcpToolApproval, mcpToolResult, confirmation, confirmationResult };
311
- });
1
+ (function (root, factory) {
2
+ if (typeof module === 'object' && module.exports) module.exports = factory();
3
+ else root.CodexServerRequests = factory();
4
+ })(globalThis, function () {
5
+ const command = 'item/commandExecution/requestApproval';
6
+ const file = 'item/fileChange/requestApproval';
7
+ const input = 'item/tool/requestUserInput';
8
+ const elicitation = 'mcpServer/elicitation/request';
9
+ const permissions = 'item/permissions/requestApproval';
10
+ const equal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
11
+ const approvalId = request => 'approval:' + (request.params.turnId || '') + ':' + typeof request.id + ':' + request.id;
12
+ function confirmation(request) {
13
+ const p = request.params || {}, value = p._meta?.['openai/confirmation'], schema = p.requestedSchema;
14
+ if (request.method !== elicitation || p.mode !== 'form' || value?.version !== 1
15
+ || typeof value.field !== 'string' || schema?.type !== 'object'
16
+ || Object.keys(schema.properties || {}).length !== 1 || schema.properties[value.field]?.type !== 'boolean'
17
+ || schema.required?.length !== 1 || schema.required[0] !== value.field) return null;
18
+ return value;
19
+ }
20
+ function confirmationResult(request, decision) {
21
+ return { action: decision, content: decision === 'accept' ? { [confirmation(request).field]: true } : null, _meta: null };
22
+ }
23
+ function browserAccess(request) {
24
+ const p = request.params || {}, meta = p._meta || {};
25
+ if (request.method !== elicitation || p.mode !== 'form' || meta.tool_name !== 'access_browser_origin'
26
+ || meta.codex_approval_kind !== 'mcp_tool_call' || meta.codex_request_type !== 'approval_request'
27
+ || meta.codex_sensitive_action !== true || p.requestedSchema?.type !== 'object'
28
+ || Object.keys(p.requestedSchema.properties || {}).length) return null;
29
+ try {
30
+ const origin = new URL(meta.origin);
31
+ if (!['http:', 'https:'].includes(origin.protocol) || origin.origin !== meta.origin) return null;
32
+ return { origin: meta.origin, persistent: meta.persist === 'always' };
33
+ } catch { return null; }
34
+ }
35
+ function browserAction(request) {
36
+ const p = request.params || {}, meta = p._meta || {};
37
+ if (browserAccess(request) || request.method !== elicitation || p.mode !== 'form'
38
+ || p.requestedSchema?.type !== 'object' || Object.keys(p.requestedSchema.properties || {}).length
39
+ || p.requestedSchema.required?.length || typeof p.message !== 'string' || !p.message.trim()) return null;
40
+ const tool = meta.codex_approval_kind === 'mcp_tool_call' && meta.connector_id === 'browser-use';
41
+ if (!tool && !['browser', 'browser-use'].includes(p.serverName)) return null;
42
+ if (meta.codex_approval_kind && meta.codex_approval_kind !== 'mcp_tool_call') return null;
43
+ const modes = meta.persist == null ? [] : Array.isArray(meta.persist) ? meta.persist : [meta.persist];
44
+ if (modes.some(mode => !['session', 'always'].includes(mode))) return null;
45
+ if (!tool && !modes.length) return null;
46
+ try {
47
+ const origin = new URL(meta.origin);
48
+ if (!['http:', 'https:'].includes(origin.protocol) || origin.origin !== meta.origin) return null;
49
+ return { origin: meta.origin, session: modes.includes('session'), persistent: modes.includes('always') };
50
+ } catch { return null; }
51
+ }
52
+ function browserActionResult(decision) {
53
+ return { action: decision === 'cancel' ? 'cancel' : 'accept', content: decision === 'cancel' ? null : {},
54
+ _meta: decision === 'allowAlways' ? { persist: 'always' }
55
+ : decision === 'allowSession' ? { persist: 'session' } : null };
56
+ }
57
+ function computerAppAccess(request) {
58
+ const p = request.params || {}, meta = p._meta || {};
59
+ if (request.method !== elicitation || p.mode !== 'form' || meta.codex_approval_kind !== 'mcp_tool_call'
60
+ || (meta.codex_request_type != null && meta.codex_request_type !== 'approval_request')
61
+ || typeof meta.connector_id !== 'string' || p.requestedSchema?.type !== 'object'
62
+ || Object.keys(p.requestedSchema.properties || {}).length || p.requestedSchema.required?.length) return null;
63
+ const connector = meta.connector_id.trim().toLowerCase().split(/[^a-z0-9]+/g).filter(Boolean).join('-');
64
+ if (connector !== 'computer-use' && !connector.startsWith('computer-use-')) return null;
65
+ const modes = Array.isArray(meta.persist) ? meta.persist : [meta.persist];
66
+ if (!modes.length || modes.some(mode => !['session', 'always'].includes(mode))) return null;
67
+ const display = Array.isArray(meta.tool_params_display) ? meta.tool_params_display.find(entry => entry?.name === 'app')?.value : null;
68
+ const app = typeof display === 'string' && display.trim() ? display : meta.tool_params?.app;
69
+ if (typeof app !== 'string' || !app.trim()) return null;
70
+ return { app: app.trim(), session: modes.includes('session'), persistent: modes.includes('always'),
71
+ subtitle: typeof meta.subtitle === 'string' ? meta.subtitle : '', highRisk: meta.riskLevel === 'high' };
72
+ }
73
+ function computerAppResult(request, decision) {
74
+ const app = computerAppAccess(request);
75
+ return { action: decision === 'decline' ? 'decline' : 'accept', content: decision === 'decline' ? null : {},
76
+ _meta: decision === 'allowAlways' ? { persist: 'always' }
77
+ : decision === 'allowConversation' && app?.session ? { persist: 'session' } : null };
78
+ }
79
+ function mcpToolApproval(request) {
80
+ const p = request.params || {}, meta = p._meta || {};
81
+ if (request.method !== elicitation || p.mode !== 'form' || meta.codex_approval_kind !== 'mcp_tool_call'
82
+ || (meta.codex_request_type != null && meta.codex_request_type !== 'approval_request')
83
+ || p.requestedSchema?.type !== 'object' || Object.keys(p.requestedSchema.properties || {}).length
84
+ || p.requestedSchema.required?.length || typeof p.message !== 'string' || !p.message.trim()
85
+ || typeof meta.connector_id !== 'string' || !meta.connector_id.trim()
86
+ || !meta.tool_params || typeof meta.tool_params !== 'object' || Array.isArray(meta.tool_params)) return null;
87
+ const connector = meta.connector_id.trim().toLowerCase().split(/[^a-z0-9]+/g).filter(Boolean).join('-');
88
+ // These requests have dedicated approval semantics in Desktop.
89
+ if (connector === 'browser-use' || connector === 'computer-use' || connector.startsWith('computer-use-')
90
+ || meta.tool_name === 'access_browser_origin' || meta.tool_name === 'access_browser_origin_with_raw_cdp'
91
+ || meta.connector_id === 'connector_20205bf7d4e99a89d7154bb849718324') return null;
92
+ const modes = meta.persist === undefined ? [] : Array.isArray(meta.persist) ? meta.persist : [meta.persist];
93
+ if ((Array.isArray(meta.persist) && (!modes.length || modes.length > 2))
94
+ || modes.some(mode => !['session', 'always'].includes(mode))) return null;
95
+ return { session: modes.includes('session'), persistent: modes.includes('always') };
96
+ }
97
+ function mcpToolResult(decision) {
98
+ return { action: decision === 'decline' ? 'decline' : 'accept', content: decision === 'decline' ? null : {},
99
+ _meta: decision === 'allowAlways' ? { persist: 'always' }
100
+ : decision === 'allowSession' ? { persist: 'session' } : null };
101
+ }
102
+ function externalAction(request) {
103
+ const p = request.params || {};
104
+ if (request.method !== elicitation || p.mode !== 'url') return null;
105
+ try {
106
+ const url = new URL(p.url);
107
+ if (url.protocol !== 'https:' || url.username || url.password) return null;
108
+ if (p.serverName !== 'codex_apps') return { url: url.href, connector: null };
109
+ const connector = p._meta?._codex_apps?.connector_auth_failure;
110
+ const trusted = host => ['chatgpt.com', 'chatgpt-staging.com'].some(domain => host === domain || host.endsWith('.' + domain));
111
+ if (!trusted(url.hostname) || connector?.is_auth_failure !== true
112
+ || typeof connector.connector_id !== 'string' || typeof connector.connector_name !== 'string'
113
+ || typeof connector.install_url !== 'string' || connector.connection_consent != null) return null;
114
+ return { url: url.href, connector };
115
+ } catch { return null; }
116
+ }
117
+ function elicitationResult(action, content = null) { return { action, content: action === 'accept' ? content : null, _meta: null }; }
118
+ function fieldOptions(field) {
119
+ const schema = field.type === 'array' ? field.items : field;
120
+ if (Array.isArray(schema?.enum)) return schema.enum.map((value, index) => ({ value, label: schema.enumNames?.[index] || value }));
121
+ const options = schema?.oneOf || schema?.anyOf;
122
+ return Array.isArray(options) ? options.map(option => ({ value: option.const, label: option.title || option.const })) : null;
123
+ }
124
+ function formSchema(request) {
125
+ const p = request.params || {}, meta = p._meta || {}, schema = p.requestedSchema;
126
+ // Specialized approvals, credentials and OpenAI forms have their own response contracts.
127
+ if (request.method !== elicitation || p.mode !== 'form' || meta.codex_approval_kind
128
+ || meta['openai/elicitation'] || meta['openai/confirmation'] || schema?.type !== 'object'
129
+ || ['browser', 'browser-use'].includes(p.serverName)
130
+ || !schema.properties || typeof schema.properties !== 'object' || Array.isArray(schema.properties)
131
+ || Object.keys(schema).some(key => !['$schema', 'type', 'properties', 'required'].includes(key))) return null;
132
+ const known = ['type', 'title', 'description', 'default', 'minLength', 'maxLength', 'format', 'minimum', 'maximum', 'enum', 'enumNames', 'oneOf', 'items', 'minItems', 'maxItems'];
133
+ for (const field of Object.values(schema.properties)) {
134
+ if (!field || Object.keys(field).some(key => !known.includes(key))
135
+ || !['string', 'number', 'integer', 'boolean', 'array'].includes(field.type)) return null;
136
+ const options = fieldOptions(field);
137
+ if ((field.type === 'array' || field.enum != null || field.oneOf != null) && !options
138
+ || options && (!options.length || options.some(option => typeof option.value !== 'string' || typeof option.label !== 'string'))) return null;
139
+ if (field.format && !['email', 'uri', 'date', 'date-time'].includes(field.format)) return null;
140
+ }
141
+ if (schema.required != null && (!Array.isArray(schema.required) || schema.required.some(key => !Object.hasOwn(schema.properties, key)))) return null;
142
+ return schema;
143
+ }
144
+ function validateForm(schema, values) {
145
+ if (!values || typeof values !== 'object' || Array.isArray(values)
146
+ || Object.keys(values).some(key => !Object.hasOwn(schema.properties, key))) throw new Error('表单内容无效');
147
+ for (const [key, field] of Object.entries(schema.properties)) {
148
+ const value = values[key], label = field.title || key;
149
+ if (value === undefined) { if (schema.required?.includes(key)) throw new Error(`请填写${label}`); continue; }
150
+ let valid = field.type === 'array' ? Array.isArray(value) : typeof value === (field.type === 'integer' ? 'number' : field.type);
151
+ if (typeof value === 'number') valid &&= Number.isFinite(value) && (field.type !== 'integer' || Number.isInteger(value))
152
+ && (field.minimum == null || value >= field.minimum) && (field.maximum == null || value <= field.maximum);
153
+ if (typeof value === 'string') {
154
+ valid &&= (field.minLength == null || [...value].length >= field.minLength) && (field.maxLength == null || [...value].length <= field.maxLength);
155
+ if (field.format === 'email') valid &&= /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
156
+ if (field.format === 'uri') { try { new URL(value); } catch { valid = false; } }
157
+ if (field.format === 'date') valid &&= /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(value));
158
+ if (field.format === 'date-time') valid &&= /^\d{4}-\d{2}-\d{2}T.+(?:Z|[+-]\d{2}:\d{2})$/.test(value) && !Number.isNaN(Date.parse(value));
159
+ }
160
+ const options = fieldOptions(field);
161
+ if (Array.isArray(value)) valid &&= new Set(value).size === value.length && (field.minItems == null || value.length >= field.minItems)
162
+ && (field.maxItems == null || value.length <= field.maxItems) && value.every(item => options?.some(option => option.value === item));
163
+ else if (options) valid &&= options.some(option => option.value === value);
164
+ if (!valid) throw new Error(`${label}的内容不符合请求要求`);
165
+ }
166
+ }
167
+ function permissionResult(request, decision) {
168
+ return { permissions: decision === 'decline' ? {} : request.params.permissions, scope: decision === 'acceptForSession' ? 'session' : 'turn' };
169
+ }
170
+ function blocksComposer(request) {
171
+ if (externalAction(request)) return false;
172
+ return request.method === input || Boolean(formSchema(request))
173
+ || request.method !== elicitation && decisions(request).length > 0
174
+ || request.method === elicitation && decisions(request).some(value => value !== 'cancel');
175
+ }
176
+ function decisions(request) {
177
+ if (confirmation(request)) return ['decline', 'accept'];
178
+ if (request.method === command) return request.params.availableDecisions ?? ['accept', 'acceptForSession', 'decline', 'cancel'];
179
+ if (request.method === file) return ['accept', 'acceptForSession', 'decline', 'cancel'];
180
+ if (request.method === permissions && request.params.permissions) return ['accept', 'acceptForSession', 'decline'];
181
+ if (externalAction(request)) return ['decline', 'accept'];
182
+ const action = browserAction(request);
183
+ if (action) return ['allowOnce', ...(action.session ? ['allowSession'] : []), ...(action.persistent ? ['allowAlways'] : []), 'cancel'];
184
+ const app = computerAppAccess(request);
185
+ if (app) return ['decline', 'allowConversation', ...(app.persistent ? ['allowAlways'] : [])];
186
+ const browser = browserAccess(request);
187
+ if (browser) return browser.persistent ? ['allowOnce', 'allowSite', 'allowAllSites', 'decline'] : ['allowOnce', 'decline'];
188
+ const tool = mcpToolApproval(request);
189
+ if (tool) return ['decline', 'allowOnce', ...(tool.session ? ['allowSession'] : []), ...(tool.persistent ? ['allowAlways'] : [])];
190
+ return request.method === elicitation ? ['cancel'] : [];
191
+ }
192
+ function validate(request, result) {
193
+ if (request.method === elicitation && equal(result, elicitationResult('cancel'))) return;
194
+ if (externalAction(request)) {
195
+ if (!['accept', 'decline'].some(action => equal(result, elicitationResult(action)))) throw new Error('外部操作结果无效');
196
+ return;
197
+ }
198
+ const schema = formSchema(request);
199
+ if (schema) {
200
+ if (equal(result, elicitationResult('decline'))) return;
201
+ if (result?.action !== 'accept' || result._meta !== null || Object.keys(result).length !== 3) throw new Error('表单响应格式无效');
202
+ validateForm(schema, result.content); return;
203
+ }
204
+ if (request.method === permissions) {
205
+ if (!decisions(request).some(decision => equal(result, permissionResult(request, decision)))) throw new Error('权限决定不属于当前请求');
206
+ return;
207
+ }
208
+ if (confirmation(request)) {
209
+ if (!decisions(request).some(decision => equal(confirmationResult(request, decision), result))) throw new Error('确认结果不属于当前请求');
210
+ return;
211
+ }
212
+ if ([command, file].includes(request.method)) {
213
+ if (!result || Object.keys(result).length !== 1 || !decisions(request).some(value => equal(value, result.decision))) throw new Error('审批选项不属于当前请求');
214
+ return;
215
+ }
216
+ if (request.method === input) {
217
+ const questions = request.params.questions || [];
218
+ if (!result?.answers || Object.keys(result).length !== 1 || Object.keys(result.answers).length !== questions.length
219
+ || questions.some(q => !Array.isArray(result.answers[q.id]?.answers) || result.answers[q.id].answers.some(a => typeof a !== 'string'))) throw new Error('回答格式无效');
220
+ return;
221
+ }
222
+ if (browserAction(request)) {
223
+ if (!decisions(request).some(decision => equal(browserActionResult(decision), result))) throw new Error('浏览器操作决定不属于当前请求');
224
+ return;
225
+ }
226
+ if (computerAppAccess(request)) {
227
+ if (!decisions(request).some(decision => equal(computerAppResult(request, decision), result))) throw new Error('应用访问决定不属于当前请求');
228
+ return;
229
+ }
230
+ const browser = browserAccess(request);
231
+ if (browser) {
232
+ const allowed = [
233
+ { action: 'accept', content: {}, _meta: null },
234
+ { action: 'decline', content: null, _meta: null },
235
+ ];
236
+ if (browser.persistent) allowed.push(
237
+ { action: 'accept', content: {}, _meta: { persist: 'always' } },
238
+ { action: 'accept', content: {}, _meta: { persist: 'always', browser_use_persistent_approval_scope: 'all-sites' } },
239
+ );
240
+ if (!allowed.some(choice => equal(choice, result))) throw new Error('网站访问决定不属于当前请求');
241
+ return;
242
+ }
243
+ if (mcpToolApproval(request)) {
244
+ if (!decisions(request).some(decision => equal(mcpToolResult(decision), result))) throw new Error('工具授权决定不属于当前请求');
245
+ return;
246
+ }
247
+ throw new Error('此类请求需在工作站处理');
248
+ }
249
+ class Store {
250
+ constructor(epoch, changed = () => {}) {
251
+ this.epoch = epoch; this.changed = changed; this.requests = new Map(); this.approvalHistory = new Map();
252
+ }
253
+ list(threadId) { return [...this.requests.values()].filter(r => r.params.threadId === threadId).map(r => ({ ...r })); }
254
+ history(threadId) { return [...this.approvalHistory.values()].filter(r => r.params.threadId === threadId).map(r => ({ ...r })); }
255
+ complete(request, status) {
256
+ const approval = this.approvalHistory.get(approvalId(request));
257
+ if (approval) { approval.status = status; approval.completedAtMs = Date.now(); }
258
+ }
259
+ receive(message) {
260
+ if (!['string', 'number'].includes(typeof message.id) || !message.params?.threadId || this.requests.has(message.id)) return;
261
+ const request = { id: message.id, method: message.method, params: message.params, epoch: this.epoch, status: 'pending' };
262
+ if (request.method !== input) this.approvalHistory.set(approvalId(request), {
263
+ type: 'approval', id: approvalId(request), requestId: request.id, method: request.method,
264
+ params: request.params, turnId: request.params.turnId, status: 'pending', receivedAtMs: Date.now(),
265
+ });
266
+ this.requests.set(message.id, request); this.changed(request.params.threadId);
267
+ }
268
+ notification(method, params) {
269
+ if (method === 'serverRequest/resolved') {
270
+ const request = this.requests.get(params.requestId);
271
+ if (request?.params.threadId === params.threadId) {
272
+ this.complete(request, 'resolved'); this.requests.delete(params.requestId); this.changed(params.threadId);
273
+ }
274
+ } else if (method === 'turn/completed' || method === 'thread/closed') {
275
+ for (const [id, r] of this.requests) if (r.params.threadId === params.threadId && (method === 'thread/closed' || r.params.turnId === params.turn?.id)) {
276
+ this.complete(r, 'expired'); this.requests.delete(id);
277
+ }
278
+ this.changed(params.threadId);
279
+ }
280
+ }
281
+ forget(threadId) {
282
+ for (const [id, request] of this.requests) if (request.params.threadId === threadId) this.requests.delete(id);
283
+ for (const [id, approval] of this.approvalHistory) if (approval.params.threadId === threadId) this.approvalHistory.delete(id);
284
+ this.changed(threadId);
285
+ }
286
+ reset(epoch) {
287
+ const threads = new Set([...this.requests.values()].map(r => r.params.threadId));
288
+ for (const request of this.requests.values()) this.complete(request, 'expired');
289
+ this.requests.clear(); this.epoch = epoch; for (const id of threads) this.changed(id);
290
+ this.approvalHistory.clear();
291
+ }
292
+ async respond(threadId, { id, epoch, result }, send) {
293
+ const request = this.requests.get(id);
294
+ if (!request || epoch !== this.epoch || request.params.threadId !== threadId) throw new Error('审批已处理或连接已变化,请等待同步');
295
+ if (request.status !== 'pending') throw new Error('决定已提交,正在等待工作站确认,请勿重复提交');
296
+ validate(request, result);
297
+ const approval = this.approvalHistory.get(approvalId(request));
298
+ if (approval) { approval.status = 'submitted'; approval.result = result; }
299
+ request.status = 'submitted'; this.changed(threadId);
300
+ try { await send({ id: request.id, result }); }
301
+ catch (error) {
302
+ if (this.requests.get(id) === request) {
303
+ request.status = 'unknown'; if (approval) approval.status = 'unknown'; this.changed(threadId);
304
+ }
305
+ throw new Error('审批发送结果未确认,请检查工作站;不会自动重发。' + error.message);
306
+ }
307
+ return { submitted: true };
308
+ }
309
+ }
310
+ return { Store, decisions, validate, command, file, input, elicitation, permissions, externalAction, elicitationResult, formSchema, fieldOptions, validateForm, permissionResult, blocksComposer, browserAccess, browserAction, browserActionResult, computerAppAccess, computerAppResult, mcpToolApproval, mcpToolResult, confirmation, confirmationResult };
311
+ });