dsh-memo-ilife 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -57,12 +57,10 @@ window.__ModuleLoader__.load({
57
57
  //#endregion
58
58
  //#region src/slot.ts
59
59
  const SLOT_TITLE = "备忘录";
60
- const PLUGIN = "dsh-memo-ilife";
61
- const SKILL_PACKAGE = "skill-memo-ilife";
62
60
  /** 版本行唯一来源(面板展示用;smoke 断言与两处 package.json 一致,防漂移)。
63
61
  * 发版 bump 时同步改这里(与 package.json 同值):0.3.2 那轮之前停在 0.2.1——
64
62
  * 面板自报家门报了旧号(#743 真机截图逮到),本票一并摆正。 */
65
- const PLUGIN_VERSION = "0.3.2";
63
+ const PLUGIN_VERSION = "0.3.3";
66
64
  const SKILL_VERSION = "0.3.0";
67
65
  //#endregion
68
66
  //#region src/settings.ts
@@ -299,9 +297,10 @@ window.__ModuleLoader__.load({
299
297
  fontSize: "0.92em"
300
298
  }
301
299
  };
302
- /** 版本行:插件与技能双版本号(用户要求:每技能设置页自报家门)。 */
300
+ /** 版本行:插件与技能双版本号(面板自报家门,排障时看这台装的是哪版)。
301
+ * 只留两个号——包名在页签上已经写着,重复一遍只是噪音(#743)。 */
303
302
  function VersionLine() {
304
- return react.createElement("div", { style: S.version }, `${PLUGIN} ${PLUGIN_VERSION} · ${SKILL_PACKAGE} ${SKILL_VERSION}`);
303
+ return react.createElement("div", { style: S.version }, `${PLUGIN_VERSION} · 技能 ${SKILL_VERSION}`);
305
304
  }
306
305
  /** 数据态取值:有 total 显示 total,有值无 total 显示原值 JSON,空值返回 null(由调用方进错误态)。 */
307
306
  function extractTotal(value) {
@@ -748,7 +747,7 @@ window.__ModuleLoader__.load({
748
747
  type: "button",
749
748
  disabled: busy,
750
749
  onClick: () => void load()
751
- }, "重新读取")), picking ? react.createElement("div", { style: S.muted }, "已唤起系统文件夹对话框:选中后自动填上,取消则不动。") : null, notice !== null ? react.createElement("div", { style: S.okText }, notice) : null, error !== null ? react.createElement("div", { style: S.error }, error) : null, react.createElement("div", { style: S.muted }, `${PLUGIN} 本页只配置;记笔记、搜笔记在对话里说。`), react.createElement(VersionLine, null));
750
+ }, "重新读取")), picking ? react.createElement("div", { style: S.muted }, "已唤起系统文件夹对话框:选中后自动填上,取消则不动。") : null, notice !== null ? react.createElement("div", { style: S.okText }, notice) : null, error !== null ? react.createElement("div", { style: S.error }, error) : null, react.createElement(VersionLine, null));
752
751
  }
