ec-web 0.0.22 → 0.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.
- package/dist/index.js +4 -2
- package/dist/ipc-client.js +5 -0
- package/dist/server.js +68 -148
- package/dist/sources/baseagent-detector.js +27 -102
- package/dist/sources/basic-settings.js +21 -4
- package/dist/sources/models.js +13 -6
- package/dist/sources/role-definitions.js +37 -12
- package/dist/static/app.js +81 -33
- package/dist/static/basic-settings.js +81 -14
- package/dist/static/index.html +1 -1
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
error: '',
|
|
12
12
|
openAgents: new Set(['claude']),
|
|
13
13
|
catalogs: Object.create(null),
|
|
14
|
+
catalogStatus: Object.create(null),
|
|
14
15
|
changes: { daemon: new Map(), defaults: new Map() },
|
|
15
16
|
mapDrafts: new Map(),
|
|
16
17
|
serviceMetadataDrafts: new Map(),
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
ecagent: 'EC Agent',
|
|
30
31
|
};
|
|
31
32
|
const baseagentMarks = { claude: 'C', codex: 'X', gemini: 'G', ecagent: 'E' };
|
|
32
|
-
const effortOptions = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
33
|
+
const effortOptions = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
33
34
|
|
|
34
35
|
const messages = {
|
|
35
36
|
'zh-CN': {
|
|
@@ -146,6 +147,9 @@
|
|
|
146
147
|
'basicSettings.queryParamsHelp': '每行一个 key = value,将附加到 Base Agent 请求 URL。',
|
|
147
148
|
'basicSettings.readonly': '只读',
|
|
148
149
|
'basicSettings.redetect': '重新检测',
|
|
150
|
+
'basicSettings.detectAdded': '检测到新的 Base Agent:{value}。是否加入默认配置?',
|
|
151
|
+
'basicSettings.detectRemoved': '检测到已移除的 Base Agent:{value}。是否从默认配置删除?',
|
|
152
|
+
'basicSettings.detectUpdated': 'Base Agent 配置已更新',
|
|
149
153
|
'basicSettings.reload': '重新读取',
|
|
150
154
|
'basicSettings.reloadConfirm': '重新读取会放弃未保存的修改,是否继续?',
|
|
151
155
|
'basicSettings.remove': '删除',
|
|
@@ -345,6 +349,9 @@
|
|
|
345
349
|
'basicSettings.queryParamsHelp': 'One key = value per line, appended to Base Agent request URLs.',
|
|
346
350
|
'basicSettings.readonly': 'Read only',
|
|
347
351
|
'basicSettings.redetect': 'Detect again',
|
|
352
|
+
'basicSettings.detectAdded': 'New Base Agent detected: {value}. Add it to the default configuration?',
|
|
353
|
+
'basicSettings.detectRemoved': 'Base Agent no longer detected: {value}. Remove it from the default configuration?',
|
|
354
|
+
'basicSettings.detectUpdated': 'Base Agent configuration updated',
|
|
348
355
|
'basicSettings.reload': 'Reload',
|
|
349
356
|
'basicSettings.reloadConfirm': 'Reloading discards unsaved changes. Continue?',
|
|
350
357
|
'basicSettings.remove': 'Remove',
|
|
@@ -578,6 +585,11 @@
|
|
|
578
585
|
}).join('');
|
|
579
586
|
}
|
|
580
587
|
|
|
588
|
+
function catalogEfforts(agent, model) {
|
|
589
|
+
const entries = state.catalogs[agent] || [];
|
|
590
|
+
return entries.find(entry => entry.id === model)?.reasoningLevels || [];
|
|
591
|
+
}
|
|
592
|
+
|
|
581
593
|
function modelOptions(agent, value) {
|
|
582
594
|
const models = state.catalogs[agent] || [];
|
|
583
595
|
const ids = models.map((item) => typeof item === 'string' ? item : item.id).filter(Boolean);
|
|
@@ -585,7 +597,11 @@
|
|
|
585
597
|
if (!ids.length) return value
|
|
586
598
|
? `<option selected value="${htmlEscape(value)}">${htmlEscape(value)}</option>`
|
|
587
599
|
: `<option value="" selected disabled>${htmlEscape(t('basicSettings.loading'))}</option>`;
|
|
588
|
-
return ids.map((id) =>
|
|
600
|
+
return ids.map((id) => {
|
|
601
|
+
const entry = models.find(item => item.id === id);
|
|
602
|
+
const suffix = !entry ? ' [current, not listed]' : entry.verification === 'unverified' ? ' [unverified]' : '';
|
|
603
|
+
return `<option value="${htmlEscape(id)}"${id === value ? ' selected' : ''}>${htmlEscape(id + suffix)}</option>`;
|
|
604
|
+
}).join('');
|
|
589
605
|
}
|
|
590
606
|
|
|
591
607
|
// Match the CLI watcher's shortLogName() so legacy rotated config values
|
|
@@ -659,7 +675,7 @@
|
|
|
659
675
|
|
|
660
676
|
function fallbackModelsMarkup(agent, config) {
|
|
661
677
|
const rows = Array.isArray(config?.fallbackModels) ? config.fallbackModels : [];
|
|
662
|
-
const body = rows.map((row, index) => `<div class="basic-settings-list-row" data-bs-fallback-row data-agent="${agent}" data-index="${index}"><select class="basic-settings-control" data-bs-fallback-model>${modelOptions(agent, row?.model || '')}</select><select class="basic-settings-control" data-bs-fallback-effort>${selectOptions(
|
|
678
|
+
const body = rows.map((row, index) => `<div class="basic-settings-list-row" data-bs-fallback-row data-agent="${agent}" data-index="${index}"><select class="basic-settings-control" data-bs-fallback-model>${modelOptions(agent, row?.model || '')}</select><select class="basic-settings-control" data-bs-fallback-effort>${selectOptions(['', ...catalogEfforts(agent, row?.model)], row?.effort || '', { '': 'Auto' })}</select><button type="button" class="basic-settings-remove" data-bs-remove-fallback aria-label="${htmlEscape(t('basicSettings.remove'))}">×</button></div>`).join('');
|
|
663
679
|
return `<div class="basic-settings-field full" data-bs-field data-path="baseagents.${agent}.fallbackModels" data-bs-fallback-list data-agent="${agent}">${fieldLabel(t('basicSettings.fallbackModels'), `baseagents.${agent}.fallbackModels[]`, t('basicSettings.fallbackHelp'), null)}<div class="basic-settings-list">${body}<button type="button" class="basic-settings-add" data-bs-add-fallback data-agent="${agent}">+ ${htmlEscape(t('basicSettings.addFallback'))}</button></div><div class="basic-settings-error"></div><div class="basic-settings-help">${htmlEscape(t('basicSettings.fallbackHelp'))}</div></div>`;
|
|
664
680
|
}
|
|
665
681
|
|
|
@@ -705,15 +721,17 @@
|
|
|
705
721
|
: t('basicSettings.configuredUndetected');
|
|
706
722
|
const common = (name) => `baseagents.${agent}.${name}`;
|
|
707
723
|
const model = scalarField({ file: 'defaults', path: common('model'), label: t('basicSettings.model'), type: 'model', value: config.model, optional: true });
|
|
708
|
-
const effort = agent === 'gemini' ? '' : scalarField({ file: 'defaults', path: common('effort'), label: t('basicSettings.effort'), type: 'select', options:
|
|
724
|
+
const effort = agent === 'gemini' ? '' : scalarField({ file: 'defaults', path: common('effort'), label: t('basicSettings.effort'), type: 'select', options: catalogEfforts(agent, config.model), value: config.effort });
|
|
709
725
|
const auxiliary = scalarField({ file: 'defaults', path: common('auxiliaryModel'), label: t('basicSettings.auxiliaryModel'), type: 'model', value: config.auxiliaryModel, help: t('basicSettings.auxiliaryHelp') });
|
|
710
|
-
const auxiliaryEffort = scalarField({ file: 'defaults', path: common('auxiliaryEffort'), label: t('basicSettings.auxiliaryEffort'), type: 'select', options:
|
|
726
|
+
const auxiliaryEffort = scalarField({ file: 'defaults', path: common('auxiliaryEffort'), label: t('basicSettings.auxiliaryEffort'), type: 'select', options: catalogEfforts(agent, config.auxiliaryModel), value: config.auxiliaryEffort });
|
|
711
727
|
const connection = scalarField({ file: 'defaults', path: common('apiKey'), label: t('basicSettings.apiKey'), value: config.apiKey, sensitive: true, placeholder: t('basicSettings.apiKeyPlaceholder') }) +
|
|
712
728
|
(agent === 'gemini' ? '' : scalarField({ file: 'defaults', path: common('baseUrl'), label: t('basicSettings.baseUrl'), value: config.baseUrl, placeholder: 'https://api.example.com' })) +
|
|
713
729
|
(agent === 'gemini' ? '' : textAreaField({ file: 'defaults', path: common('headers'), label: t('basicSettings.headers'), value: config.headers, placeholder: t('basicSettings.keyValuePlaceholder'), help: t('basicSettings.headersHelp'), full: false })) +
|
|
714
730
|
(['claude', 'codex', 'ecagent'].includes(agent) ? textAreaField({ file: 'defaults', path: common('queryParams'), label: t('basicSettings.queryParams'), value: config.queryParams, placeholder: t('basicSettings.keyValuePlaceholder'), help: t('basicSettings.queryParamsHelp'), full: false }) : '');
|
|
731
|
+
const catalogNote = state.catalogStatus[agent];
|
|
732
|
+
const catalogHint = catalogNote ? `${catalogNote.source} / ${catalogNote.freshness}${catalogNote.diagnostics?.length ? ': ' + catalogNote.diagnostics.map(d => d.message).join('; ') : ''}` : t('basicSettings.defaultFirst');
|
|
715
733
|
const body = section(t('basicSettings.connection'), t('basicSettings.secretRedacted'), formGrid(connection)) +
|
|
716
|
-
section(t('basicSettings.modelChain'),
|
|
734
|
+
section(t('basicSettings.modelChain'), catalogHint, formGrid(model + effort + fallbackModelsMarkup(agent, config) + auxiliary + auxiliaryEffort)) +
|
|
717
735
|
section(t('basicSettings.specific'), `${baseagentLabels[agent]} runner`, agentSpecificMarkup(agent, config));
|
|
718
736
|
return `<article class="basic-settings-agent${active ? ' active' : ''}${open ? ' open' : ''}${details.detected ? '' : ' unavailable'}" data-bs-agent="${agent}"><header class="basic-settings-agent-head" data-bs-agent-toggle><span class="basic-settings-agent-mark">${baseagentMarks[agent]}</span><span class="basic-settings-agent-title"><strong>${htmlEscape(baseagentLabels[agent])}</strong><small>${htmlEscape(details.detected ? t('basicSettings.detected') : t('basicSettings.notDetected'))} · ${htmlEscape(source)}</small></span><label class="basic-settings-agent-default"><input type="radio" name="basic-active-baseagent" value="${agent}" data-bs-active-baseagent${active ? ' checked' : ''}${details.detected ? '' : ' disabled'}>${htmlEscape(t('basicSettings.defaultBaseagent'))}</label><button type="button" class="basic-settings-chevron" data-bs-agent-toggle aria-expanded="${open ? 'true' : 'false'}" aria-label="${htmlEscape(t('basicSettings.expandCollapse'))}"></button></header><div class="basic-settings-agent-body">${body}</div></article>`;
|
|
719
737
|
}
|
|
@@ -1072,7 +1090,7 @@
|
|
|
1072
1090
|
if (force && state.changes.daemon.size + state.changes.defaults.size > 0 && !window.confirm(t('basicSettings.reloadConfirm'))) return;
|
|
1073
1091
|
state.loading = true; state.error = ''; render();
|
|
1074
1092
|
try {
|
|
1075
|
-
const response = await
|
|
1093
|
+
const response = await authFetch('api/basic-settings');
|
|
1076
1094
|
const payload = await response.json();
|
|
1077
1095
|
if (!response.ok || !payload.success) throw new Error(payload.error || t('basicSettings.loadFailed'));
|
|
1078
1096
|
state.snapshot = payload.data;
|
|
@@ -1095,9 +1113,12 @@
|
|
|
1095
1113
|
async function loadCatalogs(agents) {
|
|
1096
1114
|
await Promise.all(agents.map(async (item) => {
|
|
1097
1115
|
try {
|
|
1098
|
-
const response = await
|
|
1116
|
+
const response = await authFetch(`api/models/catalog?baseagent=${encodeURIComponent(item.id)}`);
|
|
1099
1117
|
const payload = await response.json();
|
|
1100
|
-
if (response.ok && payload.success)
|
|
1118
|
+
if (response.ok && payload.success) {
|
|
1119
|
+
state.catalogs[item.id] = payload.data.models || [];
|
|
1120
|
+
state.catalogStatus[item.id] = payload.data;
|
|
1121
|
+
}
|
|
1101
1122
|
} catch {}
|
|
1102
1123
|
}));
|
|
1103
1124
|
if (state.snapshot && !state.loading) render();
|
|
@@ -1105,15 +1126,61 @@
|
|
|
1105
1126
|
|
|
1106
1127
|
async function redetect() {
|
|
1107
1128
|
if (!state.snapshot || state.detecting) return;
|
|
1129
|
+
if (state.changes.daemon.size + state.changes.defaults.size > 0) {
|
|
1130
|
+
if (!window.confirm(t('basicSettings.reloadConfirm'))) return;
|
|
1131
|
+
state.changes.daemon.clear();
|
|
1132
|
+
state.changes.defaults.clear();
|
|
1133
|
+
state.mapDrafts.clear();
|
|
1134
|
+
state.serviceMetadataDrafts.clear();
|
|
1135
|
+
state.restartPaths = [];
|
|
1136
|
+
clearNotice();
|
|
1137
|
+
}
|
|
1108
1138
|
state.detecting = true;
|
|
1109
1139
|
state.detectionError = '';
|
|
1110
1140
|
render();
|
|
1111
1141
|
try {
|
|
1112
|
-
const response = await
|
|
1142
|
+
const response = await authFetch('api/basic-settings');
|
|
1113
1143
|
const payload = await response.json();
|
|
1114
1144
|
if (!response.ok || !payload.success) throw new Error(payload.error || t('basicSettings.loadFailed'));
|
|
1115
1145
|
const next = payload.data;
|
|
1116
|
-
|
|
1146
|
+
const previous = state.snapshot;
|
|
1147
|
+
const previousById = new Map((previous.baseagents || []).map((item) => [item.id, item]));
|
|
1148
|
+
const nextBaseagents = next.baseagents || [];
|
|
1149
|
+
const added = nextBaseagents.filter((item) => item.detected && !previousById.get(item.id)?.configured);
|
|
1150
|
+
const active = getFileValue('defaults', 'active_baseagent');
|
|
1151
|
+
const removed = (previous.baseagents || []).filter((item) => item.configured && !nextBaseagents.find((candidate) => candidate.id === item.id && candidate.detected));
|
|
1152
|
+
const changes = [];
|
|
1153
|
+
for (const item of added) {
|
|
1154
|
+
if (window.confirm(tf('basicSettings.detectAdded', { value: baseagentLabels[item.id] || item.id }))) {
|
|
1155
|
+
changes.push({ path: `baseagents.${item.id}`, op: 'set', value: {} });
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
for (const item of removed) {
|
|
1159
|
+
if (window.confirm(tf('basicSettings.detectRemoved', { value: baseagentLabels[item.id] || item.id }))) {
|
|
1160
|
+
// active_baseagent cannot be left pointing at a removed block. Switch
|
|
1161
|
+
// to the first still-detected agent in the same atomic PATCH.
|
|
1162
|
+
if (item.id === active) {
|
|
1163
|
+
const replacement = nextBaseagents.find((candidate) => candidate.detected && candidate.id !== item.id);
|
|
1164
|
+
if (!replacement) continue;
|
|
1165
|
+
changes.push({ path: 'active_baseagent', op: 'set', value: replacement.id });
|
|
1166
|
+
}
|
|
1167
|
+
changes.push({ path: `baseagents.${item.id}`, op: 'unset' });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (changes.length) {
|
|
1171
|
+
const response2 = await authFetch('api/basic-settings', {
|
|
1172
|
+
method: 'PATCH',
|
|
1173
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY) || ''}` },
|
|
1174
|
+
body: JSON.stringify({ file: 'defaults', revision: previous.revisions.defaults, changes }),
|
|
1175
|
+
});
|
|
1176
|
+
const saved = await response2.json();
|
|
1177
|
+
if (!response2.ok || !saved.success) throw new Error(saved.error || t('basicSettings.saveFailed'));
|
|
1178
|
+
state.snapshot = saved.data;
|
|
1179
|
+
state.notice = t('basicSettings.detectUpdated');
|
|
1180
|
+
state.noticeType = 'success';
|
|
1181
|
+
} else {
|
|
1182
|
+
state.snapshot.baseagents = nextBaseagents;
|
|
1183
|
+
}
|
|
1117
1184
|
state.snapshot.logTypes = next.logTypes || [];
|
|
1118
1185
|
state.snapshot.logTypeCounts = next.logTypeCounts || {};
|
|
1119
1186
|
const detected = state.snapshot.baseagents.filter((item) => item.detected);
|
|
@@ -1274,7 +1341,7 @@
|
|
|
1274
1341
|
updateBasicSettingsStatus();
|
|
1275
1342
|
return;
|
|
1276
1343
|
}
|
|
1277
|
-
rows.push({ model: state.catalogs[agent]?.[0]?.id || ''
|
|
1344
|
+
rows.push({ model: state.catalogs[agent]?.[0]?.id || '' });
|
|
1278
1345
|
recordChange('defaults', `baseagents.${agent}.fallbackModels`, 'set', rows);
|
|
1279
1346
|
render();
|
|
1280
1347
|
}
|
|
@@ -1374,7 +1441,7 @@
|
|
|
1374
1441
|
const restartPaths = [];
|
|
1375
1442
|
for (const file of files) {
|
|
1376
1443
|
const changes = [...state.changes[file]].map(([path, item]) => ({ path, ...item }));
|
|
1377
|
-
const response = await
|
|
1444
|
+
const response = await authFetch('api/basic-settings', {
|
|
1378
1445
|
method: 'PATCH',
|
|
1379
1446
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY) || ''}` },
|
|
1380
1447
|
body: JSON.stringify({ file, revision: state.snapshot.revisions[file], changes }),
|
|
@@ -1497,7 +1564,7 @@
|
|
|
1497
1564
|
return;
|
|
1498
1565
|
}
|
|
1499
1566
|
secretToggle.disabled = true;
|
|
1500
|
-
|
|
1567
|
+
authFetch(`api/basic-settings/secret?file=${encodeURIComponent(file)}&path=${encodeURIComponent(fieldPath)}`)
|
|
1501
1568
|
.then(async (response) => {
|
|
1502
1569
|
const payload = await readApiJson(response, t('basicSettings.revealSecretFailed'));
|
|
1503
1570
|
if (!response.ok || !payload.success || typeof payload.data?.value !== 'string') {
|
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-09 03: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>
|