cortico 0.1.0 → 0.1.2
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/README.md +7 -14
- package/package.json +1 -3
- package/src/boot.ts +18 -0
- package/src/bot.ts +41 -6
- package/src/core/README.md +4 -6
- package/src/core/blobs.ts +23 -2
- package/src/core/config-schema.ts +1 -1
- package/src/core/config.ts +2 -16
- package/src/core/core.ts +15 -5
- package/src/core/loop.ts +55 -27
- package/src/core/session.ts +3 -2
- package/src/core/timers.ts +8 -6
- package/src/core/types.ts +19 -3
- package/src/core/util.ts +17 -1
- package/src/deploy.ts +4 -1
- package/src/extensions/README.md +6 -4
- package/src/extensions/dry-mount.ts +23 -2
- package/src/extensions/manifest.ts +21 -11
- package/src/extensions.ts +147 -23
- package/src/launcher.ts +23 -9
- package/src/protocol/open-responses/context-log.ts +37 -9
- package/src/providers/README.md +32 -8
- package/src/providers/base.ts +3 -1
- package/src/providers/configuration.ts +2 -1
- package/src/providers/console/hub.ts +270 -0
- package/src/providers/console/settings.ts +8 -18
- package/src/providers/console/types.ts +5 -0
- package/src/providers/hub-api.ts +3 -0
- package/src/providers/llamacpp/config.ts +8 -6
- package/src/providers/llamacpp/console/runtime-panel.ts +5 -1
- package/src/providers/llamacpp/console/server.ts +3 -1
- package/src/providers/llamacpp/index.ts +3 -1
- package/src/providers/llamacpp/native.ts +10 -5
- package/src/providers/llamacpp/strings.ts +6 -6
- package/src/providers/name.ts +8 -0
- package/src/providers/openai-responses-compat/config.ts +13 -0
- package/src/providers/openai-responses-compat/console/client.ts +5 -0
- package/src/providers/openai-responses-compat/console/reasoning-panel.ts +84 -0
- package/src/providers/openai-responses-compat/console/server.ts +127 -0
- package/src/providers/openai-responses-compat/index.ts +38 -13
- package/src/providers/openai-responses-compat/native.ts +9 -4
- package/src/providers/openai-responses-compat/strings.ts +62 -1
- package/src/providers/registry.ts +5 -0
- package/src/providers/transport/responses-input.ts +59 -10
- package/src/web/README.md +11 -6
- package/src/web/auth.ts +80 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +5 -4
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +22 -8
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +6 -8
- package/src/web/client/console-pages/host.ts +24 -7
- package/src/web/client/core/api.ts +5 -1
- package/src/web/client/core/router.ts +15 -0
- package/src/web/client/features/core/strings.ts +2 -2
- package/src/web/client/features/extensions/index.ts +276 -41
- package/src/web/client/features/extensions/strings.ts +84 -10
- package/src/web/client/features/feature.ts +2 -2
- package/src/web/client/features/live/diagnostics.ts +32 -0
- package/src/web/client/features/live/index.ts +34 -11
- package/src/web/client/features/live/onboarding.ts +4 -1
- package/src/web/client/features/live/protocol.ts +1 -0
- package/src/web/client/features/live/strings.ts +20 -14
- package/src/web/client/features/live/timeline.ts +2 -1
- package/src/web/client/features/providers/detail.ts +262 -0
- package/src/web/client/features/providers/drafts.ts +23 -0
- package/src/web/client/features/providers/index.ts +173 -87
- package/src/web/client/features/providers/strings.ts +44 -17
- package/src/web/client/features/providers/types.ts +15 -0
- package/src/web/client/features/settings/general.ts +13 -0
- package/src/web/client/features/settings/index.ts +1 -0
- package/src/web/client/features/settings/strings.ts +6 -0
- package/src/web/client/features/worlds/index.ts +1 -0
- package/src/web/client/main.ts +4 -0
- package/src/web/client/shell/index.ts +28 -48
- package/src/web/client/shell/strings.ts +0 -22
- package/src/web/client/ui/icons.ts +14 -1
- package/src/web/client/ui/prompt-input.tsx +17 -5
- package/src/web/client/ui/strings.ts +0 -2
- package/src/web/console-pages.ts +1 -1
- package/src/web/diagnostics.ts +133 -0
- package/src/web/public/login.html +67 -0
- package/src/web/public/styles.css +143 -26
- package/src/web/server.ts +233 -19
- package/src/web/shared/client-panel.ts +4 -1
- package/src/web/shared/console-protocol.ts +4 -1
- package/src/worlds/bilibili/README.md +1 -1
- package/src/worlds/bilibili/overlay/server.ts +4 -2
- package/src/worlds/minecraft/ADAPT.md +66 -0
- package/src/worlds/minecraft/README.md +69 -11
- package/src/worlds/minecraft/cell-facts.ts +226 -0
- package/src/worlds/minecraft/chests.ts +5 -0
- package/src/worlds/minecraft/containers.ts +325 -0
- package/src/worlds/minecraft/entity-facts.ts +31 -5
- package/src/worlds/minecraft/executor.ts +283 -10348
- package/src/worlds/minecraft/inventory.ts +268 -0
- package/src/worlds/minecraft/melee.ts +419 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +55 -1
- package/src/worlds/minecraft/placed-ledger.ts +152 -0
- package/src/worlds/minecraft/placement.ts +1038 -0
- package/src/worlds/minecraft/receipt.ts +340 -0
- package/src/worlds/minecraft/skill-context.ts +358 -0
- package/src/worlds/minecraft/skills-build.ts +1169 -0
- package/src/worlds/minecraft/skills-container.ts +1343 -0
- package/src/worlds/minecraft/skills-craft.ts +333 -0
- package/src/worlds/minecraft/skills-dig.ts +624 -0
- package/src/worlds/minecraft/skills-gather.ts +1230 -0
- package/src/worlds/minecraft/skills-interact.ts +1559 -0
- package/src/worlds/minecraft/tools.ts +331 -0
- package/src/worlds/minecraft/travel.ts +763 -0
- package/src/worlds/minecraft/until.ts +75 -0
- package/src/worlds/qq/normalize.ts +14 -0
- package/src/worlds/qq/world.ts +53 -7
- package/src/worlds/terminal/world.ts +5 -3
package/src/web/auth.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 控制台访问密码。密码为空时不启用,所有请求照常放行。
|
|
3
|
+
* 登录令牌无状态:由密码与数据目录里的随机盐导出的 HMAC,进程重启后仍有效,改密码使它失效;
|
|
4
|
+
* 令牌自身不设到期,有效到改密码或退出登录为止。令牌放在 HttpOnly Cookie 里,
|
|
5
|
+
* WebSocket 升级与只能带 URL 的 GET 调用随之携带。
|
|
6
|
+
*/
|
|
7
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { dirname } from 'node:path';
|
|
10
|
+
|
|
11
|
+
export const SESSION_COOKIE = 'cortico_session';
|
|
12
|
+
export const AUTH_KEY_FILE = 'web-auth.key';
|
|
13
|
+
/** 盐的字节数;文件解码后不是这个长度即视同不存在,重新生成。 */
|
|
14
|
+
export const SALT_BYTES = 32;
|
|
15
|
+
/** Cookie 的 Max-Age:浏览器对 Cookie 寿命的上限是 400 天(RFC 6265bis),取它即一直保留。 */
|
|
16
|
+
export const SESSION_COOKIE_MAX_AGE_SEC = 400 * 24 * 60 * 60;
|
|
17
|
+
/**
|
|
18
|
+
* 每次失败的登录等这么久才回应,且失败串行处理:并发再多,猜测的总速率也不超过每秒一次。
|
|
19
|
+
* 八位随机字母数字的密码空间约 2.8e14,按此速率穷举以百万年计;不设延迟时局域网内每秒可试数千次。
|
|
20
|
+
* 正确的密码不进这条队列。
|
|
21
|
+
*/
|
|
22
|
+
export const FAILED_LOGIN_DELAY_MS = 1000;
|
|
23
|
+
|
|
24
|
+
function sameText(a: string, b: string): boolean {
|
|
25
|
+
const digest = (text: string): Buffer => createHash('sha256').update(text, 'utf8').digest();
|
|
26
|
+
return timingSafeEqual(digest(a), digest(b));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 取 Cookie 头里的一个值;没有或头不是字符串时为 null。 */
|
|
30
|
+
export function cookieValue(header: unknown, name: string): string | null {
|
|
31
|
+
if (typeof header !== 'string') return null;
|
|
32
|
+
for (const part of header.split(';')) {
|
|
33
|
+
const at = part.indexOf('=');
|
|
34
|
+
if (at > 0 && part.slice(0, at).trim() === name) return part.slice(at + 1).trim();
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class ConsoleAuth {
|
|
40
|
+
private token: string | null = null;
|
|
41
|
+
private failures: Promise<void> = Promise.resolve();
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
private readonly password: string,
|
|
45
|
+
private readonly keyFile: string,
|
|
46
|
+
private readonly failedDelayMs: number = FAILED_LOGIN_DELAY_MS,
|
|
47
|
+
) {}
|
|
48
|
+
|
|
49
|
+
get enabled(): boolean {
|
|
50
|
+
return this.password !== '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 这个密码在这个数据目录下的登录令牌;盐文件在第一次用到时生成,解码后不是 SALT_BYTES 字节时重新生成。 */
|
|
54
|
+
issue(): string {
|
|
55
|
+
if (this.token === null) {
|
|
56
|
+
let salt = existsSync(this.keyFile) ? Buffer.from(readFileSync(this.keyFile, 'utf8').trim(), 'hex') : Buffer.alloc(0);
|
|
57
|
+
if (salt.length !== SALT_BYTES) {
|
|
58
|
+
salt = randomBytes(SALT_BYTES);
|
|
59
|
+
mkdirSync(dirname(this.keyFile), { recursive: true });
|
|
60
|
+
writeFileSync(this.keyFile, `${salt.toString('hex')}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
61
|
+
}
|
|
62
|
+
const key = createHmac('sha256', salt).update(this.password, 'utf8').digest();
|
|
63
|
+
this.token = createHmac('sha256', key).update(SESSION_COOKIE, 'utf8').digest('hex');
|
|
64
|
+
}
|
|
65
|
+
return this.token;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
verify(token: string | null): boolean {
|
|
69
|
+
return token !== null && token !== '' && sameText(token, this.issue());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 密码正确立即返回 true;错误的排队等 failedDelayMs 后返回 false。 */
|
|
73
|
+
async login(candidate: string): Promise<boolean> {
|
|
74
|
+
if (sameText(candidate, this.password)) return true;
|
|
75
|
+
const turn = this.failures.then(() => new Promise<void>((resolve) => setTimeout(resolve, this.failedDelayMs)));
|
|
76
|
+
this.failures = turn;
|
|
77
|
+
await turn;
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -61,7 +61,7 @@ interface ModelEntry {
|
|
|
61
61
|
id: string;
|
|
62
62
|
contextWindow?: number;
|
|
63
63
|
}
|
|
64
|
-
interface ProbeResult {
|
|
64
|
+
export interface ProbeResult {
|
|
65
65
|
ok: boolean;
|
|
66
66
|
status: number | null;
|
|
67
67
|
elapsedMs: number;
|
|
@@ -80,7 +80,7 @@ interface ProbeResult {
|
|
|
80
80
|
|
|
81
81
|
const DEFAULT_ENDPOINT_PATH = '/responses';
|
|
82
82
|
|
|
83
|
-
/**
|
|
83
|
+
/** 模块自有段落挂在这个插槽里,排在连接与模型档之间。 */
|
|
84
84
|
const MODULE_SLOT = 'instance';
|
|
85
85
|
|
|
86
86
|
/** 开放模式的 effort 格 ↔ spec 的 thinking/reasoningEffort。 */
|
|
@@ -106,7 +106,8 @@ function datalist(ui: ConsoleUi, id: string, values: readonly string[]): HTMLDat
|
|
|
106
106
|
return list;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
/** 探活结果的呈现,模块页与连接卡片共用。 */
|
|
110
|
+
export function probeCard(ui: ConsoleUi, S: typeof panel.zh, result: ProbeResult): HTMLElement {
|
|
110
111
|
const fmt: ConsoleFormat = ui.fmt;
|
|
111
112
|
const usage = result.usage;
|
|
112
113
|
const rows = [
|
|
@@ -159,7 +160,7 @@ export const llmSettingsPanel: ConsolePanel = {
|
|
|
159
160
|
let selected = '';
|
|
160
161
|
/** 模块段落的句柄,重画前先结束上一批。 */
|
|
161
162
|
let sections: Disposable | null = null;
|
|
162
|
-
/**
|
|
163
|
+
/** 重画代号:慢一步回来的那次 load 不再往 DOM 上贴。 */
|
|
163
164
|
let generation = 0;
|
|
164
165
|
const report = ui.msgline();
|
|
165
166
|
const body = ui.h('div');
|
|
@@ -70,6 +70,7 @@ export function pricingEditor(
|
|
|
70
70
|
quotes: ModelQuote[],
|
|
71
71
|
commit: (pricing: unknown[]) => void,
|
|
72
72
|
language?: Language,
|
|
73
|
+
draft?: { raw?: string; onRaw(value: string): void },
|
|
73
74
|
) {
|
|
74
75
|
const S = language === 'en' ? panel.en : panel.zh;
|
|
75
76
|
const card = ui.sheet({
|
|
@@ -78,7 +79,6 @@ export function pricingEditor(
|
|
|
78
79
|
});
|
|
79
80
|
const labels: Record<string, string> = S.meters;
|
|
80
81
|
for (const row of quotes) {
|
|
81
|
-
if (!row.quotes.length) card.body.append(ui.msgline(S.quoteUnknown(row.model)));
|
|
82
82
|
for (const quote of row.quotes) {
|
|
83
83
|
card.body.append(
|
|
84
84
|
ui.kv([
|
|
@@ -101,11 +101,16 @@ export function pricingEditor(
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
const unset = saved.length === 0;
|
|
104
|
-
|
|
104
|
+
const pricingNote = ui.h('p', 'field-note pricing-note');
|
|
105
|
+
const updateNote = (pricing: unknown[]) => {
|
|
106
|
+
pricingNote.textContent = pricing.length ? '' : S.pricingUnset;
|
|
107
|
+
pricingNote.hidden = pricing.length > 0;
|
|
108
|
+
};
|
|
109
|
+
updateNote(saved); card.body.append(pricingNote);
|
|
105
110
|
const simple: SimpleCost | null = unset ? null : readSimple(saved);
|
|
106
111
|
const raw = ui.textarea({
|
|
107
112
|
rows: 10,
|
|
108
|
-
value: JSON.stringify(simple ? writeSimple(simple) : saved, null, 2),
|
|
113
|
+
value: draft?.raw ?? JSON.stringify(simple ? writeSimple(simple) : saved, null, 2),
|
|
109
114
|
onChange: commitDraft,
|
|
110
115
|
});
|
|
111
116
|
const currency = ui.input({ value: simple?.currency ?? 'USD', onChange: writeThrough });
|
|
@@ -139,15 +144,23 @@ export function pricingEditor(
|
|
|
139
144
|
commitDraft();
|
|
140
145
|
}
|
|
141
146
|
/** 完整规则那格可以写坏;解析不过就停在这张卡上说清楚,不往端点写。 */
|
|
142
|
-
function commitDraft():
|
|
147
|
+
function commitDraft(): boolean {
|
|
143
148
|
try {
|
|
144
|
-
const pricing = JSON.parse(raw.value)
|
|
149
|
+
const pricing: unknown = JSON.parse(raw.value.trim() || '[]');
|
|
150
|
+
if (!Array.isArray(pricing)) throw new Error(S.pricingTitle + ': JSON array required');
|
|
145
151
|
problem.textContent = '';
|
|
146
152
|
problem.classList.remove('bad');
|
|
153
|
+
raw.setAttribute('aria-invalid', 'false');
|
|
154
|
+
updateNote(pricing);
|
|
147
155
|
commit(pricing);
|
|
156
|
+
draft?.onRaw(raw.value);
|
|
157
|
+
return true;
|
|
148
158
|
} catch (error) {
|
|
149
159
|
problem.textContent = String(error);
|
|
150
160
|
problem.classList.add('bad');
|
|
161
|
+
raw.setAttribute('aria-invalid', 'true');
|
|
162
|
+
draft?.onRaw(raw.value);
|
|
163
|
+
return false;
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
166
|
card.body.append(
|
|
@@ -155,14 +168,15 @@ export function pricingEditor(
|
|
|
155
168
|
...rates.map((input, i) => ui.field(rateLabels[i], input)),
|
|
156
169
|
ui.msgline(simple || unset ? S.costFormNote : S.costFormOverridden),
|
|
157
170
|
);
|
|
158
|
-
const advanced = ui.h('details');
|
|
171
|
+
const advanced = ui.h('details', 'pricing-rules');
|
|
159
172
|
advanced.open = !simple && !unset;
|
|
160
173
|
advanced.append(ui.h('summary', null, S.editFull), raw, ui.msgline(S.fullNote), problem);
|
|
161
|
-
const preview = ui.h('details');
|
|
174
|
+
const preview = ui.h('details', 'pricing-snapshot');
|
|
162
175
|
preview.append(
|
|
163
176
|
ui.h('summary', null, S.viewSnapshot),
|
|
164
177
|
ui.h('pre', 'mono', JSON.stringify(quotes, null, 2)),
|
|
165
178
|
);
|
|
166
179
|
card.body.append(advanced, preview);
|
|
167
|
-
|
|
180
|
+
if (draft?.raw !== undefined) commitDraft();
|
|
181
|
+
return { el: card.el, body: card.body, validate: commitDraft };
|
|
168
182
|
}
|
|
@@ -27,12 +27,12 @@ const zh = {
|
|
|
27
27
|
extraBody: '附加请求体(JSON 对象)',
|
|
28
28
|
jsonObjectRequired: (label: string) => `${label} 必须是 JSON 对象`,
|
|
29
29
|
advancedProtocolTitle: '协议与请求扩展',
|
|
30
|
-
advancedProtocolDescription: '',
|
|
30
|
+
advancedProtocolDescription: '针对 OpenAI Responses 兼容端点的高级配置(端点路径与附加 JSON 字段)。',
|
|
31
31
|
modelFieldLabel: '模型标识',
|
|
32
32
|
effortFieldLabel: '推理强度',
|
|
33
33
|
temperatureFieldLabel: '采样温度',
|
|
34
34
|
specTitle: '模型档',
|
|
35
|
-
specDescription: '
|
|
35
|
+
specDescription: '配置此实例调用的模型及推理、温度和 Token 限制等生成参数。',
|
|
36
36
|
modelName: '明确模型名',
|
|
37
37
|
fetchModels: '取模型列表',
|
|
38
38
|
modelsFetched: (count: number) => `取到 ${count} 个模型。`,
|
|
@@ -76,7 +76,7 @@ const zh = {
|
|
|
76
76
|
deleted: '已删除。',
|
|
77
77
|
pricingTitle: '报价',
|
|
78
78
|
pricingDescription: '历史流水固定使用请求时的报价。自定义报价按模型与费用口径覆盖模块默认值。',
|
|
79
|
-
pricingUnset: '
|
|
79
|
+
pricingUnset: '未设自定义报价:优先使用模块价目;若无适用价目,仅记录 token,不计金额。',
|
|
80
80
|
meters: {
|
|
81
81
|
input: '输入',
|
|
82
82
|
cachedInput: '缓存命中',
|
|
@@ -85,7 +85,6 @@ const zh = {
|
|
|
85
85
|
reasoning: '推理',
|
|
86
86
|
total: '总量',
|
|
87
87
|
},
|
|
88
|
-
quoteUnknown: (model: string) => `${model}:报价未知`,
|
|
89
88
|
marginal: '边际费用',
|
|
90
89
|
equivalent: 'API 等价费用',
|
|
91
90
|
perMillion: (label: string, rate: number) => `${label} ${rate}/百万`,
|
|
@@ -131,12 +130,12 @@ const en: typeof zh = {
|
|
|
131
130
|
extraBody: 'Extra body (JSON object)',
|
|
132
131
|
jsonObjectRequired: (label: string) => `${label} must be a JSON object`,
|
|
133
132
|
advancedProtocolTitle: 'Protocol & Request Overrides',
|
|
134
|
-
advancedProtocolDescription: '',
|
|
133
|
+
advancedProtocolDescription: 'Advanced options for OpenAI Responses compatible endpoints (custom path and extra JSON fields).',
|
|
135
134
|
modelFieldLabel: 'Model identifier',
|
|
136
135
|
effortFieldLabel: 'Reasoning effort',
|
|
137
136
|
temperatureFieldLabel: 'Sampling temperature',
|
|
138
137
|
specTitle: 'Model',
|
|
139
|
-
specDescription: '
|
|
138
|
+
specDescription: 'Configure model identifier, reasoning effort, temperature and token limits for this instance.',
|
|
140
139
|
modelName: 'Exact model name',
|
|
141
140
|
fetchModels: 'Fetch models',
|
|
142
141
|
modelsFetched: (count: number) => `Fetched ${count} models.`,
|
|
@@ -182,7 +181,7 @@ const en: typeof zh = {
|
|
|
182
181
|
pricingTitle: 'Pricing',
|
|
183
182
|
pricingDescription:
|
|
184
183
|
'Historical records keep the quote in effect at request time. Custom quotes override module defaults per model and cost basis.',
|
|
185
|
-
pricingUnset: 'No pricing
|
|
184
|
+
pricingUnset: 'No custom pricing: module prices apply when available; otherwise only tokens are recorded.',
|
|
186
185
|
meters: {
|
|
187
186
|
input: 'Input',
|
|
188
187
|
cachedInput: 'Cache hit',
|
|
@@ -191,7 +190,6 @@ const en: typeof zh = {
|
|
|
191
190
|
reasoning: 'Reasoning',
|
|
192
191
|
total: 'Total',
|
|
193
192
|
},
|
|
194
|
-
quoteUnknown: (model: string) => `${model}: quote unknown`,
|
|
195
193
|
marginal: 'Marginal cost',
|
|
196
194
|
equivalent: 'API-equivalent cost',
|
|
197
195
|
perMillion: (label: string, rate: number) => `${label} ${rate}/M`,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type ConsolePageManifest,
|
|
9
9
|
type ConsolePanelManifest,
|
|
10
10
|
} from '../../shared/console-protocol.ts';
|
|
11
|
-
import type { ConsoleMemo, ConsolePanel, Disposable } from '../../shared/client-panel.ts';
|
|
11
|
+
import type { ConsoleMemo, ConsolePanel, ConsolePanelContext, Disposable } from '../../shared/client-panel.ts';
|
|
12
12
|
import { get, post } from '../core/api.ts';
|
|
13
13
|
import { Lifecycle } from '../core/lifecycle.ts';
|
|
14
14
|
import type { Router } from '../core/router.ts';
|
|
@@ -121,6 +121,23 @@ export class ConsolePageHost {
|
|
|
121
121
|
this.emitNav();
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/** Mount module panels inside a connection editor while its adapter stages configuration edits. */
|
|
125
|
+
async mountConnection(pageId: string, root: HTMLElement, scope: Readonly<Record<string, string>>,
|
|
126
|
+
adapt: (context: ConsolePanelContext) => ConsolePanelContext): Promise<Disposable> {
|
|
127
|
+
const lifecycle = new Lifecycle(this.deps.onError);
|
|
128
|
+
const page = this.find(pageId);
|
|
129
|
+
for (const panel of asArray(page?.panels).filter(panel => panel.id !== 'settings')) {
|
|
130
|
+
const box = this.deps.doc.createElement('div'); root.append(box);
|
|
131
|
+
try {
|
|
132
|
+
const impl = panel.builtin ? this.builtinPanel(panel.builtin) : await this.deps.loader.resolvePanel(pageId, panel.id, page?.client);
|
|
133
|
+
if (lifecycle.disposed) break;
|
|
134
|
+
const result = await impl.mount(adapt(this.panelContext(pageId, panel.id, box, lifecycle, this.generation, scope)));
|
|
135
|
+
if (result) lifecycle.own(result);
|
|
136
|
+
} catch (error) { box.textContent = String(error); }
|
|
137
|
+
}
|
|
138
|
+
return lifecycle;
|
|
139
|
+
}
|
|
140
|
+
|
|
124
141
|
/** 重取 manifest 并刷新导航与当前页头,**不重挂面板**。 */
|
|
125
142
|
async refresh(): Promise<void> {
|
|
126
143
|
await this.load();
|
|
@@ -380,11 +397,11 @@ export class ConsolePageHost {
|
|
|
380
397
|
|
|
381
398
|
/** Persona 页才有工具表页签,且要部署挂着 /api/tool-schemas。 */
|
|
382
399
|
private toolsTabOf(page: ConsolePageManifest): boolean {
|
|
383
|
-
if (page.kind !== 'persona') return false;
|
|
400
|
+
if (page.kind !== 'persona' || page.availability !== 'active') return false;
|
|
384
401
|
return this.snapshot?.framework?.capabilities?.toolSchemas !== false;
|
|
385
402
|
}
|
|
386
403
|
|
|
387
|
-
/**
|
|
404
|
+
/** 工具表是整份装配结果,不按页筛:模型看见的那一张表就是这一张。 */
|
|
388
405
|
private async showTools(pageId: string, gen: number): Promise<void> {
|
|
389
406
|
const { slot } = this.ensurePanes();
|
|
390
407
|
const lifecycle = new Lifecycle(this.deps.onError);
|
|
@@ -515,7 +532,8 @@ export class ConsolePageHost {
|
|
|
515
532
|
? page.lamps ?? []
|
|
516
533
|
: [{ label: S.assembly, state: 'offline', hint: page.availability === 'missing' ? S.notInstalled : S.notActivated }],
|
|
517
534
|
));
|
|
518
|
-
head.
|
|
535
|
+
head.appendChild(title);
|
|
536
|
+
chrome.append(head, ui.h('p', 'pagedesc', page.id));
|
|
519
537
|
const bar = ui.rowbar();
|
|
520
538
|
|
|
521
539
|
for (const badge of asArray(page.badges)) {
|
|
@@ -547,9 +565,8 @@ export class ConsolePageHost {
|
|
|
547
565
|
});
|
|
548
566
|
bar.appendChild(open);
|
|
549
567
|
}
|
|
550
|
-
|
|
551
|
-
if (page.reason)
|
|
552
|
-
chrome.appendChild(head);
|
|
568
|
+
if (bar.children.length > 1) chrome.appendChild(bar);
|
|
569
|
+
if (page.reason) chrome.appendChild(ui.msgline(page.reason, true));
|
|
553
570
|
|
|
554
571
|
const panels = tabbed(page);
|
|
555
572
|
const prompts = asArray(page.prompts);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
CONSOLE_AUTH_HEADER,
|
|
9
10
|
CONSOLE_MANIFEST_ROUTE,
|
|
10
11
|
panelRoute,
|
|
11
12
|
type ConsoleManifest,
|
|
@@ -57,12 +58,15 @@ function normalizeError(err: unknown, status: number): never {
|
|
|
57
58
|
|
|
58
59
|
async function send(path: string, init: RequestInit, opts?: RequestOptions): Promise<Response> {
|
|
59
60
|
try {
|
|
60
|
-
|
|
61
|
+
const res = await fetch(path, {
|
|
61
62
|
...init,
|
|
62
63
|
headers: { ...languageHeaders(), ...(init.headers as Record<string, string> | undefined) },
|
|
63
64
|
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
64
65
|
...(opts?.keepalive ? { keepalive: true } : {}),
|
|
65
66
|
});
|
|
67
|
+
// 登录态失效:重新载入入口页,服务端在那里给出登录页。
|
|
68
|
+
if (res.status === 401 && res.headers.get(CONSOLE_AUTH_HEADER) !== null) location.reload();
|
|
69
|
+
return res;
|
|
66
70
|
} catch (err) {
|
|
67
71
|
// fetch reject = 网络层没走到 HTTP,没有状态码可言,记 0。
|
|
68
72
|
normalizeError(err, 0);
|
|
@@ -70,6 +70,7 @@ export class Router {
|
|
|
70
70
|
private readonly deps: RouterDeps;
|
|
71
71
|
private readonly listeners = new Set<(route: Route) => void>();
|
|
72
72
|
private readonly guards = new Set<LeaveGuard>();
|
|
73
|
+
private readonly decisions = new Set<() => Promise<boolean>>();
|
|
73
74
|
private current: Route;
|
|
74
75
|
/** 待处理的回拨 hash;写入相同 hash 不触发 hashchange,因此按目标值识别回拨。 */
|
|
75
76
|
private pendingRevert: string | null = null;
|
|
@@ -108,6 +109,11 @@ export class Router {
|
|
|
108
109
|
return toDisposable(() => this.guards.delete(guard));
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
addLeaveDecision(decide: () => Promise<boolean>): Disposable {
|
|
113
|
+
this.decisions.add(decide);
|
|
114
|
+
return toDisposable(() => this.decisions.delete(decide));
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
navigate(segments: readonly string[], query?: Record<string, string>): void {
|
|
112
118
|
this.go(buildHash(segments, query), (hash) => { this.deps.win.location.hash = hash; });
|
|
113
119
|
}
|
|
@@ -166,6 +172,15 @@ export class Router {
|
|
|
166
172
|
return;
|
|
167
173
|
}
|
|
168
174
|
|
|
175
|
+
if (this.decisions.size) {
|
|
176
|
+
this.confirming = true;
|
|
177
|
+
let allowed = true;
|
|
178
|
+
try {
|
|
179
|
+
for (const decide of this.decisions) if (!(await decide())) { allowed = false; break; }
|
|
180
|
+
} catch (error) { allowed = false; this.deps.onError?.(error); }
|
|
181
|
+
finally { this.confirming = false; }
|
|
182
|
+
if (!allowed) { this.revertTo(this.current.raw); return; }
|
|
183
|
+
}
|
|
169
184
|
const block = this.firstBlock();
|
|
170
185
|
if (block) {
|
|
171
186
|
this.confirming = true;
|
|
@@ -12,7 +12,7 @@ const zh = {
|
|
|
12
12
|
subData: '数据',
|
|
13
13
|
subConfig: '配置',
|
|
14
14
|
dataTitle: 'Core 的数据',
|
|
15
|
-
dataDesc: '',
|
|
15
|
+
dataDesc: '事件库、session、运行日志、用量流水这些 Core 自己攒下的存储。',
|
|
16
16
|
configTitle: 'Core 配置',
|
|
17
17
|
configDesc: '修改自动保存到 config.json。标有重启要求的配置在重启后生效,其余立即生效。',
|
|
18
18
|
configEmpty: 'Core 没有可调配置。',
|
|
@@ -89,7 +89,7 @@ const en: typeof zh = {
|
|
|
89
89
|
subData: 'Data',
|
|
90
90
|
subConfig: 'Config',
|
|
91
91
|
dataTitle: 'Core data',
|
|
92
|
-
dataDesc: '',
|
|
92
|
+
dataDesc: 'Storage Core accumulates on its own: the event store, the session, run logs and the usage ledger.',
|
|
93
93
|
configTitle: 'Core config',
|
|
94
94
|
configDesc: 'Changes save to config.json automatically. Settings marked for restart apply after restarting; the rest apply immediately.',
|
|
95
95
|
configEmpty: 'Core has no tunable settings.',
|