@xiaopb/dsh-mobile-compose 0.0.0-stage → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/config.js ADDED
@@ -0,0 +1,93 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * dsh-mobile-compose 配置归一化:apply 对缺省配置宽容,不抛错。
5
+ *
6
+ * - enabled:false 时装载但零路由、零行为(客户端拿到 404 后走内置默认)。
7
+ * - hideNativeButtons:隐藏输入框左下角原生的「添加文件」「调用指令」两个
8
+ * 小按钮(由加号面板统一承载)。默认 true;关掉后原生按钮保留,面板作为
9
+ * 额外入口并存。
10
+ * - actions:动作开关表,键为动作 id(pick-image / take-photo / pick-file /
11
+ * command),值为 true/false。宿主端只负责把开关透传给客户端做能力协商;
12
+ * 客户端自身还有一层桌面端硬约束(桌面没有相机路由时 take-photo 自动禁用)。
13
+ * - hiddenCommands:从指令列表里剔除的指令名(与内置动作同义的指令,如
14
+ * /file —— 图片 / 文件 瓦片已覆盖)。数组或逗号/空白分隔字符串,默认
15
+ * ['file'];空数组/空串 = 全部展示。
16
+ */
17
+
18
+ const DEFAULT_ACTIONS = {
19
+ 'pick-image': true,
20
+ 'take-photo': true,
21
+ 'pick-file': true,
22
+ command: true,
23
+ }
24
+
25
+ const DEFAULT_HIDDEN_COMMANDS = ['file']
26
+
27
+ /** 指令名归一:去斜杠、小写、去空白;数组或逗号/空白分隔字符串均可。 */
28
+ function normalizeHiddenCommands(raw) {
29
+ const source = Array.isArray(raw)
30
+ ? raw
31
+ : typeof raw === 'string'
32
+ ? raw.split(/[,\s]+/)
33
+ : null
34
+ if (source === null) return null
35
+ const out = []
36
+ for (const item of source) {
37
+ if (typeof item !== 'string') continue
38
+ const name = item.trim().replace(/^\//, '').toLowerCase()
39
+ if (name && !out.includes(name)) out.push(name)
40
+ }
41
+ return out
42
+ }
43
+
44
+ function normalizeConfig(input = {}) {
45
+ const raw = input && typeof input === 'object' ? input : {}
46
+
47
+ const enabled = raw.enabled !== false
48
+
49
+ const hideNativeButtons = raw.hideNativeButtons !== false
50
+
51
+ const actionsSource = raw.actions && typeof raw.actions === 'object' ? raw.actions : {}
52
+ const actions = {}
53
+ for (const key of Object.keys(DEFAULT_ACTIONS)) {
54
+ const v = actionsSource[key]
55
+ actions[key] = typeof v === 'boolean' ? v : DEFAULT_ACTIONS[key]
56
+ }
57
+
58
+ // hiddenCommands:显式给出(含空串/空数组 = 全部展示)才生效;缺省或类型
59
+ // 非法(非字符串/数组)回落默认集。
60
+ const hiddenCommands = normalizeHiddenCommands(raw.hiddenCommands)
61
+ const hasHiddenKey = raw !== null && typeof raw === 'object' && raw.hiddenCommands !== undefined
62
+
63
+ return {
64
+ enabled,
65
+ hideNativeButtons,
66
+ actions,
67
+ hiddenCommands: hasHiddenKey && hiddenCommands !== null
68
+ ? hiddenCommands
69
+ : DEFAULT_HIDDEN_COMMANDS.slice(),
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 设置页 section → 生效配置的合并:
75
+ * - `enabled` 只属于插件装载配置(base),设置页不暴露(改它要重装插件);
76
+ * - `hideNativeButtons` / `actions` 以设置页 section 为准(用户层覆盖),
77
+ * section 缺字段时回落 base 值。
78
+ * 宽容缺省:section 非对象 / 字段缺失都不抛错。
79
+ */
80
+ function mergeSettingsSection(base, section) {
81
+ const b = normalizeConfig(base)
82
+ const raw = section && typeof section === 'object' ? section : {}
83
+ return normalizeConfig({
84
+ enabled: b.enabled,
85
+ hideNativeButtons: typeof raw.hideNativeButtons === 'boolean' ? raw.hideNativeButtons : b.hideNativeButtons,
86
+ actions: raw.actions && typeof raw.actions === 'object'
87
+ ? { ...b.actions, ...raw.actions }
88
+ : b.actions,
89
+ hiddenCommands: raw.hiddenCommands !== undefined ? raw.hiddenCommands : b.hiddenCommands,
90
+ })
91
+ }
92
+
93
+ module.exports = { normalizeConfig, mergeSettingsSection, normalizeHiddenCommands, DEFAULT_ACTIONS, DEFAULT_HIDDEN_COMMANDS }
package/lib/index.js ADDED
@@ -0,0 +1,97 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * dsh-mobile-compose v0.2.0 宿主端入口:cordis 插件(inject + apply)。
5
+ *
6
+ * 定位:**手机端输入体验**。客户端(lib/client.js,随 web GUI 加载)做全部
7
+ * UI 工作 —— 输入框左下角加号入口、QQ 式动作面板(图片 / 拍照 / 文件 / 指令)、
8
+ * 隐藏原生小按钮,并把选中的文件送回官方 addFiles 通道。宿主端提供两件事:
9
+ *
10
+ * 1. GET /api/dsh-mobile-compose/capabilities —— 动作能力协商(capabilities.js)。
11
+ * 来源是「设置页实时值」:用户在 DSH 设置页改动后,客户端下一次拉取即生效。
12
+ * 2. `dsh-mobile-compose` 设置页 namespace —— schemastery schema 经
13
+ * ctx.settings.installSection 注册,DSH 设置页自动渲染表单(settings.js)。
14
+ * settings 服务缺席(headless / 异常安装)时静默跳过,仅用插件装载配置。
15
+ *
16
+ * 契约:inject 恒为 [](webServer / settings 都是可选服务,动态获取,缺席即
17
+ * 降级);apply(ctx, config) 装载,返回卸载函数;所有失败路径绝不上抛。
18
+ */
19
+
20
+ const { normalizeConfig, mergeSettingsSection } = require('./config')
21
+ const { createCapabilitiesRoute } = require('./capabilities')
22
+ const { buildSettingsSchema } = require('./settings')
23
+
24
+ const SETTINGS_NAMESPACE = 'dsh-mobile-compose'
25
+
26
+ function apply(ctx, config) {
27
+ const base = normalizeConfig(config)
28
+
29
+ const log = (level, ...args) => {
30
+ try {
31
+ const lg = ctx && ctx.logger
32
+ if (lg && typeof lg[level] === 'function') lg[level](`[dsh-mobile-compose]`, ...args)
33
+ } catch (_) {
34
+ /* 日志丢失不影响主流程 */
35
+ }
36
+ }
37
+
38
+ if (base.enabled === false) {
39
+ log('info', 'disabled (enabled=false),装载但零路由')
40
+ return function dispose() {}
41
+ }
42
+
43
+ // ---- 设置页 namespace(可选服务)----
44
+ // current 恒为「当前生效配置」的 thunk:初始为插件配置;settings 服务把它
45
+ // 换成实时来源(attach 时与 detach 时都会回调 setSource)。
46
+ let current = () => base
47
+ try {
48
+ const settings = typeof ctx.get === 'function' ? ctx.get('settings') : undefined
49
+ const schema = buildSettingsSchema()
50
+ if (settings && typeof settings.installSection === 'function' && schema) {
51
+ const entry = {
52
+ hideNativeButtons: base.hideNativeButtons,
53
+ actions: { ...base.actions },
54
+ hiddenCommands: base.hiddenCommands.join(','),
55
+ }
56
+ settings.installSection(ctx, SETTINGS_NAMESPACE, schema, entry, {
57
+ setSource: (source) => {
58
+ current = () => mergeSettingsSection(base, typeof source === 'function' ? source() : source)
59
+ },
60
+ onChange: () => {
61
+ log('info', '设置页配置已更新,capabilities 下一次拉取即生效')
62
+ },
63
+ })
64
+ } else {
65
+ log('debug', 'settings 服务或 schema 不可用,仅用插件装载配置')
66
+ }
67
+ } catch (error) {
68
+ log('warn', '设置页 namespace 注册失败(不影响 capabilities,仅少一处 UI 配置入口):', error instanceof Error ? error.message : String(error))
69
+ }
70
+
71
+ // ---- capabilities 路由(可选服务)----
72
+ // webServer 是可选服务(headless profile 没有它):动态 inject 保证本插件
73
+ // 在任何 profile 都可装载,服务出现时才挂路由。
74
+ ctx.inject(['webServer'], (scoped) => {
75
+ try {
76
+ const route = createCapabilitiesRoute(() => current())
77
+ const dispose = scoped.webServer.register(route)
78
+ log('info', `capabilities 路由已挂载:${route.path}`)
79
+ return function disposeRoutes() {
80
+ try {
81
+ dispose()
82
+ } catch (_) {
83
+ /* 静默 */
84
+ }
85
+ }
86
+ } catch (error) {
87
+ log('warn', 'capabilities 路由挂载失败(客户端将走内置默认动作集):', error instanceof Error ? error.message : String(error))
88
+ return undefined
89
+ }
90
+ })
91
+
92
+ return function dispose() {
93
+ // 路由与 namespace 的注销由各自动态注入的返回函数托管(fiber unload 级联)。
94
+ }
95
+ }
96
+
97
+ module.exports = { inject: [], apply, SETTINGS_NAMESPACE }
@@ -0,0 +1,46 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * dsh-mobile-compose 设置页 namespace(`dsh-mobile-compose`)。
5
+ *
6
+ * 宿主端通过 ctx.settings.installSection(ctx, ns, schema, entry, hooks) 注册;
7
+ * DSH 设置页(schema-form)按 schemastery schema 自动渲染表单 —— 客户端 UI
8
+ * 零代码。用户在设置页的修改经 settings 服务落盘并触发 hooks.onChange,本插件
9
+ * 把「当前生效值」作为 capabilities 路由的实时来源,客户端每次打开面板都重拉
10
+ * capabilities,因此设置改动无需刷新页面即可生效。
11
+ *
12
+ * schemastery 缺失(异常安装)时 buildSettingsSchema 返回 null,宿主端跳过
13
+ * namespace 注册,仅用插件装载配置 —— 全部路径宽容降级。
14
+ */
15
+
16
+ let zCache
17
+ function getZ() {
18
+ if (zCache !== undefined) return zCache
19
+ try {
20
+ // eslint-disable-next-line global-require
21
+ zCache = require('@deepseek-ai/schemastery')
22
+ } catch (_) {
23
+ zCache = null
24
+ }
25
+ return zCache
26
+ }
27
+
28
+ /** 设置页 section 的形状(与插件配置的子集同形:不含 enabled)。 */
29
+ function buildSettingsSchema() {
30
+ const z = getZ()
31
+ if (!z || typeof z.object !== 'function') return null
32
+ return z.object({
33
+ hideNativeButtons: z.boolean().default(true),
34
+ actions: z.object({
35
+ 'pick-image': z.boolean().default(true),
36
+ 'take-photo': z.boolean().default(true),
37
+ 'pick-file': z.boolean().default(true),
38
+ command: z.boolean().default(true),
39
+ }),
40
+ // 逗号分隔的指令名(不带斜杠);留空 = 全部展示。默认剔除 /file
41
+ // (官方「添加文件」指令,与面板的 图片/文件 瓦片重复)。
42
+ hiddenCommands: z.string().default('file'),
43
+ })
44
+ }
45
+
46
+ module.exports = { buildSettingsSchema }
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "@xiaopb/dsh-mobile-compose",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.6.4",
4
+ "description": "DSH web-UI plugin: mobile-first composer experience. Replaces the composer's add-file/command buttons with one plus entry that opens a QQ-style action sheet (image picker / camera / file / command tiles; command execution follows the official dispatch decision table (input commands claim the draft, bare commands execute via the official Enter channel)), feeds every picked file back through the official addFiles intake, exposes a host capability route plus a DSH settings-page namespace so the panel can be configured in-app, and documents the bridge contract for routing actions to native Android intents.",
5
+ "main": "lib/index.js",
6
+ "exports": {
7
+ ".": "./lib/index.js",
8
+ "./client": "./lib/client.js",
9
+ "./package.json": "./package.json"
10
+ },
11
+ "files": [
12
+ "lib",
13
+ "cordis.patch.yml",
14
+ "CHANGELOG.md"
15
+ ],
16
+ "scripts": {
17
+ "test": "node --test --test-force-exit test/*.test.js",
18
+ "smoke": "node tools/smoke-e2e.mjs",
19
+ "verify:paging": "node tools/verify-paging.js",
20
+ "verify:lang": "node tools/verify-lang.js",
21
+ "check": "npm test && npm run smoke && node scripts/verify-package.mjs",
22
+ "pack:dry": "npm pack --dry-run"
23
+ },
24
+ "dsh": {
25
+ "bundle": {
26
+ "patch": "./cordis.patch.yml"
27
+ },
28
+ "client": {
29
+ "inject": [
30
+ "@deepseek-ai/dsh-client-runtime",
31
+ "@deepseek-ai/dsh-client-ui-conversation"
32
+ ],
33
+ "platform": "web"
34
+ }
35
+ },
36
+ "engines": {
37
+ "node": ">=22.12.0",
38
+ "dsh": ">=0.2.1-rc.2"
39
+ },
40
+ "peerDependencies": {
41
+ "@deepseek-ai/cordis": "^4.0.1",
42
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.5-rc.1",
43
+ "@deepseek-ai/schemastery": ">=3.18.1 <4"
44
+ },
45
+ "license": "MIT",
46
+ "devDependencies": {
47
+ "@deepseek-ai/schemastery": "^3.18.5",
48
+ "jsdom": "^30.1.3",
49
+ "playwright": "^1.64.1",
50
+ "react": "^18.3.1",
51
+ "react-dom": "^18.3.1"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "registry": "https://registry.npmjs.org/"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/XiaoPb/dsh-mobile-compose.git"
60
+ },
61
+ "homepage": "https://github.com/XiaoPb/dsh-mobile-compose#readme",
62
+ "bugs": {
63
+ "url": "https://github.com/XiaoPb/dsh-mobile-compose/issues"
64
+ },
65
+ "author": "XiaoPb",
66
+ "keywords": [
67
+ "dsh",
68
+ "deepseek-harness",
69
+ "dsh-plugin",
70
+ "plugin",
71
+ "composer",
72
+ "mobile",
73
+ "android",
74
+ "chat-ui",
75
+ "action-sheet"
76
+ ]
77
+ }