753
752
  function apply(ctx) {
754
753
  const getCall = () => ctx.connection?.rpc?.call ?? null;
@@ -756,7 +755,7 @@ window.__ModuleLoader__.load({
756
755
  ctx.slots.inject("ilife.config-tab", () => ctx.slots.register({
757
756
  name: "ilife.config-tab",
758
757
  id: "dsh-memo-ilife",
759
- order: 70,
758
+ order: 80,
760
759
  label: () => SLOT_TITLE,
761
760
  channel: RPC_CHANNEL
762
761
  }, () => react.createElement(MemoConfig, {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["React"],"sources":["../src/contract.ts","../src/slot.ts","../src/settings.ts","../src/dsh-ctx.ts","../src/client.ts"],"sourcesContent":["/** dsh-memo-ilife 端口(port):DSH 世界与备忘录 hexagon 之间的契约。\n *\n * browser 安全:零 node 导入。host 适配器(注册 RPC 通道)与 client\n * 适配器(调用 RPC)共用同一份定义;单测与 loader 回路以此为准。\n * 通道命名遵守宿主约束:单段且不等于 /api(见 cookbook「RPC 通道」)。\n *\n * #696:本包**已有**这条通道(取数端点 `read` 与 `/api/ilife-memo` 路由早在板上),设置页要读改配置,\n * 于是只在既有通道上加三个配置端点(`config.get`/`config.save`/`config.reset`);取数那条一行未动。\n */\nexport const RPC_CHANNEL = '/ilife-memo' as const;\nexport const RPC_ENDPOINT_READ = 'read' as const;\n\n/** #696 配置端点:读设置页整面、保存一份取值、重置为默认。同走既有那条通道,靠端点名分发。\n *\n * 与既有的取数端点 `read` 同住 `/ilife-memo`:本包**不另起第二条通道**,只在既有通道上加面。 */\nexport const RPC_ENDPOINT_CONFIG_GET = 'config.get' as const;\nexport const RPC_ENDPOINT_CONFIG_SAVE = 'config.save' as const;\nexport const RPC_ENDPOINT_CONFIG_RESET = 'config.reset' as const;\n/** #706 配置体检端点:只读一次,回一份报告(判据由技能侧出,本包只透传,不重写一个字)。\n *\n * 端点名与技能侧那条只读命令同名(`<技能>.config.check`);报告形状的唯一真相在技能侧\n * `packages/skill-<技能>/src/health.ts`,面板侧镜像在 `packages/plugin-manager/src/health-contract.ts`。 */\nexport const RPC_ENDPOINT_CONFIG_CHECK = 'config.check' as const;\n\n/** 读请求载荷:与技能 CLI 的 key/params 同形(见 skill-memo-ilife SKILL.md)。 */\nexport interface ReadPayload {\n readonly key: string;\n readonly params: Record<string, unknown>;\n}\n\n/** 保存载荷:一份键值(键路径 → 值)。缺项由技能侧按默认值补齐,故只收用户真改的那些。 */\nexport interface SavePayload {\n readonly values: Record<string, unknown>;\n}\n\n/** 保存载荷校验:形状不对返回 null,由调用方按 bad-request 回绝(不抛)。 */\nexport function parseSavePayload(raw: unknown): SavePayload | null {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return null;\n const { values } = raw as { values?: unknown };\n if (typeof values !== 'object' || values === null || Array.isArray(values)) return null;\n return { values: values as Record<string, unknown> };\n}\n\n/** 配置面回执:形状与技能侧 `memo.config.read` 的 data 逐字段同形(唯一定义地在那边的 cli/config.ts)。\n *\n * 这里不带默认值表——页面不猜默认值,「留空=按默认落点」由行文案说清,\n * 「重置为默认」由技能侧 `memo.config.reset` 执行。\n */\nexport interface ConfigSurfaceReply {\n /** 配置文件绝对路径。 */\n readonly path: string;\n /** 数据目录绝对路径。 */\n readonly dataDir: string;\n /** 本次是不是「文件不存在、按默认值落了一份」。 */\n readonly created: boolean;\n /** 当前取值(文件里的缺项按默认值补)。 */\n readonly values: Record<string, unknown>;\n}\n\nexport interface RpcError {\n readonly code: string;\n readonly message: string;\n readonly details: Record<string, unknown>;\n}\n\nexport type RpcResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly error: RpcError };\n\nexport function ok(value: unknown): RpcResult {\n return { ok: true, value };\n}\n\nexport function fail(code: string, message: string): RpcResult {\n return { ok: false, error: { code, message, details: {} } };\n}\n\n/** 载荷校验:形状不对返回 null,由调用方按 bad-request 回绝(不抛)。 */\nexport function parseReadPayload(raw: unknown): ReadPayload | null {\n if (typeof raw !== 'object' || raw === null) return null;\n const { key, params } = raw as { key?: unknown; params?: unknown };\n if (typeof key !== 'string' || key.length === 0) return null;\n if (typeof params !== 'object' || params === null || Array.isArray(params)) return null;\n return { key, params: params as Record<string, unknown> };\n}\n\n/** 面板默认读键:memo.search 无参直读,空库安全(items []、total 0)。 */\nexport const DEFAULT_READ_KEY = 'memo.search' as const;\n\n/** 信封守卫:供 client 拆包前校验(port 拥有信封定义,守卫住 port)。 */\nexport function isRpcResult(raw: unknown): raw is RpcResult {\n if (typeof raw !== 'object' || raw === null) return false;\n const ok = (raw as { ok?: unknown }).ok;\n return ok === true || ok === false;\n}\n","/** dsh-memo-ilife 槽位描述(P10 脚手架,P3 #4 定案)。\n *\n * 写死槽位原生组件:id 命名空间 ilife:memo,order 70。\n * 注册与打开只走 registerTab/openTab 的路径导航;子页后缀(如 ilife:memo:*)\n * 只在主面板内导航,不占栏。缺席纯条件渲染,不轮询(本文件无定时器;\n * 有界重试归一由 base-render 拥有,单品不自建)。\n */\n\nexport const SKILL = 'memo' as const;\nexport const SLOT_ID = 'ilife:memo' as const;\nexport const SLOT_ORDER = 70 as const;\nexport const SLOT_TITLE = '备忘录' as const;\nexport const PLUGIN = 'dsh-memo-ilife' as const;\nexport const MANAGER_PLUGIN = 'dsh-life-pack' as const;\nexport const SKILL_PACKAGE = 'skill-memo-ilife' as const;\n\n/** 版本行唯一来源(面板展示用;smoke 断言与两处 package.json 一致,防漂移)。\n * 发版 bump 时同步改这里(与 package.json 同值):0.3.2 那轮之前停在 0.2.1——\n * 面板自报家门报了旧号(#743 真机截图逮到),本票一并摆正。 */\nexport const PLUGIN_VERSION = '0.3.2' as const;\nexport const SKILL_VERSION = '0.3.0' as const;\n\nexport interface SlotDescriptor {\n readonly skill: string;\n readonly slotId: string;\n readonly order: number;\n readonly title: string;\n readonly kind: 'page';\n}\n\nexport function slotDescriptor(): SlotDescriptor {\n return { skill: SKILL, slotId: SLOT_ID, order: SLOT_ORDER, title: SLOT_TITLE, kind: 'page' };\n}\n\n/** 写死原生组件描述(纯数据,无按需加载,无外嵌页)。 */\nexport const TAB_COMPONENT = { kind: 'native', name: 'MemoPanel' } as const;\n\nexport interface TabsPort {\n hasTabs(): boolean;\n registerTab(entry: { readonly id: string; readonly title: string; readonly order: number; readonly single: boolean; readonly component: unknown }): () => void;\n openTab(seed: { readonly type: string; readonly path: string }, scope?: { readonly sessionId?: string }): void;\n}\n\n/** 自注册:幂等(调用方持 disposer),失败返回 false 由调用方决定重试。 */\nexport function registerSingle(port: TabsPort): (() => void) | null {\n if (!port.hasTabs()) return null;\n return port.registerTab({ id: SLOT_ID, title: SLOT_TITLE, order: SLOT_ORDER, single: true, component: TAB_COMPONENT });\n}\n\n/** 内容型打开:path seed,折叠面板自动展开。 */\nexport function openSingle(port: TabsPort, sessionId?: string): void {\n if (!port.hasTabs()) return;\n port.openTab({ type: SLOT_ID, path: SLOT_ID }, sessionId ? { sessionId } : undefined);\n}\n","/** dsh-memo-ilife 设置页元数据(#696:从只读脚手架换成真配置页)。\n *\n * 本文件是**设置页的行表**:一个可配置项一行,写清它在配置文件里的键、中文标题、\n * 分级(常用 / 高级)、控件种类与人话指引。它是页面渲染的唯一依据。\n *\n * **本文件必须零 node 依赖**:`tsconfig.client.json` 把它收进 client 侧编译,\n * 浏览器产物不许带 node 内建。默认值不住这里,也不从技能包 import——\n * 取值一律由宿主经技能 CLI 的 `memo.config.read` 端点现取(见 bridge.ts)。\n *\n * 口径:\n * · 配置表形状(键与默认值)的**唯一定义地**是技能侧 `packages/skill-memo-ilife/src/config.ts`\n * 的 `MEMO_CONFIG_DEFAULTS`(本文件是它在页面上的投影,逐键对齐由\n * `test/config-surface-696.test.mjs` 用编译产物锁死);\n * · **空串=按默认落点**(老落点由技能各取用处算),所以「空」不是「没配」,\n * 而是「还走改造前那个位置」,老数据不会看起来丢了——行文案按这个语义写;\n * · `db.dir` 那一行的落点由配置面回执的 `dataDir` 带回(解析好的绝对路径),页面上把它**预填**进\n * 该行(`prefillFrom: 'dataDir'`),用户不必自己拼路径(#743)。\n *\n * 清单出处:「六家技能的路径类配置全量调查」备忘那 8 项全部上设置页 = 行数 8:\n * 常用 4(库与产物落在哪)+ 高级 4(两个产物文件名主体 + 飞书 CLI 与二维码两处落点)。\n * 「附件目录」那一条的语义照 #712 落地后的口径写(真目录 + 真包含判定),不是旧的字符串前缀。\n */\nexport const SETTINGS_OWNER = 'dsh-memo-ilife' as const;\nexport const SETTINGS_SLOT = 'ilife:memo' as const;\n\n/** 配置文件主体名:落点 `~/.ilife/memo.yaml`(`ILIFE_CONFIG_DIR` 可整体接管)。 */\nexport const CONFIG_STEM = 'memo' as const;\n\n/** 分级:常用项直接画在页面上,其余进默认收起的「高级」组(#675 冻结口径)。 */\nexport type ConfigTier = 'common' | 'advanced';\n\n/** 控件种类:只有这四种(前三种与受限 YAML 子集的字符串/数字/布尔一一对应,没有数组;\n * `directory` 是**字符串那一档的页面形态**——取值仍是串,只是多一个唤起系统文件夹选择器的入口,见 #736)。 */\nexport type ConfigControl = 'text' | 'number' | 'switch' | 'directory';\n\nexport interface ConfigItem {\n /** 配置文件里的键路径,一层嵌套用 `.` 连接,例 `files.help`。 */\n readonly key: string;\n /** 页面上那一行的中文标题。 */\n readonly title: string;\n readonly tier: ConfigTier;\n readonly control: ConfigControl;\n /** 一行人话:这一项管什么、留空会怎样(一到两句,不写开发期口径)。 */\n readonly hint: string;\n /** 目录行的落点来源:这一行取值空着时,页面上拿配置面回执里的哪一格当它显示的绝对路径。\n * 只有 `db.dir` 标它——回执里的 `dataDir` 就是技能真会用的那个目录,逐字相同。 */\n readonly prefillFrom?: 'dataDir';\n}\n\n/** 常用项:改了就影响「库与产物落在哪」,放页面上。 */\nconst COMMON: readonly ConfigItem[] = [\n {\n key: 'db.dir',\n title: '数据目录',\n tier: 'common',\n control: 'directory',\n // 留空=技能按默认目录落文件;页面上把解析出来的绝对路径直接填进这一行(见 client.ts 的 toDraft)。\n prefillFrom: 'dataDir',\n hint: '库文件与产物的根目录。留空=用默认目录。',\n },\n {\n key: 'db.name',\n title: '库文件名',\n tier: 'common',\n control: 'text',\n hint: '数据目录下的数据库文件名。改名=换库,旧笔记不会被读入。',\n },\n {\n key: 'html.dir',\n title: 'HTML 产物目录名',\n tier: 'common',\n control: 'text',\n hint: '数据目录下存放 HELP 页面的子目录名。',\n },\n {\n key: 'media.dir',\n title: '附件目录',\n tier: 'common',\n control: 'directory',\n hint: '附件的存放根目录。留空=工作目录下的 media。',\n },\n];\n\n/** 高级项:默认收起。改错了多半只是自己撞上麻烦,不确定就别动。 */\nconst ADVANCED: readonly ConfigItem[] = [\n {\n key: 'files.help',\n title: '产物文件名主体 · HELP',\n tier: 'advanced',\n control: 'text',\n hint: 'HELP 文件名前缀,后面自动加时间戳。',\n },\n {\n key: 'files.lookup',\n title: '产物文件名主体 · 速查表',\n tier: 'advanced',\n control: 'text',\n hint: '速查表文件名前缀。',\n },\n {\n key: 'lark.cliPath',\n title: '飞书 CLI 路径',\n tier: 'advanced',\n control: 'text',\n hint: '飞书 CLI 的可执行文件路径。留空=自动探测。',\n },\n {\n key: 'lark.qrDir',\n title: '飞书授权二维码落点',\n tier: 'advanced',\n control: 'directory',\n hint: '飞书授权二维码 PNG 的存放目录,缺就现建。留空=系统临时目录。',\n },\n];\n\n/** 设置页的行表,顺序即页面顺序。 */\nexport const CONFIG_ITEMS: readonly ConfigItem[] = [...COMMON, ...ADVANCED];\n\n/** 常用项行数(页面上直接画出来的那些)。 */\nexport const COMMON_ITEM_COUNT = COMMON.length;\n\n/** 高级组标题与副文案。 */\nexport const ADVANCED_GROUP_TITLE = '高级' as const;\nexport const ADVANCED_GROUP_NOTE = '不常改。留空=用默认值。' as const;\n\n/** 按 `a.b` 路径从配置取值里读(缺层或类型不符一律回 undefined)。 */\nexport function readPath(values: Record<string, unknown>, key: string): unknown {\n const parts = key.split('.');\n let cur: unknown = values;\n for (const part of parts) {\n if (typeof cur !== 'object' || cur === null || Array.isArray(cur)) return undefined;\n cur = (cur as Record<string, unknown>)[part];\n }\n return cur;\n}\n\n/** 按 `a.b` 路径把值写进配置取值(中间层缺就建一层空组;已存在非组即覆盖成组)。 */\nexport function writePath(values: Record<string, unknown>, key: string, value: unknown): void {\n const parts = key.split('.');\n let cur: Record<string, unknown> = values;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!;\n const next = cur[part];\n if (typeof next !== 'object' || next === null || Array.isArray(next)) cur[part] = {};\n cur = cur[part] as Record<string, unknown>;\n }\n cur[parts[parts.length - 1]!] = value;\n}\n","/** DSH ctx 最小镜像:只含本仓实际使用的面(type-only,构建期擦除,零运行时)。\n *\n * 每条成员的用法出处见 docs/agents/dsh-client-contract.md(附源码路径+行)。\n * 禁止加镜外成员:要用新能力,先在 cookbook 落出处,再加镜像。用 `import type` 引用本文件。\n * skills 面出处见 cookbook §12(#56 卡路里样板,badge 同形;#150 记账/#218 大厨同形)。\n */\nexport interface SlotsRegisterOptions {\n readonly name: string;\n readonly id?: string;\n readonly order?: number;\n readonly label?: string | (() => string);\n readonly locale?: string;\n /** 本包自己那条 RPC 通道(单段,例 `/ilife-memo`):注册方写进来,读方(总管)据此**通用地**\n * 调它,不必在源码里写死任何一家的通道名。出处=总管侧同一格的镜像\n * `packages/plugin-manager/src/dsh-ctx.ts:19-21`(#706 配置体检要走它;本包 `client.ts`\n * 由 #706 的 `f8957dd8` 已在注册处写上 `channel: RPC_CHANNEL`,此处补上缺的那一格)。 */\n readonly channel?: string;\n readonly inject?: () => Record<string, unknown>;\n}\n\nexport interface SlotsFace {\n inject(key: string, callback: () => (() => void) | Iterable<() => void>): () => void;\n register(options: SlotsRegisterOptions, component: unknown): () => void;\n}\n\nexport type RpcCallResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly details: Record<string, unknown> } };\n\nexport interface RpcCallFace {\n (channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<RpcCallResult>;\n}\n\nexport interface ClientCtx {\n readonly slots: SlotsFace;\n readonly connection?: { readonly rpc?: { readonly call: RpcCallFace } };\n effect(callback: () => ((() => void) | void), label?: string): () => void;\n get?(name: string): unknown;\n}\n\nexport type RpcHandler = (endpoint: string, payload: unknown, signal?: AbortSignal) => Promise<unknown>;\n\n/** 打包技能提供方镜像(cookbook §12,badge 同形;type-only,零运行时)。\n *\n * 出处:`ctx.skills.registerProvider(create)`(`dsh-skill/lib/index.js:147`);\n * candidate 校验红线见 `validateCandidate`(同文件 `:452`)——`provider` 须等于注册名、\n * `description` 非空、`rank` 有限数;`get` 回定义须与候选同名(同文件 `:259`)。\n */\nexport interface SkillInvocationPolicy {\n readonly modelInvocable: boolean;\n readonly userInvocable: boolean;\n}\n\nexport interface SkillCandidate {\n readonly name: string;\n readonly description: string;\n readonly invocation: SkillInvocationPolicy;\n readonly provider: string;\n readonly source: string;\n readonly rank: number;\n readonly locator: unknown;\n readonly resourceBase?: { readonly kind: 'directory'; readonly path: string };\n}\n\nexport interface SkillDefinition {\n readonly name: string;\n readonly description: string;\n readonly invocation: SkillInvocationPolicy;\n readonly provider: string;\n readonly source: string;\n readonly resourceBase?: { readonly kind: 'directory'; readonly path: string };\n readonly content: string;\n}\n\nexport interface SkillProvider {\n readonly name: string;\n list(options?: unknown): Promise<readonly SkillCandidate[]>;\n get(candidate: SkillCandidate, options?: unknown): Promise<SkillDefinition | undefined>;\n}\n\nexport interface SkillsFace {\n registerProvider(\n create: (control: { readonly signal: AbortSignal; invalidate(): void }) => SkillProvider,\n ): () => void;\n}\n\n/** 本插件用到的宿主面(apply 入参)。\n *\n * `connection` 面:#80 起本插件走的是 DSH 公开的 `/api` 载体 `ctx.connection.fetch.register`\n * (见 `src/index.ts` 的拆雷注释与 `docs/agents/plugin-webserver-inject.md`),不再是早期脚手架的\n * `connection.rpc.handle`。此前镜像一直停在旧那一版(复审 F 的 S3)——现照**实现**对齐;\n * `rpc` 降为可选,因为 `contract.ts` 的信封仍按 RPC 语义命名(`RpcResult`/`RpcHandler`)。\n *\n * #696:设置页要在既有通道上加三个配置端点,宿主注册面的镜像类型补成具名的 `HostConnectionFace`\n * (出处同上一段,照 `packages/plugin-chef/src/dsh-ctx.ts` 那份写);其余面一个字未动。\n */\nexport interface ConnectionFetchRegisterOptions {\n readonly path: string;\n readonly methods?: readonly string[];\n readonly requestBody?: string;\n fetch(request: unknown): Promise<Response> | Response;\n}\n\n/** 宿主 fetch 路由注册面(`ctx.connection.fetch.register`):出处与上一件同源\n * (`@xmanrui/dsh-im` 的 `plugin-src/management-rpc.mjs`,宿主注册走 route 对象)。\n * #696 起 `index.ts` 用它做这个面的镜像类型(原先那里是就地写死的匿名结构)。 */\nexport interface HostConnectionFace {\n readonly fetch: { register(options: ConnectionFetchRegisterOptions): () => void };\n}\n\nexport interface HostCtx {\n readonly connection: {\n readonly fetch: { register(options: ConnectionFetchRegisterOptions): () => void };\n readonly rpc?: { handle(channel: string, handler: RpcHandler): () => void };\n };\n readonly skills: SkillsFace;\n /** 模型工具注册表(#734):本包把技能唯一出口开成 agent 工具。 */\n readonly tools?: ToolsFace;\n effect(callback: () => ((() => void) | void), label?: string): () => void;\n readonly logger?: unknown;\n}\n\n/** 宿主目录选择命名空间的服务名(#736;出处与禁令见 cookbook §13)。 */\nexport const REMOTE_DIRECTORY_PICKER = 'remote.directoryPicker' as const;\n\n/** 宿主目录选择命名空间(DSH 平台提供)。本包**只用 `pick` 这一格**:不给 signal(平台那格可选)。\n *\n * **回执是信封,不是路径**(#743 真机缺陷的根因):客户端把每次 Remote 调用包成\n * `{ok:true,value}`/`{ok:false,error}`(`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`:\n * 失败不抛、成功也不回裸值),第一方消费方 `@deepseek-ai/dsh-client-ui-workspace/lib/client.js`\n * 的 `pickDirectory()` 就照这个形状拆:`const r = await pick(); if (!r.ok) throw …; return r.value`。\n * `value` 为 `null`=用户取消;`ok:false`=这条路供不了(例如组合里是 browse 后端)。 */\nexport interface DirectoryPickerAnswer {\n readonly ok: boolean;\n readonly value?: string | null;\n readonly error?: { readonly code?: string; readonly message?: string };\n}\n\nexport interface DirectoryPickerFace {\n pick(): Promise<DirectoryPickerAnswer>;\n}\n\n/** 模型工具面镜像(#734;出处见 cookbook §14)。\n *\n * 出处:`@deepseek-ai/dsh-tools/lib/index.js` 的 `defineTool` 与 `ToolRuntime.register`——\n * 工具对象的形状是 `name`/`description`/`parameters`(JSON Schema)/`output{schema,render}`/\n * `execute`;`register` 只校验 `output.render` 是函数、`output.schema` 受支持、`timeoutMs` 为正、\n * 名字不是保留名 `run_code`,**不要求 `defineTool` 产物** ⇒ 本包按形状手写、零依赖。 */\nexport interface ToolTextPart {\n readonly type: 'text';\n readonly text: string;\n}\n\nexport interface ToolDefinitionMirror {\n readonly name: string;\n readonly description: string;\n readonly parameters: Record<string, unknown>;\n readonly output: {\n readonly schema: Record<string, unknown>;\n render(args: unknown, value: string): readonly ToolTextPart[];\n };\n execute(args: Record<string, unknown>, exec?: unknown): Promise<unknown>;\n}\n\nexport interface ToolsFace {\n register(definition: ToolDefinitionMirror): () => void;\n}\n","/** dsh-memo-ilife client 适配器(六边形:port=contract+dsh-ctx 镜像,adapter=本文件)。\n *\n * 产物经 tsdown 打成 loader 工厂包(browser/CJS,见 tsdown.config.ts)。\n * 本文件传递闭包:禁 node、禁 DOM 直写(document/window/process)。\n * 注册两处(grill 定案,新术语见 CONTEXT.md):\n * (a) 技能设置页 → 爱生活页签槽(总管声明的 children,各技能自研自家配置页);\n * #696 起这一页从「只读设置行」换成**真配置页**:行表来自 settings.ts,取值与保存经宿主三个端点\n * (`config.get`/`config.save`/`config.reset`),宿主再 spawn 技能 CLI 的 `memo.config.*` 三个 key。\n * 本页只配置、不干活:没有查数、记一条之类的入口(那些在 (b) 的功能页)。\n * (b) 技能功能页 → sidebar槽(better-sidebar 服务,非 DSH slot;软依赖,\n * 缺席即跳过,不断链。DSH右侧槽 + sidebar.footer.action 彻底出局,已删)。\n * 有界重试例外依据(唯一允许的定时器):注册重试非数据轮询,照抄\n * dsh-mattpocock-skills-deck/src/client/panelAssembly.js:100-108\n * (betterSidebar 服务可能晚于本模块到达,最多 10 次、间隔 1000ms;卸载清理)。\n * 只用 dsh-ctx 镜像内成员(slots.inject/register、connection.rpc.call 经闭包现取、\n * betterSidebar 与 #736 的目录选择命名空间经 ctx.get 运行时取);取数只经 connection.rpc.call 进 host 通道;\n * 缺席/错误纯条件渲染,不返空冒充。组件 React.createElement 手写,不引入 JSX。\n */\nimport * as React from 'react';\nimport { RPC_CHANNEL, RPC_ENDPOINT_READ, RPC_ENDPOINT_CONFIG_GET, RPC_ENDPOINT_CONFIG_SAVE, RPC_ENDPOINT_CONFIG_RESET, DEFAULT_READ_KEY, isRpcResult } from './contract.js';\nimport type { ConfigSurfaceReply } from './contract.js';\nimport { SLOT_TITLE, PLUGIN, SKILL_PACKAGE, PLUGIN_VERSION, SKILL_VERSION } from './slot.js';\nimport { CONFIG_ITEMS, COMMON_ITEM_COUNT, ADVANCED_GROUP_TITLE, ADVANCED_GROUP_NOTE, readPath, writePath } from './settings.js';\nimport type { ConfigItem } from './settings.js';\nimport { REMOTE_DIRECTORY_PICKER } from './dsh-ctx.js';\nimport type { ClientCtx, RpcCallFace, RpcCallResult, DirectoryPickerAnswer, DirectoryPickerFace } from './dsh-ctx.js';\n\n/** client 短名声明:只有这两个(#736 的目录选择走**可选查找**,不写进来——\n * 写进来=硬依赖,提供方缺席时整包被停靠,设置页会跟着装不上;见 cookbook §13)。 */\nexport const inject = ['slots', 'connection'];\n\n/** 调用口取用器:每次取数时现取(connection 后到也不永久缺席)。 */\nexport type GetCall = () => unknown;\n\n/** 面板视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退)。 */\nconst S = {\n card: {\n padding: '12px 14px',\n borderRadius: 10,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'var(--dsw-alias-bg-base, transparent)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n lineHeight: 1.6,\n } as React.CSSProperties,\n title: { fontSize: '1.08em', fontWeight: 700, marginBottom: 8 } as React.CSSProperties,\n total: { fontSize: 22, fontWeight: 700, margin: '2px 0 4px' } as React.CSSProperties,\n muted: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: '0.92em'} as React.CSSProperties,\n error: { color: 'var(--dsw-alias-state-error-primary, #ff6b6b)', fontSize: '1em', whiteSpace: 'pre-wrap' } as React.CSSProperties,\n okText: { color: 'var(--dsw-alias-state-success-primary, #12805c)', fontSize: '0.96em'} as React.CSSProperties,\n rows: { marginTop: 8, borderTop: '1px solid var(--dsw-alias-border, rgba(128,128,128,.25))', paddingTop: 8 } as React.CSSProperties,\n row: { marginTop: 10 } as React.CSSProperties,\n label: { fontWeight: 600 } as React.CSSProperties,\n hint: { color: 'var(--dsw-alias-label-tertiary, #8a8a8a)', fontSize: '0.92em', margin: '1px 0 4px' } as React.CSSProperties,\n input: {\n width: '100%',\n boxSizing: 'border-box',\n padding: '4px 8px',\n borderRadius: 6,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.45))',\n background: 'var(--dsw-alias-bg-base, transparent)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n } as React.CSSProperties,\n pickRow: { display: 'flex', gap: 6, alignItems: 'center' } as React.CSSProperties,\n btnPick: {\n flex: '0 0 auto',\n padding: '4px 10px',\n borderRadius: 6,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.45))',\n background: 'var(--dsw-alias-bg-base, transparent)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n whiteSpace: 'nowrap',\n cursor: 'pointer',\n } as React.CSSProperties,\n info: {\n fontSize: '0.92em',\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n overflowWrap: 'anywhere',\n } as React.CSSProperties,\n bar: { marginTop: 14, display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } as React.CSSProperties,\n btn: {\n padding: '5px 14px',\n borderRadius: 7,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.45))',\n background: 'var(--dsw-alias-bg-base, transparent)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n cursor: 'pointer',\n } as React.CSSProperties,\n btnPrimary: {\n padding: '5px 14px',\n borderRadius: 7,\n border: '1px solid var(--dsw-alias-brand-primary, #2f6fed)',\n background: 'var(--dsw-alias-brand-primary, #2f6fed)',\n color: '#fff',\n cursor: 'pointer',\n } as React.CSSProperties,\n adv: {\n marginTop: 12,\n paddingTop: 8,\n borderTop: '1px solid var(--dsw-alias-border, rgba(128,128,128,.25))',\n } as React.CSSProperties,\n advSummary: { cursor: 'pointer', fontWeight: 600 } as React.CSSProperties,\n version: {\n marginTop: 8,\n paddingTop: 8,\n borderTop: '1px dashed var(--dsw-alias-border, rgba(128,128,128,.25))',\n color: 'var(--dsw-alias-label-tertiary, #8a8a8a)',\n fontSize: '0.92em',\n } as React.CSSProperties,\n};\n\n/** 版本行:插件与技能双版本号(用户要求:每技能设置页自报家门)。 */\nfunction VersionLine(): React.ReactElement {\n return React.createElement('div', { style: S.version }, `${PLUGIN} ${PLUGIN_VERSION} · ${SKILL_PACKAGE} ${SKILL_VERSION}`);\n}\n\ntype PanelState =\n | { readonly kind: 'loading' }\n | { readonly kind: 'data'; readonly total: string }\n | { readonly kind: 'absent'; readonly message: string }\n | { readonly kind: 'error'; readonly message: string };\n\n/** 数据态取值:有 total 显示 total,有值无 total 显示原值 JSON,空值返回 null(由调用方进错误态)。 */\nfunction extractTotal(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value === 'string') return value.length > 0 ? value : null;\n if (typeof value === 'object') {\n const total = (value as { total?: unknown }).total;\n if (total !== null && total !== undefined) return String(total);\n try {\n return JSON.stringify(value);\n } catch {\n return null;\n }\n }\n return String(value);\n}\n\ntype ReadOutcome =\n | { readonly ok: true; readonly text: string }\n | { readonly ok: false; readonly absent: boolean; readonly message: string };\n\n/** 请求超时毫秒:与 host 侧 SPAWN_TIMEOUT_MS 同级,UI 永不无限转圈。 */\nconst READ_TIMEOUT_MS = 20_000 as const;\n\n/** 自家赛跑超时(有界:settle 即清 timer;不依赖传输是否搭理 AbortSignal——Desktop 自研传输会忽略 signal)。\n * 注释即 p10 有界重试门要的例外依据(文件内另有 clearTimeout/tries 上限/本注释三件套)。 */\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const limit = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n timer = undefined;\n const e = new Error(`取数超时(${Math.round(ms / 1000)}s):宿主未回,先查宿主日志与 DB 环境`);\n e.name = 'TimeoutError';\n reject(e);\n }, ms);\n });\n return Promise.race([\n promise.finally(() => {\n if (timer !== undefined) clearTimeout(timer);\n }),\n limit,\n ]);\n}\n\n/** 单次读数(纯异步函数,无 hook;挂载期调用一次,无轮询)。 */\nasync function fetchRead(call: unknown, key: string, params: Record<string, unknown>): Promise<ReadOutcome> {\n // extractRpc 同式守卫:非函数即缺席态。\n if (typeof call !== 'function') return { ok: false, absent: true, message: '宿主连接缺席:connection.rpc.call 不可用' };\n let result: RpcCallResult;\n try {\n // 防御纵深 + 自家超时双保险(AbortSignal 帮正规传输提前收工;race 保传输忽略 signal 时仍落字)。\n const raw: unknown = await withTimeout(\n // #80:走 DSH 公开的 /api 载体(host 侧为 connection.fetch.register 注册的 /api/ilife-memo)。\n call('/api', RPC_CHANNEL.slice(1), { method: RPC_ENDPOINT_READ, payload: { key, params } }, AbortSignal.timeout(READ_TIMEOUT_MS)),\n READ_TIMEOUT_MS,\n );\n if (!isRpcResult(raw)) return { ok: false, absent: false, message: '回执信封异常(非 ok 信封)' };\n result = raw;\n } catch (e) {\n if (e instanceof Error && e.name === 'AbortError') return { ok: false, absent: false, message: '取数超时(20s):宿主未回,先查宿主日志与 DB 环境' };\n return { ok: false, absent: false, message: `取数失败:${e instanceof Error ? e.message : String(e)}` };\n }\n if (!result.ok) {\n const code = result.error?.code ?? 'unknown';\n const message = result.error?.message ?? '';\n if (code === 'missing-cli') return { ok: false, absent: true, message: `技能出口缺席(missing-cli):${message}` };\n return { ok: false, absent: false, message: `取数失败(${code}):${message}` };\n }\n const text = extractTotal(result.value);\n if (text === null) return { ok: false, absent: false, message: '取数为空(不冒充正常)' };\n return { ok: true, text };\n}\n\n/** 技能功能页:sidebar槽里展开的干活区(查数调数,不管配置)。挂载期一次 RPC。 */\nfunction MemoWork(props: { getCall: GetCall }): React.ReactElement {\n const [state, setState] = React.useState<PanelState>({ kind: 'loading' });\n React.useEffect(() => {\n let alive = true;\n void (async () => {\n const r = await fetchRead(props.getCall(), DEFAULT_READ_KEY, {});\n if (!alive) return;\n if (r.ok) setState({ kind: 'data', total: r.text });\n else if (r.absent) setState({ kind: 'absent', message: r.message });\n else setState({ kind: 'error', message: r.message });\n })();\n return () => {\n alive = false;\n };\n }, [props.getCall]);\n if (state.kind === 'loading') {\n return React.createElement('div', { style: S.card }, React.createElement('div', { style: S.muted }, '备忘录加载中…'));\n }\n if (state.kind === 'data') {\n return React.createElement(\n 'div',\n { style: S.card },\n React.createElement('div', { style: S.title }, SLOT_TITLE),\n React.createElement('div', { style: S.total }, `total ${state.total}`),\n React.createElement(VersionLine, null),\n );\n }\n return React.createElement(\n 'div',\n { style: S.card },\n React.createElement('div', { style: state.kind === 'absent' ? S.muted : S.error }, state.message),\n React.createElement(VersionLine, null),\n );\n}\n\n/** 配置页三态:读取中(一次 RPC 内)/ready(真表单)/failed(人话报错 + 指引)。 */\ntype ConfigState =\n | { readonly kind: 'loading' }\n | { readonly kind: 'ready'; readonly surface: ConfigSurfaceReply }\n | { readonly kind: 'failed'; readonly message: string };\n\ntype ConfigOutcome =\n | { readonly ok: true; readonly surface: ConfigSurfaceReply }\n | { readonly ok: false; readonly message: string };\n\n/** 配置面报错 → 人话指引。\n *\n * 报文由技能侧给出:base-link-core 的 `ConfigError` 已带行号与文件名,技能 `cli/config.ts` 原样交出来,\n * 插件 bridge 原样带上(`fetch-failed:出口非 0(1):<那一句>`)。所以这里**不重写报文**,\n * 只按报错类补一句「接下来怎么办」——免得两处各写一套人话。\n */\nexport function humanizeConfigFailure(code: string, message: string): string {\n const text = message.trim().length > 0 ? message.trim() : `(${code},宿主未给报文)`;\n if (/解析|YAML|parse/i.test(text)) return `${text}\\n改回「键: 值」的写法,或点「重置为默认」。`;\n if (/不认识|未知键|UNKNOWN_KEY/i.test(text)) return `${text}\\n删掉页面上没有的行,或点「重置为默认」。`;\n if (/类型|TYPE_MISMATCH/i.test(text)) return `${text}\\n按本页的控件形状填,或点「重置为默认」。`;\n if (/缺席|missing-cli/i.test(text)) return `${text}\\n技能出口没找到:先确认技能包已装好,再点「重新读取」。`;\n return `${text}\\n改不动就点「重置为默认」,或照上面那条路径手工改配置文件。`;\n}\n\n/** 一次配置 RPC(同通道过三个端点;永不抛,失败落字)。 */\nasync function configRpc(call: unknown, method: string, payload: Record<string, unknown>): Promise<ConfigOutcome> {\n if (typeof call !== 'function') return { ok: false, message: '宿主连接缺席:connection.rpc.call 不可用' };\n let raw: unknown;\n try {\n // 防御纵深 + 自家超时双保险(AbortSignal 帮正规传输提前收工;race 保传输忽略 signal 时仍落字)。\n raw = await withTimeout(\n // #80:走 DSH 公开的 /api 载体(host 侧为 connection.fetch.register 注册的 /api/ilife-memo)。\n (call as RpcCallFace)('/api', RPC_CHANNEL.slice(1), { method, payload }, AbortSignal.timeout(READ_TIMEOUT_MS)),\n READ_TIMEOUT_MS,\n );\n } catch (e) {\n if (e instanceof Error && (e.name === 'AbortError' || e.name === 'TimeoutError')) {\n return { ok: false, message: `配置请求超时(${Math.round(READ_TIMEOUT_MS / 1000)}s):宿主未回` };\n }\n return { ok: false, message: `配置失败:${e instanceof Error ? e.message : String(e)}` };\n }\n if (!isRpcResult(raw)) return { ok: false, message: '回执信封异常(非 ok 信封)' };\n const res = raw as RpcCallResult;\n if (!res.ok) return { ok: false, message: humanizeConfigFailure(res.error?.code ?? 'unknown', res.error?.message ?? '') };\n return { ok: true, surface: res.value as ConfigSurfaceReply };\n}\n\n/** 读设置页整面。 */\nexport function fetchConfigSurface(call: unknown): Promise<ConfigOutcome> {\n return configRpc(call, RPC_ENDPOINT_CONFIG_GET, {});\n}\n\n/** 保存一份取值(写回执只有 {path, values},头部那两行要的是读整面,故由组件写后重读)。 */\nexport function saveConfigSurface(call: unknown, values: Record<string, unknown>): Promise<ConfigOutcome> {\n return configRpc(call, RPC_ENDPOINT_CONFIG_SAVE, { values });\n}\n\n/** 重置为默认(技能侧先落 .bak;同样由组件重读一份整面)。 */\nexport function resetConfigSurface(call: unknown): Promise<ConfigOutcome> {\n return configRpc(call, RPC_ENDPOINT_CONFIG_RESET, {});\n}\n\n/** 把配置取值铺成「行键 → 输入框文本」(页面表单态;值缺项即空串,不返空留白)。\n *\n * `prefill` 是**落点回执**(配置面那几格解析出来的绝对路径):标了 `prefillFrom` 的行在取值空着时\n * 直接显示那条绝对路径——用户不必自己拼路径(#743),显示的就是技能真会用的那个目录(逐字相同)。 */\nexport function toDraft(\n values: Record<string, unknown>,\n prefill: { readonly dataDir?: string } = {},\n): Record<string, string> {\n const draft: Record<string, string> = {};\n for (const item of CONFIG_ITEMS) {\n const v = readPath(values, item.key);\n const raw = v === undefined || v === null ? '' : String(v);\n const fallback = item.prefillFrom === undefined ? undefined : prefill[item.prefillFrom];\n draft[item.key] = raw === '' && typeof fallback === 'string' && fallback !== '' ? fallback : raw;\n }\n return draft;\n}\n\n/** 表单态 → 配置取值(按控件种类还原类型;空串对文本项照收,语义由「按默认落点」承担)。 */\nexport function fromDraft(draft: Record<string, string>): Record<string, unknown> {\n const values: Record<string, unknown> = {};\n for (const item of CONFIG_ITEMS) {\n const raw = draft[item.key] ?? '';\n if (item.control === 'number') {\n const n = Number(raw);\n writePath(values, item.key, Number.isFinite(n) ? n : 0);\n } else if (item.control === 'switch') {\n writePath(values, item.key, raw === 'true');\n } else {\n writePath(values, item.key, raw);\n }\n }\n return values;\n}\n\n/** 目录选择的三种结果(注入 picker 的纯归一:选中/取消/这条路供不了)。 */\nexport type PickOutcome =\n | { readonly kind: 'picked'; readonly path: string }\n | { readonly kind: 'cancelled' }\n | { readonly kind: 'unavailable'; readonly message: string };\n\n/** 形状守卫:拿到的那个东西像不像目录选择命名空间(本包只认 `pick` 是函数)。 */\nexport function isDirectoryPicker(raw: unknown): raw is DirectoryPickerFace {\n if (typeof raw !== 'object' || raw === null) return false;\n return typeof (raw as { pick?: unknown }).pick === 'function';\n}\n\n/** 现取目录选择命名空间:**软依赖**,拿不到回 null(页面据此不出入口)。\n *\n * 为什么不用 `inject` 声明:见 cookbook §13——写进声明=硬依赖,提供方缺席时整包被停靠,\n * 设置页会跟着装不上。这里按平台给的「可选查找」办:缺席只是没有入口。\n * 守卫拒绝或提供方中途卸载会抛,一律当「没有」,绝不把设置页带下来。 */\nexport function resolveDirectoryPicker(getService: unknown): DirectoryPickerFace | null {\n if (typeof getService !== 'function') return null;\n try {\n const raw = (getService as (name: string) => unknown)(REMOTE_DIRECTORY_PICKER);\n return isDirectoryPicker(raw) ? raw : null;\n } catch {\n return null;\n }\n}\n\n/** 平台回执 → 三种结果(**永不抛**)。回执是信封 `{ok, value|error}`(见 dsh-ctx.ts 的\n * `DirectoryPickerAnswer`):成功回的是 `value` 不是路径本身,被拒回的是 `ok:false` 不是抛——\n * 照裸值解会把两种情况都误判成「用户取消」(#743 真机现象:点「选择文件夹」什么都没发生)。\n * 裸串照收(老形状兜底),认不出的形状当「供不了」报出来,不当取消吞掉。 */\nexport function readPickAnswer(raw: unknown): PickOutcome {\n if (typeof raw === 'string') return raw.trim() === '' ? { kind: 'cancelled' } : { kind: 'picked', path: raw };\n const answer = (typeof raw === 'object' && raw !== null ? raw : {}) as DirectoryPickerAnswer;\n if (answer.ok === true) {\n const value = answer.value;\n return typeof value === 'string' && value.trim() !== '' ? { kind: 'picked', path: value } : { kind: 'cancelled' };\n }\n const detail = answer.error?.message?.trim() ?? '';\n return { kind: 'unavailable', message: '打不开系统文件夹对话框' + (detail === '' ? '' : '(' + detail + ')') + ':请直接在框里填绝对路径。' };\n}\n\n/** 唤起一次系统文件夹选择器并归一结果(**永不抛**)。 */\nexport async function pickDirectory(picker: DirectoryPickerFace): Promise<PickOutcome> {\n try {\n return readPickAnswer(await picker.pick());\n } catch (e) {\n const reason = e instanceof Error ? e.message : String(e);\n return { kind: 'unavailable', message: '打不开系统文件夹对话框(' + reason + '):请直接在框里填绝对路径。' };\n }\n}\n\n/** 「选择文件夹」按钮的真装配函数(目录行渲染出来的按钮,onClick 就是它)。\n *\n * 回 Promise 是为了本地可判(用例直接 await);接进 React 时调用方 `void` 掉。 */\nexport function createBrowseHandler(deps: {\n readonly picker: DirectoryPickerFace;\n readonly onChange: (key: string, next: string) => void;\n readonly onUnavailable: (message: string) => void;\n}): (key: string) => Promise<void> {\n return async (key: string) => {\n const outcome = await pickDirectory(deps.picker);\n if (outcome.kind === 'picked') deps.onChange(key, outcome.path);\n else if (outcome.kind === 'unavailable') deps.onUnavailable(outcome.message);\n };\n}\n\n/** 一行输入(四种控件对齐受限 YAML 子集:文本/数字/布尔/目录)。\n *\n * 目录档=文本框 + 一枚唤起系统文件夹选择器的按钮;`onBrowse` 缺席(命名空间拿不到、\n * 或这条路已被拒)时不画按钮,文本框照旧——那是该缝自己的契约(供不了就收起入口,不是失败)。 */\nexport function Row(props: {\n readonly item: ConfigItem;\n readonly value: string;\n readonly disabled: boolean;\n readonly onChange: (key: string, next: string) => void;\n /** 目录行才有:唤起系统文件夹选择器。 */\n readonly onBrowse?: ((key: string) => void) | undefined;\n}): React.ReactElement {\n const { item } = props;\n const control =\n item.control === 'switch'\n ? React.createElement('input', {\n type: 'checkbox',\n checked: props.value === 'true',\n disabled: props.disabled,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => props.onChange(item.key, e.target.checked ? 'true' : 'false'),\n })\n : React.createElement('input', {\n style: S.input,\n type: item.control === 'number' ? 'number' : 'text',\n value: props.value,\n disabled: props.disabled,\n spellCheck: false,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => props.onChange(item.key, e.target.value),\n });\n const browse =\n item.control === 'directory' && props.onBrowse !== undefined\n ? React.createElement(\n 'button',\n {\n style: S.btnPick,\n type: 'button',\n disabled: props.disabled,\n // 回这枚 Promise 是有意的:React 不看 onClick 的返回值,而用例能直接 await 它,\n // 拿到「唤一次 pick → 回填这一行」的确定性读数(本地可判)。\n onClick: () => props.onBrowse?.(item.key),\n },\n '选择文件夹…',\n )\n : null;\n return React.createElement(\n 'div',\n { style: S.row },\n React.createElement('div', { style: S.label }, item.title),\n React.createElement('div', { style: S.hint }, item.hint),\n browse === null ? control : React.createElement('div', { style: S.pickRow }, control, browse),\n );\n}\n\n/** 技能设置页:承载备忘录自己的全部可配置项(只配置,不干活)。\n *\n * 三态:loading(一次 RPC 内)/ready(真表单)/failed(人话报错 + 指引,不返空、不转圈)。\n * 保存与重置之后都**重新读一份整面**再提示(写回执不带 dataDir/created,头部那两行要的是读整面)。 */\nfunction MemoConfig(props: { getCall: GetCall; getPicker: () => DirectoryPickerFace | null }): React.ReactElement {\n const [state, setState] = React.useState<ConfigState>({ kind: 'loading' });\n const [pickerGone, setPickerGone] = React.useState(false);\n const [picking, setPicking] = React.useState(false);\n const [draft, setDraft] = React.useState<Record<string, string>>({});\n const [busy, setBusy] = React.useState(false);\n const [notice, setNotice] = React.useState<string | null>(null);\n const [error, setError] = React.useState<string | null>(null);\n\n const apply = React.useCallback((surface: ConfigSurfaceReply) => {\n setState({ kind: 'ready', surface });\n setDraft(toDraft(surface.values, surface));\n }, []);\n\n const load = React.useCallback(async () => {\n setNotice(null);\n setError(null);\n const r = await fetchConfigSurface(props.getCall());\n if (r.ok) apply(r.surface);\n else setState({ kind: 'failed', message: r.message });\n }, [apply, props.getCall]);\n\n React.useEffect(() => {\n let alive = true;\n void (async () => {\n const r = await fetchConfigSurface(props.getCall());\n if (!alive) return;\n if (r.ok) apply(r.surface);\n else setState({ kind: 'failed', message: r.message });\n })();\n return () => {\n alive = false;\n };\n }, [apply, props.getCall]);\n\n const onChange = React.useCallback((key: string, next: string) => {\n setDraft((prev) => ({ ...prev, [key]: next }));\n setNotice(null);\n setError(null);\n }, []);\n\n /** 目录选择:拿不到命名空间就没有入口;被拒一次即收起入口(不留死按钮)。 */\n const picker = pickerGone ? null : props.getPicker();\n const onBrowse = React.useMemo(() => {\n if (picker === null) return undefined;\n const browse = createBrowseHandler({\n picker,\n onChange,\n onUnavailable: (message: string) => {\n setError(message);\n setPickerGone(true);\n },\n });\n // 系统对话框是模态的:等它回来的这段时间给一行字,免得看着像「点了没反应」(#743)。\n return (key: string) => {\n setPicking(true);\n setError(null);\n void browse(key).finally(() => setPicking(false));\n };\n }, [picker, onChange]);\n\n const surface = state.kind === 'ready' ? state.surface : null;\n const dirty = surface !== null && CONFIG_ITEMS.some((i) => (draft[i.key] ?? '') !== (toDraft(surface.values, surface)[i.key] ?? ''));\n\n /** 写完(保存/重置)之后重新读一份整面,读到了才敢提示「已完成」。 */\n const writeThenReload = React.useCallback(async (done: string) => {\n setBusy(true);\n setNotice(null);\n setError(null);\n const r = await fetchConfigSurface(props.getCall());\n if (r.ok) {\n apply(r.surface);\n setNotice(done);\n } else {\n setState({ kind: 'failed', message: r.message });\n }\n setBusy(false);\n }, [apply, props.getCall]);\n\n const onSave = React.useCallback(async () => {\n setBusy(true);\n setNotice(null);\n setError(null);\n const r = await saveConfigSurface(props.getCall(), fromDraft(draft));\n setBusy(false);\n if (r.ok) await writeThenReload('已保存,立即生效(不用重启宿主)');\n else setError(r.message);\n }, [draft, props.getCall, writeThenReload]);\n\n const onReset = React.useCallback(async () => {\n setBusy(true);\n setNotice(null);\n setError(null);\n const r = await resetConfigSurface(props.getCall());\n setBusy(false);\n if (r.ok) await writeThenReload('已重置为默认(原配置已另存一份 .bak)');\n else setError(r.message);\n }, [props.getCall, writeThenReload]);\n\n const head = React.createElement(\n 'div',\n null,\n React.createElement('div', { style: S.title }, `${SLOT_TITLE} · 配置`),\n surface !== null\n ? React.createElement(\n 'div',\n null,\n React.createElement('div', { style: S.info }, `配置文件 ${surface.path}`),\n React.createElement('div', { style: S.info }, `数据目录 ${surface.dataDir}`),\n surface.created ? React.createElement('div', { style: S.muted }, '(配置文件刚按默认值生成)') : null,\n )\n : null,\n );\n\n if (state.kind === 'loading') {\n return React.createElement(\n 'div',\n { style: S.card },\n head,\n React.createElement('div', { style: S.muted }, '配置读取中…'),\n React.createElement(VersionLine, null),\n );\n }\n\n if (state.kind === 'failed') {\n return React.createElement(\n 'div',\n { style: S.card },\n React.createElement('div', { style: S.title }, `${SLOT_TITLE} · 配置`),\n React.createElement('div', { style: S.error }, state.message),\n React.createElement(\n 'div',\n { style: S.bar },\n React.createElement('button', { style: S.btn, type: 'button', onClick: () => void load() }, '重试'),\n React.createElement('button', { style: S.btn, type: 'button', onClick: () => void onReset() }, '重置为默认'),\n ),\n React.createElement(VersionLine, null),\n );\n }\n\n const common = CONFIG_ITEMS.slice(0, COMMON_ITEM_COUNT);\n const advanced = CONFIG_ITEMS.slice(COMMON_ITEM_COUNT);\n const renderRow = (item: ConfigItem) =>\n React.createElement(Row, { key: item.key, item, value: draft[item.key] ?? '', disabled: busy, onChange, onBrowse });\n\n return React.createElement(\n 'div',\n { style: S.card },\n head,\n React.createElement('div', { style: S.rows }, common.map(renderRow)),\n React.createElement(\n 'details',\n { style: S.adv },\n React.createElement('summary', { style: S.advSummary }, ADVANCED_GROUP_TITLE),\n React.createElement('div', { style: S.muted }, ADVANCED_GROUP_NOTE),\n advanced.map(renderRow),\n ),\n React.createElement(\n 'div',\n { style: S.bar },\n React.createElement(\n 'button',\n { style: dirty ? S.btnPrimary : S.btn, type: 'button', disabled: busy || !dirty, onClick: () => void onSave() },\n busy ? '处理中…' : '保存',\n ),\n React.createElement('button', { style: S.btn, type: 'button', disabled: busy, onClick: () => void onReset() }, '重置为默认'),\n React.createElement('button', { style: S.btn, type: 'button', disabled: busy, onClick: () => void load() }, '重新读取'),\n ),\n picking ? React.createElement('div', { style: S.muted }, '已唤起系统文件夹对话框:选中后自动填上,取消则不动。') : null,\n notice !== null ? React.createElement('div', { style: S.okText }, notice) : null,\n error !== null ? React.createElement('div', { style: S.error }, error) : null,\n React.createElement('div', { style: S.muted }, `${PLUGIN} 本页只配置;记笔记、搜笔记在对话里说。`),\n React.createElement(VersionLine, null),\n );\n}\n\ninterface BetterSidebarService {\n registerTab(descriptor: {\n readonly id: string;\n readonly title: string | (() => string);\n readonly order?: number;\n readonly single?: boolean;\n readonly component: (props: unknown) => React.ReactElement | null;\n }): () => void;\n}\n\nexport function apply(ctx: ClientCtx): void {\n // 调用口取用器透传给组件(每次取数现取;非函数由组件判缺席)。\n const getCall: GetCall = () => ctx.connection?.rpc?.call ?? null;\n // #736 目录选择:**软依赖**,每次渲染现取(提供方后到/中途卸载都不留痕);\n // 拿不到就只是没有入口,设置页照常(不声明、不停靠,见 cookbook §13)。\n const getPicker = (): DirectoryPickerFace | null =>\n typeof ctx.get === 'function' ? resolveDirectoryPicker((name: string) => ctx.get?.(name)) : null;\n\n // (a) 技能设置页 → 爱生活页签槽(总管声明;总管缺席时 inject 等待,不断链)。\n ctx.slots.inject('ilife.config-tab', () =>\n ctx.slots.register(\n {\n name: 'ilife.config-tab',\n id: 'dsh-memo-ilife',\n order: 70,\n label: () => SLOT_TITLE,\n // #706:本包自己那条 RPC 通道交给总管(各家都写这一格),总管据此调配置体检——\n // 它因此不必在源码里写死任何一家的通道名(零单品依赖照旧成立)。\n channel: RPC_CHANNEL,\n },\n () => React.createElement(MemoConfig, { getCall, getPicker }),\n ),\n );\n\n // (b) 技能功能页 → sidebar槽(软依赖:betterSidebar 缺席即跳过)。\n const ensureWorkTab = (): boolean => {\n try {\n const get = ctx.get;\n const bs = (typeof get === 'function' ? get('betterSidebar') : undefined) as BetterSidebarService | undefined;\n if (!bs || typeof bs.registerTab !== 'function') return false;\n const dispose = bs.registerTab({\n id: 'ilife-memo-ilife:work',\n title: '备忘录',\n order: 60,\n single: true,\n component: () => React.createElement(MemoWork, { getCall }),\n });\n ctx.effect(() => () => {\n try {\n dispose();\n } catch {\n /* 卸载清理失败忽略 */\n }\n }, 'dsh-memo-ilife: sidebar work tab');\n return true;\n } catch {\n return false;\n }\n };\n if (!ensureWorkTab()) {\n let tries = 0;\n const timer = setInterval(() => {\n tries++;\n if (ensureWorkTab() || tries >= 10) clearInterval(timer);\n }, 1000);\n ctx.effect(() => () => {\n clearInterval(timer);\n }, 'dsh-memo-ilife: sidebar work tab retry');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["React"],"sources":["../src/contract.ts","../src/slot.ts","../src/settings.ts","../src/dsh-ctx.ts","../src/client.ts"],"sourcesContent":["/** dsh-memo-ilife 端口(port):DSH 世界与备忘录 hexagon 之间的契约。\n *\n * browser 安全:零 node 导入。host 适配器(注册 RPC 通道)与 client\n * 适配器(调用 RPC)共用同一份定义;单测与 loader 回路以此为准。\n * 通道命名遵守宿主约束:单段且不等于 /api(见 cookbook「RPC 通道」)。\n *\n * #696:本包**已有**这条通道(取数端点 `read` 与 `/api/ilife-memo` 路由早在板上),设置页要读改配置,\n * 于是只在既有通道上加三个配置端点(`config.get`/`config.save`/`config.reset`);取数那条一行未动。\n */\nexport const RPC_CHANNEL = '/ilife-memo' as const;\nexport const RPC_ENDPOINT_READ = 'read' as const;\n\n/** #696 配置端点:读设置页整面、保存一份取值、重置为默认。同走既有那条通道,靠端点名分发。\n *\n * 与既有的取数端点 `read` 同住 `/ilife-memo`:本包**不另起第二条通道**,只在既有通道上加面。 */\nexport const RPC_ENDPOINT_CONFIG_GET = 'config.get' as const;\nexport const RPC_ENDPOINT_CONFIG_SAVE = 'config.save' as const;\nexport const RPC_ENDPOINT_CONFIG_RESET = 'config.reset' as const;\n/** #706 配置体检端点:只读一次,回一份报告(判据由技能侧出,本包只透传,不重写一个字)。\n *\n * 端点名与技能侧那条只读命令同名(`<技能>.config.check`);报告形状的唯一真相在技能侧\n * `packages/skill-<技能>/src/health.ts`,面板侧镜像在 `packages/plugin-manager/src/health-contract.ts`。 */\nexport const RPC_ENDPOINT_CONFIG_CHECK = 'config.check' as const;\n\n/** 读请求载荷:与技能 CLI 的 key/params 同形(见 skill-memo-ilife SKILL.md)。 */\nexport interface ReadPayload {\n readonly key: string;\n readonly params: Record<string, unknown>;\n}\n\n/** 保存载荷:一份键值(键路径 → 值)。缺项由技能侧按默认值补齐,故只收用户真改的那些。 */\nexport interface SavePayload {\n readonly values: Record<string, unknown>;\n}\n\n/** 保存载荷校验:形状不对返回 null,由调用方按 bad-request 回绝(不抛)。 */\nexport function parseSavePayload(raw: unknown): SavePayload | null {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return null;\n const { values } = raw as { values?: unknown };\n if (typeof values !== 'object' || values === null || Array.isArray(values)) return null;\n return { values: values as Record<string, unknown> };\n}\n\n/** 配置面回执:形状与技能侧 `memo.config.read` 的 data 逐字段同形(唯一定义地在那边的 cli/config.ts)。\n *\n * 这里不带默认值表——页面不猜默认值,「留空=按默认落点」由行文案说清,\n * 「重置为默认」由技能侧 `memo.config.reset` 执行。\n */\nexport interface ConfigSurfaceReply {\n /** 配置文件绝对路径。 */\n readonly path: string;\n /** 数据目录绝对路径。 */\n readonly dataDir: string;\n /** 本次是不是「文件不存在、按默认值落了一份」。 */\n readonly created: boolean;\n /** 当前取值(文件里的缺项按默认值补)。 */\n readonly values: Record<string, unknown>;\n}\n\nexport interface RpcError {\n readonly code: string;\n readonly message: string;\n readonly details: Record<string, unknown>;\n}\n\nexport type RpcResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly error: RpcError };\n\nexport function ok(value: unknown): RpcResult {\n return { ok: true, value };\n}\n\nexport function fail(code: string, message: string): RpcResult {\n return { ok: false, error: { code, message, details: {} } };\n}\n\n/** 载荷校验:形状不对返回 null,由调用方按 bad-request 回绝(不抛)。 */\nexport function parseReadPayload(raw: unknown): ReadPayload | null {\n if (typeof raw !== 'object' || raw === null) return null;\n const { key, params } = raw as { key?: unknown; params?: unknown };\n if (typeof key !== 'string' || key.length === 0) return null;\n if (typeof params !== 'object' || params === null || Array.isArray(params)) return null;\n return { key, params: params as Record<string, unknown> };\n}\n\n/** 面板默认读键:memo.search 无参直读,空库安全(items []、total 0)。 */\nexport const DEFAULT_READ_KEY = 'memo.search' as const;\n\n/** 信封守卫:供 client 拆包前校验(port 拥有信封定义,守卫住 port)。 */\nexport function isRpcResult(raw: unknown): raw is RpcResult {\n if (typeof raw !== 'object' || raw === null) return false;\n const ok = (raw as { ok?: unknown }).ok;\n return ok === true || ok === false;\n}\n","/** dsh-memo-ilife 槽位描述(P10 脚手架,P3 #4 定案)。\n *\n * 写死槽位原生组件:id 命名空间 ilife:memo,order 70。\n * 注册与打开只走 registerTab/openTab 的路径导航;子页后缀(如 ilife:memo:*)\n * 只在主面板内导航,不占栏。缺席纯条件渲染,不轮询(本文件无定时器;\n * 有界重试归一由 base-render 拥有,单品不自建)。\n */\n\nexport const SKILL = 'memo' as const;\nexport const SLOT_ID = 'ilife:memo' as const;\nexport const SLOT_ORDER = 80 as const;\nexport const SLOT_TITLE = '备忘录' as const;\nexport const PLUGIN = 'dsh-memo-ilife' as const;\nexport const MANAGER_PLUGIN = 'dsh-life-pack' as const;\nexport const SKILL_PACKAGE = 'skill-memo-ilife' as const;\n\n/** 版本行唯一来源(面板展示用;smoke 断言与两处 package.json 一致,防漂移)。\n * 发版 bump 时同步改这里(与 package.json 同值):0.3.2 那轮之前停在 0.2.1——\n * 面板自报家门报了旧号(#743 真机截图逮到),本票一并摆正。 */\nexport const PLUGIN_VERSION = '0.3.3' as const;\nexport const SKILL_VERSION = '0.3.0' as const;\n\nexport interface SlotDescriptor {\n readonly skill: string;\n readonly slotId: string;\n readonly order: number;\n readonly title: string;\n readonly kind: 'page';\n}\n\nexport function slotDescriptor(): SlotDescriptor {\n return { skill: SKILL, slotId: SLOT_ID, order: SLOT_ORDER, title: SLOT_TITLE, kind: 'page' };\n}\n\n/** 写死原生组件描述(纯数据,无按需加载,无外嵌页)。 */\nexport const TAB_COMPONENT = { kind: 'native', name: 'MemoPanel' } as const;\n\nexport interface TabsPort {\n hasTabs(): boolean;\n registerTab(entry: { readonly id: string; readonly title: string; readonly order: number; readonly single: boolean; readonly component: unknown }): () => void;\n openTab(seed: { readonly type: string; readonly path: string }, scope?: { readonly sessionId?: string }): void;\n}\n\n/** 自注册:幂等(调用方持 disposer),失败返回 false 由调用方决定重试。 */\nexport function registerSingle(port: TabsPort): (() => void) | null {\n if (!port.hasTabs()) return null;\n return port.registerTab({ id: SLOT_ID, title: SLOT_TITLE, order: SLOT_ORDER, single: true, component: TAB_COMPONENT });\n}\n\n/** 内容型打开:path seed,折叠面板自动展开。 */\nexport function openSingle(port: TabsPort, sessionId?: string): void {\n if (!port.hasTabs()) return;\n port.openTab({ type: SLOT_ID, path: SLOT_ID }, sessionId ? { sessionId } : undefined);\n}\n","/** dsh-memo-ilife 设置页元数据(#696:从只读脚手架换成真配置页)。\n *\n * 本文件是**设置页的行表**:一个可配置项一行,写清它在配置文件里的键、中文标题、\n * 分级(常用 / 高级)、控件种类与人话指引。它是页面渲染的唯一依据。\n *\n * **本文件必须零 node 依赖**:`tsconfig.client.json` 把它收进 client 侧编译,\n * 浏览器产物不许带 node 内建。默认值不住这里,也不从技能包 import——\n * 取值一律由宿主经技能 CLI 的 `memo.config.read` 端点现取(见 bridge.ts)。\n *\n * 口径:\n * · 配置表形状(键与默认值)的**唯一定义地**是技能侧 `packages/skill-memo-ilife/src/config.ts`\n * 的 `MEMO_CONFIG_DEFAULTS`(本文件是它在页面上的投影,逐键对齐由\n * `test/config-surface-696.test.mjs` 用编译产物锁死);\n * · **空串=按默认落点**(老落点由技能各取用处算),所以「空」不是「没配」,\n * 而是「还走改造前那个位置」,老数据不会看起来丢了——行文案按这个语义写;\n * · `db.dir` 那一行的落点由配置面回执的 `dataDir` 带回(解析好的绝对路径),页面上把它**预填**进\n * 该行(`prefillFrom: 'dataDir'`),用户不必自己拼路径(#743)。\n *\n * 清单出处:「六家技能的路径类配置全量调查」备忘那 8 项全部上设置页 = 行数 8:\n * 常用 4(库与产物落在哪)+ 高级 4(两个产物文件名主体 + 飞书 CLI 与二维码两处落点)。\n * 「附件目录」那一条的语义照 #712 落地后的口径写(真目录 + 真包含判定),不是旧的字符串前缀。\n */\nexport const SETTINGS_OWNER = 'dsh-memo-ilife' as const;\nexport const SETTINGS_SLOT = 'ilife:memo' as const;\n\n/** 配置文件主体名:落点 `~/.ilife/memo.yaml`(`ILIFE_CONFIG_DIR` 可整体接管)。 */\nexport const CONFIG_STEM = 'memo' as const;\n\n/** 分级:常用项直接画在页面上,其余进默认收起的「高级」组(#675 冻结口径)。 */\nexport type ConfigTier = 'common' | 'advanced';\n\n/** 控件种类:只有这四种(前三种与受限 YAML 子集的字符串/数字/布尔一一对应,没有数组;\n * `directory` 是**字符串那一档的页面形态**——取值仍是串,只是多一个唤起系统文件夹选择器的入口,见 #736)。 */\nexport type ConfigControl = 'text' | 'number' | 'switch' | 'directory';\n\nexport interface ConfigItem {\n /** 配置文件里的键路径,一层嵌套用 `.` 连接,例 `files.help`。 */\n readonly key: string;\n /** 页面上那一行的中文标题。 */\n readonly title: string;\n readonly tier: ConfigTier;\n readonly control: ConfigControl;\n /** 一行人话:这一项管什么、留空会怎样(一到两句,不写开发期口径)。 */\n readonly hint: string;\n /** 目录行的落点来源:这一行取值空着时,页面上拿配置面回执里的哪一格当它显示的绝对路径。\n * 只有 `db.dir` 标它——回执里的 `dataDir` 就是技能真会用的那个目录,逐字相同。 */\n readonly prefillFrom?: 'dataDir';\n}\n\n/** 常用项:改了就影响「库与产物落在哪」,放页面上。 */\nconst COMMON: readonly ConfigItem[] = [\n {\n key: 'db.dir',\n title: '数据目录',\n tier: 'common',\n control: 'directory',\n // 留空=技能按默认目录落文件;页面上把解析出来的绝对路径直接填进这一行(见 client.ts 的 toDraft)。\n prefillFrom: 'dataDir',\n hint: '库文件与产物的根目录。留空=用默认目录。',\n },\n {\n key: 'db.name',\n title: '库文件名',\n tier: 'common',\n control: 'text',\n hint: '数据目录下的数据库文件名。改名=换库,旧笔记不会被读入。',\n },\n {\n key: 'html.dir',\n title: 'HTML 产物目录名',\n tier: 'common',\n control: 'text',\n hint: '数据目录下存放 HELP 页面的子目录名。',\n },\n {\n key: 'media.dir',\n title: '附件目录',\n tier: 'common',\n control: 'directory',\n hint: '附件的存放根目录。留空=工作目录下的 media。',\n },\n];\n\n/** 高级项:默认收起。改错了多半只是自己撞上麻烦,不确定就别动。 */\nconst ADVANCED: readonly ConfigItem[] = [\n {\n key: 'files.help',\n title: '产物文件名主体 · HELP',\n tier: 'advanced',\n control: 'text',\n hint: 'HELP 文件名前缀,后面自动加时间戳。',\n },\n {\n key: 'files.lookup',\n title: '产物文件名主体 · 速查表',\n tier: 'advanced',\n control: 'text',\n hint: '速查表文件名前缀。',\n },\n {\n key: 'lark.cliPath',\n title: '飞书 CLI 路径',\n tier: 'advanced',\n control: 'text',\n hint: '飞书 CLI 的可执行文件路径。留空=自动探测。',\n },\n {\n key: 'lark.qrDir',\n title: '飞书授权二维码落点',\n tier: 'advanced',\n control: 'directory',\n hint: '飞书授权二维码 PNG 的存放目录,缺就现建。留空=系统临时目录。',\n },\n];\n\n/** 设置页的行表,顺序即页面顺序。 */\nexport const CONFIG_ITEMS: readonly ConfigItem[] = [...COMMON, ...ADVANCED];\n\n/** 常用项行数(页面上直接画出来的那些)。 */\nexport const COMMON_ITEM_COUNT = COMMON.length;\n\n/** 高级组标题与副文案。 */\nexport const ADVANCED_GROUP_TITLE = '高级' as const;\nexport const ADVANCED_GROUP_NOTE = '不常改。留空=用默认值。' as const;\n\n/** 按 `a.b` 路径从配置取值里读(缺层或类型不符一律回 undefined)。 */\nexport function readPath(values: Record<string, unknown>, key: string): unknown {\n const parts = key.split('.');\n let cur: unknown = values;\n for (const part of parts) {\n if (typeof cur !== 'object' || cur === null || Array.isArray(cur)) return undefined;\n cur = (cur as Record<string, unknown>)[part];\n }\n return cur;\n}\n\n/** 按 `a.b` 路径把值写进配置取值(中间层缺就建一层空组;已存在非组即覆盖成组)。 */\nexport function writePath(values: Record<string, unknown>, key: string, value: unknown): void {\n const parts = key.split('.');\n let cur: Record<string, unknown> = values;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!;\n const next = cur[part];\n if (typeof next !== 'object' || next === null || Array.isArray(next)) cur[part] = {};\n cur = cur[part] as Record<string, unknown>;\n }\n cur[parts[parts.length - 1]!] = value;\n}\n","/** DSH ctx 最小镜像:只含本仓实际使用的面(type-only,构建期擦除,零运行时)。\n *\n * 每条成员的用法出处见 docs/agents/dsh-client-contract.md(附源码路径+行)。\n * 禁止加镜外成员:要用新能力,先在 cookbook 落出处,再加镜像。用 `import type` 引用本文件。\n * skills 面出处见 cookbook §12(#56 卡路里样板,badge 同形;#150 记账/#218 大厨同形)。\n */\nexport interface SlotsRegisterOptions {\n readonly name: string;\n readonly id?: string;\n readonly order?: number;\n readonly label?: string | (() => string);\n readonly locale?: string;\n /** 本包自己那条 RPC 通道(单段,例 `/ilife-memo`):注册方写进来,读方(总管)据此**通用地**\n * 调它,不必在源码里写死任何一家的通道名。出处=总管侧同一格的镜像\n * `packages/plugin-manager/src/dsh-ctx.ts:19-21`(#706 配置体检要走它;本包 `client.ts`\n * 由 #706 的 `f8957dd8` 已在注册处写上 `channel: RPC_CHANNEL`,此处补上缺的那一格)。 */\n readonly channel?: string;\n readonly inject?: () => Record<string, unknown>;\n}\n\nexport interface SlotsFace {\n inject(key: string, callback: () => (() => void) | Iterable<() => void>): () => void;\n register(options: SlotsRegisterOptions, component: unknown): () => void;\n}\n\nexport type RpcCallResult =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly details: Record<string, unknown> } };\n\nexport interface RpcCallFace {\n (channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<RpcCallResult>;\n}\n\nexport interface ClientCtx {\n readonly slots: SlotsFace;\n readonly connection?: { readonly rpc?: { readonly call: RpcCallFace } };\n effect(callback: () => ((() => void) | void), label?: string): () => void;\n get?(name: string): unknown;\n}\n\nexport type RpcHandler = (endpoint: string, payload: unknown, signal?: AbortSignal) => Promise<unknown>;\n\n/** 打包技能提供方镜像(cookbook §12,badge 同形;type-only,零运行时)。\n *\n * 出处:`ctx.skills.registerProvider(create)`(`dsh-skill/lib/index.js:147`);\n * candidate 校验红线见 `validateCandidate`(同文件 `:452`)——`provider` 须等于注册名、\n * `description` 非空、`rank` 有限数;`get` 回定义须与候选同名(同文件 `:259`)。\n */\nexport interface SkillInvocationPolicy {\n readonly modelInvocable: boolean;\n readonly userInvocable: boolean;\n}\n\nexport interface SkillCandidate {\n readonly name: string;\n readonly description: string;\n readonly invocation: SkillInvocationPolicy;\n readonly provider: string;\n readonly source: string;\n readonly rank: number;\n readonly locator: unknown;\n readonly resourceBase?: { readonly kind: 'directory'; readonly path: string };\n}\n\nexport interface SkillDefinition {\n readonly name: string;\n readonly description: string;\n readonly invocation: SkillInvocationPolicy;\n readonly provider: string;\n readonly source: string;\n readonly resourceBase?: { readonly kind: 'directory'; readonly path: string };\n readonly content: string;\n}\n\nexport interface SkillProvider {\n readonly name: string;\n list(options?: unknown): Promise<readonly SkillCandidate[]>;\n get(candidate: SkillCandidate, options?: unknown): Promise<SkillDefinition | undefined>;\n}\n\nexport interface SkillsFace {\n registerProvider(\n create: (control: { readonly signal: AbortSignal; invalidate(): void }) => SkillProvider,\n ): () => void;\n}\n\n/** 本插件用到的宿主面(apply 入参)。\n *\n * `connection` 面:#80 起本插件走的是 DSH 公开的 `/api` 载体 `ctx.connection.fetch.register`\n * (见 `src/index.ts` 的拆雷注释与 `docs/agents/plugin-webserver-inject.md`),不再是早期脚手架的\n * `connection.rpc.handle`。此前镜像一直停在旧那一版(复审 F 的 S3)——现照**实现**对齐;\n * `rpc` 降为可选,因为 `contract.ts` 的信封仍按 RPC 语义命名(`RpcResult`/`RpcHandler`)。\n *\n * #696:设置页要在既有通道上加三个配置端点,宿主注册面的镜像类型补成具名的 `HostConnectionFace`\n * (出处同上一段,照 `packages/plugin-chef/src/dsh-ctx.ts` 那份写);其余面一个字未动。\n */\nexport interface ConnectionFetchRegisterOptions {\n readonly path: string;\n readonly methods?: readonly string[];\n readonly requestBody?: string;\n fetch(request: unknown): Promise<Response> | Response;\n}\n\n/** 宿主 fetch 路由注册面(`ctx.connection.fetch.register`):出处与上一件同源\n * (`@xmanrui/dsh-im` 的 `plugin-src/management-rpc.mjs`,宿主注册走 route 对象)。\n * #696 起 `index.ts` 用它做这个面的镜像类型(原先那里是就地写死的匿名结构)。 */\nexport interface HostConnectionFace {\n readonly fetch: { register(options: ConnectionFetchRegisterOptions): () => void };\n}\n\nexport interface HostCtx {\n readonly connection: {\n readonly fetch: { register(options: ConnectionFetchRegisterOptions): () => void };\n readonly rpc?: { handle(channel: string, handler: RpcHandler): () => void };\n };\n readonly skills: SkillsFace;\n /** 模型工具注册表(#734):本包把技能唯一出口开成 agent 工具。 */\n readonly tools?: ToolsFace;\n effect(callback: () => ((() => void) | void), label?: string): () => void;\n readonly logger?: unknown;\n}\n\n/** 宿主目录选择命名空间的服务名(#736;出处与禁令见 cookbook §13)。 */\nexport const REMOTE_DIRECTORY_PICKER = 'remote.directoryPicker' as const;\n\n/** 宿主目录选择命名空间(DSH 平台提供)。本包**只用 `pick` 这一格**:不给 signal(平台那格可选)。\n *\n * **回执是信封,不是路径**(#743 真机缺陷的根因):客户端把每次 Remote 调用包成\n * `{ok:true,value}`/`{ok:false,error}`(`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`:\n * 失败不抛、成功也不回裸值),第一方消费方 `@deepseek-ai/dsh-client-ui-workspace/lib/client.js`\n * 的 `pickDirectory()` 就照这个形状拆:`const r = await pick(); if (!r.ok) throw …; return r.value`。\n * `value` 为 `null`=用户取消;`ok:false`=这条路供不了(例如组合里是 browse 后端)。 */\nexport interface DirectoryPickerAnswer {\n readonly ok: boolean;\n readonly value?: string | null;\n readonly error?: { readonly code?: string; readonly message?: string };\n}\n\nexport interface DirectoryPickerFace {\n pick(): Promise<DirectoryPickerAnswer>;\n}\n\n/** 模型工具面镜像(#734;出处见 cookbook §14)。\n *\n * 出处:`@deepseek-ai/dsh-tools/lib/index.js` 的 `defineTool` 与 `ToolRuntime.register`——\n * 工具对象的形状是 `name`/`description`/`parameters`(JSON Schema)/`output{schema,render}`/\n * `execute`;`register` 只校验 `output.render` 是函数、`output.schema` 受支持、`timeoutMs` 为正、\n * 名字不是保留名 `run_code`,**不要求 `defineTool` 产物** ⇒ 本包按形状手写、零依赖。 */\nexport interface ToolTextPart {\n readonly type: 'text';\n readonly text: string;\n}\n\nexport interface ToolDefinitionMirror {\n readonly name: string;\n readonly description: string;\n readonly parameters: Record<string, unknown>;\n readonly output: {\n readonly schema: Record<string, unknown>;\n render(args: unknown, value: string): readonly ToolTextPart[];\n };\n execute(args: Record<string, unknown>, exec?: unknown): Promise<unknown>;\n}\n\nexport interface ToolsFace {\n register(definition: ToolDefinitionMirror): () => void;\n}\n","/** dsh-memo-ilife client 适配器(六边形:port=contract+dsh-ctx 镜像,adapter=本文件)。\n *\n * 产物经 tsdown 打成 loader 工厂包(browser/CJS,见 tsdown.config.ts)。\n * 本文件传递闭包:禁 node、禁 DOM 直写(document/window/process)。\n * 注册两处(grill 定案,新术语见 CONTEXT.md):\n * (a) 技能设置页 → 爱生活页签槽(总管声明的 children,各技能自研自家配置页);\n * #696 起这一页从「只读设置行」换成**真配置页**:行表来自 settings.ts,取值与保存经宿主三个端点\n * (`config.get`/`config.save`/`config.reset`),宿主再 spawn 技能 CLI 的 `memo.config.*` 三个 key。\n * 本页只配置、不干活:没有查数、记一条之类的入口(那些在 (b) 的功能页)。\n * (b) 技能功能页 → sidebar槽(better-sidebar 服务,非 DSH slot;软依赖,\n * 缺席即跳过,不断链。DSH右侧槽 + sidebar.footer.action 彻底出局,已删)。\n * 有界重试例外依据(唯一允许的定时器):注册重试非数据轮询,照抄\n * dsh-mattpocock-skills-deck/src/client/panelAssembly.js:100-108\n * (betterSidebar 服务可能晚于本模块到达,最多 10 次、间隔 1000ms;卸载清理)。\n * 只用 dsh-ctx 镜像内成员(slots.inject/register、connection.rpc.call 经闭包现取、\n * betterSidebar 与 #736 的目录选择命名空间经 ctx.get 运行时取);取数只经 connection.rpc.call 进 host 通道;\n * 缺席/错误纯条件渲染,不返空冒充。组件 React.createElement 手写,不引入 JSX。\n */\nimport * as React from 'react';\nimport { RPC_CHANNEL, RPC_ENDPOINT_READ, RPC_ENDPOINT_CONFIG_GET, RPC_ENDPOINT_CONFIG_SAVE, RPC_ENDPOINT_CONFIG_RESET, DEFAULT_READ_KEY, isRpcResult } from './contract.js';\nimport type { ConfigSurfaceReply } from './contract.js';\nimport { SLOT_TITLE, PLUGIN_VERSION, SKILL_VERSION } from './slot.js';\nimport { CONFIG_ITEMS, COMMON_ITEM_COUNT, ADVANCED_GROUP_TITLE, ADVANCED_GROUP_NOTE, readPath, writePath } from './settings.js';\nimport type { ConfigItem } from './settings.js';\nimport { REMOTE_DIRECTORY_PICKER } from './dsh-ctx.js';\nimport type { ClientCtx, RpcCallFace, RpcCallResult, DirectoryPickerAnswer, DirectoryPickerFace } from './dsh-ctx.js';\n\n/** client 短名声明:只有这两个(#736 的目录选择走**可选查找**,不写进来——\n * 写进来=硬依赖,提供方缺席时整包被停靠,设置页会跟着装不上;见 cookbook §13)。 */\nexport const inject = ['slots', 'connection'];\n\n/** 调用口取用器:每次取数时现取(connection 后到也不永久缺席)。 */\nexport type GetCall = () => unknown;\n\n/** 面板视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退)。 */\nconst S = {\n card: {\n padding: '12px 14px',\n borderRadius: 10,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'var(--dsw-alias-bg-base, transparent)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n lineHeight: 1.6,\n } as React.CSSProperties,\n title: { fontSize: '1.08em', fontWeight: 700, marginBottom: 8 } as React.CSSProperties,\n total: { fontSize: 22, fontWeight: 700, margin: '2px 0 4px' } as React.CSSProperties,\n muted: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: '0.92em'} as React.CSSProperties,\n error: { color: 'var(--dsw-alias-state-error-primary, #ff6b6b)', fontSize: '1em', whiteSpace: 'pre-wrap' } as React.CSSProperties,\n okText: { color: 'var(--dsw-alias-state-success-primary, #12805c)', fontSize: '0.96em'} as React.CSSProperties,\n rows: { marginTop: 8, borderTop: '1px solid var(--dsw-alias-border, rgba(128,128,128,.25))', paddingTop: 8 } as React.CSSProperties,\n row: { marginTop: 10 } as React.CSSProperties,\n label: { fontWeight: 600 } as React.CSSProperties,\n hint: { color: 'var(--dsw-alias-label-tertiary, #8a8a8a)', fontSize: '0.92em', margin: '1px 0 4px' } as React.CSSProperties,\n input: {\n width: '100%',\n boxSizing: 'border-box',\n padding: '4px 8px',\n borderRadius: 6,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.45))',\n background: 'var(--dsw-alias-bg-base, transparent)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n } as React.CSSProperties,\n pickRow: { display: 'flex', gap: 6, alignItems: 'center' } as React.CSSProperties,\n btnPick: {\n flex: '0 0 auto',\n padding: '4px 10px',\n borderRadius: 6,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.45))',\n background: 'var(--dsw-alias-bg-base, transparent)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n whiteSpace: 'nowrap',\n cursor: 'pointer',\n } as React.CSSProperties,\n info: {\n fontSize: '0.92em',\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n overflowWrap: 'anywhere',\n } as React.CSSProperties,\n bar: { marginTop: 14, display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } as React.CSSProperties,\n btn: {\n padding: '5px 14px',\n borderRadius: 7,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.45))',\n background: 'var(--dsw-alias-bg-base, transparent)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n cursor: 'pointer',\n } as React.CSSProperties,\n btnPrimary: {\n padding: '5px 14px',\n borderRadius: 7,\n border: '1px solid var(--dsw-alias-brand-primary, #2f6fed)',\n background: 'var(--dsw-alias-brand-primary, #2f6fed)',\n color: '#fff',\n cursor: 'pointer',\n } as React.CSSProperties,\n adv: {\n marginTop: 12,\n paddingTop: 8,\n borderTop: '1px solid var(--dsw-alias-border, rgba(128,128,128,.25))',\n } as React.CSSProperties,\n advSummary: { cursor: 'pointer', fontWeight: 600 } as React.CSSProperties,\n version: {\n marginTop: 8,\n paddingTop: 8,\n borderTop: '1px dashed var(--dsw-alias-border, rgba(128,128,128,.25))',\n color: 'var(--dsw-alias-label-tertiary, #8a8a8a)',\n fontSize: '0.92em',\n } as React.CSSProperties,\n};\n\n/** 版本行:插件与技能双版本号(面板自报家门,排障时看这台装的是哪版)。\n * 只留两个号——包名在页签上已经写着,重复一遍只是噪音(#743)。 */\nfunction VersionLine(): React.ReactElement {\n return React.createElement('div', { style: S.version }, `${PLUGIN_VERSION} · 技能 ${SKILL_VERSION}`);\n}\n\ntype PanelState =\n | { readonly kind: 'loading' }\n | { readonly kind: 'data'; readonly total: string }\n | { readonly kind: 'absent'; readonly message: string }\n | { readonly kind: 'error'; readonly message: string };\n\n/** 数据态取值:有 total 显示 total,有值无 total 显示原值 JSON,空值返回 null(由调用方进错误态)。 */\nfunction extractTotal(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value === 'string') return value.length > 0 ? value : null;\n if (typeof value === 'object') {\n const total = (value as { total?: unknown }).total;\n if (total !== null && total !== undefined) return String(total);\n try {\n return JSON.stringify(value);\n } catch {\n return null;\n }\n }\n return String(value);\n}\n\ntype ReadOutcome =\n | { readonly ok: true; readonly text: string }\n | { readonly ok: false; readonly absent: boolean; readonly message: string };\n\n/** 请求超时毫秒:与 host 侧 SPAWN_TIMEOUT_MS 同级,UI 永不无限转圈。 */\nconst READ_TIMEOUT_MS = 20_000 as const;\n\n/** 自家赛跑超时(有界:settle 即清 timer;不依赖传输是否搭理 AbortSignal——Desktop 自研传输会忽略 signal)。\n * 注释即 p10 有界重试门要的例外依据(文件内另有 clearTimeout/tries 上限/本注释三件套)。 */\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const limit = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n timer = undefined;\n const e = new Error(`取数超时(${Math.round(ms / 1000)}s):宿主未回,先查宿主日志与 DB 环境`);\n e.name = 'TimeoutError';\n reject(e);\n }, ms);\n });\n return Promise.race([\n promise.finally(() => {\n if (timer !== undefined) clearTimeout(timer);\n }),\n limit,\n ]);\n}\n\n/** 单次读数(纯异步函数,无 hook;挂载期调用一次,无轮询)。 */\nasync function fetchRead(call: unknown, key: string, params: Record<string, unknown>): Promise<ReadOutcome> {\n // extractRpc 同式守卫:非函数即缺席态。\n if (typeof call !== 'function') return { ok: false, absent: true, message: '宿主连接缺席:connection.rpc.call 不可用' };\n let result: RpcCallResult;\n try {\n // 防御纵深 + 自家超时双保险(AbortSignal 帮正规传输提前收工;race 保传输忽略 signal 时仍落字)。\n const raw: unknown = await withTimeout(\n // #80:走 DSH 公开的 /api 载体(host 侧为 connection.fetch.register 注册的 /api/ilife-memo)。\n call('/api', RPC_CHANNEL.slice(1), { method: RPC_ENDPOINT_READ, payload: { key, params } }, AbortSignal.timeout(READ_TIMEOUT_MS)),\n READ_TIMEOUT_MS,\n );\n if (!isRpcResult(raw)) return { ok: false, absent: false, message: '回执信封异常(非 ok 信封)' };\n result = raw;\n } catch (e) {\n if (e instanceof Error && e.name === 'AbortError') return { ok: false, absent: false, message: '取数超时(20s):宿主未回,先查宿主日志与 DB 环境' };\n return { ok: false, absent: false, message: `取数失败:${e instanceof Error ? e.message : String(e)}` };\n }\n if (!result.ok) {\n const code = result.error?.code ?? 'unknown';\n const message = result.error?.message ?? '';\n if (code === 'missing-cli') return { ok: false, absent: true, message: `技能出口缺席(missing-cli):${message}` };\n return { ok: false, absent: false, message: `取数失败(${code}):${message}` };\n }\n const text = extractTotal(result.value);\n if (text === null) return { ok: false, absent: false, message: '取数为空(不冒充正常)' };\n return { ok: true, text };\n}\n\n/** 技能功能页:sidebar槽里展开的干活区(查数调数,不管配置)。挂载期一次 RPC。 */\nfunction MemoWork(props: { getCall: GetCall }): React.ReactElement {\n const [state, setState] = React.useState<PanelState>({ kind: 'loading' });\n React.useEffect(() => {\n let alive = true;\n void (async () => {\n const r = await fetchRead(props.getCall(), DEFAULT_READ_KEY, {});\n if (!alive) return;\n if (r.ok) setState({ kind: 'data', total: r.text });\n else if (r.absent) setState({ kind: 'absent', message: r.message });\n else setState({ kind: 'error', message: r.message });\n })();\n return () => {\n alive = false;\n };\n }, [props.getCall]);\n if (state.kind === 'loading') {\n return React.createElement('div', { style: S.card }, React.createElement('div', { style: S.muted }, '备忘录加载中…'));\n }\n if (state.kind === 'data') {\n return React.createElement(\n 'div',\n { style: S.card },\n React.createElement('div', { style: S.title }, SLOT_TITLE),\n React.createElement('div', { style: S.total }, `total ${state.total}`),\n React.createElement(VersionLine, null),\n );\n }\n return React.createElement(\n 'div',\n { style: S.card },\n React.createElement('div', { style: state.kind === 'absent' ? S.muted : S.error }, state.message),\n React.createElement(VersionLine, null),\n );\n}\n\n/** 配置页三态:读取中(一次 RPC 内)/ready(真表单)/failed(人话报错 + 指引)。 */\ntype ConfigState =\n | { readonly kind: 'loading' }\n | { readonly kind: 'ready'; readonly surface: ConfigSurfaceReply }\n | { readonly kind: 'failed'; readonly message: string };\n\ntype ConfigOutcome =\n | { readonly ok: true; readonly surface: ConfigSurfaceReply }\n | { readonly ok: false; readonly message: string };\n\n/** 配置面报错 → 人话指引。\n *\n * 报文由技能侧给出:base-link-core 的 `ConfigError` 已带行号与文件名,技能 `cli/config.ts` 原样交出来,\n * 插件 bridge 原样带上(`fetch-failed:出口非 0(1):<那一句>`)。所以这里**不重写报文**,\n * 只按报错类补一句「接下来怎么办」——免得两处各写一套人话。\n */\nexport function humanizeConfigFailure(code: string, message: string): string {\n const text = message.trim().length > 0 ? message.trim() : `(${code},宿主未给报文)`;\n if (/解析|YAML|parse/i.test(text)) return `${text}\\n改回「键: 值」的写法,或点「重置为默认」。`;\n if (/不认识|未知键|UNKNOWN_KEY/i.test(text)) return `${text}\\n删掉页面上没有的行,或点「重置为默认」。`;\n if (/类型|TYPE_MISMATCH/i.test(text)) return `${text}\\n按本页的控件形状填,或点「重置为默认」。`;\n if (/缺席|missing-cli/i.test(text)) return `${text}\\n技能出口没找到:先确认技能包已装好,再点「重新读取」。`;\n return `${text}\\n改不动就点「重置为默认」,或照上面那条路径手工改配置文件。`;\n}\n\n/** 一次配置 RPC(同通道过三个端点;永不抛,失败落字)。 */\nasync function configRpc(call: unknown, method: string, payload: Record<string, unknown>): Promise<ConfigOutcome> {\n if (typeof call !== 'function') return { ok: false, message: '宿主连接缺席:connection.rpc.call 不可用' };\n let raw: unknown;\n try {\n // 防御纵深 + 自家超时双保险(AbortSignal 帮正规传输提前收工;race 保传输忽略 signal 时仍落字)。\n raw = await withTimeout(\n // #80:走 DSH 公开的 /api 载体(host 侧为 connection.fetch.register 注册的 /api/ilife-memo)。\n (call as RpcCallFace)('/api', RPC_CHANNEL.slice(1), { method, payload }, AbortSignal.timeout(READ_TIMEOUT_MS)),\n READ_TIMEOUT_MS,\n );\n } catch (e) {\n if (e instanceof Error && (e.name === 'AbortError' || e.name === 'TimeoutError')) {\n return { ok: false, message: `配置请求超时(${Math.round(READ_TIMEOUT_MS / 1000)}s):宿主未回` };\n }\n return { ok: false, message: `配置失败:${e instanceof Error ? e.message : String(e)}` };\n }\n if (!isRpcResult(raw)) return { ok: false, message: '回执信封异常(非 ok 信封)' };\n const res = raw as RpcCallResult;\n if (!res.ok) return { ok: false, message: humanizeConfigFailure(res.error?.code ?? 'unknown', res.error?.message ?? '') };\n return { ok: true, surface: res.value as ConfigSurfaceReply };\n}\n\n/** 读设置页整面。 */\nexport function fetchConfigSurface(call: unknown): Promise<ConfigOutcome> {\n return configRpc(call, RPC_ENDPOINT_CONFIG_GET, {});\n}\n\n/** 保存一份取值(写回执只有 {path, values},头部那两行要的是读整面,故由组件写后重读)。 */\nexport function saveConfigSurface(call: unknown, values: Record<string, unknown>): Promise<ConfigOutcome> {\n return configRpc(call, RPC_ENDPOINT_CONFIG_SAVE, { values });\n}\n\n/** 重置为默认(技能侧先落 .bak;同样由组件重读一份整面)。 */\nexport function resetConfigSurface(call: unknown): Promise<ConfigOutcome> {\n return configRpc(call, RPC_ENDPOINT_CONFIG_RESET, {});\n}\n\n/** 把配置取值铺成「行键 → 输入框文本」(页面表单态;值缺项即空串,不返空留白)。\n *\n * `prefill` 是**落点回执**(配置面那几格解析出来的绝对路径):标了 `prefillFrom` 的行在取值空着时\n * 直接显示那条绝对路径——用户不必自己拼路径(#743),显示的就是技能真会用的那个目录(逐字相同)。 */\nexport function toDraft(\n values: Record<string, unknown>,\n prefill: { readonly dataDir?: string } = {},\n): Record<string, string> {\n const draft: Record<string, string> = {};\n for (const item of CONFIG_ITEMS) {\n const v = readPath(values, item.key);\n const raw = v === undefined || v === null ? '' : String(v);\n const fallback = item.prefillFrom === undefined ? undefined : prefill[item.prefillFrom];\n draft[item.key] = raw === '' && typeof fallback === 'string' && fallback !== '' ? fallback : raw;\n }\n return draft;\n}\n\n/** 表单态 → 配置取值(按控件种类还原类型;空串对文本项照收,语义由「按默认落点」承担)。 */\nexport function fromDraft(draft: Record<string, string>): Record<string, unknown> {\n const values: Record<string, unknown> = {};\n for (const item of CONFIG_ITEMS) {\n const raw = draft[item.key] ?? '';\n if (item.control === 'number') {\n const n = Number(raw);\n writePath(values, item.key, Number.isFinite(n) ? n : 0);\n } else if (item.control === 'switch') {\n writePath(values, item.key, raw === 'true');\n } else {\n writePath(values, item.key, raw);\n }\n }\n return values;\n}\n\n/** 目录选择的三种结果(注入 picker 的纯归一:选中/取消/这条路供不了)。 */\nexport type PickOutcome =\n | { readonly kind: 'picked'; readonly path: string }\n | { readonly kind: 'cancelled' }\n | { readonly kind: 'unavailable'; readonly message: string };\n\n/** 形状守卫:拿到的那个东西像不像目录选择命名空间(本包只认 `pick` 是函数)。 */\nexport function isDirectoryPicker(raw: unknown): raw is DirectoryPickerFace {\n if (typeof raw !== 'object' || raw === null) return false;\n return typeof (raw as { pick?: unknown }).pick === 'function';\n}\n\n/** 现取目录选择命名空间:**软依赖**,拿不到回 null(页面据此不出入口)。\n *\n * 为什么不用 `inject` 声明:见 cookbook §13——写进声明=硬依赖,提供方缺席时整包被停靠,\n * 设置页会跟着装不上。这里按平台给的「可选查找」办:缺席只是没有入口。\n * 守卫拒绝或提供方中途卸载会抛,一律当「没有」,绝不把设置页带下来。 */\nexport function resolveDirectoryPicker(getService: unknown): DirectoryPickerFace | null {\n if (typeof getService !== 'function') return null;\n try {\n const raw = (getService as (name: string) => unknown)(REMOTE_DIRECTORY_PICKER);\n return isDirectoryPicker(raw) ? raw : null;\n } catch {\n return null;\n }\n}\n\n/** 平台回执 → 三种结果(**永不抛**)。回执是信封 `{ok, value|error}`(见 dsh-ctx.ts 的\n * `DirectoryPickerAnswer`):成功回的是 `value` 不是路径本身,被拒回的是 `ok:false` 不是抛——\n * 照裸值解会把两种情况都误判成「用户取消」(#743 真机现象:点「选择文件夹」什么都没发生)。\n * 裸串照收(老形状兜底),认不出的形状当「供不了」报出来,不当取消吞掉。 */\nexport function readPickAnswer(raw: unknown): PickOutcome {\n if (typeof raw === 'string') return raw.trim() === '' ? { kind: 'cancelled' } : { kind: 'picked', path: raw };\n const answer = (typeof raw === 'object' && raw !== null ? raw : {}) as DirectoryPickerAnswer;\n if (answer.ok === true) {\n const value = answer.value;\n return typeof value === 'string' && value.trim() !== '' ? { kind: 'picked', path: value } : { kind: 'cancelled' };\n }\n const detail = answer.error?.message?.trim() ?? '';\n return { kind: 'unavailable', message: '打不开系统文件夹对话框' + (detail === '' ? '' : '(' + detail + ')') + ':请直接在框里填绝对路径。' };\n}\n\n/** 唤起一次系统文件夹选择器并归一结果(**永不抛**)。 */\nexport async function pickDirectory(picker: DirectoryPickerFace): Promise<PickOutcome> {\n try {\n return readPickAnswer(await picker.pick());\n } catch (e) {\n const reason = e instanceof Error ? e.message : String(e);\n return { kind: 'unavailable', message: '打不开系统文件夹对话框(' + reason + '):请直接在框里填绝对路径。' };\n }\n}\n\n/** 「选择文件夹」按钮的真装配函数(目录行渲染出来的按钮,onClick 就是它)。\n *\n * 回 Promise 是为了本地可判(用例直接 await);接进 React 时调用方 `void` 掉。 */\nexport function createBrowseHandler(deps: {\n readonly picker: DirectoryPickerFace;\n readonly onChange: (key: string, next: string) => void;\n readonly onUnavailable: (message: string) => void;\n}): (key: string) => Promise<void> {\n return async (key: string) => {\n const outcome = await pickDirectory(deps.picker);\n if (outcome.kind === 'picked') deps.onChange(key, outcome.path);\n else if (outcome.kind === 'unavailable') deps.onUnavailable(outcome.message);\n };\n}\n\n/** 一行输入(四种控件对齐受限 YAML 子集:文本/数字/布尔/目录)。\n *\n * 目录档=文本框 + 一枚唤起系统文件夹选择器的按钮;`onBrowse` 缺席(命名空间拿不到、\n * 或这条路已被拒)时不画按钮,文本框照旧——那是该缝自己的契约(供不了就收起入口,不是失败)。 */\nexport function Row(props: {\n readonly item: ConfigItem;\n readonly value: string;\n readonly disabled: boolean;\n readonly onChange: (key: string, next: string) => void;\n /** 目录行才有:唤起系统文件夹选择器。 */\n readonly onBrowse?: ((key: string) => void) | undefined;\n}): React.ReactElement {\n const { item } = props;\n const control =\n item.control === 'switch'\n ? React.createElement('input', {\n type: 'checkbox',\n checked: props.value === 'true',\n disabled: props.disabled,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => props.onChange(item.key, e.target.checked ? 'true' : 'false'),\n })\n : React.createElement('input', {\n style: S.input,\n type: item.control === 'number' ? 'number' : 'text',\n value: props.value,\n disabled: props.disabled,\n spellCheck: false,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => props.onChange(item.key, e.target.value),\n });\n const browse =\n item.control === 'directory' && props.onBrowse !== undefined\n ? React.createElement(\n 'button',\n {\n style: S.btnPick,\n type: 'button',\n disabled: props.disabled,\n // 回这枚 Promise 是有意的:React 不看 onClick 的返回值,而用例能直接 await 它,\n // 拿到「唤一次 pick → 回填这一行」的确定性读数(本地可判)。\n onClick: () => props.onBrowse?.(item.key),\n },\n '选择文件夹…',\n )\n : null;\n return React.createElement(\n 'div',\n { style: S.row },\n React.createElement('div', { style: S.label }, item.title),\n React.createElement('div', { style: S.hint }, item.hint),\n browse === null ? control : React.createElement('div', { style: S.pickRow }, control, browse),\n );\n}\n\n/** 技能设置页:承载备忘录自己的全部可配置项(只配置,不干活)。\n *\n * 三态:loading(一次 RPC 内)/ready(真表单)/failed(人话报错 + 指引,不返空、不转圈)。\n * 保存与重置之后都**重新读一份整面**再提示(写回执不带 dataDir/created,头部那两行要的是读整面)。 */\nfunction MemoConfig(props: { getCall: GetCall; getPicker: () => DirectoryPickerFace | null }): React.ReactElement {\n const [state, setState] = React.useState<ConfigState>({ kind: 'loading' });\n const [pickerGone, setPickerGone] = React.useState(false);\n const [picking, setPicking] = React.useState(false);\n const [draft, setDraft] = React.useState<Record<string, string>>({});\n const [busy, setBusy] = React.useState(false);\n const [notice, setNotice] = React.useState<string | null>(null);\n const [error, setError] = React.useState<string | null>(null);\n\n const apply = React.useCallback((surface: ConfigSurfaceReply) => {\n setState({ kind: 'ready', surface });\n setDraft(toDraft(surface.values, surface));\n }, []);\n\n const load = React.useCallback(async () => {\n setNotice(null);\n setError(null);\n const r = await fetchConfigSurface(props.getCall());\n if (r.ok) apply(r.surface);\n else setState({ kind: 'failed', message: r.message });\n }, [apply, props.getCall]);\n\n React.useEffect(() => {\n let alive = true;\n void (async () => {\n const r = await fetchConfigSurface(props.getCall());\n if (!alive) return;\n if (r.ok) apply(r.surface);\n else setState({ kind: 'failed', message: r.message });\n })();\n return () => {\n alive = false;\n };\n }, [apply, props.getCall]);\n\n const onChange = React.useCallback((key: string, next: string) => {\n setDraft((prev) => ({ ...prev, [key]: next }));\n setNotice(null);\n setError(null);\n }, []);\n\n /** 目录选择:拿不到命名空间就没有入口;被拒一次即收起入口(不留死按钮)。 */\n const picker = pickerGone ? null : props.getPicker();\n const onBrowse = React.useMemo(() => {\n if (picker === null) return undefined;\n const browse = createBrowseHandler({\n picker,\n onChange,\n onUnavailable: (message: string) => {\n setError(message);\n setPickerGone(true);\n },\n });\n // 系统对话框是模态的:等它回来的这段时间给一行字,免得看着像「点了没反应」(#743)。\n return (key: string) => {\n setPicking(true);\n setError(null);\n void browse(key).finally(() => setPicking(false));\n };\n }, [picker, onChange]);\n\n const surface = state.kind === 'ready' ? state.surface : null;\n const dirty = surface !== null && CONFIG_ITEMS.some((i) => (draft[i.key] ?? '') !== (toDraft(surface.values, surface)[i.key] ?? ''));\n\n /** 写完(保存/重置)之后重新读一份整面,读到了才敢提示「已完成」。 */\n const writeThenReload = React.useCallback(async (done: string) => {\n setBusy(true);\n setNotice(null);\n setError(null);\n const r = await fetchConfigSurface(props.getCall());\n if (r.ok) {\n apply(r.surface);\n setNotice(done);\n } else {\n setState({ kind: 'failed', message: r.message });\n }\n setBusy(false);\n }, [apply, props.getCall]);\n\n const onSave = React.useCallback(async () => {\n setBusy(true);\n setNotice(null);\n setError(null);\n const r = await saveConfigSurface(props.getCall(), fromDraft(draft));\n setBusy(false);\n if (r.ok) await writeThenReload('已保存,立即生效(不用重启宿主)');\n else setError(r.message);\n }, [draft, props.getCall, writeThenReload]);\n\n const onReset = React.useCallback(async () => {\n setBusy(true);\n setNotice(null);\n setError(null);\n const r = await resetConfigSurface(props.getCall());\n setBusy(false);\n if (r.ok) await writeThenReload('已重置为默认(原配置已另存一份 .bak)');\n else setError(r.message);\n }, [props.getCall, writeThenReload]);\n\n const head = React.createElement(\n 'div',\n null,\n React.createElement('div', { style: S.title }, `${SLOT_TITLE} · 配置`),\n surface !== null\n ? React.createElement(\n 'div',\n null,\n React.createElement('div', { style: S.info }, `配置文件 ${surface.path}`),\n React.createElement('div', { style: S.info }, `数据目录 ${surface.dataDir}`),\n surface.created ? React.createElement('div', { style: S.muted }, '(配置文件刚按默认值生成)') : null,\n )\n : null,\n );\n\n if (state.kind === 'loading') {\n return React.createElement(\n 'div',\n { style: S.card },\n head,\n React.createElement('div', { style: S.muted }, '配置读取中…'),\n React.createElement(VersionLine, null),\n );\n }\n\n if (state.kind === 'failed') {\n return React.createElement(\n 'div',\n { style: S.card },\n React.createElement('div', { style: S.title }, `${SLOT_TITLE} · 配置`),\n React.createElement('div', { style: S.error }, state.message),\n React.createElement(\n 'div',\n { style: S.bar },\n React.createElement('button', { style: S.btn, type: 'button', onClick: () => void load() }, '重试'),\n React.createElement('button', { style: S.btn, type: 'button', onClick: () => void onReset() }, '重置为默认'),\n ),\n React.createElement(VersionLine, null),\n );\n }\n\n const common = CONFIG_ITEMS.slice(0, COMMON_ITEM_COUNT);\n const advanced = CONFIG_ITEMS.slice(COMMON_ITEM_COUNT);\n const renderRow = (item: ConfigItem) =>\n React.createElement(Row, { key: item.key, item, value: draft[item.key] ?? '', disabled: busy, onChange, onBrowse });\n\n return React.createElement(\n 'div',\n { style: S.card },\n head,\n React.createElement('div', { style: S.rows }, common.map(renderRow)),\n React.createElement(\n 'details',\n { style: S.adv },\n React.createElement('summary', { style: S.advSummary }, ADVANCED_GROUP_TITLE),\n React.createElement('div', { style: S.muted }, ADVANCED_GROUP_NOTE),\n advanced.map(renderRow),\n ),\n React.createElement(\n 'div',\n { style: S.bar },\n React.createElement(\n 'button',\n { style: dirty ? S.btnPrimary : S.btn, type: 'button', disabled: busy || !dirty, onClick: () => void onSave() },\n busy ? '处理中…' : '保存',\n ),\n React.createElement('button', { style: S.btn, type: 'button', disabled: busy, onClick: () => void onReset() }, '重置为默认'),\n React.createElement('button', { style: S.btn, type: 'button', disabled: busy, onClick: () => void load() }, '重新读取'),\n ),\n picking ? React.createElement('div', { style: S.muted }, '已唤起系统文件夹对话框:选中后自动填上,取消则不动。') : null,\n notice !== null ? React.createElement('div', { style: S.okText }, notice) : null,\n error !== null ? React.createElement('div', { style: S.error }, error) : null,\n React.createElement(VersionLine, null),\n );\n}\n\ninterface BetterSidebarService {\n registerTab(descriptor: {\n readonly id: string;\n readonly title: string | (() => string);\n readonly order?: number;\n readonly single?: boolean;\n readonly component: (props: unknown) => React.ReactElement | null;\n }): () => void;\n}\n\nexport function apply(ctx: ClientCtx): void {\n // 调用口取用器透传给组件(每次取数现取;非函数由组件判缺席)。\n const getCall: GetCall = () => ctx.connection?.rpc?.call ?? null;\n // #736 目录选择:**软依赖**,每次渲染现取(提供方后到/中途卸载都不留痕);\n // 拿不到就只是没有入口,设置页照常(不声明、不停靠,见 cookbook §13)。\n const getPicker = (): DirectoryPickerFace | null =>\n typeof ctx.get === 'function' ? resolveDirectoryPicker((name: string) => ctx.get?.(name)) : null;\n\n // (a) 技能设置页 → 爱生活页签槽(总管声明;总管缺席时 inject 等待,不断链)。\n ctx.slots.inject('ilife.config-tab', () =>\n ctx.slots.register(\n {\n name: 'ilife.config-tab',\n id: 'dsh-memo-ilife',\n order: 80,\n label: () => SLOT_TITLE,\n // #706:本包自己那条 RPC 通道交给总管(各家都写这一格),总管据此调配置体检——\n // 它因此不必在源码里写死任何一家的通道名(零单品依赖照旧成立)。\n channel: RPC_CHANNEL,\n },\n () => React.createElement(MemoConfig, { getCall, getPicker }),\n ),\n );\n\n // (b) 技能功能页 → sidebar槽(软依赖:betterSidebar 缺席即跳过)。\n const ensureWorkTab = (): boolean => {\n try {\n const get = ctx.get;\n const bs = (typeof get === 'function' ? get('betterSidebar') : undefined) as BetterSidebarService | undefined;\n if (!bs || typeof bs.registerTab !== 'function') return false;\n const dispose = bs.registerTab({\n id: 'ilife-memo-ilife:work',\n title: '备忘录',\n order: 60,\n single: true,\n component: () => React.createElement(MemoWork, { getCall }),\n });\n ctx.effect(() => () => {\n try {\n dispose();\n } catch {\n /* 卸载清理失败忽略 */\n }\n }, 'dsh-memo-ilife: sidebar work tab');\n return true;\n } catch {\n return false;\n }\n };\n if (!ensureWorkTab()) {\n let tries = 0;\n const timer = setInterval(() => {\n tries++;\n if (ensureWorkTab() || tries >= 10) clearInterval(timer);\n }, 1000);\n ctx.effect(() => () => {\n clearInterval(timer);\n }, 'dsh-memo-ilife: sidebar work tab retry');\n }\n}\n"],"mappings":"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"}
package/dist/slot.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  export declare const SKILL: "memo";
9
9
  export declare const SLOT_ID: "ilife:memo";
10
- export declare const SLOT_ORDER: 70;
10
+ export declare const SLOT_ORDER: 80;
11
11
  export declare const SLOT_TITLE: "\u5907\u5FD8\u5F55";
12
12
  export declare const PLUGIN: "dsh-memo-ilife";
13
13
  export declare const MANAGER_PLUGIN: "dsh-life-pack";
@@ -15,7 +15,7 @@ export declare const SKILL_PACKAGE: "skill-memo-ilife";
15
15
  /** 版本行唯一来源(面板展示用;smoke 断言与两处 package.json 一致,防漂移)。
16
16
  * 发版 bump 时同步改这里(与 package.json 同值):0.3.2 那轮之前停在 0.2.1——
17
17
  * 面板自报家门报了旧号(#743 真机截图逮到),本票一并摆正。 */
18
- export declare const PLUGIN_VERSION: "0.3.2";
18
+ export declare const PLUGIN_VERSION: "0.3.3";
19
19
  export declare const SKILL_VERSION: "0.3.0";
20
20
  export interface SlotDescriptor {
21
21
  readonly skill: string;
package/dist/slot.js CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  export const SKILL = 'memo';
9
9
  export const SLOT_ID = 'ilife:memo';
10
- export const SLOT_ORDER = 70;
10
+ export const SLOT_ORDER = 80;
11
11
  export const SLOT_TITLE = '备忘录';
12
12
  export const PLUGIN = 'dsh-memo-ilife';
13
13
  export const MANAGER_PLUGIN = 'dsh-life-pack';
@@ -15,7 +15,7 @@ export const SKILL_PACKAGE = 'skill-memo-ilife';
15
15
  /** 版本行唯一来源(面板展示用;smoke 断言与两处 package.json 一致,防漂移)。
16
16
  * 发版 bump 时同步改这里(与 package.json 同值):0.3.2 那轮之前停在 0.2.1——
17
17
  * 面板自报家门报了旧号(#743 真机截图逮到),本票一并摆正。 */
18
- export const PLUGIN_VERSION = '0.3.2';
18
+ export const PLUGIN_VERSION = '0.3.3';
19
19
  export const SKILL_VERSION = '0.3.0';
20
20
  export function slotDescriptor() {
21
21
  return { skill: SKILL, slotId: SLOT_ID, order: SLOT_ORDER, title: SLOT_TITLE, kind: 'page' };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-memo-ilife",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "爱生活单品插件 P10 脚手架:备忘录(ilife:memo order 70),硬依赖总管 dsh-life-pack",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",