glad-web 1.0.22 → 1.0.23

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.
@@ -5,11 +5,38 @@ const crypto = require('crypto');
5
5
  const PTYManager = require('../session/pty-manager');
6
6
 
7
7
  const PERMISSION_MODES = new Set(['untrusted', 'on-request', 'never']);
8
+ const SANDBOX_MODES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
8
9
  const FALLBACK_EFFORTS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'ultra'];
9
10
 
10
11
  function normalizePermissionMode(value) {
11
- const mode = String(value || 'on-request');
12
- return PERMISSION_MODES.has(mode) ? mode : 'on-request';
12
+ const mode = String(value || 'default');
13
+ return PERMISSION_MODES.has(mode) ? mode : null;
14
+ }
15
+
16
+ function normalizeSandboxMode(value) {
17
+ const mode = String(value || 'default');
18
+ return SANDBOX_MODES.has(mode) ? mode : null;
19
+ }
20
+
21
+ function sandboxPolicyFor(mode, workingDir, workspaceOptions = {}) {
22
+ if (mode === 'danger-full-access') return { type: 'dangerFullAccess' };
23
+ if (mode === 'read-only') return { type: 'readOnly', networkAccess: false };
24
+ if (mode === 'workspace-write') {
25
+ const roots = Array.isArray(workspaceOptions.writable_roots) ? workspaceOptions.writable_roots : [];
26
+ return { type: 'workspaceWrite', writableRoots: [workingDir, ...roots.filter(root => root !== workingDir)],
27
+ networkAccess: Boolean(workspaceOptions.network_access),
28
+ excludeTmpdirEnvVar: Boolean(workspaceOptions.exclude_tmpdir_env_var),
29
+ excludeSlashTmp: Boolean(workspaceOptions.exclude_slash_tmp) };
30
+ }
31
+ return null;
32
+ }
33
+
34
+ function sandboxModeFromPolicy(policy) {
35
+ const type = typeof policy === 'string' ? policy : policy?.type;
36
+ if (type === 'dangerFullAccess' || type === 'danger-full-access') return 'danger-full-access';
37
+ if (type === 'readOnly' || type === 'read-only') return 'read-only';
38
+ if (type === 'workspaceWrite' || type === 'workspace-write') return 'workspace-write';
39
+ return null;
13
40
  }
14
41
 
