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

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 (89) 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/build-platform.json +1 -1
  7. package/dist/get-shared-service-url.ps1 +14 -14
  8. package/dist/shared/code-browser.cjs +159 -159
  9. package/dist/shared/file-changes.cjs +31 -31
  10. package/dist/shared/file-citations.cjs +117 -117
  11. package/dist/shared/file-mentions.cjs +92 -92
  12. package/dist/shared/message-delivery.cjs +115 -115
  13. package/dist/shared/p2p-file-channel.cjs +87 -87
  14. package/dist/shared/server-requests.cjs +311 -311
  15. package/dist/shared/timeline-sync.cjs +173 -173
  16. package/dist/skills/code-relax/SKILL.md +22 -22
  17. package/dist/src/bounded-process-log.mjs +63 -63
  18. package/dist/src/celsius-client.mjs +107 -107
  19. package/dist/src/dots-approval-timeline.mjs +99 -99
  20. package/dist/src/dots-avatar.mjs +42 -42
  21. package/dist/src/dots-chat-projection.mjs +50 -50
  22. package/dist/src/dots-chat-server.mjs +483 -483
  23. package/dist/src/dots-message-images.mjs +319 -319
  24. package/dist/src/dots-profile.mjs +77 -77
  25. package/dist/src/dots-room-activity.mjs +172 -172
  26. package/dist/src/dots-room-client.mjs +135 -135
  27. package/dist/src/dots-room-projection.mjs +94 -94
  28. package/dist/src/dots-schedules.mjs +66 -66
  29. package/dist/src/dots-summary-tasks.mjs +32 -32
  30. package/dist/src/dots-task-catalog.mjs +430 -430
  31. package/dist/src/file-transfer-range.mjs +22 -22
  32. package/dist/src/file-upload-session.mjs +130 -130
  33. package/dist/src/hosted-history-app-server.mjs +103 -103
  34. package/dist/src/hosted-history-cache.mjs +113 -113
  35. package/dist/src/lan-socket-server.mjs +266 -266
  36. package/dist/src/managed-app-server.mjs +373 -373
  37. package/dist/src/managed-endpoint.mjs +25 -25
  38. package/dist/src/message-upload-files.mjs +109 -109
  39. package/dist/src/onboarding.mjs +12 -12
  40. package/dist/src/platform/README.md +1 -1
  41. package/dist/src/platform/codex-protocol.mjs +46 -46
  42. package/dist/src/platform/desktop-project-store.mjs +208 -208
  43. package/dist/src/platform/linux/desktop-host.mjs +87 -87
  44. package/dist/src/platform/linux/process.mjs +53 -53
  45. package/dist/src/platform/linux/system.mjs +50 -50
  46. package/dist/src/platform/linux/user-environment.mjs +9 -9
  47. package/dist/src/platform/macos/codex-runtime.mjs +125 -125
  48. package/dist/src/platform/macos/desktop-host.mjs +86 -86
  49. package/dist/src/platform/macos/user-environment.mjs +9 -9
  50. package/dist/src/platform/native-package.mjs +58 -58
  51. package/dist/src/platform/windows/activate-desktop.ps1 +49 -49
  52. package/dist/src/platform/windows/codex-runtime.mjs +215 -215
  53. package/dist/src/platform/windows/credential-store.mjs +27 -27
  54. package/dist/src/platform/windows/credential-store.ps1 +49 -49
  55. package/dist/src/platform/windows/desktop-host.mjs +65 -65
  56. package/dist/src/platform/windows/desktop-info.ps1 +38 -38
  57. package/dist/src/platform/windows/desktop-project-registration.mjs +4 -4
  58. package/dist/src/platform/windows/desktop-shortcuts.ps1 +14 -14
  59. package/dist/src/platform/windows/dots-profile.mjs +49 -49
  60. package/dist/src/platform/windows/launch-worker.mjs +41 -41
  61. package/dist/src/platform/windows/project-locations.mjs +16 -16
  62. package/dist/src/platform/windows/service-host.mjs +35 -35
  63. package/dist/src/platform/windows/user-environment.mjs +52 -52
  64. package/dist/src/project-management.mjs +58 -58
  65. package/dist/src/runtime-discovery.mjs +47 -47
  66. package/dist/src/self-relay/account-cleanup.mjs +30 -30
  67. package/dist/src/self-relay/admin-page.mjs +80 -80
  68. package/dist/src/self-relay/admin-style.mjs +14 -14
  69. package/dist/src/self-relay/connector.mjs +439 -439
  70. package/dist/src/self-relay/demo.mjs +31 -31
  71. package/dist/src/self-relay/diagnostics.mjs +54 -54
  72. package/dist/src/self-relay/email-accounts.mjs +183 -183
  73. package/dist/src/self-relay/file-wire.mjs +13 -13
  74. package/dist/src/self-relay/p2p-probe.mjs +168 -168
  75. package/dist/src/self-relay/privacy-page.mjs +26 -26
  76. package/dist/src/self-relay/public-releases.mjs +67 -67
  77. package/dist/src/self-relay/rtc-file-bridge.mjs +41 -41
  78. package/dist/src/self-relay/usage-meter.mjs +141 -141
  79. package/dist/src/server.mjs +5 -4
  80. package/dist/src/service-doctor.mjs +148 -148
  81. package/dist/src/service-install.mjs +97 -97
  82. package/dist/src/service-lifecycle.mjs +139 -139
  83. package/dist/src/shared-app-server.mjs +666 -666
  84. package/dist/src/shared-catalog.mjs +132 -132
  85. package/dist/src/shared-thread-stream.mjs +165 -165
  86. package/dist/src/usage-reset.mjs +17 -17
  87. package/dist/src/workstation-downloads.mjs +120 -120
  88. package/package.json +4 -4
  89. 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
+ });