@fa-software/semi2-dsh 0.0.14-preview.20260927035651 → 0.0.15
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 +3 -3
- package/lib/client.js +107 -32
- package/lib/client.js.map +1 -1
- package/lib/contract.js +0 -5
- package/lib/domains/project-profile/domain.js +0 -1
- package/lib/hub/status.js +1 -1
- package/lib/index.js +2 -8
- package/lib/service/config-service.js +2 -22
- package/lib/service/lifecycle.js +6 -28
- package/lib/service/namespaces.js +3 -9
- package/lib/service/orchestrator.js +0 -4
- package/lib/service/ports.js +0 -30
- package/lib/service/registry.js +11 -0
- package/lib/service/status.js +89 -0
- package/lib/types/contract.d.ts +0 -5
- package/lib/types/domains/project-profile/domain.d.ts +1 -1
- package/lib/types/domains/types.d.ts +1 -3
- package/lib/types/index.d.ts +1 -1
- package/lib/types/service/config-service.d.ts +3 -1
- package/lib/types/service/lifecycle.d.ts +1 -1
- package/lib/types/service/namespaces.d.ts +3 -5
- package/lib/types/service/orchestrator.d.ts +0 -2
- package/lib/types/service/ports.d.ts +1 -27
- package/lib/types/service/registry.d.ts +4 -0
- package/lib/types/service/status.d.ts +17 -0
- package/lib/types/web/routes.d.ts +4 -2
- package/lib/web/routes.js +8 -4
- package/package.json +1 -1
- package/lib/service/publish.js +0 -184
- package/lib/service/settings-sections.js +0 -40
- package/lib/types/service/publish.d.ts +0 -24
- package/lib/types/service/settings-sections.d.ts +0 -7
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
// @fa-software/semi2-dsh/service/namespaces —— 插件自身设置段与官方命名空间的读取(无环叶子模块)。
|
|
2
|
-
// dsh 的设置段 = Profile 插件条目:字段声明(settings-sections)是状态 / 健康 / 工作区 tab 的展示来源,
|
|
3
|
-
// 内容与模型路由一样经设置服务写进官方设置文档(适配器逐请求重读),不写 profile patch。
|
|
4
|
-
// 这里只留读口径与自铸名字的形状。
|
|
5
|
-
import { SEMI2_SETTINGS_ENTRY } from "../contract.js";
|
|
6
|
-
export { SEMI2_SETTINGS_ENTRY };
|
|
7
1
|
/** 本插件自铸的名字形状:路由 `semi2-<32 hex>`、密钥引用 `SEMI2_MODEL_KEY_<大写 hex>`。 */
|
|
8
2
|
export const SELF_MINTED_ROUTE = /^semi2-[\da-f]{32}$/;
|
|
9
3
|
export const SELF_MINTED_KEY_ENV = /^SEMI2_MODEL_KEY_[\dA-F]+$/;
|
|
10
4
|
/** 本插件在官方 `llm-deepseek` 段表达过的三个键:服务端撤下 DEEPSEEK 连接时一并清掉。 */
|
|
11
5
|
export const DEEPSEEK_OVERRIDE_KEYS = ['apiKeyEnv', 'baseURL', 'models'];
|
|
12
6
|
/** 一个设置段的当前有效值:官方描述符的 `value` 是不透明值,本插件只认对象段,其余按空段处理。 */
|
|
13
|
-
export function sectionValue(settings, ns
|
|
7
|
+
export function sectionValue(settings, ns) {
|
|
14
8
|
const value = settings.describe().find(entry => entry.ns === ns)?.value;
|
|
15
9
|
return isRecord(value) ? value : {};
|
|
16
10
|
}
|
|
@@ -22,14 +16,14 @@ export function hasSection(settings, ns) {
|
|
|
22
16
|
* 该段的**用户层**原始内容(只认 `describe()` 的 `user`:组合层与 schema 默认不算用户写过)。
|
|
23
17
|
* 写入前用它保住用户写在同一段里的其他键——`replace` 会把用户层整体对齐到给定内容。
|
|
24
18
|
*/
|
|
25
|
-
export function userSection(settings, ns
|
|
19
|
+
export function userSection(settings, ns) {
|
|
26
20
|
return persistedSection(settings.describe(), ns);
|
|
27
21
|
}
|
|
28
22
|
/**
|
|
29
23
|
* 该段的**组合层**内容(`describe()` 的 `base`,已带 schema 默认)=部署默认;
|
|
30
24
|
* 登出撤下默认模型时用它把选择交还给组合层。
|
|
31
25
|
*/
|
|
32
|
-
export function baseSection(settings, ns
|
|
26
|
+
export function baseSection(settings, ns) {
|
|
33
27
|
const base = settings.describe().find(entry => entry.ns === ns)?.base;
|
|
34
28
|
return isRecord(base) ? base : {};
|
|
35
29
|
}
|
|
@@ -3,7 +3,6 @@ import { initialState, } from "../hub/facts.js";
|
|
|
3
3
|
import { DOMAINS } from "./registry.js";
|
|
4
4
|
import { NOOP_WORKSPACE } from "./workspace-ops.js";
|
|
5
5
|
import { CONTRACT_TIMEOUT_MS, CREDENTIAL_RETRY } from "./connection.js";
|
|
6
|
-
import { scheduleStatusPublish } from "./publish.js";
|
|
7
6
|
/** 领域 attach 的等待上限缺省值:超时按「该端不提供此能力」审计(依赖永不到达不能永久悬空)。 */
|
|
8
7
|
const DOMAIN_ATTACH_TIMEOUT_MS = 2_000;
|
|
9
8
|
const DOMAIN_POLL_MS = 25;
|
|
@@ -67,9 +66,6 @@ export class Orchestrator {
|
|
|
67
66
|
this.ctx.emit('semi2/workspaces-updated', this.current);
|
|
68
67
|
this.notifyDomains();
|
|
69
68
|
}
|
|
70
|
-
requestStatusPublish() {
|
|
71
|
-
scheduleStatusPublish(this);
|
|
72
|
-
}
|
|
73
69
|
settings() {
|
|
74
70
|
return this.settingsService;
|
|
75
71
|
}
|
package/lib/service/ports.js
CHANGED
|
@@ -27,7 +27,6 @@ export function createPorts(ctx, warn, options = {}) {
|
|
|
27
27
|
throw new ConfigurationError('CONFIG_APPLY_FAILED');
|
|
28
28
|
};
|
|
29
29
|
const hasWorkspaceController = () => ctx.get('workspaceController', false) !== undefined;
|
|
30
|
-
const hasWebServer = () => ctx.get('webServer', false) !== undefined;
|
|
31
30
|
return {
|
|
32
31
|
settings: () => {
|
|
33
32
|
const { promise, resolve } = Promise.withResolvers();
|
|
@@ -44,17 +43,6 @@ export function createPorts(ctx, warn, options = {}) {
|
|
|
44
43
|
credentialsNow: () => ctx.get('credentials', false),
|
|
45
44
|
agentDefaultModel: () => ctx.get('agentDefaultModel', false),
|
|
46
45
|
hasWorkspaceController,
|
|
47
|
-
hasAudience: () => hasWebServer() || hasWorkspaceController(),
|
|
48
|
-
applyStatusRow: async (ns, section) => {
|
|
49
|
-
const entry = runtimeEntry(ctx, ns);
|
|
50
|
-
if (entry === undefined)
|
|
51
|
-
return false;
|
|
52
|
-
await entry.update({ config: { ...(entry.options.config ?? {}), ...section } });
|
|
53
|
-
// 运行期 config 变更本身不广播:设置服务重算摘要时才发 `settings/document-updated`。
|
|
54
|
-
const settings = ctx.get('settings', false);
|
|
55
|
-
settings?.describe();
|
|
56
|
-
return true;
|
|
57
|
-
},
|
|
58
46
|
replaceNamespace: async (settings, ns, section) => {
|
|
59
47
|
await requireNamespace(settings, ns);
|
|
60
48
|
const replace = settings.replace?.bind(settings);
|
|
@@ -75,26 +63,8 @@ export function createPorts(ctx, warn, options = {}) {
|
|
|
75
63
|
const ops = keys.map(key => ({ op: 'unset', path: [key] }));
|
|
76
64
|
await writes.run(ns, () => mutate(ns, ops));
|
|
77
65
|
},
|
|
78
|
-
hideSettingsPage: (settings, owner) => {
|
|
79
|
-
const configure = settings.configure?.bind(settings);
|
|
80
|
-
if (configure === undefined)
|
|
81
|
-
return;
|
|
82
|
-
try {
|
|
83
|
-
// 回收器挂在本插件光纤上:插件卸载后不留一条指向已死光纤的展示策略。
|
|
84
|
-
const dispose = configure({ auto: false }, owner.fiber);
|
|
85
|
-
owner.effect(() => dispose);
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
warn(`设置页自动生成关闭失败(${error instanceof Error ? error.message : String(error)})`);
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
66
|
};
|
|
92
67
|
}
|
|
93
|
-
/** 活动条目里按 id 找运行期 config 的写入口;loader 服务或条目缺席都返回 undefined。 */
|
|
94
|
-
function runtimeEntry(ctx, id) {
|
|
95
|
-
const loader = ctx.get('loader', false);
|
|
96
|
-
return loader?.entries?.().find(entry => entry.options.id === id);
|
|
97
|
-
}
|
|
98
68
|
/** 等设置段可描述(条目 ACTIVE 且声明了 volatile 字段);超时返回 false,不抛。 */
|
|
99
69
|
async function waitForNamespace(settings, ns, waitMs) {
|
|
100
70
|
const deadline = Date.now() + waitMs;
|
package/lib/service/registry.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { STATUS_BASE_FIELDS } from "../hub/status.js";
|
|
1
2
|
import { defaultModelDomain } from "../domains/default-model/index.js";
|
|
2
3
|
import { modelsDomain } from "../domains/models/index.js";
|
|
3
4
|
import { projectProfileDomain } from "../domains/project-profile/index.js";
|
|
@@ -17,3 +18,13 @@ export const DOMAINS = [
|
|
|
17
18
|
projectProfileDomain,
|
|
18
19
|
redmineDomain,
|
|
19
20
|
];
|
|
21
|
+
/** 完整状态字段顺序(基座 + 按注册表顺序的领域字段);状态视图与健康字段共用这一段。 */
|
|
22
|
+
export const STATUS_FIELDS = [
|
|
23
|
+
...STATUS_BASE_FIELDS,
|
|
24
|
+
...DOMAINS.flatMap(domain => [...domain.statusKeys]),
|
|
25
|
+
];
|
|
26
|
+
/** 健康字段键(能力 + 领域,值是 `状态:说明` 形式的编码串;读侧拆码)。 */
|
|
27
|
+
export const HEALTH_FIELDS = [
|
|
28
|
+
'healthWeb',
|
|
29
|
+
...DOMAINS.map(domain => `health:${domain.id}`),
|
|
30
|
+
];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { statusFields, workspaceTabs, WORKSPACE_TABS_FIELD, } from "../hub/status.js";
|
|
2
|
+
import { aggregateHealth, healthText } from "./health.js";
|
|
3
|
+
import { DOMAINS } from "./registry.js";
|
|
4
|
+
import { reportEvent } from "./audit.js";
|
|
5
|
+
/** 契约钉了凭据(部署目录 env)= 只读部署:登录 / 登出 / 同步对用户关闭(`CREDENTIAL_SOURCE_READONLY`)。 */
|
|
6
|
+
export function credentialsWritable(engine) {
|
|
7
|
+
const credentials = engine.contract()?.credentials;
|
|
8
|
+
if (credentials === undefined)
|
|
9
|
+
return true;
|
|
10
|
+
const filled = (value) => {
|
|
11
|
+
const trimmed = value?.trim();
|
|
12
|
+
return trimmed !== undefined && trimmed !== '';
|
|
13
|
+
};
|
|
14
|
+
return !(filled(credentials.configUrl) || filled(credentials.accessToken));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 一份状态视图:`writable` + 状态字段 + 健康编码(`状态:说明` 串,读侧拆码)+ 工作区 tab。
|
|
18
|
+
* 值全部是展示标量,不含任何密钥;每次请求从编排层现算。
|
|
19
|
+
*/
|
|
20
|
+
export function statusSection(engine) {
|
|
21
|
+
const facts = {
|
|
22
|
+
credentialSource: engine.connection()?.source ?? null,
|
|
23
|
+
platform: engine.platform,
|
|
24
|
+
startupDir: engine.startupDir,
|
|
25
|
+
};
|
|
26
|
+
const slices = [];
|
|
27
|
+
for (const descriptor of DOMAINS) {
|
|
28
|
+
const domain = engine.domains.get(descriptor.id);
|
|
29
|
+
if (domain !== undefined)
|
|
30
|
+
slices.push(domain.statusFields(engine.state()));
|
|
31
|
+
}
|
|
32
|
+
const health = healthSnapshot(engine);
|
|
33
|
+
return {
|
|
34
|
+
writable: credentialsWritable(engine),
|
|
35
|
+
...statusFields(engine.state(), facts, slices),
|
|
36
|
+
...Object.fromEntries(Object.entries(health).map(([key, item]) => [key, `${item.state}:${healthText(item)}`])),
|
|
37
|
+
[WORKSPACE_TABS_FIELD]: workspaceTabs(engine.state()),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** 整体读取失败:只落 state.error 与失败态,不注入任何配置(可重载重试)。 */
|
|
41
|
+
export async function failedState(engine, code) {
|
|
42
|
+
engine.patchState({
|
|
43
|
+
status: 'failed',
|
|
44
|
+
error: code,
|
|
45
|
+
workspaces: engine.workspace().tracked(),
|
|
46
|
+
});
|
|
47
|
+
engine.emitConfigUpdated();
|
|
48
|
+
// 启动期的整体失败落一条运行端事件:判「本端起没起来」不必翻容器日志。
|
|
49
|
+
await reportEvent(engine, {
|
|
50
|
+
action: 'SESSION_FAILED',
|
|
51
|
+
outcome: 'ERROR',
|
|
52
|
+
code,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** boot 事实 → ready 状态(技能切片取自 skills 领域;项目缓存随装配作废,由工作区同步重取)。 */
|
|
56
|
+
export function appliedState(engine, boot, compiled, errorCode) {
|
|
57
|
+
const defaultModelId = boot.runtimeDefaultModelId ?? null;
|
|
58
|
+
const selection = compiled === null || defaultModelId === null
|
|
59
|
+
? null
|
|
60
|
+
: (compiled.selections.get(defaultModelId) ?? null);
|
|
61
|
+
const skills = engine.peer('skills')?.skills() ?? null;
|
|
62
|
+
const current = engine.state();
|
|
63
|
+
return {
|
|
64
|
+
...current,
|
|
65
|
+
status: 'ready',
|
|
66
|
+
error: errorCode,
|
|
67
|
+
consoleUrl: engine.connection()?.url ?? current.consoleUrl,
|
|
68
|
+
loadedAt: new Date().toISOString(),
|
|
69
|
+
identity: boot.runtimeViewer ?? null,
|
|
70
|
+
model: selection === null
|
|
71
|
+
? null
|
|
72
|
+
: { id: selection.model, provider: selection.provider },
|
|
73
|
+
skills,
|
|
74
|
+
projects: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** 健康快照:领域项按注册表(缺席记 unavailable)+ 能力贡献者按登记。 */
|
|
78
|
+
export function healthSnapshot(engine) {
|
|
79
|
+
return aggregateHealth(DOMAINS.map(domain => domain.id), (id) => {
|
|
80
|
+
// 本端没有 settings 服务:没有任何领域被装配(写不进去就不装),整端记不适用。
|
|
81
|
+
if (engine.settingsMissing()) {
|
|
82
|
+
return { state: 'unavailable', note: '本端无 settings 服务,不注入配置' };
|
|
83
|
+
}
|
|
84
|
+
const domain = engine.domains.get(id);
|
|
85
|
+
if (domain === undefined)
|
|
86
|
+
return { state: 'unavailable', note: '本端不提供该能力(依赖缺席)' };
|
|
87
|
+
return domain.health();
|
|
88
|
+
}, engine.healthContributorsView());
|
|
89
|
+
}
|
package/lib/types/contract.d.ts
CHANGED
|
@@ -10,11 +10,6 @@ export declare const SEMI2_SECTION_ORDERS: {
|
|
|
10
10
|
readonly cnbWorkstation: 4;
|
|
11
11
|
};
|
|
12
12
|
export type Semi2SectionKey = keyof typeof SEMI2_SECTION_ORDERS;
|
|
13
|
-
/**
|
|
14
|
-
* 插件自身设置段的条目 id:dsh 的设置命名空间就是 Profile 插件条目 id(bundle patch 里那一行的 id),
|
|
15
|
-
* 状态 / 健康 / 工作区映射都写在这一段里。host 与浏览器半边共用这一个常量。
|
|
16
|
-
*/
|
|
17
|
-
export declare const SEMI2_SETTINGS_ENTRY = "semi2-dsh";
|
|
18
13
|
/** 动作通道前缀:登录 / 登出 / 同步都经官方 connection 的逻辑 RPC,一次调用落在 POST <前缀>/<endpoint>。 */
|
|
19
14
|
export declare const SEMI2_ACTION_CHANNEL = "/semi2";
|
|
20
15
|
/** 只读 systemPrompt 的号段解析接口:老宿主没有 `getSectionOrder` 时回落本表偏移。 */
|
|
@@ -19,7 +19,7 @@ export interface ProjectProfile {
|
|
|
19
19
|
} | null;
|
|
20
20
|
}
|
|
21
21
|
/** 项目画像实际消费的宿主接口;测试只需要铸造这些成员。 */
|
|
22
|
-
export interface ProjectProfileHost extends Pick<DomainHost, 'patchState' | '
|
|
22
|
+
export interface ProjectProfileHost extends Pick<DomainHost, 'patchState' | 'state' | 'warn'> {
|
|
23
23
|
api: Pick<DomainHost['api'], 'projectProfile'>;
|
|
24
24
|
peer: (id: 'workspace') => {
|
|
25
25
|
projectForCwd: (cwd: string | undefined) => Promise<{
|
|
@@ -130,7 +130,7 @@ export interface DomainService {
|
|
|
130
130
|
export interface DomainHost {
|
|
131
131
|
/** 领域 apply 内同步调用:登记本领域;编排层等齐注册表全部领域后才开始首轮取数。 */
|
|
132
132
|
attach(service: DomainService): void;
|
|
133
|
-
/**
|
|
133
|
+
/** 共享状态打补丁(编排层持有唯一权威副本,事件也由它负责;状态视图按请求现算)。 */
|
|
134
134
|
patchState(patch: Partial<Semi2ConfigState>): void;
|
|
135
135
|
readonly api: Semi2Api;
|
|
136
136
|
readonly ports: Ports;
|
|
@@ -145,8 +145,6 @@ export interface DomainHost {
|
|
|
145
145
|
/** 当前是否持有连接凭据(未连接时按需取数直接跳过,不问 hub)。 */
|
|
146
146
|
hasConnection(): boolean;
|
|
147
147
|
emitWorkspacesUpdated(): void;
|
|
148
|
-
/** 会话期产生的新状态(项目画像同步结果):请求编排层立刻回写状态页;fire-and-forget。 */
|
|
149
|
-
requestStatusPublish(): void;
|
|
150
148
|
/** 迟绑定的跨领域事实:按 id 取 peer 接口;该领域未就位返回 undefined。 */
|
|
151
149
|
peer<K extends DomainId>(id: K): DomainPeers[K] | undefined;
|
|
152
150
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type { RuntimeIdentity, RuntimeSkillFact, RuntimeSkillFileContentFact, Ru
|
|
|
5
5
|
export type { ConfigStatus, Semi2Config, Semi2Audit, Semi2Health, Semi2Session, } from './service/config-service.ts';
|
|
6
6
|
export type { HeadlessModelOutcome } from './domains/types.ts';
|
|
7
7
|
export type { Semi2PlatformKind } from './hub/facts.ts';
|
|
8
|
-
export { SEMI2_RUNTIME_SERVICE, SEMI2_SECTION_ORDERS,
|
|
8
|
+
export { SEMI2_RUNTIME_SERVICE, SEMI2_SECTION_ORDERS, runtimeFromCnbEnv, semi2SectionOrder, } from './contract.ts';
|
|
9
9
|
export type { SectionOrderSource, Semi2Credentials, Semi2Platform, Semi2Runtime, Semi2SectionKey, } from './contract.ts';
|
|
10
10
|
import './events.ts';
|
|
11
11
|
export declare const name = "semi2-dsh";
|
|
@@ -66,7 +66,9 @@ export interface ConfigurationInstall {
|
|
|
66
66
|
session: Semi2Session;
|
|
67
67
|
/** 诊断接口 → `ctx.semi2Health`。 */
|
|
68
68
|
health: Semi2Health;
|
|
69
|
-
/**
|
|
69
|
+
/** 状态视图(`GET /semi2/status` 按请求现算)→ web 路由。 */
|
|
70
|
+
status(): Record<string, unknown>;
|
|
71
|
+
/** 上报接口 → `ctx.semi2Audit`:运行端(NPC 端)回报生命周期与交付事实,失败只 warn。 */
|
|
70
72
|
audit: Semi2Audit;
|
|
71
73
|
/** 能力项(web 应用)登记健康贡献者。 */
|
|
72
74
|
registerHealth(contributor: HealthContributor): void;
|
|
@@ -5,7 +5,7 @@ export declare function start(engine: OrchestratorInternals): Promise<void>;
|
|
|
5
5
|
export declare function reload(engine: OrchestratorInternals): Promise<ReturnType<OrchestratorInternals['state']>>;
|
|
6
6
|
/**
|
|
7
7
|
* 界面「同步」动作:`workspace:<slug>` 只同步那一个工作区,其余(含空串)整体重载。
|
|
8
|
-
*
|
|
8
|
+
* 失败只 warn(面板上的错误由状态字段承载,浏览器半边动作返回后会重新拉取)。
|
|
9
9
|
*/
|
|
10
10
|
export declare function syncByScope(engine: OrchestratorInternals, scope: string): Promise<void>;
|
|
11
11
|
/** 读取一次运行配置;整体失败只落 state.error,绝不 throw(start/reload 均可重试)。 */
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { SEMI2_SETTINGS_ENTRY } from '../contract.ts';
|
|
2
1
|
import type { SettingsLike } from './ports.ts';
|
|
3
|
-
export { SEMI2_SETTINGS_ENTRY };
|
|
4
2
|
/** 本插件自铸的名字形状:路由 `semi2-<32 hex>`、密钥引用 `SEMI2_MODEL_KEY_<大写 hex>`。 */
|
|
5
3
|
export declare const SELF_MINTED_ROUTE: RegExp;
|
|
6
4
|
export declare const SELF_MINTED_KEY_ENV: RegExp;
|
|
7
5
|
/** 本插件在官方 `llm-deepseek` 段表达过的三个键:服务端撤下 DEEPSEEK 连接时一并清掉。 */
|
|
8
6
|
export declare const DEEPSEEK_OVERRIDE_KEYS: readonly ["apiKeyEnv", "baseURL", "models"];
|
|
9
7
|
/** 一个设置段的当前有效值:官方描述符的 `value` 是不透明值,本插件只认对象段,其余按空段处理。 */
|
|
10
|
-
export declare function sectionValue(settings: SettingsLike, ns
|
|
8
|
+
export declare function sectionValue(settings: SettingsLike, ns: string): Record<string, unknown>;
|
|
11
9
|
/** 设置段是否已可描述(条目 ACTIVE 且声明了 volatile 字段)。 */
|
|
12
10
|
export declare function hasSection(settings: SettingsLike, ns: string): boolean;
|
|
13
11
|
/**
|
|
14
12
|
* 该段的**用户层**原始内容(只认 `describe()` 的 `user`:组合层与 schema 默认不算用户写过)。
|
|
15
13
|
* 写入前用它保住用户写在同一段里的其他键——`replace` 会把用户层整体对齐到给定内容。
|
|
16
14
|
*/
|
|
17
|
-
export declare function userSection(settings: SettingsLike, ns
|
|
15
|
+
export declare function userSection(settings: SettingsLike, ns: string): Record<string, unknown>;
|
|
18
16
|
/**
|
|
19
17
|
* 该段的**组合层**内容(`describe()` 的 `base`,已带 schema 默认)=部署默认;
|
|
20
18
|
* 登出撤下默认模型时用它把选择交还给组合层。
|
|
21
19
|
*/
|
|
22
|
-
export declare function baseSection(settings: SettingsLike, ns
|
|
20
|
+
export declare function baseSection(settings: SettingsLike, ns: string): Record<string, unknown>;
|
|
@@ -14,7 +14,6 @@ export interface OrchestratorInternals {
|
|
|
14
14
|
readonly api: Semi2Api;
|
|
15
15
|
readonly ports: Ports;
|
|
16
16
|
readonly environment: NodeJS.ProcessEnv;
|
|
17
|
-
/** 本进程唯一的内存配置覆盖器(含上一轮行集与残留警告去重)。 */
|
|
18
17
|
/** 启动目录(迁移与状态展示共用同一个事实;状态渲染因此保持纯函数)。 */
|
|
19
18
|
readonly startupDir: string;
|
|
20
19
|
readonly contractTimeoutMs: number;
|
|
@@ -85,7 +84,6 @@ export declare class Orchestrator implements DomainHost, OrchestratorInternals {
|
|
|
85
84
|
setCurrent(next: Semi2ConfigState): void;
|
|
86
85
|
patchState(patch: Partial<Semi2ConfigState>): void;
|
|
87
86
|
emitWorkspacesUpdated(): void;
|
|
88
|
-
requestStatusPublish(): void;
|
|
89
87
|
settings(): SettingsLike | undefined;
|
|
90
88
|
setSettings(settings: SettingsLike | undefined): void;
|
|
91
89
|
settingsMissing(): boolean;
|
|
@@ -18,22 +18,10 @@ export type SettingsLike = Pick<SettingsForms, 'describe' | 'update'> & Partial<
|
|
|
18
18
|
export type AgentDefaultModelLike = Partial<Pick<AgentDefaultModelConfig, 'currentSelection'>> & {
|
|
19
19
|
saveSelection(next: ModelSelection): Promise<void>;
|
|
20
20
|
};
|
|
21
|
-
/** loader 里的一个 profile 条目:只留运行期 config 的读写面(`update` 走官方 volatile 提交)。 */
|
|
22
|
-
export interface RuntimeEntryLike {
|
|
23
|
-
options: {
|
|
24
|
-
id?: string | undefined;
|
|
25
|
-
config?: Record<string, unknown> | undefined;
|
|
26
|
-
};
|
|
27
|
-
update: (options: {
|
|
28
|
-
config: Record<string, unknown>;
|
|
29
|
-
}) => Promise<unknown>;
|
|
30
|
-
}
|
|
31
21
|
/**
|
|
32
|
-
* 本插件消费的 dsh loader 服务接口:`
|
|
33
|
-
* `update({ config })` 只改运行期 config(volatile 字段原地提交、不重挂载),因此不落盘。
|
|
22
|
+
* 本插件消费的 dsh loader 服务接口:`await()` 等整棵树空闲(设置写入的宿主就绪门)。
|
|
34
23
|
*/
|
|
35
24
|
export interface LoaderLike {
|
|
36
|
-
entries?: (() => RuntimeEntryLike[]) | undefined;
|
|
37
25
|
await?: (() => Promise<unknown>) | undefined;
|
|
38
26
|
}
|
|
39
27
|
export interface PortsOptions {
|
|
@@ -55,18 +43,6 @@ export interface Ports {
|
|
|
55
43
|
agentDefaultModel(): AgentDefaultModelLike | undefined;
|
|
56
44
|
/** workspaceController 的即时宽松读取(缺席 = headless 应用,走隐式工作区)。 */
|
|
57
45
|
hasWorkspaceController(): boolean;
|
|
58
|
-
/**
|
|
59
|
-
* 本端有没有状态行的读者:有网页服务(客户端)或有 dsh 工作区服务都算。
|
|
60
|
-
* 两个都没有 = NPC 那样的一次性 headless 容器,状态段发了也没人看。
|
|
61
|
-
*/
|
|
62
|
-
hasAudience(): boolean;
|
|
63
|
-
/**
|
|
64
|
-
* 状态段的内存行:把展示状态写进该 loader 条目的**运行期 config**(官方 volatile 提交,不落盘、不重挂载),
|
|
65
|
-
* 盘上组合层里用户写的调参原样保留。随后让官方设置服务重算一次摘要——浏览器半边的 describe 镜像靠
|
|
66
|
-
* 它发出的 `settings/document-updated` 重读,否则运行期 config 变更对客户端不可见。
|
|
67
|
-
* 返回 false = 条目还不在 loader 树里(激活时序),调用方按「没发出去」重判。
|
|
68
|
-
*/
|
|
69
|
-
applyStatusRow(ns: string, section: Record<string, unknown>): Promise<boolean>;
|
|
70
46
|
/**
|
|
71
47
|
* 权威写入:把该条目的用户层整体对齐到给定内容。
|
|
72
48
|
* `update` 是**递归合并**(dsh-settings 的 mergeLayers),只增不减——服务端撤掉的条目
|
|
@@ -76,7 +52,5 @@ export interface Ports {
|
|
|
76
52
|
replaceNamespace(settings: SettingsLike, ns: string, section: object): Promise<void>;
|
|
77
53
|
/** 按路径删除本插件写过的键(合并写入删不掉;命中组合层的键会回继承该值)。 */
|
|
78
54
|
clearKeys(settings: SettingsLike, ns: string, keys: readonly string[]): Promise<void>;
|
|
79
|
-
/** 关掉该条目自动生成的设置页(状态段只作展示,不是给用户编辑的表单)。 */
|
|
80
|
-
hideSettingsPage(settings: SettingsLike, owner: Context): void;
|
|
81
55
|
}
|
|
82
56
|
export declare function createPorts(ctx: Context, warn: (message: string) => void, options?: PortsOptions): Ports;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { DomainPlugin } from '../domains/types.ts';
|
|
2
2
|
/** 挂载顺序 = 领域间数据流顺序:models 先落地编译产物(secrets 的回收与 default-model 的选择都依赖它)。 */
|
|
3
3
|
export declare const DOMAINS: readonly DomainPlugin[];
|
|
4
|
+
/** 完整状态字段顺序(基座 + 按注册表顺序的领域字段);状态视图与健康字段共用这一段。 */
|
|
5
|
+
export declare const STATUS_FIELDS: readonly string[];
|
|
6
|
+
/** 健康字段键(能力 + 领域,值是 `状态:说明` 形式的编码串;读侧拆码)。 */
|
|
7
|
+
export declare const HEALTH_FIELDS: readonly string[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RuntimeBootFacts } from '../api/index.ts';
|
|
2
|
+
import type { LoadRun } from '../domains/types.ts';
|
|
3
|
+
import type { Semi2ConfigState } from '../hub/facts.ts';
|
|
4
|
+
import type { OrchestratorInternals } from './orchestrator.ts';
|
|
5
|
+
/** 契约钉了凭据(部署目录 env)= 只读部署:登录 / 登出 / 同步对用户关闭(`CREDENTIAL_SOURCE_READONLY`)。 */
|
|
6
|
+
export declare function credentialsWritable(engine: OrchestratorInternals): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* 一份状态视图:`writable` + 状态字段 + 健康编码(`状态:说明` 串,读侧拆码)+ 工作区 tab。
|
|
9
|
+
* 值全部是展示标量,不含任何密钥;每次请求从编排层现算。
|
|
10
|
+
*/
|
|
11
|
+
export declare function statusSection(engine: OrchestratorInternals): Record<string, unknown>;
|
|
12
|
+
/** 整体读取失败:只落 state.error 与失败态,不注入任何配置(可重载重试)。 */
|
|
13
|
+
export declare function failedState(engine: OrchestratorInternals, code: string): Promise<void>;
|
|
14
|
+
/** boot 事实 → ready 状态(技能切片取自 skills 领域;项目缓存随装配作废,由工作区同步重取)。 */
|
|
15
|
+
export declare function appliedState(engine: OrchestratorInternals, boot: RuntimeBootFacts, compiled: LoadRun['compiled'], errorCode: string | null): Semi2ConfigState;
|
|
16
|
+
/** 健康快照:领域项按注册表(缺席记 unavailable)+ 能力贡献者按登记。 */
|
|
17
|
+
export declare function healthSnapshot(engine: OrchestratorInternals): import("./health.ts").HealthSnapshot;
|
|
@@ -4,15 +4,17 @@ import { type RequestFence } from './fence.ts';
|
|
|
4
4
|
/** 注册路由所需的 webServer 服务(dsh-host-webserver 的 `register`)。 */
|
|
5
5
|
export type WebServerRouteSink = Pick<WebServer, 'register'>;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* 本模块消费的配置项,由入口按运行实例直接传入。
|
|
8
8
|
* 不用 `ctx.get('semi2Health')`——同 fiber 在 apply 里刚 provide 的严格查找会拿到 undefined。
|
|
9
9
|
*/
|
|
10
10
|
export interface RouteRuntime {
|
|
11
11
|
/** 健康快照(诊断与端到端断言用;GET /semi2/health)。 */
|
|
12
12
|
health(): Record<string, unknown>;
|
|
13
|
+
/** 状态视图(GET /semi2/status):浏览器半边经它拉取展示状态。 */
|
|
14
|
+
status(): Record<string, unknown>;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
17
|
+
* 注册只读路由:依赖全部由调用方传入(webServer 来自 `inject(['webServer'])` 作用域、fence 来自
|
|
16
18
|
* `connection`)。模块内不做「依赖缺席就静默不装」的判断;注册一律走 ctx.effect 保证可回收。
|
|
17
19
|
*/
|
|
18
20
|
export declare function installRoutes(ctx: Context, webServer: WebServerRouteSink, runtime: RouteRuntime, fence: RequestFence): void;
|
package/lib/web/routes.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { rejected } from "./fence.js";
|
|
2
2
|
import { sendJson as send } from "./json.js";
|
|
3
3
|
const HEALTH_PATH = '/semi2/health';
|
|
4
|
+
/** 浏览器半边唯一的状态来源:状态 / 健康 / 工作区 tab 按请求现算。 */
|
|
5
|
+
const STATUS_PATH = '/semi2/status';
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
7
|
+
* 注册只读路由:依赖全部由调用方传入(webServer 来自 `inject(['webServer'])` 作用域、fence 来自
|
|
6
8
|
* `connection`)。模块内不做「依赖缺席就静默不装」的判断;注册一律走 ctx.effect 保证可回收。
|
|
7
9
|
*/
|
|
8
10
|
export function installRoutes(ctx, webServer, runtime, fence) {
|
|
9
|
-
ctx.effect(() => webServer.register({
|
|
11
|
+
const get = (path, produce) => ctx.effect(() => webServer.register({
|
|
10
12
|
kind: 'exact',
|
|
11
|
-
path
|
|
13
|
+
path,
|
|
12
14
|
handler: (req, res) => {
|
|
13
15
|
if (rejected(req, res, fence))
|
|
14
16
|
return;
|
|
@@ -16,7 +18,9 @@ export function installRoutes(ctx, webServer, runtime, fence) {
|
|
|
16
18
|
send(res, 405, { code: 'METHOD_NOT_ALLOWED' });
|
|
17
19
|
return;
|
|
18
20
|
}
|
|
19
|
-
send(res, 200,
|
|
21
|
+
send(res, 200, produce());
|
|
20
22
|
},
|
|
21
23
|
}));
|
|
24
|
+
get(HEALTH_PATH, () => runtime.health());
|
|
25
|
+
get(STATUS_PATH, () => runtime.status());
|
|
22
26
|
}
|