ec-web 0.0.23 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -107,7 +107,7 @@ for (const list of Object.values(ifaces)) {
107
107
  }
108
108
  process.stdout.write(`\n${BOLD}${CYAN}EC Web — EvolCore${RST} ${DIM}(home: ${p.root})${RST}\n\n`);
109
109
  process.stdout.write(handle.pairingCode
110
- ? ` ${BOLD}配对码:${RST} ${GREEN}${BOLD}${handle.pairingCode}${RST} ${DIM}(daemon 提供,5 分钟内有效,配对后 token 缓存 30 天,有访问自动续期)${RST}\n\n`
110
+ ? ` ${BOLD}配对码:${RST} ${GREEN}${BOLD}${handle.pairingCode}${RST} ${DIM}(daemon 提供,5 分钟内有效,配对后 token 缓存 7 天,有访问自动续期)${RST}\n\n`
111
111
  : ` ${BOLD}配对码:${RST} ${YELLOW}daemon 暂不可用,请先启动 EvolCore${RST}\n\n`);
112
112
  process.stdout.write(` ${BOLD}本机:${RST} http://localhost:${handle.port}\n`);
113
113
  for (const ip of lanIps)
package/dist/server.js CHANGED
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * - HTTP: 静态资源 + 配对 API
5
5
  * - WebSocket: 订阅式实时推送(aid / msg / session)
6
- * - 鉴权: daemon 通过本地 IPC 提供 6 位配对码(5 分钟有效)和远程访问 token(30 天滑动续期)
7
- * - 安全: 绑定 0.0.0.0(支持远程访问),token 校验;远程连接可执行受控管理操作,不能修改 daemon 配置或读取 secret
6
+ * - 鉴权: daemon 通过本地 IPC 提供 6 位配对码(5 分钟有效)和远程访问 token(7 天滑动续期)
7
+ * - 安全: 绑定 0.0.0.0(支持远程访问);本地自动登录,远程配对后使用 token,登录后权限一致
8
8
  *
9
9
  * 与 evolcore daemon 通过 IPC socket(状态、Menu 和远程访问凭据)+ 文件系统(历史数据)旁路通信。
10
10
  */
@@ -437,7 +437,7 @@ function handlePair(req, res, log) {
437
437
  return;
438
438
  }
439
439
  res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({ ok: true, token: result.token }));
