dsh-calorie 0.1.1 → 0.1.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
@@ -1,20 +1,265 @@
1
- /** dsh-calorie 客户端存根(P10 脚手架)。
2
- *
3
- * 写死槽位原生组件,不做动态按需加载,不做外嵌页。
4
- * 面板取数只经 host.call 进 host 桥(见 bridge.ts),不 import 技能实现。
5
- * 缺席纯条件渲染,无轮询(本文件无任何定时器)。
6
- */
7
- import { TAB_COMPONENT, registerSingle, openSingle } from './slot.js';
8
- import { HOST_CALL_METHOD } from './bridge.js';
9
- export const CLIENT_COMPONENT = TAB_COMPONENT;
10
- export const CLIENT_METHOD = HOST_CALL_METHOD;
11
- export function mountSingleClient(port) {
12
- return registerSingle(port);
13
- }
14
- export function openSingleClient(port, sessionId) {
15
- openSingle(port, sessionId);
16
- }
17
- export function requestReadViaHost(host, key, params = {}) {
18
- return host.call(HOST_CALL_METHOD, { key, params });
19
- }
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-calorie",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ //#region \0rolldown/runtime.js
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
16
+ key = keys[i];
17
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
18
+ get: ((k) => from[k]).bind(null, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
25
+ value: mod,
26
+ enumerable: true
27
+ }) : target, mod));
28
+ //#endregion
29
+ let react = require("react");
30
+ react = __toESM(react, 1);
31
+ //#region src/contract.ts
32
+ /** dsh-calorie 端口(port):DSH 世界与卡路里 hexagon 之间的契约。
33
+ *
34
+ * browser 安全:零 node 导入。host 适配器(注册 RPC 通道)与 client
35
+ * 适配器(调用 RPC)共用同一份定义;单测与 loader 回路以此为准。
36
+ * 通道命名遵守宿主约束:单段且不等于 /api(见 cookbook「RPC 通道」)。
37
+ */
38
+ const RPC_CHANNEL = "/ilife-calorie";
39
+ const RPC_ENDPOINT_READ = "read";
40
+ /** 面板默认读键(与 HITL 一致性对比同键)。 */
41
+ const DEFAULT_READ_KEY = "calorie.view.home";
42
+ /** 信封守卫:供 client 拆包前校验(port 拥有信封定义,守卫住 port)。 */
43
+ function isRpcResult(raw) {
44
+ if (typeof raw !== "object" || raw === null) return false;
45
+ const ok = raw.ok;
46
+ return ok === true || ok === false;
47
+ }
48
+ //#endregion
49
+ //#region src/slot.ts
50
+ const SLOT_TITLE = "卡路里";
51
+ //#endregion
52
+ //#region src/settings.ts
53
+ /** 脚手架期唯一设置行:总开关(缺省启用,与官方关闭语义对齐)。 */
54
+ const SETTING_ROWS = [{
55
+ key: "enabled",
56
+ title: "卡路里面板启用",
57
+ control: "switch"
58
+ }];
59
+ //#endregion
60
+ //#region src/client.ts
61
+ /** dsh-calorie client 适配器(六边形:port=contract+dsh-ctx 镜像,adapter=本文件)。
62
+ *
63
+ * 产物经 tsdown 打成 loader 工厂包(browser/CJS,见 tsdown.config.ts)。
64
+ * 本文件传递闭包:禁 node、禁 DOM 直写(document/window/process)。
65
+ * 注册两处(grill 定案,新术语见 CONTEXT.md):
66
+ * (a) 技能设置页 → 爱生活页签槽(总管声明的 children,各技能自研自家配置页);
67
+ * (b) 技能功能页 → sidebar槽(better-sidebar 服务,非 DSH slot;软依赖,
68
+ * 缺席即跳过,不断链。DSH右侧槽 + sidebar.footer.action 彻底出局,已删)。
69
+ * 有界重试例外依据(唯一允许的定时器):注册重试非数据轮询,照抄
70
+ * dsh-mattpocock-skills-deck/src/client/panelAssembly.js:100-108
71
+ * (betterSidebar 服务可能晚于本模块到达,最多 10 次、间隔 1000ms;卸载清理)。
72
+ * 只用 dsh-ctx 镜像内成员(slots.inject/register、connection.rpc.call 经闭包现取、
73
+ * betterSidebar 经 ctx.get 运行时取);取数只经 connection.rpc.call 进 host 通道;
74
+ * 缺席/错误纯条件渲染,不返空冒充。组件 React.createElement 手写,不引入 JSX。
75
+ */
76
+ const inject = ["slots"];
77
+ /** 本地今日 YYYY-MM-DD(与面板默认读键的 date 参数同形)。 */
78
+ function todayString() {
79
+ const d = /* @__PURE__ */ new Date();
80
+ const m = String(d.getMonth() + 1).padStart(2, "0");
81
+ const day = String(d.getDate()).padStart(2, "0");
82
+ return `${d.getFullYear()}-${m}-${day}`;
83
+ }
84
+ /** 数据态取值:有 total 显示 total,有值无 total 显示原值 JSON,空值返回 null(由调用方进错误态)。 */
85
+ function extractTotal(value) {
86
+ if (value === null || value === void 0) return null;
87
+ if (typeof value === "string") return value.length > 0 ? value : null;
88
+ if (typeof value === "object") {
89
+ const total = value.total;
90
+ if (total !== null && total !== void 0) return String(total);
91
+ try {
92
+ return JSON.stringify(value);
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+ return String(value);
98
+ }
99
+ /** 单次读数(纯异步函数,无 hook;挂载期调用一次,无轮询)。 */
100
+ async function fetchRead(call, key, params) {
101
+ if (typeof call !== "function") return {
102
+ ok: false,
103
+ absent: true,
104
+ message: "宿主连接缺席:connection.rpc.call 不可用"
105
+ };
106
+ let result;
107
+ try {
108
+ const raw = await call(RPC_CHANNEL, RPC_ENDPOINT_READ, {
109
+ key,
110
+ params
111
+ });
112
+ if (!isRpcResult(raw)) return {
113
+ ok: false,
114
+ absent: false,
115
+ message: "回执信封异常(非 ok 信封)"
116
+ };
117
+ result = raw;
118
+ } catch (e) {
119
+ return {
120
+ ok: false,
121
+ absent: false,
122
+ message: `取数失败:${e instanceof Error ? e.message : String(e)}`
123
+ };
124
+ }
125
+ if (!result.ok) {
126
+ const code = result.error?.code ?? "unknown";
127
+ const message = result.error?.message ?? "";
128
+ if (code === "missing-cli") return {
129
+ ok: false,
130
+ absent: true,
131
+ message: `技能出口缺席(missing-cli):${message}`
132
+ };
133
+ return {
134
+ ok: false,
135
+ absent: false,
136
+ message: `取数失败(${code}):${message}`
137
+ };
138
+ }
139
+ const text = extractTotal(result.value);
140
+ if (text === null) return {
141
+ ok: false,
142
+ absent: false,
143
+ message: "取数为空(不冒充正常)"
144
+ };
145
+ return {
146
+ ok: true,
147
+ text
148
+ };
149
+ }
150
+ /** 技能功能页:sidebar槽里展开的干活区(查数调数,不管配置)。挂载期一次 RPC。 */
151
+ function CalorieWork(props) {
152
+ const [state, setState] = react.useState({ kind: "loading" });
153
+ react.useEffect(() => {
154
+ let alive = true;
155
+ const date = todayString();
156
+ (async () => {
157
+ const r = await fetchRead(props.getCall(), DEFAULT_READ_KEY, { date });
158
+ if (!alive) return;
159
+ if (r.ok) setState({
160
+ kind: "data",
161
+ date,
162
+ total: r.text
163
+ });
164
+ else if (r.absent) setState({
165
+ kind: "absent",
166
+ message: r.message
167
+ });
168
+ else setState({
169
+ kind: "error",
170
+ message: r.message
171
+ });
172
+ })();
173
+ return () => {
174
+ alive = false;
175
+ };
176
+ }, [props.getCall]);
177
+ if (state.kind === "loading") return react.createElement("div", null, "卡路里加载中…");
178
+ if (state.kind === "data") return react.createElement("div", null, react.createElement("div", null, `卡路里 · ${state.date}`), react.createElement("div", null, `total ${state.total}`));
179
+ return react.createElement("div", null, state.message);
180
+ }
181
+ function controlLabel(control) {
182
+ if (control === "switch") return "开关(缺省启用,只读)";
183
+ if (control === "text") return "文本(只读)";
184
+ if (control === "number") return "数字(只读)";
185
+ return `${control}(只读)`;
186
+ }
187
+ /** 技能设置页:爱生活页签条下的一页(只读设置行 + 状态读数,禁做假开关)。 */
188
+ function CalorieConfig(props) {
189
+ const [state, setState] = react.useState({ kind: "loading" });
190
+ react.useEffect(() => {
191
+ let alive = true;
192
+ const date = todayString();
193
+ (async () => {
194
+ const r = await fetchRead(props.getCall(), DEFAULT_READ_KEY, { date });
195
+ if (!alive) return;
196
+ if (r.ok) setState({
197
+ kind: "data",
198
+ date,
199
+ total: r.text
200
+ });
201
+ else if (r.absent) setState({
202
+ kind: "absent",
203
+ message: r.message
204
+ });
205
+ else setState({
206
+ kind: "error",
207
+ message: r.message
208
+ });
209
+ })();
210
+ return () => {
211
+ alive = false;
212
+ };
213
+ }, [props.getCall]);
214
+ const status = state.kind === "loading" ? "状态读取中…" : state.kind === "data" ? `今日 ${state.date} · total ${state.total}` : state.message;
215
+ return react.createElement("div", null, react.createElement("div", null, status), react.createElement("div", null, SETTING_ROWS.map((row) => react.createElement("div", { key: row.key }, `${row.title} · ${controlLabel(row.control)}`))));
216
+ }
217
+ function apply(ctx) {
218
+ const getCall = () => ctx.connection?.rpc?.call ?? null;
219
+ ctx.slots.inject("ilife.config-tab", () => ctx.slots.register({
220
+ name: "ilife.config-tab",
221
+ id: "dsh-calorie",
222
+ order: 75,
223
+ label: () => SLOT_TITLE
224
+ }, () => react.createElement(CalorieConfig, { getCall })));
225
+ const ensureWorkTab = () => {
226
+ try {
227
+ const get = ctx.get;
228
+ const bs = typeof get === "function" ? get("betterSidebar") : void 0;
229
+ if (!bs || typeof bs.registerTab !== "function") return false;
230
+ const dispose = bs.registerTab({
231
+ id: "ilife-calorie:work",
232
+ title: "卡路里",
233
+ order: 61,
234
+ single: true,
235
+ component: () => react.createElement(CalorieWork, { getCall })
236
+ });
237
+ ctx.effect(() => () => {
238
+ try {
239
+ dispose();
240
+ } catch {}
241
+ }, "dsh-calorie: sidebar work tab");
242
+ return true;
243
+ } catch {
244
+ return false;
245
+ }
246
+ };
247
+ if (!ensureWorkTab()) {
248
+ let tries = 0;
249
+ const timer = setInterval(() => {
250
+ tries++;
251
+ if (ensureWorkTab() || tries >= 10) clearInterval(timer);
252
+ }, 1e3);
253
+ ctx.effect(() => () => {
254
+ clearInterval(timer);
255
+ }, "dsh-calorie: sidebar work tab retry");
256
+ }
257
+ }
258
+ //#endregion
259
+ exports.apply = apply;
260
+ exports.inject = inject;
261
+ return module.exports;
262
+ }
263
+ });
264
+
20
265
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAM/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAC9C,MAAM,CAAC,MAAM,aAAa,GAAG,gBAAgB,CAAC;AAE9C,MAAM,UAAU,iBAAiB,CAAC,IAAc;IAC9C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAc,EAAE,SAAkB;IACjE,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAgB,EAAE,GAAW,EAAE,SAAkC,EAAE;IACpG,OAAO,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;AACtD,CAAC"}
1
+ {"version":3,"file":"client.js","names":["React"],"sources":["../src/contract.ts","../src/slot.ts","../src/settings.ts","../src/client.ts"],"sourcesContent":["/** dsh-calorie 端口(port):DSH 世界与卡路里 hexagon 之间的契约。\n *\n * browser 安全:零 node 导入。host 适配器(注册 RPC 通道)与 client\n * 适配器(调用 RPC)共用同一份定义;单测与 loader 回路以此为准。\n * 通道命名遵守宿主约束:单段且不等于 /api(见 cookbook「RPC 通道」)。\n */\nexport const RPC_CHANNEL = '/ilife-calorie' as const;\nexport const RPC_ENDPOINT_READ = 'read' as const;\n\n/** 读请求载荷:与技能 CLI 的 key/params 同形(见 skill-calorie SKILL.md)。 */\nexport interface ReadPayload {\n readonly key: string;\n readonly params: 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/** 面板默认读键(与 HITL 一致性对比同键)。 */\nexport const DEFAULT_READ_KEY = 'calorie.view.home' 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-calorie 槽位描述(P10 脚手架,P3 #4 定案)。\n *\n * 写死槽位原生组件:id 命名空间 ilife:calorie,order 75。\n * 注册与打开只走 registerTab/openTab 的路径导航;子页后缀(如 ilife:calorie:*)\n * 只在主面板内导航,不占栏。缺席纯条件渲染,不轮询(本文件无定时器;\n * 有界重试归一由 base-render 拥有,单品不自建)。\n */\n\nexport const SKILL = 'calorie' as const;\nexport const SLOT_ID = 'ilife:calorie' as const;\nexport const SLOT_ORDER = 75 as const;\nexport const SLOT_TITLE = '卡路里' as const;\nexport const PLUGIN = 'dsh-calorie' as const;\nexport const MANAGER_PLUGIN = 'dsh-life-pack' 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: 'CaloriePanel' } 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-calorie 设置页(P10 脚手架:设置页住单品包)。\n *\n * 一次 registerTab,两处呈现:边栏 + 号菜单多一项,DSH 设置页侧边卡片\n * 分区自动多一张小卡片(注册表驱动、无硬编码,开关缺省启用)。\n * 本文件只声明本单品自己的设置行;总管不声明设置(见 boundaries 断言)。\n */\n\nexport const SETTINGS_OWNER = 'dsh-calorie' as const;\nexport const SETTINGS_SLOT = 'ilife:calorie' as const;\n\nexport interface SettingRow {\n readonly key: string;\n readonly title: string;\n readonly control: 'switch' | 'text' | 'number';\n}\n\n/** 脚手架期唯一设置行:总开关(缺省启用,与官方关闭语义对齐)。 */\nexport const SETTING_ROWS: readonly SettingRow[] = [\n { key: 'enabled', title: '卡路里面板启用', control: 'switch' },\n];\n","/** dsh-calorie 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 * (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 经 ctx.get 运行时取);取数只经 connection.rpc.call 进 host 通道;\n * 缺席/错误纯条件渲染,不返空冒充。组件 React.createElement 手写,不引入 JSX。\n */\nimport * as React from 'react';\nimport { RPC_CHANNEL, RPC_ENDPOINT_READ, DEFAULT_READ_KEY, isRpcResult } from './contract.js';\nimport { SLOT_TITLE } from './slot.js';\nimport { SETTING_ROWS } from './settings.js';\nimport type { ClientCtx, RpcCallResult } from './dsh-ctx.js';\n\nexport const inject = ['slots'];\n\n/** 调用口取用器:每次取数时现取(connection 后到也不永久缺席)。 */\nexport type GetCall = () => unknown;\n\ntype PanelState =\n | { readonly kind: 'loading' }\n | { readonly kind: 'data'; readonly date: string; readonly total: string }\n | { readonly kind: 'absent'; readonly message: string }\n | { readonly kind: 'error'; readonly message: string };\n\n/** 本地今日 YYYY-MM-DD(与面板默认读键的 date 参数同形)。 */\nfunction todayString(): string {\n const d = new Date();\n const m = String(d.getMonth() + 1).padStart(2, '0');\n const day = String(d.getDate()).padStart(2, '0');\n return `${d.getFullYear()}-${m}-${day}`;\n}\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/** 单次读数(纯异步函数,无 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 const raw: unknown = await call(RPC_CHANNEL, RPC_ENDPOINT_READ, { key, params });\n if (!isRpcResult(raw)) return { ok: false, absent: false, message: '回执信封异常(非 ok 信封)' };\n result = raw;\n } catch (e) {\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 CalorieWork(props: { getCall: GetCall }): React.ReactElement {\n const [state, setState] = React.useState<PanelState>({ kind: 'loading' });\n React.useEffect(() => {\n let alive = true;\n const date = todayString();\n void (async () => {\n const r = await fetchRead(props.getCall(), DEFAULT_READ_KEY, { date });\n if (!alive) return;\n if (r.ok) setState({ kind: 'data', date, 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', null, '卡路里加载中…');\n }\n if (state.kind === 'data') {\n return React.createElement(\n 'div',\n null,\n React.createElement('div', null, `卡路里 · ${state.date}`),\n React.createElement('div', null, `total ${state.total}`),\n );\n }\n return React.createElement('div', null, state.message);\n}\n\nfunction controlLabel(control: string): string {\n if (control === 'switch') return '开关(缺省启用,只读)';\n if (control === 'text') return '文本(只读)';\n if (control === 'number') return '数字(只读)';\n return `${control}(只读)`;\n}\n\n/** 技能设置页:爱生活页签条下的一页(只读设置行 + 状态读数,禁做假开关)。 */\nfunction CalorieConfig(props: { getCall: GetCall }): React.ReactElement {\n const [state, setState] = React.useState<PanelState>({ kind: 'loading' });\n React.useEffect(() => {\n let alive = true;\n const date = todayString();\n void (async () => {\n const r = await fetchRead(props.getCall(), DEFAULT_READ_KEY, { date });\n if (!alive) return;\n if (r.ok) setState({ kind: 'data', date, 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 const status =\n state.kind === 'loading'\n ? '状态读取中…'\n : state.kind === 'data'\n ? `今日 ${state.date} · total ${state.total}`\n : state.message;\n return React.createElement(\n 'div',\n null,\n React.createElement('div', null, status),\n React.createElement(\n 'div',\n null,\n SETTING_ROWS.map((row) =>\n React.createElement('div', { key: row.key }, `${row.title} · ${controlLabel(row.control)}`),\n ),\n ),\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\n // (a) 技能设置页 → 爱生活页签槽(总管声明;总管缺席时 inject 等待,不断链)。\n ctx.slots.inject('ilife.config-tab', () =>\n ctx.slots.register(\n {\n name: 'ilife.config-tab',\n id: 'dsh-calorie',\n order: 75,\n label: () => SLOT_TITLE,\n },\n () => React.createElement(CalorieConfig, { getCall }),\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-calorie:work',\n title: '卡路里',\n order: 61,\n single: true,\n component: () => React.createElement(CalorieWork, { getCall }),\n });\n ctx.effect(() => () => {\n try {\n dispose();\n } catch {\n /* 卸载清理失败忽略 */\n }\n }, 'dsh-calorie: 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-calorie: sidebar work tab retry');\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /** dsh-calorie 端口(port):DSH 世界与卡路里 hexagon 之间的契约。
2
+ *
3
+ * browser 安全:零 node 导入。host 适配器(注册 RPC 通道)与 client
4
+ * 适配器(调用 RPC)共用同一份定义;单测与 loader 回路以此为准。
5
+ * 通道命名遵守宿主约束:单段且不等于 /api(见 cookbook「RPC 通道」)。
6
+ */
7
+ export declare const RPC_CHANNEL: "/ilife-calorie";
8
+ export declare const RPC_ENDPOINT_READ: "read";
9
+ /** 读请求载荷:与技能 CLI 的 key/params 同形(见 skill-calorie SKILL.md)。 */
10
+ export interface ReadPayload {
11
+ readonly key: string;
12
+ readonly params: Record<string, unknown>;
13
+ }
14
+ export interface RpcError {
15
+ readonly code: string;
16
+ readonly message: string;
17
+ readonly details: Record<string, unknown>;
18
+ }
19
+ export type RpcResult = {
20
+ readonly ok: true;
21
+ readonly value: unknown;
22
+ } | {
23
+ readonly ok: false;
24
+ readonly error: RpcError;
25
+ };
26
+ export declare function ok(value: unknown): RpcResult;
27
+ export declare function fail(code: string, message: string): RpcResult;
28
+ /** 载荷校验:形状不对返回 null,由调用方按 bad-request 回绝(不抛)。 */
29
+ export declare function parseReadPayload(raw: unknown): ReadPayload | null;
30
+ /** 面板默认读键(与 HITL 一致性对比同键)。 */
31
+ export declare const DEFAULT_READ_KEY: "calorie.view.home";
32
+ /** 信封守卫:供 client 拆包前校验(port 拥有信封定义,守卫住 port)。 */
33
+ export declare function isRpcResult(raw: unknown): raw is RpcResult;
34
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAG,gBAAyB,CAAC;AACrD,eAAO,MAAM,iBAAiB,EAAG,MAAe,CAAC;AAEjD,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC9C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,CAAC;AAErD,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAE5C;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAE7D;AAED,iDAAiD;AACjD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAMjE;AAED,8BAA8B;AAC9B,eAAO,MAAM,gBAAgB,EAAG,mBAA4B,CAAC;AAE7D,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,SAAS,CAI1D"}
@@ -0,0 +1,35 @@
1
+ /** dsh-calorie 端口(port):DSH 世界与卡路里 hexagon 之间的契约。
2
+ *
3
+ * browser 安全:零 node 导入。host 适配器(注册 RPC 通道)与 client
4
+ * 适配器(调用 RPC)共用同一份定义;单测与 loader 回路以此为准。
5
+ * 通道命名遵守宿主约束:单段且不等于 /api(见 cookbook「RPC 通道」)。
6
+ */
7
+ export const RPC_CHANNEL = '/ilife-calorie';
8
+ export const RPC_ENDPOINT_READ = 'read';
9
+ export function ok(value) {
10
+ return { ok: true, value };
11
+ }
12
+ export function fail(code, message) {
13
+ return { ok: false, error: { code, message, details: {} } };
14
+ }
15
+ /** 载荷校验:形状不对返回 null,由调用方按 bad-request 回绝(不抛)。 */
16
+ export function parseReadPayload(raw) {
17
+ if (typeof raw !== 'object' || raw === null)
18
+ return null;
19
+ const { key, params } = raw;
20
+ if (typeof key !== 'string' || key.length === 0)
21
+ return null;
22
+ if (typeof params !== 'object' || params === null || Array.isArray(params))
23
+ return null;
24
+ return { key, params: params };
25
+ }
26
+ /** 面板默认读键(与 HITL 一致性对比同键)。 */
27
+ export const DEFAULT_READ_KEY = 'calorie.view.home';
28
+ /** 信封守卫:供 client 拆包前校验(port 拥有信封定义,守卫住 port)。 */
29
+ export function isRpcResult(raw) {
30
+ if (typeof raw !== 'object' || raw === null)
31
+ return false;
32
+ const ok = raw.ok;
33
+ return ok === true || ok === false;
34
+ }
35
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,gBAAyB,CAAC;AACrD,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAe,CAAC;AAkBjD,MAAM,UAAU,EAAE,CAAC,KAAc;IAC/B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,OAAe;IAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC;AAC9D,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACzD,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAA0C,CAAC;IACnE,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACxF,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAiC,EAAE,CAAC;AAC5D,CAAC;AAED,8BAA8B;AAC9B,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAA4B,CAAC;AAE7D,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,EAAE,GAAI,GAAwB,CAAC,EAAE,CAAC;IACxC,OAAO,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,KAAK,CAAC;AACrC,CAAC"}
@@ -0,0 +1,52 @@
1
+ /** DSH ctx 最小镜像:只含本仓实际使用的面(type-only,构建期擦除,零运行时)。
2
+ *
3
+ * 每条成员的用法出处见 docs/agents/dsh-client-contract.md(附源码路径+行)。
4
+ * 禁止加镜外成员:要用新能力,先在 cookbook 落出处,再加镜像。用 `import type` 引用本文件。
5
+ */
6
+ export interface SlotsRegisterOptions {
7
+ readonly name: string;
8
+ readonly id?: string;
9
+ readonly order?: number;
10
+ readonly label?: string | (() => string);
11
+ readonly locale?: string;
12
+ readonly inject?: () => Record<string, unknown>;
13
+ }
14
+ export interface SlotsFace {
15
+ inject(key: string, callback: () => (() => void) | Iterable<() => void>): () => void;
16
+ register(options: SlotsRegisterOptions, component: unknown): () => void;
17
+ }
18
+ export type RpcCallResult = {
19
+ readonly ok: true;
20
+ readonly value: unknown;
21
+ } | {
22
+ readonly ok: false;
23
+ readonly error: {
24
+ readonly code: string;
25
+ readonly message: string;
26
+ readonly details: Record<string, unknown>;
27
+ };
28
+ };
29
+ export interface RpcCallFace {
30
+ (channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<RpcCallResult>;
31
+ }
32
+ export interface ClientCtx {
33
+ readonly slots: SlotsFace;
34
+ readonly connection?: {
35
+ readonly rpc?: {
36
+ readonly call: RpcCallFace;
37
+ };
38
+ };
39
+ effect(callback: () => ((() => void) | void), label?: string): () => void;
40
+ get?(name: string): unknown;
41
+ }
42
+ export type RpcHandler = (endpoint: string, payload: unknown, signal?: AbortSignal) => Promise<unknown>;
43
+ export interface HostCtx {
44
+ readonly connection: {
45
+ readonly rpc: {
46
+ handle(channel: string, handler: RpcHandler): () => void;
47
+ };
48
+ };
49
+ effect(callback: () => ((() => void) | void), label?: string): () => void;
50
+ readonly logger?: unknown;
51
+ }
52
+ //# sourceMappingURL=dsh-ctx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsh-ctx.d.ts","sourceRoot":"","sources":["../src/dsh-ctx.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC;IACrF,QAAQ,CAAC,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,IAAI,CAAC;CACzE;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC9C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;CAAE,CAAC;AAE3I,MAAM,WAAW,WAAW;IAC1B,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACrG;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,CAAC,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;SAAE,CAAA;KAAE,CAAC;IACxE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,IAAI,CAAC;IAC1E,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC7B;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAExG,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE;YAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,IAAI,CAAA;SAAE,CAAA;KAAE,CAAC;IACpG,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,IAAI,CAAC;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dsh-ctx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsh-ctx.js","sourceRoot":"","sources":["../src/dsh-ctx.ts"],"names":[],"mappings":""}
package/dist/index.d.ts CHANGED
@@ -1,16 +1,12 @@
1
- /** dsh-calorie host 半(P10 脚手架,照抄 DSH 官方插件包模式)。
2
- *
3
- * 形态:cordis 插件(name/inject/apply)+ dsh.bundle.patch 装配行。
4
- * 单品硬依赖总管(dependencies,非 peer,见 package.json);设置页住本包。
5
- */
1
+ import type { HostCtx } from './dsh-ctx.js';
6
2
  export declare const name = "dsh-calorie";
7
- export declare const inject: readonly string[];
8
- export declare function apply(_ctx: unknown): void;
3
+ export declare const inject: string[];
4
+ export declare function apply(ctx: HostCtx): void;
9
5
  export { SKILL, SLOT_ID, SLOT_ORDER, SLOT_TITLE, PLUGIN, MANAGER_PLUGIN, slotDescriptor, TAB_COMPONENT, registerSingle, openSingle } from './slot.js';
10
6
  export type { SlotDescriptor, TabsPort } from './slot.js';
11
7
  export { SETTINGS_OWNER, SETTINGS_SLOT, SETTING_ROWS } from './settings.js';
12
8
  export type { SettingRow } from './settings.js';
13
9
  export { SKILL_PACKAGE, SKILL_CLI, SKILL_CLI_REL, HOST_CALL_METHOD, MANAGER_MISSING_HINT, SkillBridgeError, cliPath, assertCliPresent, handleHostCall, requestViaHost, readViaCli } from './bridge.js';
14
- export { CLIENT_COMPONENT, CLIENT_METHOD, mountSingleClient, openSingleClient, requestReadViaHost } from './client.js';
15
- export type { HostCaller } from './client.js';
10
+ export { RPC_CHANNEL, RPC_ENDPOINT_READ, DEFAULT_READ_KEY, ok, fail, parseReadPayload } from './contract.js';
11
+ export type { ReadPayload, RpcError, RpcResult } from './contract.js';
16
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,IAAI,gBAAgB,CAAC;AAClC,eAAO,MAAM,MAAM,EAAE,SAAS,MAAM,EAAO,CAAC;AAE5C,wBAAgB,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAEzC;AAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACtJ,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5E,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvM,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACvH,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAc,MAAM,cAAc,CAAC;AAExD,eAAO,MAAM,IAAI,gBAAgB,CAAC;AAClC,eAAO,MAAM,MAAM,UAAiB,CAAC;AAqBrC,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAiBxC;AAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACtJ,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5E,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvM,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC7G,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,15 +1,50 @@
1
- /** dsh-calorie host 半(P10 脚手架,照抄 DSH 官方插件包模式)。
1
+ /** dsh-calorie host 适配器(六边形:port=contract,adapter=本文件+bridge)。
2
2
  *
3
- * 形态:cordis 插件(name/inject/apply)+ dsh.bundle.patch 装配行。
3
+ * 形态:cordis 插件(name/inject/apply)。RPC 通道与信封由 contract 定义,
4
+ * apply 真注册通道,取数只经 bridge.readViaCli(spawn 技能 CLI),
5
+ * 从不 import 任何技能实现。
4
6
  * 单品硬依赖总管(dependencies,非 peer,见 package.json);设置页住本包。
5
7
  */
8
+ import { RPC_CHANNEL, RPC_ENDPOINT_READ, ok, fail, parseReadPayload } from './contract.js';
9
+ import { SkillBridgeError, readViaCli } from './bridge.js';
6
10
  export const name = 'dsh-calorie';
7
- export const inject = [];
8
- export function apply(_ctx) {
9
- void _ctx;
11
+ export const inject = ['connection'];
12
+ /** RPC 处理函数:endpoint 分发 → 载荷校验 → readViaCli → 信封包装(companion rpc.ts 同形)。 */
13
+ const handleCalorieRpc = async (endpoint, payload) => {
14
+ if (endpoint !== RPC_ENDPOINT_READ)
15
+ return fail('bad-request', `未知端点:${String(endpoint)}`);
16
+ const parsed = parseReadPayload(payload ?? {});
17
+ if (!parsed)
18
+ return fail('bad-request', '载荷须为 {key: string, params: object}');
19
+ try {
20
+ return ok(readViaCli(parsed.key, parsed.params));
21
+ }
22
+ catch (e) {
23
+ if (e instanceof SkillBridgeError)
24
+ return fail(e.code, e.message);
25
+ return fail('internal', e instanceof Error ? e.message : String(e));
26
+ }
27
+ };
28
+ export function apply(ctx) {
29
+ const loggerSource = ctx?.logger;
30
+ const logger = (typeof loggerSource === 'function'
31
+ ? loggerSource(name)
32
+ : loggerSource) ?? console;
33
+ try {
34
+ const dispose = ctx.connection.rpc.handle(RPC_CHANNEL, handleCalorieRpc);
35
+ ctx.effect(() => () => dispose(), 'dsh-calorie: rpc channel cleanup');
36
+ }
37
+ catch (error) {
38
+ // 通道是宿主共享单例:重装配时上一实例可能已注册,此时退让(照抄 companion),他错重抛。
39
+ if (String(error?.message ?? error).includes('duplicate prefix route')) {
40
+ logger.warn?.('[dsh-calorie] rpc channel ' + RPC_CHANNEL + ' already registered by another instance; yielding');
41
+ return;
42
+ }
43
+ throw error;
44
+ }
10
45
  }
11
46
  export { SKILL, SLOT_ID, SLOT_ORDER, SLOT_TITLE, PLUGIN, MANAGER_PLUGIN, slotDescriptor, TAB_COMPONENT, registerSingle, openSingle } from './slot.js';
12
47
  export { SETTINGS_OWNER, SETTINGS_SLOT, SETTING_ROWS } from './settings.js';
13
48
  export { SKILL_PACKAGE, SKILL_CLI, SKILL_CLI_REL, HOST_CALL_METHOD, MANAGER_MISSING_HINT, SkillBridgeError, cliPath, assertCliPresent, handleHostCall, requestViaHost, readViaCli } from './bridge.js';
14
- export { CLIENT_COMPONENT, CLIENT_METHOD, mountSingleClient, openSingleClient, requestReadViaHost } from './client.js';
49
+ export { RPC_CHANNEL, RPC_ENDPOINT_READ, DEFAULT_READ_KEY, ok, fail, parseReadPayload } from './contract.js';
15
50
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,IAAI,GAAG,aAAa,CAAC;AAClC,MAAM,CAAC,MAAM,MAAM,GAAsB,EAAE,CAAC;AAE5C,MAAM,UAAU,KAAK,CAAC,IAAa;IACjC,KAAK,IAAI,CAAC;AACZ,CAAC;AAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEtJ,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE5E,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvM,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-calorie",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "爱生活单品插件 P10 脚手架:卡路里(ilife:calorie order 75),硬依赖总管 dsh-life-pack",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,11 +34,22 @@
34
34
  },
35
35
  "client": {
36
36
  "platform": "web",
37
- "inject": []
37
+ "inject": [
38
+ "@deepseek-ai/dsh-client-ui-slots",
39
+ "@deepseek-ai/dsh-client-connection"
40
+ ]
38
41
  }
39
42
  },
40
43
  "scripts": {
41
- "build": "tsc -b",
44
+ "build": "npm run build:host && npm run build:client",
45
+ "build:host": "tsc -b",
46
+ "build:client": "tsdown",
47
+ "typecheck": "tsc -p tsconfig.client.json",
42
48
  "test": "node --test test/smoke.test.mjs"
49
+ },
50
+ "devDependencies": {
51
+ "@types/react": "19.2.18",
52
+ "react": "19.2.8",
53
+ "tsdown": "0.22.14"
43
54
  }
44
55
  }