@forgeax/interface 0.4.1 → 0.6.0

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.
Files changed (45) hide show
  1. package/README.md +51 -0
  2. package/dist/package/browser/ApplicationShell.js +1 -1
  3. package/dist/package/browser/appHostBootstrap.js +0 -2
  4. package/dist/package/browser/application.d.ts +0 -10
  5. package/dist/package/browser/application.js +0 -10
  6. package/dist/package/browser/components/CommandPalette/CommandPalette.js +1 -1
  7. package/dist/package/browser/core/app-shell/host.js +5 -2
  8. package/dist/package/browser/core/app-shell/types.d.ts +3 -1
  9. package/dist/package/browser/core/extensions/builtin-commands.js +1 -0
  10. package/dist/package/browser/core/extensions/trajectory.js +1 -1
  11. package/dist/package/browser/i18n/locales/en.json +0 -10
  12. package/dist/package/browser/i18n/locales/zh.json +0 -10
  13. package/dist/package/browser/lib/action-dom-discovery.js +1 -1
  14. package/dist/package/browser/lib/ai-intents.js +1 -1
  15. package/dist/package/browser/lib/builtin-actions.js +1 -1
  16. package/dist/package/browser/lib/global-shortcuts.d.ts +5 -74
  17. package/dist/package/browser/lib/global-shortcuts.js +29 -228
  18. package/dist/package/browser/lib/ui-action-highlight.js +1 -1
  19. package/dist/package/browser/lib/ui-bridge.js +1 -1
  20. package/dist/package/browser/lib/ui-trajectory.js +1 -1
  21. package/dist/package/browser/lib/vag-action-bridge.js +1 -1
  22. package/dist/package/node/ApplicationShell.js +1 -1
  23. package/dist/package/node/appHostBootstrap.js +0 -2
  24. package/dist/package/node/application.js +0 -10
  25. package/dist/package/node/components/CommandPalette/CommandPalette.js +1 -1
  26. package/dist/package/node/core/app-shell/host.js +5 -2
  27. package/dist/package/node/core/extensions/builtin-commands.js +1 -0
  28. package/dist/package/node/core/extensions/trajectory.js +1 -1
  29. package/dist/package/node/i18n/locales/en.json +0 -10
  30. package/dist/package/node/i18n/locales/zh.json +0 -10
  31. package/dist/package/node/lib/action-dom-discovery.js +1 -1
  32. package/dist/package/node/lib/ai-intents.js +1 -1
  33. package/dist/package/node/lib/builtin-actions.js +1 -1
  34. package/dist/package/node/lib/global-shortcuts.js +29 -228
  35. package/dist/package/node/lib/ui-action-highlight.js +1 -1
  36. package/dist/package/node/lib/ui-bridge.js +1 -1
  37. package/dist/package/node/lib/ui-trajectory.js +1 -1
  38. package/dist/package/node/lib/vag-action-bridge.js +1 -1
  39. package/package.json +2 -2
  40. package/dist/package/browser/core/extensions/editor-commands.d.ts +0 -2
  41. package/dist/package/browser/core/extensions/editor-commands.js +0 -182
  42. package/dist/package/browser/lib/action-registry.d.ts +0 -94
  43. package/dist/package/browser/lib/action-registry.js +0 -210
  44. package/dist/package/node/core/extensions/editor-commands.js +0 -182
  45. package/dist/package/node/lib/action-registry.js +0 -210
