@fa-software/semi2-dsh 0.0.16 → 0.0.17-preview.20260929031749

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/lib/api/login.js CHANGED
@@ -1,4 +1,4 @@
1
- // @fa-software/semi2-dsh/configuration/login —— 设置页「登录 semi2-hub」(本地宿主)。
1
+ // @fa-software/semi2-dsh/api/login —— 设置页「登录 semi2-hub」(本地宿主)。
2
2
  // `POST {semi2-hub}/auth/token` 一步换 JWT:无 cookie / CSRF / 服务端会话;semi2-hub 要求 Origin 命中其公开地址,故显式带上 origin。
3
3
  // 密码只存在本次请求内存里(不写日志、不进设置命名空间);凭据落盘由调用方写 dsh credentials 存储,本模块不落盘。
4
4
  import { ConfigurationError } from "../hub/errors.js";
package/lib/client.js CHANGED
@@ -404,11 +404,12 @@ window.__ModuleLoader__.load({
404
404
  });
405
405
  }
406
406
  function WorkspaceTab({ t, controller, tab }) {
407
- if (tab === void 0) return null;
408
407
  const { fields } = useController(controller).projected;
408
+ const slug = tab?.slug ?? "";
409
409
  (0, react.useEffect)(() => {
410
- if (tab.slug !== "") controller.sync(`workspace:${tab.slug}`);
411
- }, [tab.slug]);
410
+ if (slug !== "") controller.sync(`workspace:${slug}`);
411
+ }, [slug, controller]);
412
+ if (tab === void 0) return null;
412
413
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
413
414
  className: "semi2-section",