440
- log(`✓ 配对成功 from ${ip}(daemon token 缓存 30 天,有访问自动续期)`);
440
+ log(`✓ 配对成功 from ${ip}(daemon token 缓存 7 天,有访问自动续期)`);
441
441
  }).catch(error => {
442
442
  res.writeHead(503, { 'Content-Type': 'application/json' }).end(JSON.stringify({ ok: false, reason: 'daemon unavailable' }));
443
443
  log(`✗ 配对失败(daemon unavailable: ${error instanceof Error ? error.message : String(error)}) from ${ip}`);
@@ -790,22 +790,13 @@ export async function startWatchWebServer(opts = {}) {
790
790
  if (autoToken)
791
791
  token = autoToken;
792
792
  }
793
- if (!token || !(await validateRemoteToken(token))) {
793
+ const tokenRecord = token ? await validateRemoteToken(token) : null;
794
+ if (!token || !tokenRecord) {
794
795
  res.writeHead(401, { 'Content-Type': 'application/json' });
795
796
  res.end(JSON.stringify({ success: false, error: 'unauthorized' }));
796
797
  return;
797
798
  }
798
- if (!localDirect && pathname === '/api/basic-settings/secret') {
799
- res.writeHead(403, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
800
- res.end(JSON.stringify({ success: false, error: 'remote ECWeb connections cannot reveal API keys' }));
801
- return;
802
- }
803
- if (!localDirect && req.method !== 'GET') {
804
- res.writeHead(403, { 'Content-Type': 'application/json' });
805
- res.end(JSON.stringify({ success: false, error: 'remote ECWeb connections cannot modify daemon settings' }));
806
- return;
807
- }
808
- void handleBasicSettingsApi(req, res, { redactConnectionMaps: !localDirect });
799
+ void handleBasicSettingsApi(req, res, { redactConnectionMaps: !isLocalDirect(req), remoteAccess: !isLocalDirect(req) });
809
800
  }
810
801
  else if (req.method === 'GET' && (req.url || '').startsWith('/api/stats/')) {
811
802
  // Stats API — 本地直连自动发 token(免鉴权),远程需鉴权
@@ -767,6 +767,9 @@ export async function handleBasicSettingsApi(req, res, options = {}) {
767
767
  return;
768
768
  }
769
769
  if (req.method === 'GET' && pathname === '/api/basic-settings/secret') {
770
+ if (options.remoteAccess) {
771
+ throw Object.assign(new Error('Remote access cannot reveal configuration secrets'), { statusCode: 403 });
772
+ }
770
773
  const file = url.searchParams.get('file') || 'defaults';
771
774
  const fieldPath = url.searchParams.get('path') || '';
772
775
  if (!requestedSecretPath(file, fieldPath)) {
@@ -782,6 +785,9 @@ export async function handleBasicSettingsApi(req, res, options = {}) {
782
785
  return;
783
786
  }
784
787
  if (req.method === 'PATCH' && pathname === '/api/basic-settings') {
788
+ if (options.remoteAccess) {
789
+ throw Object.assign(new Error('Remote access cannot mutate basic settings'), { statusCode: 403 });
790
+ }
785
791
  const body = await readJsonBody(req);
786
792
  if (!isRecord(body) || (body.file !== 'daemon' && body.file !== 'defaults'))
787
793
  throw new Error('file must be daemon or defaults');
@@ -4711,8 +4711,17 @@ async function syncAgentCreateOptionFields(ctx, values) {
4711
4711
  setAgentSelectLoading(modelSelect, currentModel, currentModel ? `${currentModel} (${t('common.loadingSuffix')})` : t('common.loading'));
4712
4712
  setAgentSelectLoading(effortSelect, currentEffort, currentEffort ? `${currentEffort} (${t('common.loadingSuffix')})` : t('common.loading'));
4713
4713
 
4714
+ let catalog;
4714
4715
  try {
4715
- const models = await agentFetchCreateModelOptions(baseagent);
4716
+ // Load the catalog once for both selectors. The catalog endpoint is
4717
+ // connection-scoped and may perform a live gateway refresh, so issuing a
4718
+ // second identical request for effort metadata is both wasteful and can
4719
+ // produce mismatched model/effort options during a refresh.
4720
+ catalog = await fetchModelCatalogData(baseagent);
4721
+ const models = (catalog?.models || []).map(model => ({
4722
+ value: model.id,
4723
+ label: `${model.id}${model.verification === 'unverified' ? ' [unverified]' : ''}`,
4724
+ }));
4716
4725
  if (_agentCreate !== ctx || ctx.optionLoadToken !== token) return;
4717
4726
  modelSelect.innerHTML = agentSelectOptions(models, currentModel, t('common.defaultModel'));
4718
4727
  } catch (e) {
@@ -4724,8 +4733,15 @@ async function syncAgentCreateOptionFields(ctx, values) {
4724
4733
  if (_agentCreate === ctx && ctx.optionLoadToken === token) modelSelect.disabled = Boolean(ctx.saving);
4725
4734
  }
4726
4735
 
4727
- const selectedModel = modelSelect?.value || currentModel;
4728
- await syncAgentCreateEffortOptions(ctx, baseagent, selectedModel, currentEffort, token);
4736
+ // A freshly rendered select may not expose its browser-selected value until
4737
+ // the next layout tick (and lightweight embedders may not implement the DOM
4738
+ // value reflection at all). Use the first catalog model as the default so
4739
+ // its reasoning levels are available immediately.
4740
+ const selectedModel = modelSelect?.value || currentModel || catalog?.models?.[0]?.id || '';
4741
+ const efforts = catalog?.models?.find(entry => entry.id === selectedModel)?.reasoningLevels || [];
4742
+ if (_agentCreate !== ctx || ctx.optionLoadToken !== token) return;
4743
+ effortSelect.innerHTML = agentSelectOptions(efforts, currentEffort, t('common.default'));
4744
+ effortSelect.disabled = Boolean(ctx.saving);
4729
4745
  }
4730
4746
 
4731
4747
  async function syncAgentCreateEffortOptions(ctx, baseagent, model, currentEffort, token) {
@@ -49,7 +49,7 @@
49
49
 
50
50
  </nav>
51
51
  <span style="flex:1"></span>
52
- 2026-09-09 03:48
52
+ 2026-09-14 01:48
53
53
  <span id="conn-status" class="conn-status" data-i18n="status.connecting">连接中…</span>
54
54
  <button id="lang-btn" class="theme-btn" data-i18n-title="language.buttonTitle" title="切换语言">🌐</button>
55
55
  <button id="theme-btn" class="theme-btn" data-i18n-title="theme.buttonTitle" title="主题">🌗</button>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ec-web",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "Web console and monitoring dashboard for EvolCore",
5
5
  "type": "module",
6
6
  "bin": {