@@ -1,210 +0,0 @@
1
- /** action-registry —— UI 语义操作层(产品 AI 化 P0)的单一真值源:
2
- * 「本产品当前暴露哪些可被人 / AI 调用的功能」。
3
- *
4
- * 一张注册表服务三个消费者(压缩公理:消掉「AI 能做的和人能做的是不是同一套?」):
5
- * 1. 按钮 onClick —— 组件经 `dispatchAction(id, args)` 派发(与 AI 同一条路);
6
- * 2. 命令面板(P2,cmdk 激活时数据源即本表);
7
- * 3. AI 工具调用 —— `ui_snapshot` 读清单 / `ui_invoke` 派发,经 lib/ui-bridge.ts
8
- * 的 perception 往返接到编排层(@forgeax/orchestrator)。
9
- *
10
- * 契约形态(评审 2.1):`schema` 的 SSOT 是 **JSON Schema 纯对象**(AI 侧 ToolSpec 与
11
- * postMessage 结构化克隆都只吃 JSON;函数永不过 wire)。`available` / `run` 只活在
12
- * 本模块,manifest(`buildManifest`)只导出可序列化子集。
13
- *
14
- * headless 边界(方案 §5):`surface:'both'` 的 action,UI `run()` 必须调 server 的
15
- * 同一 HTTP API(server 是行为 SSOT),UI 侧只许加视觉反馈——不许长出独立业务逻辑。
16
- */
17
- // ─── 注册表本体(模块级 Map,页级单例)────────────────────────────────────────
18
- const actions = new Map();
19
- const stateSlices = new Map();
20
- const changeListeners = new Set();
21
- function notifyChange() {
22
- for (const cb of changeListeners) {
23
- try {
24
- cb();
25
- }
26
- catch {
27
- /* listener 异常不传染 */
28
- }
29
- }
30
- }
31
- /** 注册一个 action(同 id 重复注册 = 替换,幂等;HMR 安全)。返回注销函数。 */
32
- export function registerAction(def) {
33
- actions.set(def.id, def);
34
- notifyChange();
35
- return () => {
36
- if (actions.get(def.id) === def) {
37
- actions.delete(def.id);
38
- notifyChange();
39
- }
40
- };
41
- }
42
- /** 注册一片状态摘要(同 id 替换,幂等)。返回注销函数。 */
43
- export function registerStateSlice(id, selector) {
44
- stateSlices.set(id, selector);
45
- notifyChange();
46
- return () => {
47
- if (stateSlices.get(id) === selector) {
48
- stateSlices.delete(id);
49
- notifyChange();
50
- }
51
- };
52
- }
53
- /** 注册表(action 或 state slice)变更订阅 —— ui-bridge 据此 debounce 重推 manifest。 */
54
- export function onRegistryChange(cb) {
55
- changeListeners.add(cb);
56
- return () => changeListeners.delete(cb);
57
- }
58
- export function getAction(id) {
59
- return actions.get(id);
60
- }
61
- // ─── 派发单入口(评审 2.4:data-* 只做发现,执行永远走这里)──────────────────
62
- /** AI 派发时打的来源标记;window 事件供 ghost 高亮层(P1)与 telemetry / 轨迹追踪衔接。
63
- * detail 形:`{ id, source, args }` —— args 让轨迹追踪(lib/ui-trajectory)记得下「做了啥」,
64
- * ghost 高亮只用 id/source(向后兼容,多带的字段无害)。 */
65
- export const UI_ACTION_DISPATCH_EVENT = 'forgeax:ui-action-dispatch';
66
- /** 极简 JSON Schema 参数校验(P0:required + 顶层 properties 的原始类型)。
67
- * 不引 ajv——契约漂移由编排层契约测试兜,这里挡住明显错参即可(Fail Fast)。 */
68
- function validateArgs(schema, args) {
69
- if (!schema)
70
- return true;
71
- const required = Array.isArray(schema.required) ? schema.required : [];
72
- for (const k of required) {
73
- if (typeof k === 'string' && !(k in args))
74
- return `missing required arg "${k}"`;
75
- }
76
- const props = schema.properties && typeof schema.properties === 'object'
77
- ? schema.properties
78
- : {};
79
- for (const [k, v] of Object.entries(args)) {
80
- const p = props[k];
81
- if (!p)
82
- continue; // 未声明的多余参数放过(向前兼容),由 run 自行忽略
83
- const t = p.type;
84
- if (typeof t === 'string') {
85
- const actual = Array.isArray(v) ? 'array' : v === null ? 'null' : typeof v;
86
- const okType = (t === 'string' && actual === 'string') ||
87
- (t === 'number' && actual === 'number') ||
88
- (t === 'integer' && actual === 'number' && Number.isInteger(v)) ||
89
- (t === 'boolean' && actual === 'boolean') ||
90
- (t === 'object' && actual === 'object') ||
91
- (t === 'array' && actual === 'array');
92
- if (!okType)
93
- return `arg "${k}" should be ${t}, got ${actual}`;
94
- }
95
- if (Array.isArray(p.enum) && !p.enum.includes(v)) {
96
- return `arg "${k}" must be one of ${JSON.stringify(p.enum)}`;
97
- }
98
- }
99
- return true;
100
- }
101
- /** 派发一个 action —— 按钮与 AI 共用的**唯一**执行入口(未注册的 id 拒绝,fail-closed)。 */
102
- export async function dispatchAction(id, args = {}, opts = {}) {
103
- const src = opts.source ?? 'human';
104
- // [fx-action-trace] 定位「点了/调了没反应」的最小充分打点:
105
- // ① entry —— 派发是否发生 + args/source(缺此行 = 根本没触发 dispatch)。
106
- // ② terminal —— 唯一终态行:completed/accepted 带 stateDigest(判「到底改了啥」),
107
- // rejected 带 reason(未注册/不可用/参数错/run 抛错 已统一收敛进 reason)。
108
- // 两行即可定位:有 entry 无 terminal = 卡在 run();rejected+reason = 为何被拒;
109
- // completed 但 stateDigest 没变 = action 效果与 UI 脱节。故只留这两点(+抛错补栈)。
110
- console.info(`%c[fx-action]%c ▶ ${id} (${src})`, 'color:#818cf8;font-weight:bold', 'color:inherit', { args });
111
- const done = (r) => {
112
- if (r.status === 'rejected') {
113
- console.warn(`[fx-action] ✗ ${id} rejected: ${r.reason ?? ''}`);
114
- }
115
- else {
116
- console.info(`%c[fx-action]%c ✔ ${id} → ${r.status}`, 'color:#34d399;font-weight:bold', 'color:inherit', r.stateDigest !== undefined ? { stateDigest: r.stateDigest } : {});
117
- }
118
- return r;
119
- };
120
- const def = actions.get(id);
121
- if (!def)
122
- return done({ status: 'rejected', reason: `unknown action "${id}" (not in the registry)` });
123
- const avail = def.available ? safeAvailable(def) : true;
124
- if (avail !== true)
125
- return done({ status: 'rejected', reason: avail });
126
- const valid = validateArgs(def.schema, args);
127
- if (valid !== true)
128
- return done({ status: 'rejected', reason: valid });
129
- try {
130
- window.dispatchEvent(new CustomEvent(UI_ACTION_DISPATCH_EVENT, { detail: { id, source: src, args } }));
131
- }
132
- catch {
133
- /* SSR / no window — ignore */
134
- }
135
- try {
136
- const out = await def.run(args);
137
- return done(out ?? { status: 'completed' });
138
- }
139
- catch (e) {
140
- console.error(`[fx-action] ✗ ${id} threw`, e); // 抛错补一行栈(定位真异常)
141
- return done({ status: 'rejected', reason: `action "${id}" threw: ${e instanceof Error ? e.message : String(e)}` });
142
- }
143
- }
144
- function safeAvailable(def) {
145
- try {
146
- return def.available();
147
- }
148
- catch (e) {
149
- return `availability check threw: ${e instanceof Error ? e.message : String(e)}`;
150
- }
151
- }
152
- /** ui_snapshot 的 action 视图。分层(评审 2.7):默认轻量清单;detail:'schema' + ids
153
- * 按需展开 schema 与详细说明(长尾几百 action 也不炸 token)。 */
154
- export function snapshotActions(detail, ids) {
155
- const expand = detail === 'schema' ? new Set(ids ?? []) : null;
156
- const out = [];
157
- for (const def of actions.values()) {
158
- const avail = def.available ? safeAvailable(def) : true;
159
- const row = {
160
- id: def.id,
161
- title: def.title,
162
- available: avail === true,
163
- ...(avail === true ? {} : { reason: avail }),
164
- };
165
- if (expand?.has(def.id)) {
166
- if (def.description)
167
- row.description = def.description;
168
- row.inputSchema = def.schema ?? { type: 'object', properties: {} };
169
- }
170
- out.push(row);
171
- }
172
- return out;
173
- }
174
- /** ui_snapshot 的状态摘要:逐片 derive,单片异常不传染(fail-soft)。 */
175
- export function snapshotState() {
176
- const out = {};
177
- for (const [id, selector] of stateSlices) {
178
- try {
179
- out[id] = selector();
180
- }
181
- catch (e) {
182
- out[id] = { error: `state slice threw: ${e instanceof Error ? e.message : String(e)}` };
183
- }
184
- }
185
- return out;
186
- }
187
- /** manifest(POST /:sid/ui-manifest 的 payload)—— 只含可序列化声明,函数永不出墙。
188
- * 它是编排层 trust-gate 的权限输入:capability 必须如实声明。 */
189
- export function buildManifest() {
190
- const out = [];
191
- for (const def of actions.values()) {
192
- out.push({
193
- id: def.id,
194
- title: def.title,
195
- ...(def.description ? { description: def.description } : {}),
196
- inputSchema: def.schema ?? { type: 'object', properties: {} },
197
- capability: def.capability,
198
- ...(def.surface ? { surface: def.surface } : {}),
199
- ...(def.timeoutMs ? { timeoutMs: def.timeoutMs } : {}),
200
- ...(def.firstClass ? { firstClass: true } : {}),
201
- });
202
- }
203
- return out;
204
- }
205
- /** 测试用:清空注册表(生产代码不要调)。 */
206
- export function __resetRegistryForTest() {
207
- actions.clear();
208
- stateSlices.clear();
209
- changeListeners.clear();
210
- }