414
415
  children: [
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["relativeTime","useState","useMemo","Row","IconLinkOutlineRegular","useState","Input","Button","StateDot","DisclosureRow","IconInfoOutlineRegular","IconWarningOutlineRegular","useMemo"],"sources":["../src/contract.ts","../src/client/projection.ts","../src/client/controller.ts","../src/client/domains.ts","../src/client/workspace-tab.tsx","../src/client/page.tsx","../src/client/locales.ts","../src/client/styles.ts","../src/client/index.tsx"],"sourcesContent":["// @fa-software/semi2-dsh/contract —— 运行端契约(本包定义,两个运行端各自 provide)。\n// 适配器在 apply 里同步 provide(插件在首次领取前等它,见 runtime.ts),服务缺席 = 宿主直跑;headless/web 轴由 dsh 服务存在性决定,提示词段由适配器注入。\n\n// import 本模块才能形成模块增强(否则上面的 interface 会遮蔽 Context)。\nimport type {} from '@deepseek-ai/cordis'\n\n/**\n * semi2 提示词段的号段分配(偏移量,基准 = 官方 persona 段)。\n * 四段都落在官方人设区(persona + 1..4),不与任何官方工具 schema 号段重叠;\n * 段名与绝对位置由 `semi2SectionOrder` 现算,官方占用 1..4 时改这里并同步守卫。\n */\nexport const SEMI2_SECTION_ORDERS = {\n selfPortrait: 1,\n projectProfile: 2,\n npcContract: 3,\n cnbWorkstation: 4,\n} as const\n\nexport type Semi2SectionKey = keyof typeof SEMI2_SECTION_ORDERS\n\n/** 动作通道前缀:登录 / 登出 / 同步都经官方 connection 的逻辑 RPC,一次调用落在 POST <前缀>/<endpoint>。 */\nexport const SEMI2_ACTION_CHANNEL = '/semi2'\n\n/** 官方人设前缀段名(号段基准);服务不认这个名字时按 0 兜底。 */\nconst PERSONA_PREFIX_ORDER_NAME = 'DEPLOYMENT_PERSONA_PREFIX'\n\n/** 只读 systemPrompt 的号段解析接口:老宿主没有 `getSectionOrder` 时回落本表偏移。 */\nexport interface SectionOrderSource {\n getSectionOrder?(name: string): number | undefined\n}\n\n/** 段的绝对位置 = 官方 persona 段号 + 本表偏移(服务缺席 / 不认名字时以偏移值兜底)。 */\nexport function semi2SectionOrder(\n systemPrompt: SectionOrderSource | undefined,\n key: Semi2SectionKey,\n): number {\n const persona = systemPrompt?.getSectionOrder?.(PERSONA_PREFIX_ORDER_NAME)\n const base =\n typeof persona === 'number' && Number.isFinite(persona) ? persona : 0\n return base + SEMI2_SECTION_ORDERS[key]\n}\n\n/** 连接 semi2-hub 的凭据;两项齐备才算可连。 */\nexport interface Semi2Credentials {\n configUrl?: string\n accessToken?: string\n}\n\n/** 运行端平台:适配器声明的事实;宿主直跑(无契约)由插件判为 `host`。 */\nexport type Semi2Platform = 'cnb'\n\n/** 运行端标识:hub 的审查日志按它区分来源(`host` = 无适配器的宿主直跑)。 */\nexport type Semi2Channel = 'npc' | 'workstation' | 'host'\n\nexport interface Semi2Runtime {\n /** 由 runtimeFrom*Env 工厂盖章(适配器不自己填)。 */\n platform: Semi2Platform\n /** 同上:本端是哪个运行端,hub 审查日志的来源。 */\n channel: Semi2Channel\n /** semi2-hub 连接凭据;缺省 = 回落 dsh credentials 登录存储。 */\n credentials?: Semi2Credentials\n /**\n * 本次运行的稳定引用(NPC 端 = `<仓库>#<issue|pr>#<号>`),由适配器解析触发后填。\n * 客户端的会话日志按它把「起手 → 交付 → 会话 → 核对」并成一条:会话尚未创建的事件也带得上。\n */\n taskRef?: string\n}\n\n/** cordis 服务名(适配器 provide / 插件 get 共用同一常量)。 */\nexport const SEMI2_RUNTIME_SERVICE = 'semi2Runtime'\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n semi2Runtime: Semi2Runtime\n }\n}\n\nfunction text(value: string | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed === undefined || trimmed === '' ? undefined : trimmed\n}\n\n/**\n * 进程环境表:宿主半边拿 `process.env`,浏览器半边拿到的是一份普通对象。\n * 不用 `NodeJS.ProcessEnv`:浏览器半边的 tsconfig 不带 node 类型。\n */\nexport type EnvLike = Record<string, string | undefined>\n\nfunction credentialsFromEnv(\n env: EnvLike,\n): Semi2Credentials | undefined {\n const configUrl = text(env.SEMI2_CONFIG_URL)\n // Hub 签发的 CNB 接入密钥(经 secret import 注入 SEMI2_ACCESS_TOKEN)优先:它由 Hub 自己校验,\n // 直接作为 Bearer 使用。缺失时才回退平台短期 CNB_TOKEN(换用户密钥)。\n const accessToken =\n text(env.SEMI2_ACCESS_TOKEN) === undefined ? text(env.CNB_TOKEN) : undefined\n return configUrl === undefined && accessToken === undefined\n ? undefined\n : {\n ...(configUrl === undefined ? {} : { configUrl }),\n ...(accessToken === undefined ? {} : { accessToken }),\n }\n}\n\n/** 按需字段:显式 undefined 与缺席等价(exactOptionalPropertyTypes)。 */\ninterface Semi2RuntimeFields {\n credentials?: Semi2Credentials | undefined\n}\n\nfunction runtime(\n platform: Semi2Platform,\n channel: Semi2Channel,\n fields: Semi2RuntimeFields,\n): Semi2Runtime {\n const contract: Semi2Runtime = { platform, channel }\n if (fields.credentials !== undefined) contract.credentials = fields.credentials\n return contract\n}\n\n/**\n * CNB 运行端:优先用 secret import 注入的 hub 接入密钥(`SEMI2_ACCESS_TOKEN`),\n * 缺失时才回退平台短期 `CNB_TOKEN`(插件启动时换成 hub 用户密钥)。\n * channel 由调用它的适配器给(NPC 端 = `npc`,云开发工作站端 = `workstation`)。\n */\nexport function runtimeFromCnbEnv(\n env: EnvLike,\n channel: 'npc' | 'workstation',\n): Semi2Runtime {\n return runtime('cnb', channel, { credentials: credentialsFromEnv(env) })\n}\n","// @fa-software/semi2-dsh/client/projection —— `GET /semi2/status` 应答 → 页面快照的纯投影(无 React / 无官方原子包依赖)。\n// 就绪口径:只有「宿主已从 hub 读到运行配置」(`status: ready`)才算登录成功,其余一律登录态;\n// 空值(请求还没回来 / 早期 boot 的空串)不是状态,沿用上一轮,免得界面闪回登录态。\n\nexport interface WorkspaceTabData {\n slug: string\n title: string\n projectId: string\n project?: string\n syncedAt?: string\n}\n\nexport interface ProjectedSnapshot {\n /** 展示字段(`/semi2/status` 的应答标量);没拿到过数据时为空表。 */\n fields: Record<string, unknown>\n tabs: WorkspaceTabData[]\n writable: boolean\n /** 登录成功 = 宿主已从 hub 读到运行配置。 */\n connected: boolean\n}\n\n/** 一次状态请求的应答帧(缺 `section` = 这一拍没有可用数据)。 */\nexport interface StatusFrame {\n section?: Record<string, unknown>\n writable?: boolean\n}\n\n/** 空快照:还没拿到过任何数据(页面据此停在登录态 / 错误卡)。 */\nfunction emptySnapshot(frame: StatusFrame): ProjectedSnapshot {\n return {\n fields: {},\n tabs: [],\n writable: frame.writable === true,\n connected: false,\n }\n}\n\n/** 投影一帧状态应答;空值沿用上一轮,其余按 `status` 决定是否进控制台。 */\nexport function projectSnapshot(\n frame: StatusFrame,\n previous: ProjectedSnapshot | undefined,\n): ProjectedSnapshot {\n const value = frame.section\n if (value === undefined || typeof value.status !== 'string' || value.status === '') {\n return previous === undefined ? emptySnapshot(frame) : { ...previous, writable: frame.writable === true }\n }\n const tabs = Array.isArray(value.workspaceTabs) ? (value.workspaceTabs as WorkspaceTabData[]) : []\n return {\n fields: value,\n tabs,\n writable: frame.writable === true,\n connected: value.status === 'ready',\n }\n}\n","// @fa-software/semi2-dsh/client/controller —— Semi2 控制器:状态拉取 + 动作(登录/登出/同步)。\n// 交互语义照官方:输入只在提交时发出;写入失败保留草稿并报错;离开页面丢弃草稿。\n// 状态 = 同源 `fetch('/semi2/status')`(信任与会话由官方栅栏施加):\n// 首拉、每个动作返回后、连接重置 / 重连、页面可见时的慢心跳;动作经 `/semi2/<endpoint>` 的 RPC 信封路由\n// (见 web/action-routes);未登录成功一律停在登录态,从未拿到过数据且拉取失败时停在错误卡。\nimport { useEffect, useMemo, useState } from 'react'\nimport { relativeTime } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { SEMI2_ACTION_CHANNEL } from '../contract.ts'\nimport { projectSnapshot } from './projection.ts'\nimport type { ClientContext, Translate } from './official.ts'\nimport type { ProjectedSnapshot } from './projection.ts'\n\nexport type { ProjectedSnapshot, WorkspaceTabData } from './projection.ts'\nexport type { StatusFrame } from './projection.ts'\n\nexport const OVERVIEW_KEY = 'overview'\n\n/** 状态路由:唯一的状态来源。 */\nconst STATUS_PATH = '/semi2/status'\n/** 页面可见时的慢心跳:状态页不是仪表盘,30s 足够接住会话期事实(项目画像等)。 */\nconst REFRESH_INTERVAL_MS = 30_000\n\n/** 数据来源状态:`data` = 拿到过至少一帧(之后的失败沿用旧值,不闪错误卡)。 */\nexport type SourceView = 'loading' | 'error' | 'data'\n\nexport interface ControllerSnapshot {\n projected: ProjectedSnapshot\n view: SourceView\n loginBusy: boolean\n logoutBusy: boolean\n actionError: string | null\n}\n\n/** 一次动作调用的结果:失败只带错误码,成功带 host 回的正文。 */\nexport interface ActionOutcome {\n ok: boolean\n code: string\n body?: unknown\n}\n\nexport interface LoginDraft {\n url: string\n username: string\n password: string\n}\n\n/** 运行平台原值(数据层只存 cnb / host)→ 展示标签(本地化)。 */\nexport function platformText(t: Translate, platform: unknown): string {\n if (typeof platform !== 'string' || platform === '') return '—'\n return t(`platform${platform}`) || platform\n}\n\nexport function statusText(t: Translate, status: unknown): string {\n const table: Record<string, string> = {\n local: t('statusLocal'),\n loading: t('statusLoading'),\n ready: t('statusReady'),\n failed: t('statusFailed'),\n }\n const key = String(status)\n return table[key] ?? key\n}\n\n/** 时间分桶(官方 relativeTime)→ 本字典措辞;解析不了的时间戳原样返回。 */\nfunction timeUnitKey(unit: string): string | null {\n switch (unit) {\n case 'minutes':\n return 'timeMinutes'\n case 'hours':\n return 'timeHours'\n case 'days':\n return 'timeDays'\n case 'months':\n return 'timeMonths'\n case 'years':\n return 'timeYears'\n default:\n return null\n }\n}\n\nexport function timeText(t: Translate, value: string | undefined): string {\n if (value === undefined || value === '') return '—'\n const at = Date.parse(value)\n if (Number.isNaN(at)) return value\n const { unit, n } = relativeTime(at, Date.now())\n const key = timeUnitKey(unit)\n return key === null ? t('timeNow') : t(key, { n })\n}\n\n/** 错误码 → 一句人话(表驱动;查不到原样显示码,非 JSON 应答由调用方落 HTTP 码)。 */\nexport function errorText(t: Translate, code: string): string {\n return t(`err${code}`) || code\n}\n\n/**\n * 一次动作调用的统一出口:官方 connection 的 RPC 客户端 POST 到 `/semi2/<endpoint>` 的官方信封\n * (Host/Origin 信任与浏览器 cookie 认证由官方栅栏施加)。客户端缺席按 WEB_ROUTE_UNAVAILABLE;\n * 失败只回错误码,不伪装成 LOGIN_FAILED。\n */\nasync function callAction(ctx: ClientContext, endpoint: string, payload?: unknown): Promise<ActionOutcome> {\n const rpc = ctx.connection?.rpc\n if (rpc === undefined) return { ok: false, code: 'WEB_ROUTE_UNAVAILABLE' }\n try {\n const result = await rpc.call(SEMI2_ACTION_CHANNEL, endpoint, payload ?? {})\n if (result.ok !== true) {\n const code = typeof result.error?.code === 'string' ? result.error.code : ''\n return { ok: false, code: code === '' ? 'LOGIN_FAILED' : code }\n }\n return { ok: true, code: '', body: result.value }\n } catch {\n return { ok: false, code: 'CONFIG_REQUEST_FAILED' }\n }\n}\n\nexport class Semi2Controller {\n private readonly ctx: ClientContext\n private readonly listeners = new Set<() => void>()\n private readonly disposers: (() => void)[] = []\n private readonly doFetch: typeof fetch\n private projected: ProjectedSnapshot\n private view: SourceView = 'loading'\n private loginBusy = false\n private logoutBusy = false\n private actionError: string | null = null\n\n constructor(ctx: ClientContext) {\n this.ctx = ctx\n this.doFetch = globalThis.fetch.bind(globalThis)\n this.projected = projectSnapshot({}, undefined)\n const offReset = ctx.on?.('connection/reset', () => {\n void this.refresh()\n })\n if (offReset !== undefined) this.disposers.push(() => { offReset() })\n const state = ctx.connection?.state\n if (state !== undefined) {\n this.disposers.push(\n state.subscribe(() => {\n if (state.getSnapshot() === 'connected') void this.refresh()\n }),\n )\n }\n // 无 document 的宿主(测试 / SSR)不心跳:页面不可见时也不必刷新。\n // DOM lib 把 globalThis.document 定成恒在,这里按「可能缺席」的宽视图读,判空不走断言。\n const browser = globalThis as { document?: { visibilityState?: string } }\n if (browser.document !== undefined) {\n const timer = setInterval(() => {\n if (browser.document?.visibilityState !== 'hidden') void this.refresh()\n }, REFRESH_INTERVAL_MS)\n this.disposers.push(() => { clearInterval(timer) })\n }\n void this.refresh()\n }\n\n dispose(): void {\n for (const dispose of this.disposers) dispose()\n this.disposers.length = 0\n this.listeners.clear()\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getSnapshot(): ControllerSnapshot {\n return {\n projected: this.projected,\n view: this.view,\n loginBusy: this.loginBusy,\n logoutBusy: this.logoutBusy,\n actionError: this.actionError,\n }\n }\n\n notify(): void {\n for (const listener of this.listeners) listener()\n }\n\n /** 拉一帧状态:失败只落 `view`(从未拿到过数据才显示错误卡),拿到过数据就沿用旧值。 */\n async refresh(): Promise<void> {\n try {\n const response = await this.doFetch(STATUS_PATH, { headers: { accept: 'application/json' } })\n if (!response.ok) throw new Error(`HTTP ${String(response.status)}`)\n const body: unknown = await response.json()\n if (body === null || typeof body !== 'object' || Array.isArray(body)) throw new Error('invalid payload')\n const section = body as Record<string, unknown>\n this.projected = projectSnapshot(\n { section, writable: section.writable === true },\n this.projected,\n )\n this.view = 'data'\n } catch {\n if (this.view !== 'data') this.view = 'error'\n }\n this.notify()\n }\n\n /** 登录:草稿只在提交时发出;成功清空密码,失败保留草稿并报错;动作后重拉状态。 */\n async login(draft: LoginDraft): Promise<ActionOutcome | undefined> {\n if (this.loginBusy) return\n this.loginBusy = true\n this.actionError = null\n this.notify()\n const outcome = await callAction(this.ctx, 'login', draft)\n this.loginBusy = false\n if (!outcome.ok) this.actionError = outcome.code\n this.notify()\n await this.refresh()\n return outcome\n }\n\n async logout(): Promise<ActionOutcome | undefined> {\n if (this.logoutBusy) return\n this.logoutBusy = true\n this.actionError = null\n this.notify()\n const outcome = await callAction(this.ctx, 'logout', {})\n this.logoutBusy = false\n if (!outcome.ok) this.actionError = outcome.code\n this.notify()\n await this.refresh()\n return outcome\n }\n\n /**\n * 同步:经动作通道调用 host(与登录 / 登出同一条通道),host 侧据此重载。\n * value 带范围:`global`(默认)或 `workspace:<slug>`(只同步该工作区)。\n */\n async sync(value = 'global'): Promise<void> {\n if (!this.getSnapshot().projected.writable) return\n const outcome = await callAction(this.ctx, 'sync', { scope: value })\n if (!outcome.ok) {\n this.actionError = outcome.code\n this.notify()\n }\n await this.refresh()\n }\n}\n\n/** useSyncExternalStore 适配(控制器快照是稳定引用,动作后手动 bump)。 */\nexport function useController(controller: Semi2Controller): ControllerSnapshot & { version: number } {\n const [version, setVersion] = useState(0)\n useEffect(\n () =>\n controller.subscribe(() => {\n setVersion(n => n + 1)\n }),\n [controller],\n )\n return useMemo(() => ({ ...controller.getSnapshot(), version }), [controller, version])\n}\n","// @fa-software/semi2-dsh/client/domains —— 领域行渲染(与 host 注册表对账:五者一致契约测试钉住)。\n// 每个领域一个条目:总览 tab 的领域行 + 工作区 tab 的领域切片;页面骨架只按这张表渲染。\nimport { timeText } from './controller.ts'\nimport type { WorkspaceTabData } from './controller.ts'\nimport type { Translate } from './official.ts'\n\n/** 一行展示:label / value 都已本地化。 */\nexport interface DomainRow {\n key: string\n label: string\n value: string\n}\n\nexport interface DomainSection {\n id: string\n labelKey: string\n /** 总览 tab 的领域行;null = 这个领域在总览里不出行。 */\n overviewRow?: ((t: Translate, fields: Record<string, unknown>) => DomainRow[]) | null\n /** 工作区 tab 的领域切片;缺省 = 该领域不出工作区行。 */\n workspaceRow?: ((t: Translate, tab: WorkspaceTabData) => DomainRow[]) | null\n}\n\n/** host 侧字段是扁平标量:非标量一律按「没有」处理(与文本拼接口径一致)。 */\nexport function scalarText(value: unknown): string {\n return typeof value === 'string' || typeof value === 'number' ? String(value) : ''\n}\n\nexport const DOMAIN_SECTIONS: DomainSection[] = [\n {\n id: 'models',\n labelKey: 'domainModels',\n /** 总览行:默认模型(hub 下发的全局默认)。 */\n overviewRow: (t, fields) => [\n { key: 'model', label: t('model'), value: scalarText(fields.model) || t('none') },\n ],\n },\n {\n id: 'secrets',\n labelKey: 'domainSecrets',\n overviewRow: null,\n },\n {\n id: 'skills',\n labelKey: 'domainSkills',\n /** 总览行:控制台技能条数(空串 = 没拿到权威目录)。 */\n overviewRow: (t, fields) => [\n { key: 'skills', label: t('skills'), value: scalarText(fields.skills) || '—' },\n ],\n },\n {\n id: 'workspace',\n labelKey: 'domainWorkspace',\n /** 总览行:工作区与项目绑定。 */\n overviewRow: (t, fields) => [\n { key: 'workspaceCount', label: t('workspaceTitle'), value: scalarText(fields.workspaceCount) },\n { key: 'projectCount', label: t('projects'), value: scalarText(fields.projectCount) },\n ],\n /** 工作区 tab 切片:项目绑定(只显示该工作区自己的解析结果)。 */\n workspaceRow: (t, tab) => [\n { key: 'slug', label: t('workspaceSlug'), value: tab.slug || '—' },\n {\n key: 'project',\n label: t('workspaceProject'),\n value: tab.project || tab.projectId || '—',\n },\n { key: 'syncedAt', label: t('workspaceSyncedAt'), value: timeText(t, tab.syncedAt) },\n ],\n },\n {\n id: 'default-model',\n labelKey: 'domainDefaultModel',\n overviewRow: null,\n },\n {\n id: 'self-portrait',\n labelKey: 'domainSelfPortrait',\n /** 总览行:注入状态(未配置 / 读取失败 / 已注入 · N 字)。 */\n overviewRow: (t, fields) => [\n {\n key: 'selfPortrait',\n label: t('domainSelfPortrait'),\n value: scalarText(fields.selfPortrait) || t('none'),\n },\n ],\n },\n {\n id: 'project-profile',\n labelKey: 'domainProjectProfile',\n /** 总览行:会话期画像同步结果(未关联 / 已同步 · N 字节 / 读取失败;不展示画像正文)。 */\n overviewRow: (t, fields) => [\n {\n key: 'projectProfile',\n label: t('domainProjectProfile'),\n value: scalarText(fields.projectProfile) || t('none'),\n },\n ],\n },\n {\n id: 'redmine',\n labelKey: 'domainRedmine',\n /** 总览行:hub 下发的 Redmine 地址 + 本人密钥是否齐备(未配置 / 读取失败 / 已启用)。 */\n overviewRow: (t, fields) => [\n { key: 'redmine', label: t('domainRedmine'), value: scalarText(fields.redmine) || t('none') },\n ],\n },\n]\n\n/** 健康项解码(host 写的是 `state:text` 编码串;解析失败按未知处理)。 */\nexport interface HealthItem {\n state: string\n note: string\n}\n\nexport function decodeHealth(raw: unknown): HealthItem | null {\n if (typeof raw !== 'string' || raw === '') return null\n const separator = raw.indexOf(':')\n if (separator === -1) return { state: raw, note: '' }\n return { state: raw.slice(0, separator), note: raw.slice(separator + 1) }\n}\n\n/** 健康行(key = host 命名空间字段;labelKey 自己带,不挂在领域上)。 */\nexport const HEALTH_ROWS = [\n { key: 'healthWeb', labelKey: 'healthWebLabel' },\n { key: 'health:models', labelKey: 'domainModels' },\n { key: 'health:secrets', labelKey: 'domainSecrets' },\n { key: 'health:skills', labelKey: 'domainSkills' },\n { key: 'health:workspace', labelKey: 'domainWorkspace' },\n { key: 'health:default-model', labelKey: 'domainDefaultModel' },\n { key: 'health:self-portrait', labelKey: 'domainSelfPortrait' },\n { key: 'health:project-profile', labelKey: 'domainProjectProfile' },\n { key: 'health:redmine', labelKey: 'domainRedmine' },\n]\n","// @fa-software/semi2-dsh/client/workspace-tab —— 单个工作区 tab 内容(只读该工作区的数据桶)。\n// 该工作区自己的解析结果与「最近更新时间」;不混其它工作区的数据。\n// 只出内容段:页面容器(padding / 页头 / tab 条 / 滚动)与操作按钮归 Semi2Page。\nimport { useEffect } from 'react'\nimport { DOMAIN_SECTIONS } from './domains.ts'\nimport { scalarText } from './domains.ts'\nimport { useController, timeText } from './controller.ts'\nimport type { Semi2Controller, WorkspaceTabData } from './controller.ts'\nimport type { Translate } from './official.ts'\n\nfunction Row({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"semi2-row\">\n <span className=\"semi2-rowKey\">{label}</span>\n <span className=\"semi2-rowValue\">{value}</span>\n </div>\n )\n}\n\nexport function WorkspaceTab(\n { t, controller, tab }: { t: Translate; controller: Semi2Controller; tab: WorkspaceTabData | undefined },\n) {\n if (tab === undefined) return null\n const state = useController(controller)\n const { fields } = state.projected\n // 该工作区 tab 打开即请求一次按需刷新(host 侧按 slug 点名取,不拉全量表)。\n useEffect(() => {\n if (tab.slug !== '') void controller.sync(`workspace:${tab.slug}`)\n }, [tab.slug])\n return (\n <>\n <section className=\"semi2-section\">\n <h2 className=\"semi2-sectionTitle\">{tab.title}</h2>\n {DOMAIN_SECTIONS.map((section) => {\n const rows = section.workspaceRow?.(t, tab) ?? null\n if (rows === null) return null\n return (\n <section className=\"semi2-section\" key={section.id}>\n <h3 className=\"semi2-sectionTitle\">{t(section.labelKey)}</h3>\n <div className=\"semi2-rows\">\n {rows.map(row => (\n <Row key={row.key} label={row.label} value={row.value} />\n ))}\n </div>\n </section>\n )\n })}\n {tab.projectId === '' && <p className=\"semi2-error\">{t('workspaceUnresolved')}</p>}\n </section>\n <p className=\"semi2-hint\">{`${t('loadedAt')}:${timeText(t, scalarText(fields.loadedAt))}`}</p>\n </>\n )\n}\n","// @fa-software/semi2-dsh/client/page —— 主列整页(页头 + tab 条 + 总览/工作区内容)与侧栏图标。\n// 版面照官方主列页:页面容器自带 padding / 居中 / 960 上限,页头 h1+intro 且右侧放操作按钮,\n// tab 为下划线形态且只有一个时不渲染;未连接态只有一张登录卡片。\n// 控件全部来自官方原子包(Button/Input/StateDot/DisclosureRow/图标),样式走 styles.js。\nimport { useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Button, Input, StateDot, DisclosureRow } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { IconInfoOutlineRegular, IconLinkOutlineRegular, IconWarningOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { DOMAIN_SECTIONS, HEALTH_ROWS, decodeHealth } from './domains.ts'\nimport { scalarText } from './domains.ts'\nimport { WorkspaceTab } from './workspace-tab.tsx'\nimport type { LoginDraft, ProjectedSnapshot, Semi2Controller, WorkspaceTabData } from './controller.ts'\nimport type { HealthItem } from './domains.ts'\nimport type { Translate } from './official.ts'\nimport {\n OVERVIEW_KEY,\n errorText,\n platformText,\n statusText,\n timeText,\n useController,\n} from './controller.ts'\nimport type { SourceView } from './controller.ts'\n\n/** 侧栏导航行图标(官方写法:function PanelIcon({ size }))。 */\nexport function Semi2PanelIcon({ size }: { size: number }) {\n return <IconLinkOutlineRegular size={size} />\n}\n\n/** tab 键:slug 暂未解析出来的工作区退回标题占位。 */\nfunction tabKey(tab: WorkspaceTabData): string {\n return tab.slug === '' ? `title:${tab.title}` : `workspace:${tab.slug}`\n}\n\n/** 未登录成功(宿主没读到运行配置 / 状态行还没发布)一律登录态;只有 ready 才进控制台。 */\nfunction connectionMode(projected: ProjectedSnapshot): { showLogin: boolean } {\n return { showLogin: !projected.connected }\n}\n\n/** 动作失败码 → 一句人话(非 JSON 应答已是 HTTP 码,原样显示)。 */\nfunction actionErrorText(t: Translate, code: string): string {\n return code.startsWith('HTTP') ? code : errorText(t, code)\n}\n\nfunction Row({ label, value, muted }: { label: string; value: string; muted?: boolean }) {\n return (\n <div className=\"semi2-row\">\n <span className=\"semi2-rowKey\">{label}</span>\n <span className={muted === true ? 'semi2-rowValue semi2-muted' : 'semi2-rowValue'}>{value}</span>\n </div>\n )\n}\n\n/** 页头:左列标题/说明/告示,右侧 `toolbar` 子节点放当前面的操作按钮(没有就不渲染工具条)。 */\nfunction PageHead(\n { t, hint, error, toolbar }:\n { t: Translate; hint?: string | undefined; error?: string | undefined; toolbar?: ReactNode },\n) {\n return (\n <header className=\"semi2-head\">\n <div className=\"semi2-headText\">\n <h1 className=\"semi2-title\">{t('title')}</h1>\n <p className=\"semi2-intro\">{t('intro')}</p>\n {hint !== undefined && <p className=\"semi2-hint\">{hint}</p>}\n {error !== undefined && <p className=\"semi2-error\">{error}</p>}\n </div>\n {toolbar !== undefined && <div className=\"semi2-toolbar\">{toolbar}</div>}\n </header>\n )\n}\n\nconst DOT_OF_STATUS: Record<string, StateDotState> = { local: 'idle', loading: 'ongoing', ready: 'done', failed: 'error' }\n\nfunction tabDot(fields: Record<string, unknown>, tab: WorkspaceTabData | null): StateDotState {\n if (fields.status === 'failed') return 'error'\n if (tab === null) return DOT_OF_STATUS[scalarText(fields.status)] ?? 'idle'\n return tab.projectId !== '' ? 'done' : 'idle'\n}\n\n/** 登录卡片:草稿只在提交时发出;未登录成功时整页只有它(含宿主回报的失败原因)。 */\nfunction LoginCard(\n { t, projected, controller, actionError }:\n { t: Translate; projected: ProjectedSnapshot; controller: Semi2Controller; actionError: string | undefined },\n) {\n const { fields, writable } = projected\n const hostError = scalarText(fields.error)\n const [draft, setDraft] = useState<LoginDraft>(() => ({\n url: scalarText(fields.consoleUrl),\n username: '',\n password: '',\n }))\n const snapshot = controller.getSnapshot()\n return (\n <section className=\"semi2-card\">\n <h2 className=\"semi2-cardTitle\">{t('loginTitle')}</h2>\n <form\n className=\"semi2-form\"\n onSubmit={(event) => {\n event.preventDefault()\n void controller.login(draft)\n }}\n >\n <Input\n aria-label={t('loginUrl')}\n placeholder={t('loginUrl')}\n value={draft.url}\n readOnly={!writable}\n onChange={(event) =>{ setDraft(prev => ({ ...prev, url: event.target.value })) }}\n />\n <Input\n aria-label={t('loginUsername')}\n placeholder={t('loginUsername')}\n value={draft.username}\n onChange={(event) =>{ setDraft(prev => ({ ...prev, username: event.target.value })) }}\n />\n <Input\n aria-label={t('loginPassword')}\n placeholder={t('loginPassword')}\n type=\"password\"\n value={draft.password}\n onChange={(event) =>{ setDraft(prev => ({ ...prev, password: event.target.value })) }}\n />\n <div className=\"semi2-formActions\">\n <Button type=\"submit\" variant=\"primary\" disabled={snapshot.loginBusy}>\n {snapshot.loginBusy ? t('loginBusy') : t('loginSubmit')}\n </Button>\n </div>\n </form>\n {!writable && <p className=\"semi2-hint\">{t('errCREDENTIAL_SOURCE_READONLY')}</p>}\n {actionError !== undefined && <p className=\"semi2-error\">{actionError}</p>}\n {actionError === undefined && hostError !== '' && (\n <p className=\"semi2-error\">{errorText(t, hostError)}</p>\n )}\n </section>\n )\n}\n\n/** 从未拿到过数据且拉取失败:错误卡 + 手动重试(不伪装成登录表单)。 */\nfunction ErrorCard(\n { t, controller }:\n { t: Translate; controller: Semi2Controller },\n) {\n return (\n <section className=\"semi2-card\">\n <h2 className=\"semi2-cardTitle\">{t('statusUnavailableTitle')}</h2>\n <p className=\"semi2-hint\">{t('statusUnavailableHint')}</p>\n <div className=\"semi2-formActions\">\n <Button variant=\"primary\" onClick={() =>{ void controller.refresh() }}>\n {t('retry')}\n </Button>\n </div>\n </section>\n )\n}\n\nfunction OverviewTab(\n { t, projected, controller, actionError, view }:\n { t: Translate; projected: ProjectedSnapshot; controller: Semi2Controller; actionError: string | undefined; view: SourceView },\n) {\n const { fields } = projected\n const [diagnosticsOpen, setDiagnosticsOpen] = useState(false)\n const { showLogin } = connectionMode(projected)\n if (view === 'error') return <ErrorCard t={t} controller={controller} />\n if (showLogin) return <LoginCard t={t} projected={projected} controller={controller} actionError={actionError} />\n const healthRows = HEALTH_ROWS.map((row): { key: string; labelKey: string; item: HealthItem | null } => ({\n ...row,\n item: decodeHealth(fields[row.key]),\n })).filter((row): row is { key: string; labelKey: string; item: HealthItem } => row.item !== null)\n const degraded = healthRows.filter(row => row.item.state !== 'ok')\n return (\n <>\n <section className=\"semi2-section\">\n <h2 className=\"semi2-sectionTitle\">{t('status')}</h2>\n <div className=\"semi2-rows\">\n <Row label={t('status')} value={statusText(t, fields.status)} />\n <Row label={t('identity')} value={scalarText(fields.identity) || '—'} />\n <Row label={t('platform')} value={platformText(t, fields.platform)} />\n <Row label={t('consoleUrl')} value={scalarText(fields.consoleUrl) || '—'} />\n <Row label={t('loadedAt')} value={timeText(t, scalarText(fields.loadedAt))} />\n </div>\n </section>\n {degraded.length > 0 && (\n <section className=\"semi2-section\">\n <h2 className=\"semi2-sectionTitle\">{t('healthTitle')}</h2>\n <div className=\"semi2-rows\">\n {degraded.map(row => (\n <div className=\"semi2-row\" key={row.key}>\n <span className=\"semi2-rowKey\">{t(row.labelKey)}</span>\n <span className=\"semi2-rowValue\">\n <StateDot state={row.item.state === 'unavailable' ? 'idle' : 'warning'} />\n {row.item.note || row.item.state}\n </span>\n </div>\n ))}\n </div>\n </section>\n )}\n {DOMAIN_SECTIONS.map((section) => {\n const rows = section.overviewRow?.(t, fields) ?? null\n if (rows === null) return null\n return (\n <section className=\"semi2-section\" key={section.id}>\n <h2 className=\"semi2-sectionTitle\">{t(section.labelKey)}</h2>\n <div className=\"semi2-rows\">\n {rows.map(row => (\n <Row key={row.key} label={row.label} value={row.value} />\n ))}\n </div>\n </section>\n )\n })}\n <DisclosureRow\n className=\"semi2-disclosure\"\n icon={fields.error === '' ? <IconInfoOutlineRegular /> : <IconWarningOutlineRegular />}\n title={fields.error === '' ? t('diagnostics') : `${t('diagnostics')}:${scalarText(fields.error)}`}\n open={diagnosticsOpen}\n expandable\n expandOnRowClick\n onToggle={() => {\n setDiagnosticsOpen(open => !open)\n }}\n >\n <div className=\"semi2-rows\">\n <Row label={t('error')} value={fields.error === '' ? t('none') : errorText(t, scalarText(fields.error))} />\n <Row label={t('workspaces')} value={scalarText(fields.workspaces) || t('workspaceNone')} muted />\n <Row label={t('projects')} value={scalarText(fields.projects) || t('none')} muted />\n </div>\n </DisclosureRow>\n </>\n )\n}\n\n/**\n * 主列整页:页头(未连接时无操作按钮)+ 「总览」tab + 按已登记工作区动态生成的 tab\n * (登记表增减即增减;只有一个时不渲染 tab 条)。同步按钮按当前 tab 定域:总览 = 全局,工作区 = 只同步该工作区。\n * `renderSlot` 由官方槽位机制注入(页面声明 keyed 子槽 `semi2.workspace`,各 tab 按 key 贡献内容)。\n */\nexport function Semi2Page(\n props: {\n t: Translate\n controller: Semi2Controller\n renderSlot?: (name: string, params: { view: string }, options: { entryKey: string }) => ReactNode\n },\n) {\n const { t, controller, renderSlot } = props\n const state = useController(controller)\n const { projected } = state\n const [active, setActive] = useState(OVERVIEW_KEY)\n const tabs = useMemo(\n () => projected.tabs.filter(tab => tab.title !== ''),\n [projected.tabs],\n )\n const keys = useMemo(() => [OVERVIEW_KEY, ...tabs.map(tabKey)], [tabs])\n useEffect(() => {\n if (!keys.includes(active)) setActive(OVERVIEW_KEY)\n }, [active, keys])\n const activeTab = tabs.find(tab => tabKey(tab) === active) ?? null\n const snapshot = controller.getSnapshot()\n const { showLogin } = connectionMode(projected)\n const actionError =\n snapshot.actionError === null ? undefined : actionErrorText(t, snapshot.actionError)\n const syncScope = activeTab === null || activeTab.slug === '' ? 'global' : `workspace:${activeTab.slug}`\n return (\n <div className=\"semi2-page\">\n <div className=\"semi2-column\">\n <PageHead\n t={t}\n hint={!showLogin && !projected.writable ? t('errCREDENTIAL_SOURCE_READONLY') : undefined}\n error={showLogin ? undefined : actionError}\n toolbar={\n showLogin ? undefined : (\n <>\n <Button variant=\"outline\" disabled={!projected.writable} onClick={() => void controller.sync(syncScope)}>\n {syncScope === 'global' ? t('sync') : t('syncWorkspace')}\n </Button>\n <Button\n variant=\"outline\"\n disabled={!projected.writable || snapshot.logoutBusy}\n onClick={() => void controller.logout()}\n >\n {snapshot.logoutBusy ? t('logoutBusy') : t('logout')}\n </Button>\n </>\n )\n }\n />\n {tabs.length > 0 && (\n <nav className=\"semi2-tabs\" aria-label={t('tabMenuLabel')}>\n {keys.map((key, index) => {\n const tab =\n index === 0 ? null : (tabs.find(candidate => tabKey(candidate) === key) ?? null)\n const selected = key === active\n return (\n <button\n key={key}\n type=\"button\"\n className=\"semi2-tab\"\n data-active={selected ? 'true' : undefined}\n aria-current={selected ? 'page' : undefined}\n onClick={() =>{ setActive(key) }}\n >\n <span className=\"semi2-tabLabel\">{tab === null ? t('tabOverview') : tab.title}</span>\n <StateDot state={tabDot(projected.fields, tab)} />\n </button>\n )\n })}\n </nav>\n )}\n {active === OVERVIEW_KEY ? (\n <OverviewTab\n t={t}\n projected={projected}\n controller={controller}\n actionError={showLogin ? actionError : undefined}\n view={state.view}\n />\n ) : renderSlot !== undefined ? (\n renderSlot('semi2.workspace', { view: 'page' }, { entryKey: active })\n ) : (\n <WorkspaceTab t={t} controller={controller} tab={activeTab ?? tabs[0]} />\n )}\n </div>\n </div>\n )\n}\n","// @fa-software/semi2-dsh/client/locales —— 浏览器半边字典(NS=semi2;键与 host 字段一一对应)。\n// 中文为主、英文键兜底;错误码对照表是硬契约(§3.6:绝不再把非凭据错误伪装成 LOGIN_FAILED)。\nexport const NS = 'semi2'\n\nexport const zh = {\n panel: 'Semi2',\n title: 'Semi2',\n intro: 'semi2-hub 配置总览',\n tabOverview: '总览',\n tabMenuLabel: '切换工作区',\n status: '状态',\n statusLocal: '未连接',\n statusLoading: '读取中',\n statusReady: '已连接',\n statusFailed: '失败',\n identity: '身份',\n platform: '运行平台',\n platformcnb: 'CNB',\n platformhost: '宿主直跑',\n consoleUrl: 'hub 地址',\n loadedAt: '最近更新',\n timeNow: '刚刚',\n timeMinutes: '{n}分钟',\n timeHours: '{n}小时',\n timeDays: '{n}天',\n timeMonths: '{n}个月',\n timeYears: '{n}年',\n error: '最近失败',\n healthTitle: '能力健康',\n healthWebLabel: '网页能力',\n actions: '操作',\n diagnostics: '诊断',\n loginTitle: '登录 semi2-hub',\n loginUrl: '地址',\n loginUsername: '用户名',\n loginPassword: '密码',\n loginSubmit: '登录',\n logout: '登出',\n logoutBusy: '正在登出…',\n loginBusy: '正在登录…',\n sync: '同步',\n syncWorkspace: '只同步该工作区',\n workspaceTitle: '工作区',\n workspaceSlug: 'git slug',\n workspaceProject: '项目',\n workspaceSyncedAt: '同步时间',\n workspaceUnresolved: '未解析(不归 semi2-hub 管)',\n workspaceNone: '没有已登记的工作区',\n statusUnavailableTitle: '状态暂不可用',\n statusUnavailableHint: '取不到宿主状态(宿主插件未就绪或版本不匹配),可重试',\n retry: '重试',\n domainModels: '模型',\n domainSecrets: '模型密钥',\n domainSkills: '技能',\n domainWorkspace: '工作区',\n domainDefaultModel: '默认模型',\n domainSelfPortrait: '自画像',\n domainProjectProfile: '项目画像',\n domainRedmine: 'Redmine',\n workspaces: '工作区列表',\n projects: '项目绑定',\n model: '默认模型(hub 下发)',\n skills: '控制台技能',\n none: '无',\n errINVALID_CREDENTIALS: '账号或密码不正确',\n errINVALID_CONFIG_URL: 'hub 地址不合法(必须 https,或本机回环 http)',\n errORIGIN_DENIED: '该 hub 不接受这个地址(请用 hub 自己的域名)',\n errCONFIG_REQUEST_FAILED: '连不上 hub(网络、域名解析或证书问题)',\n errCREDENTIAL_SOURCE_READONLY: '地址由部署环境固定,不能在这里修改',\n errCREDENTIAL_STORE_UNAVAILABLE: '本机凭据存储不可用,登录态无法保存',\n errWEB_ROUTE_UNAVAILABLE: '宿主插件没有注册动作接口(版本或装配异常;重启 dsh 或升级插件)',\n errINVALID_INPUT: '输入不完整',\n errMETHOD_NOT_ALLOWED: '请求方法不受支持',\n errLOGIN_FAILED: '登录失败',\n errLOGOUT_FAILED: '登出失败',\n}\n\nexport const en = {\n panel: 'Semi2',\n title: 'Semi2',\n intro: 'semi2-hub configuration',\n tabOverview: 'Overview',\n tabMenuLabel: 'Switch workspace',\n status: 'Status',\n statusLocal: 'Not connected',\n statusLoading: 'Loading',\n statusReady: 'Connected',\n statusFailed: 'Failed',\n identity: 'Identity',\n platform: 'Platform',\n platformcnb: 'CNB',\n platformhost: 'Host',\n consoleUrl: 'Hub URL',\n loadedAt: 'Updated',\n timeNow: 'now',\n timeMinutes: '{n}min',\n timeHours: '{n}h',\n timeDays: '{n}d',\n timeMonths: '{n}mo',\n timeYears: '{n}y',\n error: 'Last error',\n healthTitle: 'Capability health',\n healthWebLabel: 'Web capability',\n actions: 'Actions',\n diagnostics: 'Diagnostics',\n loginTitle: 'Sign in to semi2-hub',\n loginUrl: 'URL',\n loginUsername: 'Username',\n loginPassword: 'Password',\n loginSubmit: 'Sign in',\n logout: 'Sign out',\n logoutBusy: 'Signing out…',\n loginBusy: 'Signing in…',\n sync: 'Sync',\n syncWorkspace: 'Sync this workspace only',\n workspaceTitle: 'Workspace',\n workspaceSlug: 'git slug',\n workspaceProject: 'Project',\n workspaceSyncedAt: 'Synced at',\n workspaceUnresolved: 'Unresolved (not managed by semi2-hub)',\n workspaceNone: 'No registered workspaces',\n statusUnavailableTitle: 'Status unavailable',\n statusUnavailableHint: 'Cannot read host status (host plugin not ready or version mismatch); retry',\n retry: 'Retry',\n domainModels: 'Models',\n domainSecrets: 'Model keys',\n domainSkills: 'Skills',\n domainWorkspace: 'Workspaces',\n domainDefaultModel: 'Default model',\n domainSelfPortrait: 'Self-portrait',\n domainProjectProfile: 'Project profile',\n domainRedmine: 'Redmine',\n workspaces: 'Workspaces',\n projects: 'Project bindings',\n model: 'Default model (from hub)',\n skills: 'Console skills',\n none: 'None',\n errINVALID_CREDENTIALS: 'Incorrect username or password',\n errINVALID_CONFIG_URL: 'Invalid hub URL (https required, or loopback http)',\n errORIGIN_DENIED: 'The hub rejects this address (use the hub\\u2019s own domain)',\n errCONFIG_REQUEST_FAILED: 'Cannot reach the hub (network, DNS, or certificate issue)',\n errCREDENTIAL_SOURCE_READONLY: 'The URL is pinned by the deployment environment',\n errCREDENTIAL_STORE_UNAVAILABLE: 'Local credential store unavailable; sign-in cannot persist',\n errWEB_ROUTE_UNAVAILABLE: 'Host plugin did not register the action routes (version or assembly issue; restart dsh or upgrade the plugin)',\n errINVALID_INPUT: 'Incomplete input',\n errMETHOD_NOT_ALLOWED: 'Method not allowed',\n errLOGIN_FAILED: 'Sign-in failed',\n errLOGOUT_FAILED: 'Sign-out failed',\n}\n","// @fa-software/semi2-dsh/client/styles —— 唯一的自绘样式模块(页面骨架 + tab 条 + 行)。\n// 控件本体全部来自官方原子包(样式随组件自带);版面值照官方主列页(plugin-manager 的 page/tabs/row),\n// 文字层级一律用 --dsw-alias-label-* 语义色,不拿 opacity 压暗。\nconst STYLE_ID = 'semi2-dsh-client-styles'\n\nconst CSS = `\n.semi2-page {\n box-sizing: border-box;\n height: 100%;\n overflow: auto;\n display: flex;\n justify-content: center;\n align-items: flex-start;\n padding: 28px clamp(24px, 4vw, 48px) 48px;\n color: var(--dsw-alias-label-primary);\n}\n.semi2-column { display: flex; flex-direction: column; gap: 32px; width: 100%; max-width: 960px; }\n.semi2-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }\n.semi2-headText { display: flex; flex-direction: column; gap: 4px; min-width: 0; }\n.semi2-title { margin: 0; font-size: 20px; font-weight: 500; line-height: 28px; }\n.semi2-intro { margin: 0; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-secondary); }\n.semi2-toolbar { display: flex; align-items: center; gap: 8px; flex: none; }\n.semi2-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n max-width: 420px;\n padding: 16px;\n border: 0.5px solid var(--dsw-alias-border-l3);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n}\n.semi2-cardTitle { margin: 0; font-size: 14px; font-weight: 500; line-height: 20px; }\n.semi2-tabs {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-end;\n gap: 22px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.semi2-tab {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 240px;\n padding: 7px 1px 9px;\n font: inherit;\n font-size: 13px;\n line-height: 20px;\n color: var(--dsw-alias-label-tertiary);\n background: none;\n border: 0;\n cursor: pointer;\n}\n.semi2-tab:hover, .semi2-tab[data-active='true'] { color: var(--dsw-alias-label-primary); }\n.semi2-tab[data-active='true']::after {\n content: '';\n position: absolute;\n inset: auto 0 -1px;\n height: 2px;\n background: var(--dsw-alias-label-primary);\n}\n.semi2-tabLabel { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.semi2-section { display: flex; flex-direction: column; gap: 4px; }\n.semi2-section > .semi2-section { margin-top: 12px; }\n.semi2-sectionTitle { margin: 0; font-size: 14px; font-weight: 500; line-height: 20px; }\n.semi2-rows { display: flex; flex-direction: column; }\n.semi2-row {\n display: flex;\n gap: 16px;\n align-items: baseline;\n padding: 12px 2px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.semi2-rowKey { flex: none; min-width: 120px; color: var(--dsw-alias-label-tertiary); }\n.semi2-rowValue { display: inline-flex; align-items: center; gap: 6px; min-width: 0; margin: 0; overflow-wrap: anywhere; }\n.semi2-empty { margin: 0; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-secondary); }\n.semi2-form { display: flex; flex-direction: column; gap: 8px; }\n.semi2-formActions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n.semi2-hint { margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }\n.semi2-error { margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-error, var(--dsw-alias-state-error-primary)); }\n.semi2-muted { color: var(--dsw-alias-label-tertiary); }\n.semi2-disclosure { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }\n`\n\n/** 幂等注入一次性 <style>(令牌缺位的极端情况由兜底值兜住)。 */\nexport function injectStyles() {\n if (typeof document === 'undefined') return\n if (document.getElementById(STYLE_ID) !== null) return\n const element = document.createElement('style')\n element.id = STYLE_ID\n element.textContent = CSS\n document.head.append(element)\n}\n","// @fa-software/semi2-dsh/client —— 浏览器半边:侧栏一级入口(与「插件」同级)+ 主列整页。\n// 面板常驻注册(数据自取:`GET /semi2/status`,见 controller);动作走官方逻辑 RPC 通道\n// (信任与认证由官方栅栏施加)。宿主插件缺席时状态路由 404,页面停在错误卡,不留坏数据。\nimport { Semi2Controller } from './controller.ts'\nimport { Semi2Page, Semi2PanelIcon } from './page.tsx'\nimport { WorkspaceTab } from './workspace-tab.tsx'\nimport { NS, zh, en } from './locales.ts'\nimport { injectStyles } from './styles.ts'\nimport type { ClientContext } from './official.ts'\n\nexport const PANEL_ID = 'semi2'\n\n/** 控制器类随入口导出:浏览器模块表不消费它,测试经构建产物驱动状态机。 */\nexport { Semi2Controller }\n\nexport const inject = ['slots', 'connection', 'locale']\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() =>{ ctx.locale.register(NS, { zh, en }) }, 'semi2: dictionaries')\n const t = ctx.locale.bind(NS)\n injectStyles()\n const controller = new Semi2Controller(ctx)\n ctx.effect(\n () => () => {\n controller.dispose()\n },\n 'semi2: controller',\n )\n\n // 工作区 tab 的 keyed 子槽条目:登记表增减即增减(每个 tab 按 key 贡献一份内容)。\n const workspaceEntries = new Map<string, () => void>()\n const syncWorkspaceEntries = (): void => {\n const { tabs } = controller.getSnapshot().projected\n const wanted = new Set(\n tabs.filter(tab => tab.title !== '').map(tab => (tab.slug === '' ? `title:${tab.title}` : `workspace:${tab.slug}`)),\n )\n for (const [key, dispose] of workspaceEntries) {\n if (wanted.has(key)) continue\n dispose()\n workspaceEntries.delete(key)\n }\n for (const key of wanted) {\n if (workspaceEntries.has(key)) continue\n const tab = tabs.find(candidate =>\n candidate.slug === '' ? `title:${candidate.title}` === key : `workspace:${candidate.slug}` === key,\n )\n if (tab === undefined) continue\n const dispose = ctx.slots.inject('semi2.workspace', () =>\n ctx.slots.register(\n {\n name: 'semi2.workspace',\n key,\n locale: NS,\n inject: () => ({ controller, tab }),\n },\n WorkspaceTab,\n ),\n )\n workspaceEntries.set(key, dispose)\n }\n }\n\n // 常驻注册(面板 + 侧栏入口 + 工作区子槽),插件卸载时整组回收——\n // keyed 子槽留着不清的话,重新上架后旧条目再也回收不掉。\n ctx.effect(\n () => {\n syncWorkspaceEntries()\n const registered = new Map<string, () => void>()\n registered.set('tabs', controller.subscribe(syncWorkspaceEntries))\n registered.set(\n 'panel',\n ctx.slots.inject('main', () =>\n ctx.slots.register(\n {\n name: 'main',\n key: PANEL_ID,\n locale: NS,\n children: { 'semi2.workspace': { kind: 'keyed', scope: 'root' } },\n inject: () => ({ controller }),\n },\n Semi2Page,\n ),\n ),\n )\n registered.set(\n 'sidebar',\n ctx.slots.inject('sidebar.panellist', () =>\n ctx.slots.register(\n {\n name: 'sidebar.panellist',\n id: PANEL_ID,\n order: 10,\n label: () => t('panel'),\n locale: NS,\n },\n Semi2PanelIcon,\n ),\n ),\n )\n return () => {\n for (const dispose of registered.values()) dispose()\n registered.clear()\n for (const dispose of workspaceEntries.values()) dispose()\n workspaceEntries.clear()\n }\n },\n 'semi2: sidebar entry + main page',\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["relativeTime","useState","useMemo","Row","IconLinkOutlineRegular","useState","Input","Button","StateDot","DisclosureRow","IconInfoOutlineRegular","IconWarningOutlineRegular","useMemo"],"sources":["../src/contract.ts","../src/client/projection.ts","../src/client/controller.ts","../src/client/domains.ts","../src/client/workspace-tab.tsx","../src/client/page.tsx","../src/client/locales.ts","../src/client/styles.ts","../src/client/index.tsx"],"sourcesContent":["// @fa-software/semi2-dsh/contract —— 运行端契约(本包定义,两个运行端各自 provide)。\n// 适配器在 apply 里同步 provide(插件在首次领取前等它,见 service/connection.ts),服务缺席 = 宿主直跑;headless/web 轴由 dsh 服务存在性决定,提示词段由适配器注入。\n\n// import 本模块才能形成模块增强(否则上面的 interface 会遮蔽 Context)。\nimport type {} from '@deepseek-ai/cordis'\n\n/**\n * semi2 提示词段的号段分配(偏移量,基准 = 官方 persona 段)。\n * 四段都落在官方人设区(persona + 1..4),不与任何官方工具 schema 号段重叠;\n * 段名与绝对位置由 `semi2SectionOrder` 现算,官方占用 1..4 时改这里并同步守卫。\n */\nexport const SEMI2_SECTION_ORDERS = {\n selfPortrait: 1,\n projectProfile: 2,\n npcContract: 3,\n cnbWorkstation: 4,\n} as const\n\nexport type Semi2SectionKey = keyof typeof SEMI2_SECTION_ORDERS\n\n/** 动作通道前缀:登录 / 登出 / 同步都经官方 connection 的逻辑 RPC,一次调用落在 POST <前缀>/<endpoint>。 */\nexport const SEMI2_ACTION_CHANNEL = '/semi2'\n\n/** 官方人设前缀段名(号段基准);服务不认这个名字时按 0 兜底。 */\nconst PERSONA_PREFIX_ORDER_NAME = 'DEPLOYMENT_PERSONA_PREFIX'\n\n/** 只读 systemPrompt 的号段解析接口:老宿主没有 `getSectionOrder` 时回落本表偏移。 */\nexport interface SectionOrderSource {\n getSectionOrder?(name: string): number | undefined\n}\n\n/** 段的绝对位置 = 官方 persona 段号 + 本表偏移(服务缺席 / 不认名字时以偏移值兜底)。 */\nexport function semi2SectionOrder(\n systemPrompt: SectionOrderSource | undefined,\n key: Semi2SectionKey,\n): number {\n const persona = systemPrompt?.getSectionOrder?.(PERSONA_PREFIX_ORDER_NAME)\n const base =\n typeof persona === 'number' && Number.isFinite(persona) ? persona : 0\n return base + SEMI2_SECTION_ORDERS[key]\n}\n\n/** 连接 semi2-hub 的凭据;两项齐备才算可连。 */\nexport interface Semi2Credentials {\n configUrl?: string\n accessToken?: string\n}\n\n/** 运行端平台:适配器声明的事实;宿主直跑(无契约)由插件判为 `host`。 */\nexport type Semi2Platform = 'cnb'\n\n/** 运行端标识:hub 的审查日志按它区分来源(`host` = 无适配器的宿主直跑)。 */\nexport type Semi2Channel = 'npc' | 'workstation' | 'host'\n\nexport interface Semi2Runtime {\n /** 由 runtimeFrom*Env 工厂盖章(适配器不自己填)。 */\n platform: Semi2Platform\n /** 同上:本端是哪个运行端,hub 审查日志的来源。 */\n channel: Semi2Channel\n /** semi2-hub 连接凭据;缺省 = 回落 dsh credentials 登录存储。 */\n credentials?: Semi2Credentials\n /**\n * 本次运行的稳定引用(NPC 端 = `<仓库>#<issue|pr>#<号>`),由适配器解析触发后填。\n * 客户端的会话日志按它把「起手 → 交付 → 会话 → 核对」并成一条:会话尚未创建的事件也带得上。\n */\n taskRef?: string\n}\n\n/** cordis 服务名(适配器 provide / 插件 get 共用同一常量)。 */\nexport const SEMI2_RUNTIME_SERVICE = 'semi2Runtime'\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n semi2Runtime: Semi2Runtime\n }\n}\n\nfunction text(value: string | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed === undefined || trimmed === '' ? undefined : trimmed\n}\n\n/**\n * 进程环境表:宿主半边拿 `process.env`,浏览器半边拿到的是一份普通对象。\n * 不用 `NodeJS.ProcessEnv`:浏览器半边的 tsconfig 不带 node 类型。\n */\nexport type EnvLike = Record<string, string | undefined>\n\nfunction credentialsFromEnv(\n env: EnvLike,\n): Semi2Credentials | undefined {\n const configUrl = text(env.SEMI2_CONFIG_URL)\n // Hub 签发的 CNB 接入密钥(经 secret import 注入 SEMI2_ACCESS_TOKEN)优先:它由 Hub 自己校验,\n // 直接作为 Bearer 使用。缺失时才回退平台短期 CNB_TOKEN(换用户密钥)。\n const accessToken =\n text(env.SEMI2_ACCESS_TOKEN) === undefined ? text(env.CNB_TOKEN) : undefined\n return configUrl === undefined && accessToken === undefined\n ? undefined\n : {\n ...(configUrl === undefined ? {} : { configUrl }),\n ...(accessToken === undefined ? {} : { accessToken }),\n }\n}\n\n/** 按需字段:显式 undefined 与缺席等价(exactOptionalPropertyTypes)。 */\ninterface Semi2RuntimeFields {\n credentials?: Semi2Credentials | undefined\n}\n\nfunction runtime(\n platform: Semi2Platform,\n channel: Semi2Channel,\n fields: Semi2RuntimeFields,\n): Semi2Runtime {\n const contract: Semi2Runtime = { platform, channel }\n if (fields.credentials !== undefined) contract.credentials = fields.credentials\n return contract\n}\n\n/**\n * CNB 运行端:优先用 secret import 注入的 hub 接入密钥(`SEMI2_ACCESS_TOKEN`),\n * 缺失时才回退平台短期 `CNB_TOKEN`(插件启动时换成 hub 用户密钥)。\n * channel 由调用它的适配器给(NPC 端 = `npc`,云开发工作站端 = `workstation`)。\n */\nexport function runtimeFromCnbEnv(\n env: EnvLike,\n channel: 'npc' | 'workstation',\n): Semi2Runtime {\n return runtime('cnb', channel, { credentials: credentialsFromEnv(env) })\n}\n","// @fa-software/semi2-dsh/client/projection —— `GET /semi2/status` 应答 → 页面快照的纯投影(无 React / 无官方原子包依赖)。\n// 就绪口径:只有「宿主已从 hub 读到运行配置」(`status: ready`)才算登录成功,其余一律登录态;\n// 空值(请求还没回来 / 早期 boot 的空串)不是状态,沿用上一轮,免得界面闪回登录态。\n\nexport interface WorkspaceTabData {\n slug: string\n title: string\n projectId: string\n project?: string\n syncedAt?: string\n}\n\nexport interface ProjectedSnapshot {\n /** 展示字段(`/semi2/status` 的应答标量);没拿到过数据时为空表。 */\n fields: Record<string, unknown>\n tabs: WorkspaceTabData[]\n writable: boolean\n /** 登录成功 = 宿主已从 hub 读到运行配置。 */\n connected: boolean\n}\n\n/** 一次状态请求的应答帧(缺 `section` = 这一拍没有可用数据)。 */\nexport interface StatusFrame {\n section?: Record<string, unknown>\n writable?: boolean\n}\n\n/** 空快照:还没拿到过任何数据(页面据此停在登录态 / 错误卡)。 */\nfunction emptySnapshot(frame: StatusFrame): ProjectedSnapshot {\n return {\n fields: {},\n tabs: [],\n writable: frame.writable === true,\n connected: false,\n }\n}\n\n/** 投影一帧状态应答;空值沿用上一轮,其余按 `status` 决定是否进控制台。 */\nexport function projectSnapshot(\n frame: StatusFrame,\n previous: ProjectedSnapshot | undefined,\n): ProjectedSnapshot {\n const value = frame.section\n if (value === undefined || typeof value.status !== 'string' || value.status === '') {\n return previous === undefined ? emptySnapshot(frame) : { ...previous, writable: frame.writable === true }\n }\n const tabs = Array.isArray(value.workspaceTabs) ? (value.workspaceTabs as WorkspaceTabData[]) : []\n return {\n fields: value,\n tabs,\n writable: frame.writable === true,\n connected: value.status === 'ready',\n }\n}\n","// @fa-software/semi2-dsh/client/controller —— Semi2 控制器:状态拉取 + 动作(登录/登出/同步)。\n// 交互语义照官方:输入只在提交时发出;写入失败保留草稿并报错;离开页面丢弃草稿。\n// 状态 = 同源 `fetch('/semi2/status')`(信任与会话由官方栅栏施加):\n// 首拉、每个动作返回后、连接重置 / 重连、页面可见时的慢心跳;动作经 `/semi2/<endpoint>` 的 RPC 信封路由\n// (见 web/action-routes);未登录成功一律停在登录态,从未拿到过数据且拉取失败时停在错误卡。\nimport { useEffect, useMemo, useState } from 'react'\nimport { relativeTime } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { SEMI2_ACTION_CHANNEL } from '../contract.ts'\nimport { projectSnapshot } from './projection.ts'\nimport type { ClientContext, Translate } from './official.ts'\nimport type { ProjectedSnapshot } from './projection.ts'\n\nexport type { ProjectedSnapshot, WorkspaceTabData } from './projection.ts'\nexport type { StatusFrame } from './projection.ts'\n\nexport const OVERVIEW_KEY = 'overview'\n\n/** 状态路由:唯一的状态来源。 */\nconst STATUS_PATH = '/semi2/status'\n/** 页面可见时的慢心跳:状态页不是仪表盘,30s 足够接住会话期事实(项目画像等)。 */\nconst REFRESH_INTERVAL_MS = 30_000\n\n/** 数据来源状态:`data` = 拿到过至少一帧(之后的失败沿用旧值,不闪错误卡)。 */\nexport type SourceView = 'loading' | 'error' | 'data'\n\nexport interface ControllerSnapshot {\n projected: ProjectedSnapshot\n view: SourceView\n loginBusy: boolean\n logoutBusy: boolean\n actionError: string | null\n}\n\n/** 一次动作调用的结果:失败只带错误码,成功带 host 回的正文。 */\nexport interface ActionOutcome {\n ok: boolean\n code: string\n body?: unknown\n}\n\nexport interface LoginDraft {\n url: string\n username: string\n password: string\n}\n\n/** 运行平台原值(数据层只存 cnb / host)→ 展示标签(本地化)。 */\nexport function platformText(t: Translate, platform: unknown): string {\n if (typeof platform !== 'string' || platform === '') return '—'\n return t(`platform${platform}`) || platform\n}\n\nexport function statusText(t: Translate, status: unknown): string {\n const table: Record<string, string> = {\n local: t('statusLocal'),\n loading: t('statusLoading'),\n ready: t('statusReady'),\n failed: t('statusFailed'),\n }\n const key = String(status)\n return table[key] ?? key\n}\n\n/** 时间分桶(官方 relativeTime)→ 本字典措辞;解析不了的时间戳原样返回。 */\nfunction timeUnitKey(unit: string): string | null {\n switch (unit) {\n case 'minutes':\n return 'timeMinutes'\n case 'hours':\n return 'timeHours'\n case 'days':\n return 'timeDays'\n case 'months':\n return 'timeMonths'\n case 'years':\n return 'timeYears'\n default:\n return null\n }\n}\n\nexport function timeText(t: Translate, value: string | undefined): string {\n if (value === undefined || value === '') return '—'\n const at = Date.parse(value)\n if (Number.isNaN(at)) return value\n const { unit, n } = relativeTime(at, Date.now())\n const key = timeUnitKey(unit)\n return key === null ? t('timeNow') : t(key, { n })\n}\n\n/** 错误码 → 一句人话(表驱动;查不到原样显示码,非 JSON 应答由调用方落 HTTP 码)。 */\nexport function errorText(t: Translate, code: string): string {\n return t(`err${code}`) || code\n}\n\n/**\n * 一次动作调用的统一出口:官方 connection 的 RPC 客户端 POST 到 `/semi2/<endpoint>` 的官方信封\n * (Host/Origin 信任与浏览器 cookie 认证由官方栅栏施加)。客户端缺席按 WEB_ROUTE_UNAVAILABLE;\n * 失败只回错误码,不伪装成 LOGIN_FAILED。\n */\nasync function callAction(ctx: ClientContext, endpoint: string, payload?: unknown): Promise<ActionOutcome> {\n const rpc = ctx.connection?.rpc\n if (rpc === undefined) return { ok: false, code: 'WEB_ROUTE_UNAVAILABLE' }\n try {\n const result = await rpc.call(SEMI2_ACTION_CHANNEL, endpoint, payload ?? {})\n if (result.ok !== true) {\n const code = typeof result.error?.code === 'string' ? result.error.code : ''\n return { ok: false, code: code === '' ? 'LOGIN_FAILED' : code }\n }\n return { ok: true, code: '', body: result.value }\n } catch {\n return { ok: false, code: 'CONFIG_REQUEST_FAILED' }\n }\n}\n\nexport class Semi2Controller {\n private readonly ctx: ClientContext\n private readonly listeners = new Set<() => void>()\n private readonly disposers: (() => void)[] = []\n private readonly doFetch: typeof fetch\n private projected: ProjectedSnapshot\n private view: SourceView = 'loading'\n private loginBusy = false\n private logoutBusy = false\n private actionError: string | null = null\n\n constructor(ctx: ClientContext) {\n this.ctx = ctx\n this.doFetch = globalThis.fetch.bind(globalThis)\n this.projected = projectSnapshot({}, undefined)\n const offReset = ctx.on?.('connection/reset', () => {\n void this.refresh()\n })\n if (offReset !== undefined) this.disposers.push(() => { offReset() })\n const state = ctx.connection?.state\n if (state !== undefined) {\n this.disposers.push(\n state.subscribe(() => {\n if (state.getSnapshot() === 'connected') void this.refresh()\n }),\n )\n }\n // 无 document 的宿主(测试 / SSR)不心跳:页面不可见时也不必刷新。\n // DOM lib 把 globalThis.document 定成恒在,这里按「可能缺席」的宽视图读,判空不走断言。\n const browser = globalThis as { document?: { visibilityState?: string } }\n if (browser.document !== undefined) {\n const timer = setInterval(() => {\n if (browser.document?.visibilityState !== 'hidden') void this.refresh()\n }, REFRESH_INTERVAL_MS)\n this.disposers.push(() => { clearInterval(timer) })\n }\n void this.refresh()\n }\n\n dispose(): void {\n for (const dispose of this.disposers) dispose()\n this.disposers.length = 0\n this.listeners.clear()\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getSnapshot(): ControllerSnapshot {\n return {\n projected: this.projected,\n view: this.view,\n loginBusy: this.loginBusy,\n logoutBusy: this.logoutBusy,\n actionError: this.actionError,\n }\n }\n\n notify(): void {\n for (const listener of this.listeners) listener()\n }\n\n /** 拉一帧状态:失败只落 `view`(从未拿到过数据才显示错误卡),拿到过数据就沿用旧值。 */\n async refresh(): Promise<void> {\n try {\n const response = await this.doFetch(STATUS_PATH, { headers: { accept: 'application/json' } })\n if (!response.ok) throw new Error(`HTTP ${String(response.status)}`)\n const body: unknown = await response.json()\n if (body === null || typeof body !== 'object' || Array.isArray(body)) throw new Error('invalid payload')\n const section = body as Record<string, unknown>\n this.projected = projectSnapshot(\n { section, writable: section.writable === true },\n this.projected,\n )\n this.view = 'data'\n } catch {\n if (this.view !== 'data') this.view = 'error'\n }\n this.notify()\n }\n\n /** 登录:草稿只在提交时发出;成功清空密码,失败保留草稿并报错;动作后重拉状态。 */\n async login(draft: LoginDraft): Promise<ActionOutcome | undefined> {\n if (this.loginBusy) return\n this.loginBusy = true\n this.actionError = null\n this.notify()\n const outcome = await callAction(this.ctx, 'login', draft)\n this.loginBusy = false\n if (!outcome.ok) this.actionError = outcome.code\n this.notify()\n await this.refresh()\n return outcome\n }\n\n async logout(): Promise<ActionOutcome | undefined> {\n if (this.logoutBusy) return\n this.logoutBusy = true\n this.actionError = null\n this.notify()\n const outcome = await callAction(this.ctx, 'logout', {})\n this.logoutBusy = false\n if (!outcome.ok) this.actionError = outcome.code\n this.notify()\n await this.refresh()\n return outcome\n }\n\n /**\n * 同步:经动作通道调用 host(与登录 / 登出同一条通道),host 侧据此重载。\n * value 带范围:`global`(默认)或 `workspace:<slug>`(只同步该工作区)。\n */\n async sync(value = 'global'): Promise<void> {\n if (!this.getSnapshot().projected.writable) return\n const outcome = await callAction(this.ctx, 'sync', { scope: value })\n if (!outcome.ok) {\n this.actionError = outcome.code\n this.notify()\n }\n await this.refresh()\n }\n}\n\n/** useSyncExternalStore 适配(控制器快照是稳定引用,动作后手动 bump)。 */\nexport function useController(controller: Semi2Controller): ControllerSnapshot & { version: number } {\n const [version, setVersion] = useState(0)\n useEffect(\n () =>\n controller.subscribe(() => {\n setVersion(n => n + 1)\n }),\n [controller],\n )\n return useMemo(() => ({ ...controller.getSnapshot(), version }), [controller, version])\n}\n","// @fa-software/semi2-dsh/client/domains —— 领域行渲染(与 host 注册表对账:五者一致契约测试钉住)。\n// 每个领域一个条目:总览 tab 的领域行 + 工作区 tab 的领域切片;页面骨架只按这张表渲染。\nimport { timeText } from './controller.ts'\nimport type { WorkspaceTabData } from './controller.ts'\nimport type { Translate } from './official.ts'\n\n/** 一行展示:label / value 都已本地化。 */\nexport interface DomainRow {\n key: string\n label: string\n value: string\n}\n\nexport interface DomainSection {\n id: string\n labelKey: string\n /** 总览 tab 的领域行;null = 这个领域在总览里不出行。 */\n overviewRow?: ((t: Translate, fields: Record<string, unknown>) => DomainRow[]) | null\n /** 工作区 tab 的领域切片;缺省 = 该领域不出工作区行。 */\n workspaceRow?: ((t: Translate, tab: WorkspaceTabData) => DomainRow[]) | null\n}\n\n/** host 侧字段是扁平标量:非标量一律按「没有」处理(与文本拼接口径一致)。 */\nexport function scalarText(value: unknown): string {\n return typeof value === 'string' || typeof value === 'number' ? String(value) : ''\n}\n\nexport const DOMAIN_SECTIONS: DomainSection[] = [\n {\n id: 'models',\n labelKey: 'domainModels',\n /** 总览行:默认模型(hub 下发的全局默认)。 */\n overviewRow: (t, fields) => [\n { key: 'model', label: t('model'), value: scalarText(fields.model) || t('none') },\n ],\n },\n {\n id: 'secrets',\n labelKey: 'domainSecrets',\n overviewRow: null,\n },\n {\n id: 'skills',\n labelKey: 'domainSkills',\n /** 总览行:控制台技能条数(空串 = 没拿到权威目录)。 */\n overviewRow: (t, fields) => [\n { key: 'skills', label: t('skills'), value: scalarText(fields.skills) || '—' },\n ],\n },\n {\n id: 'workspace',\n labelKey: 'domainWorkspace',\n /** 总览行:工作区与项目绑定。 */\n overviewRow: (t, fields) => [\n { key: 'workspaceCount', label: t('workspaceTitle'), value: scalarText(fields.workspaceCount) },\n { key: 'projectCount', label: t('projects'), value: scalarText(fields.projectCount) },\n ],\n /** 工作区 tab 切片:项目绑定(只显示该工作区自己的解析结果)。 */\n workspaceRow: (t, tab) => [\n { key: 'slug', label: t('workspaceSlug'), value: tab.slug || '—' },\n {\n key: 'project',\n label: t('workspaceProject'),\n value: tab.project || tab.projectId || '—',\n },\n { key: 'syncedAt', label: t('workspaceSyncedAt'), value: timeText(t, tab.syncedAt) },\n ],\n },\n {\n id: 'default-model',\n labelKey: 'domainDefaultModel',\n overviewRow: null,\n },\n {\n id: 'self-portrait',\n labelKey: 'domainSelfPortrait',\n /** 总览行:注入状态(未配置 / 读取失败 / 已注入 · N 字)。 */\n overviewRow: (t, fields) => [\n {\n key: 'selfPortrait',\n label: t('domainSelfPortrait'),\n value: scalarText(fields.selfPortrait) || t('none'),\n },\n ],\n },\n {\n id: 'project-profile',\n labelKey: 'domainProjectProfile',\n /** 总览行:会话期画像同步结果(未关联 / 已同步 · N 字节 / 读取失败;不展示画像正文)。 */\n overviewRow: (t, fields) => [\n {\n key: 'projectProfile',\n label: t('domainProjectProfile'),\n value: scalarText(fields.projectProfile) || t('none'),\n },\n ],\n },\n {\n id: 'redmine',\n labelKey: 'domainRedmine',\n /** 总览行:hub 下发的 Redmine 地址 + 本人密钥是否齐备(未配置 / 读取失败 / 已启用)。 */\n overviewRow: (t, fields) => [\n { key: 'redmine', label: t('domainRedmine'), value: scalarText(fields.redmine) || t('none') },\n ],\n },\n]\n\n/** 健康项解码(host 写的是 `state:text` 编码串;解析失败按未知处理)。 */\nexport interface HealthItem {\n state: string\n note: string\n}\n\nexport function decodeHealth(raw: unknown): HealthItem | null {\n if (typeof raw !== 'string' || raw === '') return null\n const separator = raw.indexOf(':')\n if (separator === -1) return { state: raw, note: '' }\n return { state: raw.slice(0, separator), note: raw.slice(separator + 1) }\n}\n\n/** 健康行(key = host 命名空间字段;labelKey 自己带,不挂在领域上)。 */\nexport const HEALTH_ROWS = [\n { key: 'healthWeb', labelKey: 'healthWebLabel' },\n { key: 'health:models', labelKey: 'domainModels' },\n { key: 'health:secrets', labelKey: 'domainSecrets' },\n { key: 'health:skills', labelKey: 'domainSkills' },\n { key: 'health:workspace', labelKey: 'domainWorkspace' },\n { key: 'health:default-model', labelKey: 'domainDefaultModel' },\n { key: 'health:self-portrait', labelKey: 'domainSelfPortrait' },\n { key: 'health:project-profile', labelKey: 'domainProjectProfile' },\n { key: 'health:redmine', labelKey: 'domainRedmine' },\n]\n","// @fa-software/semi2-dsh/client/workspace-tab —— 单个工作区 tab 内容(只读该工作区的数据桶)。\n// 该工作区自己的解析结果与「最近更新时间」;不混其它工作区的数据。\n// 只出内容段:页面容器(padding / 页头 / tab 条 / 滚动)与操作按钮归 Semi2Page。\nimport { useEffect } from 'react'\nimport { DOMAIN_SECTIONS } from './domains.ts'\nimport { scalarText } from './domains.ts'\nimport { useController, timeText } from './controller.ts'\nimport type { Semi2Controller, WorkspaceTabData } from './controller.ts'\nimport type { Translate } from './official.ts'\n\nfunction Row({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"semi2-row\">\n <span className=\"semi2-rowKey\">{label}</span>\n <span className=\"semi2-rowValue\">{value}</span>\n </div>\n )\n}\n\nexport function WorkspaceTab(\n { t, controller, tab }: { t: Translate; controller: Semi2Controller; tab: WorkspaceTabData | undefined },\n) {\n const state = useController(controller)\n const { fields } = state.projected\n const slug = tab?.slug ?? ''\n // 该工作区 tab 打开即请求一次按需刷新(host 侧按 slug 点名取,不拉全量表)。\n useEffect(() => {\n if (slug !== '') void controller.sync(`workspace:${slug}`)\n }, [slug, controller])\n if (tab === undefined) return null\n return (\n <>\n <section className=\"semi2-section\">\n <h2 className=\"semi2-sectionTitle\">{tab.title}</h2>\n {DOMAIN_SECTIONS.map((section) => {\n const rows = section.workspaceRow?.(t, tab) ?? null\n if (rows === null) return null\n return (\n <section className=\"semi2-section\" key={section.id}>\n <h3 className=\"semi2-sectionTitle\">{t(section.labelKey)}</h3>\n <div className=\"semi2-rows\">\n {rows.map(row => (\n <Row key={row.key} label={row.label} value={row.value} />\n ))}\n </div>\n </section>\n )\n })}\n {tab.projectId === '' && <p className=\"semi2-error\">{t('workspaceUnresolved')}</p>}\n </section>\n <p className=\"semi2-hint\">{`${t('loadedAt')}:${timeText(t, scalarText(fields.loadedAt))}`}</p>\n </>\n )\n}\n","// @fa-software/semi2-dsh/client/page —— 主列整页(页头 + tab 条 + 总览/工作区内容)与侧栏图标。\n// 版面照官方主列页:页面容器自带 padding / 居中 / 960 上限,页头 h1+intro 且右侧放操作按钮,\n// tab 为下划线形态且只有一个时不渲染;未连接态只有一张登录卡片。\n// 控件全部来自官方原子包(Button/Input/StateDot/DisclosureRow/图标),样式走 styles.js。\nimport { useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Button, Input, StateDot, DisclosureRow } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { IconInfoOutlineRegular, IconLinkOutlineRegular, IconWarningOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { DOMAIN_SECTIONS, HEALTH_ROWS, decodeHealth } from './domains.ts'\nimport { scalarText } from './domains.ts'\nimport { WorkspaceTab } from './workspace-tab.tsx'\nimport type { LoginDraft, ProjectedSnapshot, Semi2Controller, WorkspaceTabData } from './controller.ts'\nimport type { HealthItem } from './domains.ts'\nimport type { Translate } from './official.ts'\nimport {\n OVERVIEW_KEY,\n errorText,\n platformText,\n statusText,\n timeText,\n useController,\n} from './controller.ts'\nimport type { SourceView } from './controller.ts'\n\n/** 侧栏导航行图标(官方写法:function PanelIcon({ size }))。 */\nexport function Semi2PanelIcon({ size }: { size: number }) {\n return <IconLinkOutlineRegular size={size} />\n}\n\n/** tab 键:slug 暂未解析出来的工作区退回标题占位。 */\nfunction tabKey(tab: WorkspaceTabData): string {\n return tab.slug === '' ? `title:${tab.title}` : `workspace:${tab.slug}`\n}\n\n/** 未登录成功(宿主没读到运行配置 / 状态行还没发布)一律登录态;只有 ready 才进控制台。 */\nfunction connectionMode(projected: ProjectedSnapshot): { showLogin: boolean } {\n return { showLogin: !projected.connected }\n}\n\n/** 动作失败码 → 一句人话(非 JSON 应答已是 HTTP 码,原样显示)。 */\nfunction actionErrorText(t: Translate, code: string): string {\n return code.startsWith('HTTP') ? code : errorText(t, code)\n}\n\nfunction Row({ label, value, muted }: { label: string; value: string; muted?: boolean }) {\n return (\n <div className=\"semi2-row\">\n <span className=\"semi2-rowKey\">{label}</span>\n <span className={muted === true ? 'semi2-rowValue semi2-muted' : 'semi2-rowValue'}>{value}</span>\n </div>\n )\n}\n\n/** 页头:左列标题/说明/告示,右侧 `toolbar` 子节点放当前面的操作按钮(没有就不渲染工具条)。 */\nfunction PageHead(\n { t, hint, error, toolbar }:\n { t: Translate; hint?: string | undefined; error?: string | undefined; toolbar?: ReactNode },\n) {\n return (\n <header className=\"semi2-head\">\n <div className=\"semi2-headText\">\n <h1 className=\"semi2-title\">{t('title')}</h1>\n <p className=\"semi2-intro\">{t('intro')}</p>\n {hint !== undefined && <p className=\"semi2-hint\">{hint}</p>}\n {error !== undefined && <p className=\"semi2-error\">{error}</p>}\n </div>\n {toolbar !== undefined && <div className=\"semi2-toolbar\">{toolbar}</div>}\n </header>\n )\n}\n\nconst DOT_OF_STATUS: Record<string, StateDotState> = { local: 'idle', loading: 'ongoing', ready: 'done', failed: 'error' }\n\nfunction tabDot(fields: Record<string, unknown>, tab: WorkspaceTabData | null): StateDotState {\n if (fields.status === 'failed') return 'error'\n if (tab === null) return DOT_OF_STATUS[scalarText(fields.status)] ?? 'idle'\n return tab.projectId !== '' ? 'done' : 'idle'\n}\n\n/** 登录卡片:草稿只在提交时发出;未登录成功时整页只有它(含宿主回报的失败原因)。 */\nfunction LoginCard(\n { t, projected, controller, actionError }:\n { t: Translate; projected: ProjectedSnapshot; controller: Semi2Controller; actionError: string | undefined },\n) {\n const { fields, writable } = projected\n const hostError = scalarText(fields.error)\n const [draft, setDraft] = useState<LoginDraft>(() => ({\n url: scalarText(fields.consoleUrl),\n username: '',\n password: '',\n }))\n const snapshot = controller.getSnapshot()\n return (\n <section className=\"semi2-card\">\n <h2 className=\"semi2-cardTitle\">{t('loginTitle')}</h2>\n <form\n className=\"semi2-form\"\n onSubmit={(event) => {\n event.preventDefault()\n void controller.login(draft)\n }}\n >\n <Input\n aria-label={t('loginUrl')}\n placeholder={t('loginUrl')}\n value={draft.url}\n readOnly={!writable}\n onChange={(event) => { setDraft(prev => ({ ...prev, url: event.target.value })) }}\n />\n <Input\n aria-label={t('loginUsername')}\n placeholder={t('loginUsername')}\n value={draft.username}\n onChange={(event) => { setDraft(prev => ({ ...prev, username: event.target.value })) }}\n />\n <Input\n aria-label={t('loginPassword')}\n placeholder={t('loginPassword')}\n type=\"password\"\n value={draft.password}\n onChange={(event) => { setDraft(prev => ({ ...prev, password: event.target.value })) }}\n />\n <div className=\"semi2-formActions\">\n <Button type=\"submit\" variant=\"primary\" disabled={snapshot.loginBusy}>\n {snapshot.loginBusy ? t('loginBusy') : t('loginSubmit')}\n </Button>\n </div>\n </form>\n {!writable && <p className=\"semi2-hint\">{t('errCREDENTIAL_SOURCE_READONLY')}</p>}\n {actionError !== undefined && <p className=\"semi2-error\">{actionError}</p>}\n {actionError === undefined && hostError !== '' && (\n <p className=\"semi2-error\">{errorText(t, hostError)}</p>\n )}\n </section>\n )\n}\n\n/** 从未拿到过数据且拉取失败:错误卡 + 手动重试(不伪装成登录表单)。 */\nfunction ErrorCard(\n { t, controller }:\n { t: Translate; controller: Semi2Controller },\n) {\n return (\n <section className=\"semi2-card\">\n <h2 className=\"semi2-cardTitle\">{t('statusUnavailableTitle')}</h2>\n <p className=\"semi2-hint\">{t('statusUnavailableHint')}</p>\n <div className=\"semi2-formActions\">\n <Button variant=\"primary\" onClick={() => { void controller.refresh() }}>\n {t('retry')}\n </Button>\n </div>\n </section>\n )\n}\n\nfunction OverviewTab(\n { t, projected, controller, actionError, view }:\n { t: Translate; projected: ProjectedSnapshot; controller: Semi2Controller; actionError: string | undefined; view: SourceView },\n) {\n const { fields } = projected\n const [diagnosticsOpen, setDiagnosticsOpen] = useState(false)\n const { showLogin } = connectionMode(projected)\n if (view === 'error') return <ErrorCard t={t} controller={controller} />\n if (showLogin) return <LoginCard t={t} projected={projected} controller={controller} actionError={actionError} />\n const healthRows = HEALTH_ROWS.map((row): { key: string; labelKey: string; item: HealthItem | null } => ({\n ...row,\n item: decodeHealth(fields[row.key]),\n })).filter((row): row is { key: string; labelKey: string; item: HealthItem } => row.item !== null)\n const degraded = healthRows.filter(row => row.item.state !== 'ok')\n return (\n <>\n <section className=\"semi2-section\">\n <h2 className=\"semi2-sectionTitle\">{t('status')}</h2>\n <div className=\"semi2-rows\">\n <Row label={t('status')} value={statusText(t, fields.status)} />\n <Row label={t('identity')} value={scalarText(fields.identity) || '—'} />\n <Row label={t('platform')} value={platformText(t, fields.platform)} />\n <Row label={t('consoleUrl')} value={scalarText(fields.consoleUrl) || '—'} />\n <Row label={t('loadedAt')} value={timeText(t, scalarText(fields.loadedAt))} />\n </div>\n </section>\n {degraded.length > 0 && (\n <section className=\"semi2-section\">\n <h2 className=\"semi2-sectionTitle\">{t('healthTitle')}</h2>\n <div className=\"semi2-rows\">\n {degraded.map(row => (\n <div className=\"semi2-row\" key={row.key}>\n <span className=\"semi2-rowKey\">{t(row.labelKey)}</span>\n <span className=\"semi2-rowValue\">\n <StateDot state={row.item.state === 'unavailable' ? 'idle' : 'warning'} />\n {row.item.note || row.item.state}\n </span>\n </div>\n ))}\n </div>\n </section>\n )}\n {DOMAIN_SECTIONS.map((section) => {\n const rows = section.overviewRow?.(t, fields) ?? null\n if (rows === null) return null\n return (\n <section className=\"semi2-section\" key={section.id}>\n <h2 className=\"semi2-sectionTitle\">{t(section.labelKey)}</h2>\n <div className=\"semi2-rows\">\n {rows.map(row => (\n <Row key={row.key} label={row.label} value={row.value} />\n ))}\n </div>\n </section>\n )\n })}\n <DisclosureRow\n className=\"semi2-disclosure\"\n icon={fields.error === '' ? <IconInfoOutlineRegular /> : <IconWarningOutlineRegular />}\n title={fields.error === '' ? t('diagnostics') : `${t('diagnostics')}:${scalarText(fields.error)}`}\n open={diagnosticsOpen}\n expandable\n expandOnRowClick\n onToggle={() => {\n setDiagnosticsOpen(open => !open)\n }}\n >\n <div className=\"semi2-rows\">\n <Row label={t('error')} value={fields.error === '' ? t('none') : errorText(t, scalarText(fields.error))} />\n <Row label={t('workspaces')} value={scalarText(fields.workspaces) || t('workspaceNone')} muted />\n <Row label={t('projects')} value={scalarText(fields.projects) || t('none')} muted />\n </div>\n </DisclosureRow>\n </>\n )\n}\n\n/**\n * 主列整页:页头(未连接时无操作按钮)+ 「总览」tab + 按已登记工作区动态生成的 tab\n * (登记表增减即增减;只有一个时不渲染 tab 条)。同步按钮按当前 tab 定域:总览 = 全局,工作区 = 只同步该工作区。\n * `renderSlot` 由官方槽位机制注入(页面声明 keyed 子槽 `semi2.workspace`,各 tab 按 key 贡献内容)。\n */\nexport function Semi2Page(\n props: {\n t: Translate\n controller: Semi2Controller\n renderSlot?: (name: string, params: { view: string }, options: { entryKey: string }) => ReactNode\n },\n) {\n const { t, controller, renderSlot } = props\n const state = useController(controller)\n const { projected } = state\n const [active, setActive] = useState(OVERVIEW_KEY)\n const tabs = useMemo(\n () => projected.tabs.filter(tab => tab.title !== ''),\n [projected.tabs],\n )\n const keys = useMemo(() => [OVERVIEW_KEY, ...tabs.map(tabKey)], [tabs])\n useEffect(() => {\n if (!keys.includes(active)) setActive(OVERVIEW_KEY)\n }, [active, keys])\n const activeTab = tabs.find(tab => tabKey(tab) === active) ?? null\n const snapshot = controller.getSnapshot()\n const { showLogin } = connectionMode(projected)\n const actionError =\n snapshot.actionError === null ? undefined : actionErrorText(t, snapshot.actionError)\n const syncScope = activeTab === null || activeTab.slug === '' ? 'global' : `workspace:${activeTab.slug}`\n return (\n <div className=\"semi2-page\">\n <div className=\"semi2-column\">\n <PageHead\n t={t}\n hint={!showLogin && !projected.writable ? t('errCREDENTIAL_SOURCE_READONLY') : undefined}\n error={showLogin ? undefined : actionError}\n toolbar={\n showLogin ? undefined : (\n <>\n <Button variant=\"outline\" disabled={!projected.writable} onClick={() => void controller.sync(syncScope)}>\n {syncScope === 'global' ? t('sync') : t('syncWorkspace')}\n </Button>\n <Button\n variant=\"outline\"\n disabled={!projected.writable || snapshot.logoutBusy}\n onClick={() => void controller.logout()}\n >\n {snapshot.logoutBusy ? t('logoutBusy') : t('logout')}\n </Button>\n </>\n )\n }\n />\n {tabs.length > 0 && (\n <nav className=\"semi2-tabs\" aria-label={t('tabMenuLabel')}>\n {keys.map((key, index) => {\n const tab =\n index === 0 ? null : (tabs.find(candidate => tabKey(candidate) === key) ?? null)\n const selected = key === active\n return (\n <button\n key={key}\n type=\"button\"\n className=\"semi2-tab\"\n data-active={selected ? 'true' : undefined}\n aria-current={selected ? 'page' : undefined}\n onClick={() => { setActive(key) }}\n >\n <span className=\"semi2-tabLabel\">{tab === null ? t('tabOverview') : tab.title}</span>\n <StateDot state={tabDot(projected.fields, tab)} />\n </button>\n )\n })}\n </nav>\n )}\n {active === OVERVIEW_KEY ? (\n <OverviewTab\n t={t}\n projected={projected}\n controller={controller}\n actionError={showLogin ? actionError : undefined}\n view={state.view}\n />\n ) : renderSlot !== undefined ? (\n renderSlot('semi2.workspace', { view: 'page' }, { entryKey: active })\n ) : (\n <WorkspaceTab t={t} controller={controller} tab={activeTab ?? tabs[0]} />\n )}\n </div>\n </div>\n )\n}\n","// @fa-software/semi2-dsh/client/locales —— 浏览器半边字典(NS=semi2;键与 host 字段一一对应)。\n// 中文为主、英文键兜底;错误码对照表是硬契约(§3.6:绝不再把非凭据错误伪装成 LOGIN_FAILED)。\nexport const NS = 'semi2'\n\nexport const zh = {\n panel: 'Semi2',\n title: 'Semi2',\n intro: 'semi2-hub 配置总览',\n tabOverview: '总览',\n tabMenuLabel: '切换工作区',\n status: '状态',\n statusLocal: '未连接',\n statusLoading: '读取中',\n statusReady: '已连接',\n statusFailed: '失败',\n identity: '身份',\n platform: '运行平台',\n platformcnb: 'CNB',\n platformhost: '宿主直跑',\n consoleUrl: 'hub 地址',\n loadedAt: '最近更新',\n timeNow: '刚刚',\n timeMinutes: '{n}分钟',\n timeHours: '{n}小时',\n timeDays: '{n}天',\n timeMonths: '{n}个月',\n timeYears: '{n}年',\n error: '最近失败',\n healthTitle: '能力健康',\n healthWebLabel: '网页能力',\n actions: '操作',\n diagnostics: '诊断',\n loginTitle: '登录 semi2-hub',\n loginUrl: '地址',\n loginUsername: '用户名',\n loginPassword: '密码',\n loginSubmit: '登录',\n logout: '登出',\n logoutBusy: '正在登出…',\n loginBusy: '正在登录…',\n sync: '同步',\n syncWorkspace: '只同步该工作区',\n workspaceTitle: '工作区',\n workspaceSlug: 'git slug',\n workspaceProject: '项目',\n workspaceSyncedAt: '同步时间',\n workspaceUnresolved: '未解析(不归 semi2-hub 管)',\n workspaceNone: '没有已登记的工作区',\n statusUnavailableTitle: '状态暂不可用',\n statusUnavailableHint: '取不到宿主状态(宿主插件未就绪或版本不匹配),可重试',\n retry: '重试',\n domainModels: '模型',\n domainSecrets: '模型密钥',\n domainSkills: '技能',\n domainWorkspace: '工作区',\n domainDefaultModel: '默认模型',\n domainSelfPortrait: '自画像',\n domainProjectProfile: '项目画像',\n domainRedmine: 'Redmine',\n workspaces: '工作区列表',\n projects: '项目绑定',\n model: '默认模型(hub 下发)',\n skills: '控制台技能',\n none: '无',\n errINVALID_CREDENTIALS: '账号或密码不正确',\n errINVALID_CONFIG_URL: 'hub 地址不合法(必须 https,或本机回环 http)',\n errORIGIN_DENIED: '该 hub 不接受这个地址(请用 hub 自己的域名)',\n errCONFIG_REQUEST_FAILED: '连不上 hub(网络、域名解析或证书问题)',\n errCREDENTIAL_SOURCE_READONLY: '地址由部署环境固定,不能在这里修改',\n errCREDENTIAL_STORE_UNAVAILABLE: '本机凭据存储不可用,登录态无法保存',\n errWEB_ROUTE_UNAVAILABLE: '宿主插件没有注册动作接口(版本或装配异常;重启 dsh 或升级插件)',\n errINVALID_INPUT: '输入不完整',\n errMETHOD_NOT_ALLOWED: '请求方法不受支持',\n errLOGIN_FAILED: '登录失败',\n errLOGOUT_FAILED: '登出失败',\n}\n\nexport const en = {\n panel: 'Semi2',\n title: 'Semi2',\n intro: 'semi2-hub configuration',\n tabOverview: 'Overview',\n tabMenuLabel: 'Switch workspace',\n status: 'Status',\n statusLocal: 'Not connected',\n statusLoading: 'Loading',\n statusReady: 'Connected',\n statusFailed: 'Failed',\n identity: 'Identity',\n platform: 'Platform',\n platformcnb: 'CNB',\n platformhost: 'Host',\n consoleUrl: 'Hub URL',\n loadedAt: 'Updated',\n timeNow: 'now',\n timeMinutes: '{n}min',\n timeHours: '{n}h',\n timeDays: '{n}d',\n timeMonths: '{n}mo',\n timeYears: '{n}y',\n error: 'Last error',\n healthTitle: 'Capability health',\n healthWebLabel: 'Web capability',\n actions: 'Actions',\n diagnostics: 'Diagnostics',\n loginTitle: 'Sign in to semi2-hub',\n loginUrl: 'URL',\n loginUsername: 'Username',\n loginPassword: 'Password',\n loginSubmit: 'Sign in',\n logout: 'Sign out',\n logoutBusy: 'Signing out…',\n loginBusy: 'Signing in…',\n sync: 'Sync',\n syncWorkspace: 'Sync this workspace only',\n workspaceTitle: 'Workspace',\n workspaceSlug: 'git slug',\n workspaceProject: 'Project',\n workspaceSyncedAt: 'Synced at',\n workspaceUnresolved: 'Unresolved (not managed by semi2-hub)',\n workspaceNone: 'No registered workspaces',\n statusUnavailableTitle: 'Status unavailable',\n statusUnavailableHint: 'Cannot read host status (host plugin not ready or version mismatch); retry',\n retry: 'Retry',\n domainModels: 'Models',\n domainSecrets: 'Model keys',\n domainSkills: 'Skills',\n domainWorkspace: 'Workspaces',\n domainDefaultModel: 'Default model',\n domainSelfPortrait: 'Self-portrait',\n domainProjectProfile: 'Project profile',\n domainRedmine: 'Redmine',\n workspaces: 'Workspaces',\n projects: 'Project bindings',\n model: 'Default model (from hub)',\n skills: 'Console skills',\n none: 'None',\n errINVALID_CREDENTIALS: 'Incorrect username or password',\n errINVALID_CONFIG_URL: 'Invalid hub URL (https required, or loopback http)',\n errORIGIN_DENIED: 'The hub rejects this address (use the hub\\u2019s own domain)',\n errCONFIG_REQUEST_FAILED: 'Cannot reach the hub (network, DNS, or certificate issue)',\n errCREDENTIAL_SOURCE_READONLY: 'The URL is pinned by the deployment environment',\n errCREDENTIAL_STORE_UNAVAILABLE: 'Local credential store unavailable; sign-in cannot persist',\n errWEB_ROUTE_UNAVAILABLE: 'Host plugin did not register the action routes (version or assembly issue; restart dsh or upgrade the plugin)',\n errINVALID_INPUT: 'Incomplete input',\n errMETHOD_NOT_ALLOWED: 'Method not allowed',\n errLOGIN_FAILED: 'Sign-in failed',\n errLOGOUT_FAILED: 'Sign-out failed',\n}\n","// @fa-software/semi2-dsh/client/styles —— 唯一的自绘样式模块(页面骨架 + tab 条 + 行)。\n// 控件本体全部来自官方原子包(样式随组件自带);版面值照官方主列页(plugin-manager 的 page/tabs/row),\n// 文字层级一律用 --dsw-alias-label-* 语义色,不拿 opacity 压暗。\nconst STYLE_ID = 'semi2-dsh-client-styles'\n\nconst CSS = `\n.semi2-page {\n box-sizing: border-box;\n height: 100%;\n overflow: auto;\n display: flex;\n justify-content: center;\n align-items: flex-start;\n padding: 28px clamp(24px, 4vw, 48px) 48px;\n color: var(--dsw-alias-label-primary);\n}\n.semi2-column { display: flex; flex-direction: column; gap: 32px; width: 100%; max-width: 960px; }\n.semi2-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }\n.semi2-headText { display: flex; flex-direction: column; gap: 4px; min-width: 0; }\n.semi2-title { margin: 0; font-size: 20px; font-weight: 500; line-height: 28px; }\n.semi2-intro { margin: 0; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-secondary); }\n.semi2-toolbar { display: flex; align-items: center; gap: 8px; flex: none; }\n.semi2-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n max-width: 420px;\n padding: 16px;\n border: 0.5px solid var(--dsw-alias-border-l3);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-1);\n}\n.semi2-cardTitle { margin: 0; font-size: 14px; font-weight: 500; line-height: 20px; }\n.semi2-tabs {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-end;\n gap: 22px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.semi2-tab {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 240px;\n padding: 7px 1px 9px;\n font: inherit;\n font-size: 13px;\n line-height: 20px;\n color: var(--dsw-alias-label-tertiary);\n background: none;\n border: 0;\n cursor: pointer;\n}\n.semi2-tab:hover, .semi2-tab[data-active='true'] { color: var(--dsw-alias-label-primary); }\n.semi2-tab[data-active='true']::after {\n content: '';\n position: absolute;\n inset: auto 0 -1px;\n height: 2px;\n background: var(--dsw-alias-label-primary);\n}\n.semi2-tabLabel { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.semi2-section { display: flex; flex-direction: column; gap: 4px; }\n.semi2-section > .semi2-section { margin-top: 12px; }\n.semi2-sectionTitle { margin: 0; font-size: 14px; font-weight: 500; line-height: 20px; }\n.semi2-rows { display: flex; flex-direction: column; }\n.semi2-row {\n display: flex;\n gap: 16px;\n align-items: baseline;\n padding: 12px 2px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.semi2-rowKey { flex: none; min-width: 120px; color: var(--dsw-alias-label-tertiary); }\n.semi2-rowValue { display: inline-flex; align-items: center; gap: 6px; min-width: 0; margin: 0; overflow-wrap: anywhere; }\n.semi2-empty { margin: 0; font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-secondary); }\n.semi2-form { display: flex; flex-direction: column; gap: 8px; }\n.semi2-formActions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n.semi2-hint { margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }\n.semi2-error { margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-error, var(--dsw-alias-state-error-primary)); }\n.semi2-muted { color: var(--dsw-alias-label-tertiary); }\n.semi2-disclosure { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }\n`\n\n/** 幂等注入一次性 <style>(令牌缺位的极端情况由兜底值兜住)。 */\nexport function injectStyles() {\n if (typeof document === 'undefined') return\n if (document.getElementById(STYLE_ID) !== null) return\n const element = document.createElement('style')\n element.id = STYLE_ID\n element.textContent = CSS\n document.head.append(element)\n}\n","// @fa-software/semi2-dsh/client —— 浏览器半边:侧栏一级入口(与「插件」同级)+ 主列整页。\n// 面板常驻注册(数据自取:`GET /semi2/status`,见 controller);动作走官方逻辑 RPC 通道\n// (信任与认证由官方栅栏施加)。宿主插件缺席时状态路由 404,页面停在错误卡,不留坏数据。\nimport { Semi2Controller } from './controller.ts'\nimport { Semi2Page, Semi2PanelIcon } from './page.tsx'\nimport { WorkspaceTab } from './workspace-tab.tsx'\nimport { NS, zh, en } from './locales.ts'\nimport { injectStyles } from './styles.ts'\nimport type { ClientContext } from './official.ts'\n\nexport const PANEL_ID = 'semi2'\n\n/** 控制器类随入口导出:浏览器模块表不消费它,测试经构建产物驱动状态机。 */\nexport { Semi2Controller }\n\nexport const inject = ['slots', 'connection', 'locale']\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => { ctx.locale.register(NS, { zh, en }) }, 'semi2: dictionaries')\n const t = ctx.locale.bind(NS)\n injectStyles()\n const controller = new Semi2Controller(ctx)\n ctx.effect(\n () => () => {\n controller.dispose()\n },\n 'semi2: controller',\n )\n\n // 工作区 tab 的 keyed 子槽条目:登记表增减即增减(每个 tab 按 key 贡献一份内容)。\n const workspaceEntries = new Map<string, () => void>()\n const syncWorkspaceEntries = (): void => {\n const { tabs } = controller.getSnapshot().projected\n const wanted = new Set(\n tabs.filter(tab => tab.title !== '').map(tab => (tab.slug === '' ? `title:${tab.title}` : `workspace:${tab.slug}`)),\n )\n for (const [key, dispose] of workspaceEntries) {\n if (wanted.has(key)) continue\n dispose()\n workspaceEntries.delete(key)\n }\n for (const key of wanted) {\n if (workspaceEntries.has(key)) continue\n const tab = tabs.find(candidate =>\n candidate.slug === '' ? `title:${candidate.title}` === key : `workspace:${candidate.slug}` === key,\n )\n if (tab === undefined) continue\n const dispose = ctx.slots.inject('semi2.workspace', () =>\n ctx.slots.register(\n {\n name: 'semi2.workspace',\n key,\n locale: NS,\n inject: () => ({ controller, tab }),\n },\n WorkspaceTab,\n ),\n )\n workspaceEntries.set(key, dispose)\n }\n }\n\n // 常驻注册(面板 + 侧栏入口 + 工作区子槽),插件卸载时整组回收——\n // keyed 子槽留着不清的话,重新上架后旧条目再也回收不掉。\n ctx.effect(\n () => {\n syncWorkspaceEntries()\n const registered = new Map<string, () => void>()\n registered.set('tabs', controller.subscribe(syncWorkspaceEntries))\n registered.set(\n 'panel',\n ctx.slots.inject('main', () =>\n ctx.slots.register(\n {\n name: 'main',\n key: PANEL_ID,\n locale: NS,\n children: { 'semi2.workspace': { kind: 'keyed', scope: 'root' } },\n inject: () => ({ controller }),\n },\n Semi2Page,\n ),\n ),\n )\n registered.set(\n 'sidebar',\n ctx.slots.inject('sidebar.panellist', () =>\n ctx.slots.register(\n {\n name: 'sidebar.panellist',\n id: PANEL_ID,\n order: 10,\n label: () => t('panel'),\n locale: NS,\n },\n Semi2PanelIcon,\n ),\n ),\n )\n return () => {\n for (const dispose of registered.values()) dispose()\n registered.clear()\n for (const dispose of workspaceEntries.values()) dispose()\n workspaceEntries.clear()\n }\n },\n 'semi2: sidebar entry + main page',\n )\n}\n"],"mappings":"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"}
package/lib/contract.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @fa-software/semi2-dsh/contract —— 运行端契约(本包定义,两个运行端各自 provide)。
2
- // 适配器在 apply 里同步 provide(插件在首次领取前等它,见 runtime.ts),服务缺席 = 宿主直跑;headless/web 轴由 dsh 服务存在性决定,提示词段由适配器注入。
2
+ // 适配器在 apply 里同步 provide(插件在首次领取前等它,见 service/connection.ts),服务缺席 = 宿主直跑;headless/web 轴由 dsh 服务存在性决定,提示词段由适配器注入。
3
3
  /**
4
4
  * semi2 提示词段的号段分配(偏移量,基准 = 官方 persona 段)。
5
5
  * 四段都落在官方人设区(persona + 1..4),不与任何官方工具 schema 号段重叠;
@@ -1,12 +1,12 @@
1
- import { compileBoot, connectionRoute, providerProfile, remapSelections, } from "../../hub/compile.js";
2
- import { DEEPSEEK_OVERRIDE_KEYS, SELF_MINTED_ROUTE, sectionValue, userSection, } from "../../service/namespaces.js";
1
+ import { compileBoot, connectionRoute, providerProfile, remapSelections } from "../../hub/compile.js";
2
+ import { DEEPSEEK_OVERRIDE_KEYS, SELF_MINTED_ROUTE } from "../../service/namespaces.js";
3
3
  import { syncWebSearchKeyEnv } from "./web-search.js";
4
4
  /** 从既有 provider profile 上取密钥引用名(回收自铸路由时连带清理其密钥)。 */
5
5
  function apiKeyEnvOf(profile) {
6
6
  const value = profile?.apiKeyEnv;
7
7
  return typeof value === 'string' && value !== '' ? value : undefined;
8
8
  }
9
- /** 段里除某一个键之外的其余键(写 `providers` 时用户写在同一段里的其他键原样带回)。 */
9
+ /** 段里除某一个键之外的其余键(写 `llm-pi-ai` 时用户写在同一段里的其他键原样带回)。 */
10
10
  function withoutKey(section, key) {
11
11
  const rest = {};
12
12
  for (const [name, value] of Object.entries(section))
@@ -14,6 +14,11 @@ function withoutKey(section, key) {
14
14
  rest[name] = value;
15
15
  return rest;
16
16
  }
17
+ function providersOf(section) {
18
+ const providers = section.providers;
19
+ const plain = providers !== null && typeof providers === 'object' && !Array.isArray(providers);
20
+ return plain ? providers : {};
21
+ }
17
22
  export class ModelsDomain {
18
23
  host;
19
24
  id = 'models';
@@ -48,12 +53,19 @@ export class ModelsDomain {
48
53
  await this.host.ports.clearKeys(run.settings, 'llm-deepseek', DEEPSEEK_OVERRIDE_KEYS);
49
54
  }
50
55
  else {
51
- await this.host.ports.replaceNamespace(run.settings, 'llm-deepseek', {
52
- ...userSection(run.settings, 'llm-deepseek'),
53
- apiKeyEnv: remapped.deepseek.apiKeyEnv,
54
- baseURL: remapped.deepseek.baseURL,
55
- models: remapped.deepseek.models,
56
- });
56
+ const deepseek = remapped.deepseek;
57
+ await this.host.ports.replaceNamespace(run.settings, 'llm-deepseek', view => ({
58
+ write: {
59
+ kind: 'section',
60
+ section: {
61
+ ...view.user,
62
+ apiKeyEnv: deepseek.apiKeyEnv,
63
+ baseURL: deepseek.baseURL,
64
+ models: deepseek.models,
65
+ },
66
+ },
67
+ result: undefined,
68
+ }));
57
69
  }
58
70
  this.searchKeyEnv = await syncWebSearchKeyEnv(this.host, run, remapped.deepseek, this.searchKeyEnv);
59
71
  run.compiled = remapped;
@@ -63,20 +75,26 @@ export class ModelsDomain {
63
75
  }
64
76
  /** 登出 / 服务端整体撤下:撤下本领域写进设置段的条目(用户自己的路由原样保留)。 */
65
77
  async revert(run) {
66
- const user = userSection(run.settings, 'llm-pi-ai');
67
- const kept = Object.fromEntries(Object.entries(this.readProviders(run.settings)).filter(([route, profile]) => {
68
- if (this.appliedRoutes.has(route)) {
69
- const env = apiKeyEnvOf(profile);
70
- if (env !== undefined)
71
- run.staleCredentials.push(env);
72
- return false;
73
- }
74
- return true;
75
- }));
76
- await this.host.ports.replaceNamespace(run.settings, 'llm-pi-ai', {
77
- ...withoutKey(user, 'providers'),
78
- providers: kept,
78
+ const stale = await this.host.ports.replaceNamespace(run.settings, 'llm-pi-ai', (view) => {
79
+ const staleCredentials = [];
80
+ const kept = Object.fromEntries(Object.entries(providersOf(view.user)).filter(([route, profile]) => {
81
+ if (this.appliedRoutes.has(route)) {
82
+ const env = apiKeyEnvOf(profile);
83
+ if (env !== undefined)
84
+ staleCredentials.push(env);
85
+ return false;
86
+ }
87
+ return true;
88
+ }));
89
+ return {
90
+ write: {
91
+ kind: 'section',
92
+ section: { ...withoutKey(view.user, 'providers'), providers: kept },
93
+ },
94
+ result: staleCredentials,
95
+ };
79
96
  });
97
+ run.staleCredentials.push(...stale);
80
98
  await this.host.ports.clearKeys(run.settings, 'llm-deepseek', DEEPSEEK_OVERRIDE_KEYS);
81
99
  if (this.searchKeyEnv !== null)
82
100
  await this.host.ports.clearKeys(run.settings, 'web-search-deepseek', ['apiKeyEnv']);
@@ -101,53 +119,54 @@ export class ModelsDomain {
101
119
  * 发行 `llm-pi-ai` 的 `providers` 段:本插件的路由完全对齐服务端现状(含回收),用户自己写的路由原样保留。
102
120
  *
103
121
  * 预设路由键就是 dsh 的提供方 id,与用户手写的路由同名时以用户为准(改用自铸路由),
104
- * 否则会把人家配置里的 `anthropic:` 段整段替换掉。
122
+ * 否则会把人家配置中的 `anthropic:` 段整段替换掉。让位判定看生效值(value 层),
123
+ * 认领回收只认用户层里的自铸名字——组合层条目由官方 `replace` 的 base 合并自行带回。
105
124
  *
106
125
  * @returns 实际落地的路由表(原路由键 → 最终路由键),调用侧据此改写默认模型与项目选择——
107
126
  * 让位后若仍按原路由键选模型,`agent-default-model` 会指向一条属于用户的路由。
108
127
  */
109
128
  async applyProviders(run, compiled) {
110
- const existing = this.readProviders(run.settings);
111
- const injected = {};
112
- const routes = new Map();
113
- for (const [route, provider] of Object.entries(compiled)) {
114
- const taken = Object.hasOwn(existing, route) && !this.appliedRoutes.has(route);
115
- if (!taken) {
116
- injected[route] = providerProfile(provider);
117
- routes.set(route, route);
118
- continue;
129
+ const outcome = await this.host.ports.replaceNamespace(run.settings, 'llm-pi-ai', (view) => {
130
+ const existing = providersOf(view.value);
131
+ const injected = {};
132
+ const routes = new Map();
133
+ for (const [route, provider] of Object.entries(compiled)) {
134
+ const taken = Object.hasOwn(existing, route) && !this.appliedRoutes.has(route);
135
+ if (!taken) {
136
+ injected[route] = providerProfile(provider);
137
+ routes.set(route, route);
138
+ continue;
139
+ }
140
+ // 让位:自铸路由没有目录兜底,必须自带 api / baseURL(providerProfile 会带上)。
141
+ const fallback = connectionRoute(provider.connectionId);
142
+ this.host.warn(`provider 路由 ${route} 已被本地配置占用,改用自铸路由 ${fallback}`);
143
+ injected[fallback] = providerProfile({ ...provider, preset: false });
144
+ routes.set(route, fallback);
119
145
  }
120
- // 让位:自铸路由没有目录兜底,必须自带 api / baseURL(providerProfile 会带上)。
121
- const fallback = connectionRoute(provider.connectionId);
122
- this.host.warn(`provider 路由 ${route} 已被本地配置占用,改用自铸路由 ${fallback}`);
123
- injected[fallback] = providerProfile({ ...provider, preset: false });
124
- routes.set(route, fallback);
125
- }
126
- // 自铸路由只经设置段发行:段里出现的自铸名字按名字认领回收,密钥引用交给 secrets。
127
- const staleCredentials = [];
128
- const kept = Object.fromEntries(Object.entries(existing).filter(([id, profile]) => {
129
- if (this.appliedRoutes.has(id))
146
+ // 自铸路由只经设置段发行:用户层里出现的自铸名字按名字认领回收,密钥引用交给 secrets。
147
+ const staleCredentials = [];
148
+ const kept = Object.fromEntries(Object.entries(providersOf(view.user)).filter(([id, profile]) => {
149
+ if (this.appliedRoutes.has(id))
150
+ return false;
151
+ if (!SELF_MINTED_ROUTE.test(id))
152
+ return true;
153
+ const env = apiKeyEnvOf(profile);
154
+ if (env !== undefined)
155
+ staleCredentials.push(env);
130
156
  return false;
131
- if (!SELF_MINTED_ROUTE.test(id))
132
- return true;
133
- const env = apiKeyEnvOf(profile);
134
- if (env !== undefined)
135
- staleCredentials.push(env);
136
- return false;
137
- }));
138
- await this.host.ports.replaceNamespace(run.settings, 'llm-pi-ai', {
139
- ...withoutKey(userSection(run.settings, 'llm-pi-ai'), 'providers'),
140
- providers: { ...kept, ...injected },
157
+ }));
158
+ return {
159
+ write: {
160
+ kind: 'section',
161
+ section: {
162
+ ...withoutKey(view.user, 'providers'),
163
+ providers: { ...kept, ...injected },
164
+ },
165
+ },
166
+ result: { routes, staleCredentials },
167
+ };
141
168
  });
142
- this.appliedRoutes = new Set(routes.values());
143
- return { routes, staleCredentials };
144
- }
145
- readProviders(settings) {
146
- const providers = sectionValue(settings, 'llm-pi-ai').providers;
147
- return providers !== null &&
148
- typeof providers === 'object' &&
149
- !Array.isArray(providers)
150
- ? providers
151
- : {};
169
+ this.appliedRoutes = new Set(outcome.routes.values());
170
+ return outcome;
152
171
  }
153
172
  }
@@ -1,11 +1,11 @@
1
1
  import { codeOf } from "../../hub/errors.js";
2
- import { hasSection, sectionValue, userSection } from "../../service/namespaces.js";
2
+ import { hasSection } from "../../service/namespaces.js";
3
3
  const WEB_SEARCH_DEEPSEEK_NAMESPACE = 'web-search-deepseek';
4
4
  const WEB_SEARCH_API_KEY_ENV_KEY = 'apiKeyEnv';
5
5
  const DEFAULT_DEEPSEEK_SEARCH_KEY_ENV = 'DEEPSEEK_API_KEY';
6
6
  /**
7
7
  * 官方搜索默认解析 `DEEPSEEK_API_KEY`,而 hub 下发的是连接级引用;这里只对齐引用,
8
- * 搜索端点与其他搜索配置仍归本地配置的所有者。
8
+ * 搜索端点与其他搜索配置仍归本地配置的所有者。接管判定用写瞬间的生效值(官方事务内)。
9
9
  * @returns 本进程写进该段的引用;null = 没写或已交还用户。
10
10
  */
11
11
  export async function syncWebSearchKeyEnv(host, run, deepseek, previous) {
@@ -15,25 +15,29 @@ export async function syncWebSearchKeyEnv(host, run, deepseek, previous) {
15
15
  host.warn('DeepSeek 网页搜索密钥引用同步失败,搜索按当前本地配置继续');
16
16
  return previous;
17
17
  }
18
- const current = webSearchKeyEnvOf(run.settings);
19
- // 用户自写的引用(或组合层默认之外的既有值)不接管:只有我们写过的前值才覆盖。
20
- if (current !== undefined &&
21
- current !== previous &&
22
- current !== DEFAULT_DEEPSEEK_SEARCH_KEY_ENV)
23
- return null;
24
18
  try {
25
- await host.ports.replaceNamespace(run.settings, WEB_SEARCH_DEEPSEEK_NAMESPACE, {
26
- ...userSection(run.settings, WEB_SEARCH_DEEPSEEK_NAMESPACE),
27
- [WEB_SEARCH_API_KEY_ENV_KEY]: deepseek.apiKeyEnv,
19
+ return await host.ports.replaceNamespace(run.settings, WEB_SEARCH_DEEPSEEK_NAMESPACE, (view) => {
20
+ const current = keyEnvOf(view);
21
+ // 用户自写的引用(或组合层默认之外的既有值)不接管:只有我们写过的前值才覆盖。
22
+ if (current !== undefined &&
23
+ current !== previous &&
24
+ current !== DEFAULT_DEEPSEEK_SEARCH_KEY_ENV)
25
+ return { write: null, result: null };
26
+ return {
27
+ write: {
28
+ kind: 'section',
29
+ section: { ...view.user, [WEB_SEARCH_API_KEY_ENV_KEY]: deepseek.apiKeyEnv },
30
+ },
31
+ result: deepseek.apiKeyEnv,
32
+ };
28
33
  });
29
34
  }
30
35
  catch (error) {
31
36
  host.warn(`DeepSeek 网页搜索密钥引用同步失败(${codeOf(error)}),搜索按当前本地配置继续`);
32
37
  return previous;
33
38
  }
34
- return deepseek.apiKeyEnv;
35
39
  }
36
- function webSearchKeyEnvOf(settings) {
37
- const value = sectionValue(settings, WEB_SEARCH_DEEPSEEK_NAMESPACE).apiKeyEnv;
40
+ function keyEnvOf(view) {
41
+ const value = view.value[WEB_SEARCH_API_KEY_ENV_KEY];
38
42
  return typeof value === 'string' && value !== '' ? value : undefined;
39
43
  }
@@ -18,6 +18,14 @@ export function installConfiguration(ctx, options = {}) {
18
18
  // 无 logger 的宿主(单测)忽略。
19
19
  }
20
20
  };
21
+ // 生命周期操作单飞(官方 saveSelection 同款 promise 链):load / 登录 / 登出 / 同步交错时
22
+ // 按到达顺序整段落定——设置事务、credentials 与 saveSelection 三类写由此一并有序。
23
+ let opTail = Promise.resolve();
24
+ const run = (op) => {
25
+ const next = opTail.then(op, op);
26
+ opTail = next.then(() => undefined, () => undefined);
27
+ return next;
28
+ };
21
29
  const ports = createPorts(ctx, warn, options);
22
30
  // api 门面读连接凭据的桥:闭包延迟求值(编排层构造完成后才可能被调用)。
23
31
  const engineRef = { current: undefined };
@@ -34,16 +42,16 @@ export function installConfiguration(ctx, options = {}) {
34
42
  installSessionReporting(orchestrator);
35
43
  void orchestrator
36
44
  .mountDomains()
37
- .then(() => lifecycle.start(orchestrator))
45
+ .then(() => run(() => lifecycle.start(orchestrator)))
38
46
  .catch((error) => {
39
47
  warn(`首次领取失败:${error instanceof Error ? error.message : String(error)}`);
40
48
  });
41
49
  return {
42
50
  config: new ConfigService(orchestrator),
43
51
  session: {
44
- login: input => lifecycle.login(orchestrator, input),
45
- logout: () => lifecycle.logout(orchestrator),
46
- sync: scope => lifecycle.syncByScope(orchestrator, scope),
52
+ login: input => run(() => lifecycle.login(orchestrator, input)),
53
+ logout: () => run(() => lifecycle.logout(orchestrator)),
54
+ sync: scope => run(() => lifecycle.syncByScope(orchestrator, scope)),
47
55
  },
48
56
  health: { snapshot: () => healthSnapshot(orchestrator) },
49
57
  status: () => statusSection(orchestrator),
@@ -1,24 +1,11 @@
1
- /** 本插件自铸的名字形状:路由 `semi2-<32 hex>`、密钥引用 `SEMI2_MODEL_KEY_<大写 hex>`。 */
1
+ /** 本插件自铸的路由名形状:`semi2-<32 hex>`(密钥引用名由 hub/compile 的 connectionApiKeyEnv 自铸)。 */
2
2
  export const SELF_MINTED_ROUTE = /^semi2-[\da-f]{32}$/;
3
- export const SELF_MINTED_KEY_ENV = /^SEMI2_MODEL_KEY_[\dA-F]+$/;
4
3
  /** 本插件在官方 `llm-deepseek` 段表达过的三个键:服务端撤下 DEEPSEEK 连接时一并清掉。 */
5
4
  export const DEEPSEEK_OVERRIDE_KEYS = ['apiKeyEnv', 'baseURL', 'models'];
6
- /** 一个设置段的当前有效值:官方描述符的 `value` 是不透明值,本插件只认对象段,其余按空段处理。 */
7
- export function sectionValue(settings, ns) {
8
- const value = settings.describe().find(entry => entry.ns === ns)?.value;
9
- return isRecord(value) ? value : {};
10
- }
11
5
  /** 设置段是否已可描述(条目 ACTIVE 且声明了 volatile 字段)。 */
12
6
  export function hasSection(settings, ns) {
13
7
  return settings.describe().some(entry => entry.ns === ns);
14
8
  }
15
- /**
16
- * 该段的**用户层**原始内容(只认 `describe()` 的 `user`:组合层与 schema 默认不算用户写过)。
17
- * 写入前用它保住用户写在同一段里的其他键——`replace` 会把用户层整体对齐到给定内容。
18
- */
19
- export function userSection(settings, ns) {
20
- return persistedSection(settings.describe(), ns);
21
- }
22
9
  /**
23
10
  * 该段的**组合层**内容(`describe()` 的 `base`,已带 schema 默认)=部署默认;
24
11
  * 登出撤下默认模型时用它把选择交还给组合层。
@@ -30,8 +17,3 @@ export function baseSection(settings, ns) {
30
17
  function isRecord(value) {
31
18
  return value !== null && typeof value === 'object' && !Array.isArray(value);
32
19
  }
33
- /** 用户层事实(`describe()` 的 `value` 含默认与组合层,`user` 才是用户写下的那一层)。 */
34
- function persistedSection(entries, ns) {
35
- const entry = entries.find(row => row.ns === ns);
36
- return isRecord(entry?.user) ? entry.user : {};
37
- }
@@ -1,31 +1,17 @@
1
- import { setTimeout as delay } from 'node:timers/promises';
2
- import { ConfigurationError } from "../hub/errors.js";
3
- import { createHostWrites } from "./host-writes.js";
1
+ import { transact, } from "./settings-tx.js";
4
2
  const SETTINGS_TIMEOUT_MS = 5_000;
5
3
  const NAMESPACE_WAIT_MS = 2_000;
6
- /** 设置段就绪轮询间隔(纯内部预算,不单列到 Config)。 */
7
- const NAMESPACE_POLL_MS = 50;
4
+ const WRITE_TIMEOUT_MS = 3_000;
8
5
  export function createPorts(ctx, warn, options = {}) {
9
6
  const settingsTimeoutMs = options.settingsTimeoutMs ?? SETTINGS_TIMEOUT_MS;
10
7
  const namespaceWaitMs = options.namespaceWaitMs ?? NAMESPACE_WAIT_MS;
11
- const writes = createHostWrites(warn, {
12
- // 宿主就绪门 = profile 的 loader 条目树:设置写入内部等的就是它。服务缺席(宿主直跑 / 假宿主)立即放行。
13
- ready: () => {
14
- const loader = ctx.get('loader', false);
15
- return Promise.resolve(loader?.await?.());
16
- },
17
- writeTimeoutMs: options.writeTimeoutMs,
18
- });
8
+ const writeTimeoutMs = options.writeTimeoutMs ?? WRITE_TIMEOUT_MS;
9
+ const tx = { warn, namespaceWaitMs, writeTimeoutMs };
19
10
  const service = (key) => new Promise((resolve) => {
20
11
  ctx.inject([key], (injected) => {
21
12
  resolve(injected.get(key));
22
13
  });
23
14
  });
24
- const requireNamespace = async (settings, ns) => {
25
- if (await waitForNamespace(settings, ns, namespaceWaitMs))
26
- return;
27
- throw new ConfigurationError('CONFIG_APPLY_FAILED');
28
- };
29
15
  const hasWorkspaceController = () => ctx.get('workspaceController', false) !== undefined;
30
16
  return {
31
17
  settings: () => {
@@ -43,36 +29,10 @@ export function createPorts(ctx, warn, options = {}) {
43
29
  credentialsNow: () => ctx.get('credentials', false),
44
30
  agentDefaultModel: () => ctx.get('agentDefaultModel', false),
45
31
  hasWorkspaceController,
46
- replaceNamespace: async (settings, ns, section) => {
47
- await requireNamespace(settings, ns);
48
- const replace = settings.replace?.bind(settings);
49
- if (replace === undefined) {
50
- warn(`宿主 settings 无 replace,${ns} 退回合并写入:服务端已撤下的条目可能残留`);
51
- await writes.run(ns, () => settings.update(ns, section));
52
- return;
53
- }
54
- await writes.run(ns, () => replace(ns, section));
55
- },
56
- clearKeys: async (settings, ns, keys) => {
57
- const mutate = settings.mutate?.bind(settings);
58
- if (mutate === undefined) {
59
- warn(`宿主 settings 无 mutate,${ns} 无法按路径删除:服务端已撤销的覆盖可能残留`);
60
- return;
61
- }
62
- await requireNamespace(settings, ns);
63
- const ops = keys.map(key => ({ op: 'unset', path: [key] }));
64
- await writes.run(ns, () => mutate(ns, ops));
65
- },
32
+ replaceNamespace: (settings, ns, plan) => transact(settings, ns, plan, tx),
33
+ clearKeys: (settings, ns, keys) => transact(settings, ns, () => ({
34
+ write: { kind: 'ops', ops: keys.map(key => ({ op: 'unset', path: [key] })) },
35
+ result: undefined,
36
+ }), tx),
66
37
  };
67
38
  }
68
- /** 等设置段可描述(条目 ACTIVE 且声明了 volatile 字段);超时返回 false,不抛。 */
69
- async function waitForNamespace(settings, ns, waitMs) {
70
- const deadline = Date.now() + waitMs;
71
- for (;;) {
72
- if (settings.describe().some(entry => entry.ns === ns))
73
- return true;
74
- if (Date.now() >= deadline)
75
- return false;
76
- await delay(NAMESPACE_POLL_MS);
77
- }
78
- }
@@ -36,7 +36,7 @@ async function scopeOf(engine, cwd) {
36
36
  /** 会话创建:带上工作区与默认模型选择,hub 据此建行。 */
37
37
  async function reportCreated(engine, session) {
38
38
  const scope = await scopeOf(engine, session.header.cwd);
39
- const selection = engine.ctx.get('agentDefaultModel', false)?.currentSelection?.();
39
+ const selection = engine.ports.agentDefaultModel()?.currentSelection?.();
40
40
  await reportEvent(engine, {
41
41
  action: 'SESSION_CREATED',
42
42
  outcome: 'SUCCESS',
@@ -0,0 +1,109 @@
1
+ // @fa-software/semi2-dsh/service/settings-tx —— 官方设置事务:describe 现读 → 带 expectedRevision 写 → 冲突重读重算 → 超时降级。
2
+ // 官方 write 在文件锁内与 Loader 热重载串行(dsh-config-editor 的 edit),跨写者互斥不归本模块;
3
+ // 这里只补官方没有的两样:读改写一致性(CAS + 重算重试)与调用方有界等待。
4
+ // 被超时放弃的那笔仍会照常落定或迟到时被 CAS 拒掉——两种结局都比无条件覆盖安全。
5
+ import { SettingsConflictError } from '@deepseek-ai/dsh-settings';
6
+ import { setTimeout as delay } from 'node:timers/promises';
7
+ import { ConfigurationError } from "../hub/errors.js";
8
+ /** 冲突重试上限(含首次):并发编辑同一段(用户正在设置页写)给两次重算机会。 */
9
+ const MAX_ATTEMPTS = 3;
10
+ /** 设置段就绪轮询间隔(纯内部预算,不单列到 Config)。 */
11
+ const NAMESPACE_POLL_MS = 50;
12
+ /** 到点标记:与「底层真的返回了 undefined」区分开。 */
13
+ const EXPIRED = Symbol('expired');
14
+ /**
15
+ * 一笔官方设置事务。plan 必须是 view 的纯函数(外部输入放闭包里):
16
+ * 冲突重试会用新鲜视图重调它,副作用(记账、集合填充)应以 result 带出而不是改外部状态。
17
+ */
18
+ export async function transact(settings, ns, plan, options) {
19
+ if (!(await waitForNamespace(settings, ns, options.namespaceWaitMs)))
20
+ throw new ConfigurationError('CONFIG_APPLY_FAILED');
21
+ for (let attempt = 1;; attempt += 1) {
22
+ const view = viewOf(settings, ns);
23
+ const outcome = plan(view);
24
+ if (outcome.write == null)
25
+ return outcome.result;
26
+ try {
27
+ const settled = await within(applyWrite(settings, ns, outcome.write, view.revision, options), options.writeTimeoutMs);
28
+ if (settled === EXPIRED) {
29
+ options.warn(`${ns} 设置写入超时(${String(options.writeTimeoutMs)}ms),本端继续,状态可能落后`);
30
+ throw new ConfigurationError('CONFIG_APPLY_FAILED');
31
+ }
32
+ return outcome.result;
33
+ }
34
+ catch (error) {
35
+ if (isConflict(error)) {
36
+ if (attempt < MAX_ATTEMPTS)
37
+ continue;
38
+ options.warn(`${ns} 设置写入连续冲突(${String(MAX_ATTEMPTS)} 次),本轮降级`);
39
+ throw new ConfigurationError('CONFIG_APPLY_FAILED');
40
+ }
41
+ throw error;
42
+ }
43
+ }
44
+ }
45
+ /** 写瞬间视图;宿主不提供 revision 时按 undefined(不带 CAS)透传。 */
46
+ function viewOf(settings, ns) {
47
+ const entry = settings.describe().find(row => row.ns === ns);
48
+ return {
49
+ value: recordOf(entry?.value),
50
+ user: recordOf(entry?.user),
51
+ revision: typeof entry?.revision === 'number' ? entry.revision : undefined,
52
+ };
53
+ }
54
+ function recordOf(value) {
55
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
56
+ ? value
57
+ : {};
58
+ }
59
+ /** 落一笔写:section 走 replace(缺则回退 update 的合并写入)、ops 走 mutate;都带上 CAS。 */
60
+ function applyWrite(settings, ns, write, revision, options) {
61
+ if (write.kind === 'ops') {
62
+ const mutate = settings.mutate?.bind(settings);
63
+ if (mutate === undefined) {
64
+ options.warn(`宿主 settings 无 mutate,${ns} 无法按路径删除:服务端已撤销的覆盖可能残留`);
65
+ return Promise.resolve();
66
+ }
67
+ return mutate(ns, write.ops, revision);
68
+ }
69
+ const replace = settings.replace?.bind(settings);
70
+ if (replace === undefined) {
71
+ options.warn(`宿主 settings 无 replace,${ns} 退回合并写入:服务端已撤下的条目可能残留`);
72
+ return settings.update(ns, write.section, revision);
73
+ }
74
+ return replace(ns, write.section, revision);
75
+ }
76
+ /** 官方冲突(读与写之间有人改了段):按结构判 code,防闭包内模块实例分裂时漏认。 */
77
+ function isConflict(error) {
78
+ return error instanceof SettingsConflictError ||
79
+ (error instanceof Error && error.code === 'SETTINGS_CONFLICT');
80
+ }
81
+ /** 到点返回 {@link EXPIRED},否则原样给出结果;定时器总在返回前清掉。 */
82
+ async function within(promise, ms) {
83
+ let timer;
84
+ try {
85
+ return await Promise.race([
86
+ promise,
87
+ new Promise((resolve) => {
88
+ timer = globalThis.setTimeout(() => {
89
+ resolve(EXPIRED);
90
+ }, ms);
91
+ }),
92
+ ]);
93
+ }
94
+ finally {
95
+ if (timer !== undefined)
96
+ globalThis.clearTimeout(timer);
97
+ }
98
+ }
99
+ /** 等设置段可描述(条目 ACTIVE 且声明了 volatile 字段);超时返回 false,不抛。 */
100
+ export async function waitForNamespace(settings, ns, waitMs) {
101
+ const deadline = Date.now() + waitMs;
102
+ for (;;) {
103
+ if (settings.describe().some(entry => entry.ns === ns))
104
+ return true;
105
+ if (Date.now() >= deadline)
106
+ return false;
107
+ await delay(NAMESPACE_POLL_MS);
108
+ }
109
+ }
@@ -1,5 +1,5 @@
1
1
  import type { DomainHost, DomainService, LoadRun, ModelsPeer, RevertRun } from '../types.ts';
2
- import { type CompiledConfiguration } from '../../hub/compile.ts';
2
+ import type { CompiledConfiguration } from '../../hub/compile.ts';
3
3
  import type { Semi2ConfigState } from '../../hub/facts.ts';
4
4
  export declare class ModelsDomain implements DomainService, ModelsPeer {
5
5
  private readonly host;
@@ -29,11 +29,11 @@ export declare class ModelsDomain implements DomainService, ModelsPeer {
29
29
  * 发行 `llm-pi-ai` 的 `providers` 段:本插件的路由完全对齐服务端现状(含回收),用户自己写的路由原样保留。
30
30
  *
31
31
  * 预设路由键就是 dsh 的提供方 id,与用户手写的路由同名时以用户为准(改用自铸路由),
32
- * 否则会把人家配置里的 `anthropic:` 段整段替换掉。
32
+ * 否则会把人家配置中的 `anthropic:` 段整段替换掉。让位判定看生效值(value 层),
33
+ * 认领回收只认用户层里的自铸名字——组合层条目由官方 `replace` 的 base 合并自行带回。
33
34
  *
34
35
  * @returns 实际落地的路由表(原路由键 → 最终路由键),调用侧据此改写默认模型与项目选择——
35
36
  * 让位后若仍按原路由键选模型,`agent-default-model` 会指向一条属于用户的路由。
36
37
  */
37
38
  private applyProviders;
38
- private readProviders;
39
39
  }
@@ -2,7 +2,7 @@ import type { CompiledDeepSeek } from '../../hub/compile.ts';
2
2
  import type { DomainHost, LoadRun } from '../types.ts';
3
3
  /**
4
4
  * 官方搜索默认解析 `DEEPSEEK_API_KEY`,而 hub 下发的是连接级引用;这里只对齐引用,
5
- * 搜索端点与其他搜索配置仍归本地配置的所有者。
5
+ * 搜索端点与其他搜索配置仍归本地配置的所有者。接管判定用写瞬间的生效值(官方事务内)。
6
6
  * @returns 本进程写进该段的引用;null = 没写或已交还用户。
7
7
  */
8
8
  export declare function syncWebSearchKeyEnv(host: DomainHost, run: LoadRun, deepseek: CompiledDeepSeek | null, previous: string | null): Promise<string | null>;
@@ -1,7 +1,7 @@
1
1
  import type { InputModality, ModelProtocol, ReasoningLevel, RuntimeBootQuery, RuntimeWorkspaceProjectsQuery } from '../api/generated/graphql.ts';
2
2
  /**
3
3
  * 生效可用目录的一条(连接启用 ∧ 模型启用 ∧ 存在性 ∧ 密钥齐备)。
4
- * 根字段可空:取数失败只让该字段为 null,故取 NonNullable 元素类型,null 由 runtime.ts 归一为空集合。
4
+ * 根字段可空:取数失败只让该字段为 null(形状见 api/index.ts 的 RuntimeBootFacts),各领域按降级处理。
5
5
  */
6
6
  export type RuntimeModel = NonNullable<RuntimeBootQuery['runtimeModels']>[number];
7
7
  export type RuntimeConnection = RuntimeModel['connection'];
@@ -1,18 +1,10 @@
1
- import type { SettingsLike } from './ports.ts';
2
- /** 本插件自铸的名字形状:路由 `semi2-<32 hex>`、密钥引用 `SEMI2_MODEL_KEY_<大写 hex>`。 */
1
+ import type { SettingsLike } from './settings-tx.ts';
2
+ /** 本插件自铸的路由名形状:`semi2-<32 hex>`(密钥引用名由 hub/compile 的 connectionApiKeyEnv 自铸)。 */
3
3
  export declare const SELF_MINTED_ROUTE: RegExp;
4
- export declare const SELF_MINTED_KEY_ENV: RegExp;
5
4
  /** 本插件在官方 `llm-deepseek` 段表达过的三个键:服务端撤下 DEEPSEEK 连接时一并清掉。 */
6
5
  export declare const DEEPSEEK_OVERRIDE_KEYS: readonly ["apiKeyEnv", "baseURL", "models"];
7
- /** 一个设置段的当前有效值:官方描述符的 `value` 是不透明值,本插件只认对象段,其余按空段处理。 */
8
- export declare function sectionValue(settings: SettingsLike, ns: string): Record<string, unknown>;
9
6
  /** 设置段是否已可描述(条目 ACTIVE 且声明了 volatile 字段)。 */
10
7
  export declare function hasSection(settings: SettingsLike, ns: string): boolean;
11
- /**
12
- * 该段的**用户层**原始内容(只认 `describe()` 的 `user`:组合层与 schema 默认不算用户写过)。
13
- * 写入前用它保住用户写在同一段里的其他键——`replace` 会把用户层整体对齐到给定内容。
14
- */
15
- export declare function userSection(settings: SettingsLike, ns: string): Record<string, unknown>;
16
8
  /**
17
9
  * 该段的**组合层**内容(`describe()` 的 `base`,已带 schema 默认)=部署默认;
18
10
  * 登出撤下默认模型时用它把选择交还给组合层。
@@ -1,15 +1,9 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  import type { CredentialProvider } from '@deepseek-ai/dsh-credentials';
3
3
  import type { AgentDefaultModelConfig } from '@deepseek-ai/dsh-agent-default-model';
4
- import type { SettingsForms } from '@deepseek-ai/dsh-settings';
5
4
  import type { ModelSelection } from '../hub/compile.ts';
6
- /**
7
- * 本插件消费的 host settings 服务接口:读 / 写口径照官方 `SettingsForms`。
8
- * 设置段 = Profile 插件条目 id,`describe()` 只列「光纤 ACTIVE 且 Config 声明了 volatile 字段」的条目,
9
- * 因此磁盘清理写与读都要先等目标条目可描述(内部 `requireNamespace`);
10
- * `replace` / `mutate` / `configure` 是可缺的降级口。
11
- */
12
- export type SettingsLike = Pick<SettingsForms, 'describe' | 'update'> & Partial<Pick<SettingsForms, 'replace' | 'mutate' | 'configure'>>;
5
+ import { type SettingsLike, type SettingsView, type TxPlan } from './settings-tx.ts';
6
+ export type { SettingsLike, SettingsView, TxPlan } from './settings-tx.ts';
13
7
  /**
14
8
  * 本插件消费的 dsh agentDefaultModel 服务接口(默认模型与无头角色选择)。
15
9
  * 读口取自官方 `AgentDefaultModelConfig`;写入口径不变,档位是 hub 下发的普通字符串
@@ -18,18 +12,12 @@ export type SettingsLike = Pick<SettingsForms, 'describe' | 'update'> & Partial<
18
12
  export type AgentDefaultModelLike = Partial<Pick<AgentDefaultModelConfig, 'currentSelection'>> & {
19
13
  saveSelection(next: ModelSelection): Promise<void>;
20
14
  };
21
- /**
22
- * 本插件消费的 dsh loader 服务接口:`await()` 等整棵树空闲(设置写入的宿主就绪门)。
23
- */
24
- export interface LoaderLike {
25
- await?: (() => Promise<unknown>) | undefined;
26
- }
27
15
  export interface PortsOptions {
28
16
  /** 等 settings 服务的上限(ms)。 */
29
17
  settingsTimeoutMs?: number | undefined;
30
18
  /** 等设置段可读可写的上限(ms)。 */
31
19
  namespaceWaitMs?: number | undefined;
32
- /** 单次写盘的上限(ms;缺省见 host-writes)。 */
20
+ /** 单次写盘的上限(ms;缺省见 settings-tx)。 */
33
21
  writeTimeoutMs?: number | undefined;
34
22
  }
35
23
  export interface Ports {
@@ -44,13 +32,12 @@ export interface Ports {
44
32
  /** workspaceController 的即时宽松读取(缺席 = headless 应用,走隐式工作区)。 */
45
33
  hasWorkspaceController(): boolean;
46
34
  /**
47
- * 权威写入:把该条目的用户层整体对齐到给定内容。
48
- * `update` 是**递归合并**(dsh-settings 的 mergeLayers),只增不减——服务端撤掉的条目
49
- * 会在本地留成残渣,provider 路由尤其致命:目录里已不存在的模型继续可选。
50
- * `replace` 先回到组合层再合并给定字段,所以只用于本插件全权决定的段(`llm-pi-ai` 的 `providers`)。
35
+ * 权威写入(官方设置事务):plan 拿写瞬间的 value / user / revision,冲突自动重算重试。
36
+ * `replace` 语义 = base ⊕ section,只用于本插件全权决定的段(`llm-pi-ai` 的 `providers`、
37
+ * `llm-deepseek` 的三键、`web-search-deepseek` 的 `apiKeyEnv`);别的键用 `view.user` 原样带回。
51
38
  */
52
- replaceNamespace(settings: SettingsLike, ns: string, section: object): Promise<void>;
53
- /** 按路径删除本插件写过的键(合并写入删不掉;命中组合层的键会回继承该值)。 */
39
+ replaceNamespace<T>(settings: SettingsLike, ns: string, plan: (view: SettingsView) => TxPlan<T>): Promise<T>;
40
+ /** 按路径删除本插件写过的键(官方 mutate;命中组合层的键会回继承该值)。 */
54
41
  clearKeys(settings: SettingsLike, ns: string, keys: readonly string[]): Promise<void>;
55
42
  }
56
43
  export declare function createPorts(ctx: Context, warn: (message: string) => void, options?: PortsOptions): Ports;
@@ -1,10 +1,13 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  export type { Session } from '@deepseek-ai/dsh-session';
3
3
  import { type AuditEngine } from './audit.ts';
4
+ import type { Ports } from './ports.ts';
4
5
  import type { WorkspaceEntry } from '../hub/workspace-book.ts';
5
6
  /** 会话上报实际消费的编排事实与工作区 peer。 */
6
7
  export interface SessionEngine extends AuditEngine {
7
8
  ctx: Context;
9
+ /** 默认模型读口(经 ports 的统一宽松查找,不另碰 ctx.get)。 */
10
+ ports: Pick<Ports, 'agentDefaultModel'>;
8
11
  workspace(): {
9
12
  tracked(): WorkspaceEntry[];
10
13
  };
@@ -0,0 +1,42 @@
1
+ import type { SettingsForms, SettingsPathOp } from '@deepseek-ai/dsh-settings';
2
+ /**
3
+ * 本插件消费的 host settings 服务接口:读 / 写口径照官方 `SettingsForms`。
4
+ * 设置段 = Profile 插件条目 id,`describe()` 只列「光纤 ACTIVE 且 Config 声明了 volatile 字段」的条目,
5
+ * 因此写入前要先等目标条目可描述;`replace` / `mutate` 是可缺的降级口。
6
+ */
7
+ export type SettingsLike = Pick<SettingsForms, 'describe' | 'update'> & Partial<Pick<SettingsForms, 'replace' | 'mutate' | 'configure'>>;
8
+ /** 一次事务里 plan 拿到的写瞬间视图:生效值 / 用户层 / 官方修订号。 */
9
+ export interface SettingsView {
10
+ /** 该段当前有效值(官方 value:schema 默认 → 组合层 → 用户层)。 */
11
+ value: Record<string, unknown>;
12
+ /** 该段的用户层原始内容(`describe()` 的 `user`):保住别人写在同一段里的键靠它。 */
13
+ user: Record<string, unknown>;
14
+ /** 官方修订号(宿主不支持时 undefined = 不带 CAS 写)。 */
15
+ revision: number | undefined;
16
+ }
17
+ /** 事务的一次产出:`write` 为 null 表示无需写盘、只取 result。 */
18
+ export interface TxPlan<T> {
19
+ write: {
20
+ kind: 'section';
21
+ section: object;
22
+ } | {
23
+ kind: 'ops';
24
+ ops: readonly SettingsPathOp[];
25
+ } | null;
26
+ /** 事务产出(冲突重算后以最后一次 plan 为准)。 */
27
+ result: T;
28
+ }
29
+ export interface TransactOptions {
30
+ warn(message: string): void;
31
+ /** 等设置段可描述的上限(ms)。 */
32
+ namespaceWaitMs: number;
33
+ /** 单次写盘的上限(ms):到点调用方降级,那笔仍在官方锁内照常落定。 */
34
+ writeTimeoutMs: number;
35
+ }
36
+ /**
37
+ * 一笔官方设置事务。plan 必须是 view 的纯函数(外部输入放闭包里):
38
+ * 冲突重试会用新鲜视图重调它,副作用(记账、集合填充)应以 result 带出而不是改外部状态。
39
+ */
40
+ export declare function transact<T>(settings: SettingsLike, ns: string, plan: (view: SettingsView) => TxPlan<T>, options: TransactOptions): Promise<T>;
41
+ /** 等设置段可描述(条目 ACTIVE 且声明了 volatile 字段);超时返回 false,不抛。 */
42
+ export declare function waitForNamespace(settings: SettingsLike, ns: string, waitMs: number): Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fa-software/semi2-dsh",
3
- "version": "0.0.16",
3
+ "version": "0.0.17-preview.20260929031749",
4
4
  "description": "Semi2 单插件:semi2-hub 配置引导 + 公共 Web 能力。",
5
5
  "homepage": "https://cnb.cool/fa-software/semi2/dsh/dsh-mono",
6
6
  "bugs": "https://cnb.cool/fa-software/semi2/dsh/dsh-mono/-/issues",
@@ -1,79 +0,0 @@
1
- // @fa-software/semi2-dsh/service/host-writes —— 磁盘写入的唯一闸口:串行队列 + 宿主就绪门 + 两段独立预算。
2
- // 只剩「盘上自铸内容的认领清理」走这里;状态段与工作区映射都是内存覆盖行,不落盘、不过这道门。
3
- // dsh 0.1.7 的 settings.update 是 Profile 配置编辑,内部 `ctx.loader.await()` 要等整棵 loader 空闲:
4
- // 排队(含等宿主就绪)到点只让调用方降级、写入留在队列继续等,写盘到点才按 writeTimeoutMs 降级。
5
- import { ConfigurationError } from "../hub/errors.js";
6
- /** 排队(含等宿主就绪)的调用方上限缺省值(ms)。 */
7
- const QUEUE_WAIT_MS = 15_000;
8
- /** 单次写盘的上限缺省值(ms)。 */
9
- const WRITE_TIMEOUT_MS = 3_000;
10
- /** 到点标记:与「底层真的返回了 undefined」区分开。 */
11
- const EXPIRED = Symbol('expired');
12
- export function createHostWrites(warn, options = {}) {
13
- const queueWaitMs = options.queueWaitMs ?? QUEUE_WAIT_MS;
14
- const writeTimeoutMs = options.writeTimeoutMs ?? WRITE_TIMEOUT_MS;
15
- /** 队列尾:只按底层任务真正落定前进,调用方是否已降级与它无关。 */
16
- let tail = Promise.resolve();
17
- /** 排队告警去重(按段):成功落定一次即复位。 */
18
- const reported = new Set();
19
- return {
20
- run(ns, task) {
21
- const started = Promise.withResolvers();
22
- const execution = tail.then(async () => {
23
- await waitHostReady(options.ready);
24
- started.resolve();
25
- await task();
26
- });
27
- // 队列尾按底层任务真正落定前进:被放弃的那笔仍占着 profile 的 writer 锁,下一笔不能抢跑。
28
- tail = execution.then(() => undefined, () => undefined);
29
- // 结果处理先接住拒绝:调用方降级后任务仍会跑完,迟到失败不该变成未处理拒绝。
30
- const outcome = execution.then(() => null, (error) => (error instanceof Error ? error : new Error(String(error))));
31
- return (async () => {
32
- if ((await within(started.promise, queueWaitMs)) === EXPIRED) {
33
- if (!reported.has(ns)) {
34
- reported.add(ns);
35
- warn(`${ns} 写入排队中(宿主仍在启动,等满 ${queueWaitMs}ms),本轮先降级;宿主就绪后照常落定`);
36
- }
37
- throw new ConfigurationError('CONFIG_APPLY_FAILED');
38
- }
39
- const result = await within(outcome, writeTimeoutMs);
40
- if (result === EXPIRED) {
41
- warn(`${ns} 设置写入超时(${writeTimeoutMs}ms),本端继续,状态可能落后`);
42
- throw new ConfigurationError('CONFIG_APPLY_FAILED');
43
- }
44
- if (result !== null)
45
- throw result;
46
- reported.delete(ns);
47
- })();
48
- },
49
- };
50
- }
51
- /** 宿主就绪门:等 profile 的 loader 条目树空闲——设置写入内部等的就是它;没有该端(宿主直跑 / 假宿主)立即放行。 */
52
- async function waitHostReady(ready) {
53
- if (ready === undefined)
54
- return;
55
- try {
56
- await ready();
57
- }
58
- catch {
59
- // 就绪门只负责等:树自身异常不该拦下写入,交给写盘预算与调用方的降级。
60
- }
61
- }
62
- /** 到点返回 {@link EXPIRED},否则原样给出结果;定时器总在返回前清掉。 */
63
- async function within(promise, ms) {
64
- let timer;
65
- try {
66
- return await Promise.race([
67
- promise,
68
- new Promise((resolve) => {
69
- timer = globalThis.setTimeout(() => {
70
- resolve(EXPIRED);
71
- }, ms);
72
- }),
73
- ]);
74
- }
75
- finally {
76
- if (timer !== undefined)
77
- globalThis.clearTimeout(timer);
78
- }
79
- }
@@ -1,16 +0,0 @@
1
- export interface HostWritesOptions {
2
- /** 宿主就绪门(调用方从 `ctx.get('loader', false)` 取;缺席即立刻放行)。 */
3
- ready?: (() => Promise<unknown>) | undefined;
4
- /** 调用方在队列里等宿主就绪的上限(ms,缺省 {@link QUEUE_WAIT_MS})。 */
5
- queueWaitMs?: number | undefined;
6
- /** 调用方等真正写盘的上限(ms,缺省 {@link WRITE_TIMEOUT_MS})。 */
7
- writeTimeoutMs?: number | undefined;
8
- }
9
- export interface HostWrites {
10
- /**
11
- * 排进本进程唯一的写入队列:前一笔**真正落定**后才轮到自己,轮到时先等宿主(loader 条目树)空闲,再执行 task。
12
- * 调用方在任一预算到期时按 `CONFIG_APPLY_FAILED` 降级;task 不被取消——排队超时的这笔仍会照常落定。
13
- */
14
- run(ns: string, task: () => Promise<unknown>): Promise<void>;
15
- }
16
- export declare function createHostWrites(warn: (message: string) => void, options?: HostWritesOptions): HostWrites;