15
42
  function safeJson(value) {
@@ -96,6 +123,12 @@ class CodexStructuredSession extends EventEmitter {
96
123
  this.currentTurnId = null;
97
124
  this.currentTurnStartedAt = null;
98
125
  this.permissionMode = normalizePermissionMode(options.permissionMode);
126
+ this.sandboxMode = normalizeSandboxMode(options.sandboxMode);
127
+ this.effectivePermissionMode = null;
128
+ this.effectiveSandboxMode = null;
129
+ this.configPermissionMode = null;
130
+ this.configSandboxMode = null;
131
+ this.configSandboxWorkspaceWrite = {};
99
132
  this.model = options.model || null;
100
133
  this.effort = options.effort || null;
101
134
  this.models = [];
@@ -135,7 +168,9 @@ class CodexStructuredSession extends EventEmitter {
135
168
  }
136
169
 
137
170
  getControlState() {
138
- return { permissionMode: this.permissionMode, model: this.model, effort: this.effort,
171
+ return { permissionMode: this.permissionMode || 'default', sandboxMode: this.sandboxMode || 'default',
172
+ effectivePermissionMode: this.effectivePermissionMode, effectiveSandboxMode: this.effectiveSandboxMode,
173
+ model: this.model, effort: this.effort,
139
174
  status: this.status, threadId: this.threadId, presentation: this.presentation,
140
175
  canAbort: this.presentation === 'structured' && this.status !== 'idle',
141
176
  canSwitchToTerminal: this.presentation === 'structured' && this.status === 'idle' && Boolean(this.threadId),
@@ -209,6 +244,7 @@ class CodexStructuredSession extends EventEmitter {
209
244
  lines.on('line', line => this.handleRpcLine(line));
210
245
  this.request('initialize', { clientInfo: { name: 'glad-web', title: 'Glad', version: '1.0' }, capabilities: { experimentalApi: true } })
211
246
  .then(async () => {
247
+ try { await this.refreshConfigDefaults(); } catch (error) { this.logger.debugInfo?.(`[codex-app-server] config/read failed: ${error.message}`); }
212
248
  try { await this.refreshModels(); } catch (error) { this.logger.debugInfo?.(`[codex-app-server] model/list failed: ${error.message}`); }
213
249
  resolve();
214
250
  }).catch(fail);
@@ -330,7 +366,8 @@ class CodexStructuredSession extends EventEmitter {
330
366
  const settings = params.threadSettings || {};
331
367
  this.model = settings.model || this.model;
332
368
  this.effort = settings.effort || this.effort;
333
- this.permissionMode = settings.approvalPolicy || this.permissionMode;
369
+ this.effectivePermissionMode = settings.approvalPolicy || this.effectivePermissionMode;
370
+ this.effectiveSandboxMode = sandboxModeFromPolicy(settings.sandboxPolicy) || this.effectiveSandboxMode;
334
371
  this.emitEvent({ type: 'state', state: this.getControlState() });
335
372
  return;
336
373
  }
@@ -405,6 +442,18 @@ class CodexStructuredSession extends EventEmitter {
405
442
  return models;
406
443
  }
407
444
 
445
+ async refreshConfigDefaults() {
446
+ const result = await this.request('config/read', { cwd: this.workingDir, includeLayers: false });
447
+ const config = result?.config || {};
448
+ this.configPermissionMode = config.approval_policy || null;
449
+ this.configSandboxMode = normalizeSandboxMode(config.sandbox_mode);
450
+ this.configSandboxWorkspaceWrite = config.sandbox_workspace_write || {};
451
+ if (!this.permissionMode) this.effectivePermissionMode = this.configPermissionMode;
452
+ if (!this.sandboxMode) this.effectiveSandboxMode = this.configSandboxMode;
453
+ this.emitEvent({ type: 'state', state: this.getControlState() });
454
+ return config;
455
+ }
456
+
408
457
  async listResumeThreads() {
409
458
  await this.ensureProcess();
410
459
  const result = await this.request('thread/list', {
@@ -427,11 +476,30 @@ class CodexStructuredSession extends EventEmitter {
427
476
 
428
477
  async updateSettings(settings = {}) {
429
478
  if (settings.permissionMode !== undefined) this.permissionMode = normalizePermissionMode(settings.permissionMode);
479
+ if (settings.sandboxMode !== undefined) this.sandboxMode = normalizeSandboxMode(settings.sandboxMode);
430
480
  if (settings.model !== undefined) this.model = settings.model || null;
431
481
  if (settings.effort !== undefined) this.effort = settings.effort || null;
482
+ const needsConfigDefaults = (settings.permissionMode !== undefined && !this.permissionMode)
483
+ || (settings.sandboxMode !== undefined && !this.sandboxMode);
484
+ if (needsConfigDefaults && this.presentation === 'structured') {
485
+ await this.ensureProcess();
486
+ await this.refreshConfigDefaults();
487
+ }
432
488
  if (this.threadId && this.presentation === 'structured') {
433
489
  await this.ensureProcess();
434
- await this.request('thread/settings/update', { threadId: this.threadId, approvalPolicy: this.permissionMode, model: this.model, effort: this.effort });
490
+ const params = { threadId: this.threadId };
491
+ if (settings.permissionMode !== undefined) {
492
+ const approvalPolicy = this.permissionMode || this.configPermissionMode;
493
+ if (approvalPolicy) params.approvalPolicy = approvalPolicy;
494
+ }
495
+ if (settings.sandboxMode !== undefined) {
496
+ const sandboxPolicy = sandboxPolicyFor(this.sandboxMode || this.configSandboxMode, this.workingDir,
497
+ this.sandboxMode ? {} : this.configSandboxWorkspaceWrite);
498
+ if (sandboxPolicy) params.sandboxPolicy = sandboxPolicy;
499
+ }
500
+ if (settings.model !== undefined) params.model = this.model;
501
+ if (settings.effort !== undefined) params.effort = this.effort;
502
+ if (Object.keys(params).length > 1) await this.request('thread/settings/update', params);
435
503
  }
436
504
  this.emitEvent({ type: 'state', state: this.getControlState() });
437
505
  return this.getControlState();
@@ -444,17 +512,24 @@ class CodexStructuredSession extends EventEmitter {
444
512
  this.append({ kind: 'user', text: prompt });
445
513
  await this.ensureProcess();
446
514
  if (!this.threadId) {
447
- const started = await this.request('thread/start', { model: this.model, cwd: this.workingDir, approvalPolicy: this.permissionMode, sandbox: 'workspace-write' });
515
+ const params = { model: this.model, cwd: this.workingDir };
516
+ if (this.permissionMode) params.approvalPolicy = this.permissionMode;
517
+ if (this.sandboxMode) params.sandbox = this.sandboxMode;
518
+ const started = await this.request('thread/start', params);
448
519
  this.threadId = started.thread?.id;
449
520
  this.model = started.model || this.model;
450
521
  this.effort = started.reasoningEffort || this.effort;
522
+ this.effectivePermissionMode = started.approvalPolicy || this.effectivePermissionMode;
523
+ this.effectiveSandboxMode = sandboxModeFromPolicy(started.sandbox) || this.effectiveSandboxMode;
451
524
  this.emitEvent({ type: 'state', state: this.getControlState() });
452
525
  }
453
526
  this.setStatus('running');
454
- const started = await this.request('turn/start', { threadId: this.threadId, input: [{ type: 'text', text: prompt }], cwd: this.workingDir,
455
- approvalPolicy: this.permissionMode,
456
- sandboxPolicy: { type: 'workspaceWrite', writableRoots: [this.workingDir], networkAccess: false, excludeTmpdirEnvVar: false, excludeSlashTmp: false },
457
- model: this.model, effort: this.effort, summary: 'auto' });
527
+ const params = { threadId: this.threadId, input: [{ type: 'text', text: prompt }], cwd: this.workingDir,
528
+ model: this.model, effort: this.effort, summary: 'auto' };
529
+ if (this.permissionMode) params.approvalPolicy = this.permissionMode;
530
+ const sandboxPolicy = sandboxPolicyFor(this.sandboxMode, this.workingDir);
531
+ if (sandboxPolicy) params.sandboxPolicy = sandboxPolicy;
532
+ const started = await this.request('turn/start', params);
458
533
  this.currentTurnId = started?.turn?.id || started?.turnId || this.currentTurnId;
459
534
  return true;
460
535
  }
@@ -521,9 +596,14 @@ class CodexStructuredSession extends EventEmitter {
521
596
  const target = String(threadId || this.threadId || '').trim();
522
597
  if (!target || this.presentation !== 'structured' || this.status !== 'idle') return false;
523
598
  await this.ensureProcess();
524
- const result = await this.request('thread/resume', { threadId: target, model: this.model, cwd: this.workingDir, approvalPolicy: this.permissionMode, sandbox: 'workspace-write' });
599
+ const params = { threadId: target, model: this.model, cwd: this.workingDir };
600
+ if (this.permissionMode) params.approvalPolicy = this.permissionMode;
601
+ if (this.sandboxMode) params.sandbox = this.sandboxMode;
602
+ const result = await this.request('thread/resume', params);
525
603
  this.threadId = result.thread?.id || target;
526
604
  this.model = result.model || this.model;
605
+ this.effectivePermissionMode = result.approvalPolicy || this.effectivePermissionMode;
606
+ this.effectiveSandboxMode = sandboxModeFromPolicy(result.sandbox) || this.effectiveSandboxMode;
527
607
  const history = await this.request('thread/read', { threadId: this.threadId, includeTurns: true });
528
608
  this.messages = [];
529
609
  this.completedPermissions = [];
@@ -117,8 +117,12 @@
117
117
  .codex-inline-permission .claude-permission-actions { margin-top: 8px; }
118
118
  @keyframes codex-spin { to { transform: rotate(360deg); } }
119
119
  #codex-control-panel { display: none; width: min(100%, var(--control-content-max)); margin: 0 auto; padding: 8px 14px 10px; border-bottom: 1px solid #222; background: #121212; box-sizing: border-box; }
120
- .codex-control-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 7px; align-items: center; }
121
- .codex-select { appearance: none; max-width: 116px; min-width: 0; height: 32px; padding: 0 8px; border: 1px solid rgba(255,255,255,.12); border-radius: 7px; background: rgba(255,255,255,.08); color: #f5f5f7; font-weight: 700; font-size: 12px; }
120
+ .codex-control-row { display: grid; grid-template-columns: repeat(5, minmax(0, 116px)); justify-content: center; gap: 7px; align-items: center; }
121
+ .codex-control-row > * { width: 100%; }
122
+ .codex-select-control { position: relative; display: block; min-width: 0; height: 32px; }
123
+ .codex-select-label { display: flex; width: 100%; height: 100%; align-items: center; justify-content: center; box-sizing: border-box; border: 1px solid rgba(255,255,255,.1); border-radius: 16px; background: rgba(255,255,255,.08); color: #f5f5f7; font-size: 11px; font-weight: 800; white-space: nowrap; }
124
+ .codex-select-control:focus-within .codex-select-label { background: rgba(255,255,255,.14); border-color: rgba(0,122,255,.45); }
125
+ .codex-select { appearance: none; position: absolute; inset: 0; width: 100%; min-width: 0; height: 100%; opacity: 0; cursor: pointer; }
122
126
  .codex-select option { background: #1c1c1e; color: #fff; }
123
127
  #codex-model-panel, #codex-resume-panel { display: none; margin-top: 8px; border: 1px solid rgba(255,255,255,.1); background: rgba(28,28,30,.99); border-radius: 8px; box-shadow: 0 16px 36px rgba(0,0,0,.34); overflow: hidden; }
124
128
  #codex-model-panel.active, #codex-resume-panel.active { display: block; }
@@ -128,7 +132,8 @@
128
132
  .codex-picker-column + .codex-picker-column { border-left: 1px solid rgba(255,255,255,.08); }
129
133
  .codex-picker-option { display: block; width: 100%; min-height: 38px; padding: 8px 10px; border: 0; border-bottom: 1px solid rgba(255,255,255,.06); background: transparent; color: #f5f5f7; text-align: left; font-size: 12px; font-weight: 700; overflow-wrap: anywhere; }
130
134
  .codex-picker-option.selected { background: rgba(0,122,255,.18); color: #fff; }
131
- #codex-state-bar { display: none; align-items: center; gap: 7px; min-height: 24px; margin-top: 8px; color: var(--text-dim); font-size: 11px; }
135
+ #codex-state-bar { display: flex; align-items: center; gap: 7px; min-height: 24px; margin-top: 8px; color: var(--text-dim); font-size: 11px; overflow-x: auto; scrollbar-width: none; white-space: nowrap; }
136
+ #codex-state-bar::-webkit-scrollbar { display: none; }
132
137
  #codex-resume-panel { max-height: min(280px, 38dvh); overflow-y: auto; }
133
138
  .claude-context-size-badge { position: sticky; top: 0; z-index: 2; width: max-content; max-width: 100%; margin: 0 0 8px auto; border: 1px solid rgba(255,255,255,0.1); background: rgba(28,28,30,0.94); border-radius: 999px; padding: 4px 9px; color: #d1d5db; font-size: 11px; font-weight: 800; box-shadow: 0 8px 18px rgba(0,0,0,0.24); }
134
139
  .claude-message { max-width: 92%; margin: 0 0 10px 0; padding: 10px 12px; border-radius: 8px; overflow-wrap: anywhere; line-height: 1.45; font-size: 14px; }
@@ -287,8 +292,10 @@
287
292
  #nav-bar > div:last-child { gap: 5px !important; }
288
293
  #nav-bar > div:last-child .icon-btn { min-width: 34px; min-height: 30px; padding: 5px 7px !important; }
289
294
  #input-row { padding-left: 10px; padding-right: 10px; }
290
- #codex-control-panel { padding-left: 10px; padding-right: 10px; }
291
- .codex-control-row { gap: 6px; }
295
+ #codex-control-panel { padding-left: 8px; padding-right: 8px; }
296
+ .codex-control-row { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 5px; }
297
+ .codex-control-row .claude-ctrl-btn { height: 36px; padding-left: 4px; padding-right: 4px; }
298
+ .codex-select-control { height: 36px; }
292
299
  #codex-chat-container { padding: 10px 10px calc(22px + env(safe-area-inset-bottom)); }
293
300
  .codex-message.user { max-width: 94%; }
294
301
  .claude-permission-actions { justify-content: flex-start; }
@@ -405,9 +412,18 @@
405
412
  </div>
406
413
  <div id="codex-control-panel">
407
414
  <div class="codex-control-row">
408
- <select id="codex-permission-select" class="codex-select" title="Permission mode" onchange="updateCodexSettingsFromControls()">
409
- <option value="untrusted">Untrusted</option><option value="on-request">On request</option><option value="never">Never ask</option>
410
- </select>
415
+ <label class="codex-select-control" title="Sandbox mode">
416
+ <span class="codex-select-label" aria-hidden="true">Sandbox</span>
417
+ <select id="codex-sandbox-select" class="codex-select" aria-label="Sandbox mode" onchange="updateCodexSettingsFromControls()">
418
+ <option value="default">Default</option><option value="read-only">Read only</option><option value="workspace-write">Workspace write</option><option value="danger-full-access">Full access</option>
419
+ </select>
420
+ </label>
421
+ <label class="codex-select-control" title="Approval policy">
422
+ <span class="codex-select-label" aria-hidden="true">Ask</span>
423
+ <select id="codex-permission-select" class="codex-select" aria-label="Approval policy" onchange="updateCodexSettingsFromControls()">
424
+ <option value="default">Default</option><option value="untrusted">Untrusted</option><option value="on-request">On request</option><option value="never">Never ask</option>
425
+ </select>
426
+ </label>
411
427
  <button id="codex-model-btn" class="claude-ctrl-btn" onclick="toggleCodexModelPanel()" title="Model and effort">Model</button>
412
428
  <button id="codex-abort-btn" class="claude-ctrl-btn danger" onclick="abortCodexSession()" title="Abort current Codex turn">Abort</button>
413
429
  <button id="codex-resume-btn" class="claude-ctrl-btn primary" onclick="toggleCodexResumePanel()" title="Resume a Codex thread">Resume</button>
@@ -607,7 +623,7 @@
607
623
  let claudeResumeItemsLoaded = false;
608
624
  let codexMessages = [];
609
625
  let codexPendingPermissions = [];
610
- let codexState = { permissionMode: 'on-request', model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canSwitchToTerminal: false, canSwitchToStructured: false };
626
+ let codexState = { permissionMode: 'default', sandboxMode: 'default', effectivePermissionMode: null, effectiveSandboxMode: null, model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canSwitchToTerminal: false, canSwitchToStructured: false };
611
627
  let codexModelPanelOpen = false;
612
628
  let codexModelCandidate = null;
613
629
  let codexResumePanelOpen = false;
@@ -2113,23 +2129,43 @@
2113
2129
  i += 1;
2114
2130
  }
2115
2131
  for (const request of codexPendingPermissions) if (!usedPermissions.has(request.id)) parts.push(renderCodexPermission(request));
2132
+ if (codexState.status === 'running') {
2133
+ parts.push('<div class="claude-status">Codex is working...</div>');
2134
+ }
2116
2135
  container.innerHTML = `<div class="codex-conversation">${parts.join('') || '<div class="codex-message event">Send a message to start Codex.</div>'}</div>`;
2117
2136
  requestAnimationFrame(() => { container.scrollTop = container.scrollHeight; });
2118
2137
  }
2119
2138
 
2120
2139
  function applyCodexState(state = {}) {
2140
+ const presentationChanged = state.presentation !== undefined && state.presentation !== codexState.presentation;
2121
2141
  codexState = { ...codexState, ...state };
2122
2142
  const permission = document.getElementById('codex-permission-select');
2123
- if (permission) permission.value = codexState.permissionMode || 'on-request';
2143
+ if (permission) {
2144
+ const defaultOption = permission.querySelector('option[value="default"]');
2145
+ const labels = { untrusted: 'Untrusted', 'on-request': 'On request', never: 'Never ask' };
2146
+ if (defaultOption) defaultOption.textContent = codexState.effectivePermissionMode
2147
+ ? `Default (${labels[codexState.effectivePermissionMode] || codexState.effectivePermissionMode})`
2148
+ : 'Default';
2149
+ permission.value = codexState.permissionMode || 'default';
2150
+ }
2151
+ const sandbox = document.getElementById('codex-sandbox-select');
2152
+ if (sandbox) {
2153
+ const defaultOption = sandbox.querySelector('option[value="default"]');
2154
+ const labels = { 'read-only': 'Read only', 'workspace-write': 'Workspace write', 'danger-full-access': 'Full access' };
2155
+ if (defaultOption) defaultOption.textContent = codexState.effectiveSandboxMode
2156
+ ? `Default (${labels[codexState.effectiveSandboxMode] || codexState.effectiveSandboxMode})`
2157
+ : 'Default';
2158
+ sandbox.value = codexState.sandboxMode || 'default';
2159
+ }
2124
2160
  const modelButton = document.getElementById('codex-model-btn');
2125
- if (modelButton) modelButton.textContent = codexState.model ? codexState.model.replace(/^gpt-/, '').slice(0, 10) : 'Model';
2161
+ if (modelButton) modelButton.textContent = 'Model';
2126
2162
  const abort = document.getElementById('codex-abort-btn');
2127
2163
  if (abort) abort.disabled = !codexState.canAbort;
2128
2164
  const terminal = document.getElementById('codex-terminal-switch');
2129
2165
  if (terminal) { terminal.textContent = codexState.presentation === 'terminal' ? 'CHAT' : 'TERM'; terminal.disabled = codexState.presentation === 'structured' && !codexState.canSwitchToTerminal; terminal.title = codexState.presentation === 'terminal' ? 'Return to Codex chat' : 'Switch to Codex terminal'; }
2130
2166
  renderCodexStateBar();
2131
2167
  renderCodexModelPanel();
2132
- setClaudeModeEnabled(isClaudeSession());
2168
+ if (presentationChanged) setClaudeModeEnabled(isClaudeSession());
2133
2169
  renderCodexChat();
2134
2170
  }
2135
2171
 
@@ -2185,7 +2221,12 @@
2185
2221
  }
2186
2222
 
2187
2223
  function sendCodexSettings(settings) { if (currentSocket?.readyState === 1) currentSocket.send(JSON.stringify({ type: 'codex-settings', settings })); }
2188
- function updateCodexSettingsFromControls() { const permissionMode = document.getElementById('codex-permission-select').value; applyCodexState({ permissionMode }); sendCodexSettings({ permissionMode }); }
2224
+ function updateCodexSettingsFromControls() {
2225
+ const permissionMode = document.getElementById('codex-permission-select').value;
2226
+ const sandboxMode = document.getElementById('codex-sandbox-select').value;
2227
+ applyCodexState({ permissionMode, sandboxMode });
2228
+ sendCodexSettings({ permissionMode, sandboxMode });
2229
+ }
2189
2230
  function abortCodexSession() { if (currentSocket?.readyState === 1) currentSocket.send(JSON.stringify({ type: 'codex-abort' })); }
2190
2231
  function respondCodexPermission(id, decision) { if (currentSocket?.readyState === 1) currentSocket.send(JSON.stringify({ type: 'codex-permission', id, decision })); }
2191
2232
  async function toggleCodexResumePanel() {
@@ -2345,7 +2386,7 @@
2345
2386
  codexResumePanelOpen = false;
2346
2387
  document.getElementById('codex-model-panel').classList.remove('active');
2347
2388
  document.getElementById('codex-resume-panel').classList.remove('active');
2348
- codexState = { permissionMode: 'on-request', model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canSwitchToTerminal: false, canSwitchToStructured: false };
2389
+ codexState = { permissionMode: 'default', sandboxMode: 'default', effectivePermissionMode: null, effectiveSandboxMode: null, model: null, effort: null, status: 'idle', threadId: null, presentation: 'structured', models: [], canAbort: false, canSwitchToTerminal: false, canSwitchToStructured: false };
2349
2390
  setClaudeModeEnabled(false);
2350
2391
  applyCodexState(codexState);
2351
2392
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glad-web",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "description": "Glad transforms terminal-based AI coding tools into a polished, mobile-friendly local Web interface.",
5
5
  "main": "index.js",
6
6
  "bin": {