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 +1 -1
- package/dist/server.js +6 -15
- package/dist/sources/basic-settings.js +6 -0
- package/dist/static/app.js +19 -3
- package/dist/static/index.html +1 -1
- package/package.json +1 -1
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 缓存
|
|
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(
|
|
7
|
-
* - 安全: 绑定 0.0.0.0
|
|
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 缓存
|
|
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
|
-
|
|
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
|
-
|
|
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');
|
package/dist/static/app.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
4728
|
-
|
|
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) {
|
package/dist/static/index.html
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
</nav>
|
|
51
51
|
<span style="flex:1"></span>
|
|
52
|
-
2026-09-
|
|
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>
|