@openvcs/sdk 0.2.11 → 0.2.12

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.
@@ -5,7 +5,7 @@ type MenubarMenuOptions = {
5
5
  before?: string;
6
6
  after?: string;
7
7
  };
8
- /** Runs one registered action handler by id. */
8
+ /** Runs a registered action handler by id. */
9
9
  export declare function runRegisteredAction(actionId: string, ...args: unknown[]): Promise<boolean>;
10
10
  export interface MenuHandle {
11
11
  id: string;
@@ -17,26 +17,26 @@ export interface MenuHandle {
17
17
  }
18
18
  /** Returns a menu by id, or null when it does not exist. */
19
19
  export declare function getMenu(menuId: string): MenuHandle | null;
20
- /** Returns a menu by id, creating it when necessary. */
20
+ /** Returns a menu by id, creating it if needed. */
21
21
  export declare function getOrCreateMenu(menuId: string, label: string): MenuHandle | null;
22
- /** Creates a menu at a specific insertion point. */
22
+ /** Creates a menu at a specific position. */
23
23
  export declare function createMenu(menuId: string, label: string, options?: MenubarMenuOptions): MenuHandle | null;
24
- /** Adds one action item to a menu. */
24
+ /** Adds one item to a menu. */
25
25
  export declare function addMenuItem(menuId: string, item: MenubarItem): void;
26
- /** Adds one separator item to a menu. */
26
+ /** Adds one separator to a menu. */
27
27
  export declare function addMenuSeparator(menuId: string, beforeAction?: string): void;
28
- /** Removes one menu entirely. */
28
+ /** Removes one menu from the registry. */
29
29
  export declare function removeMenu(menuId: string): void;
30
- /** Hides one menu from the host UI. */
30
+ /** Hides one menu from the registry. */
31
31
  export declare function hideMenu(menuId: string): void;
32
- /** Shows one previously hidden menu. */
32
+ /** Shows one menu from the registry. */
33
33
  export declare function showMenu(menuId: string): void;
34
34
  /** Registers an action handler by id. */
35
35
  export declare function registerAction(id: string, handler: (...args: unknown[]) => unknown): void;
36
- /** Invokes a host command when the host exposes a direct invoke helper. */
36
+ /** Invokes a host command when a host helper is available. */
37
37
  export declare function invoke<T = unknown>(cmd: string, args?: unknown): Promise<T>;
38
- /** Emits a user notification when the host exposes one. */
38
+ /** Emits a notification when the host helper is available. */
39
39
  export declare function notify(msg: string): void;
40
- /** Builds the SDK plugin delegates contributed by the menu registry. */
40
+ /** Builds SDK delegates from the local menu/action registries. */
41
41
  export declare function createMenuPluginDelegates(): PluginDelegates<PluginRuntimeContext>;
42
42
  export {};
@@ -18,31 +18,35 @@ exports.createMenuPluginDelegates = createMenuPluginDelegates;
18
18
  const menus = new Map();
19
19
  const menuOrder = [];
20
20
  const actionHandlers = new Map();
21
- let nextSyntheticId = 0;
22
- /** Returns the OpenVCS global, when the host exposes one. */
21
+ let syntheticId = 0;
22
+ /** Returns the host-side OpenVCS helper, when the environment provides one. */
23
23
  function getOpenVCS() {
24
24
  return globalThis.OpenVCS;
25
25
  }
26
- /** Normalizes menu ids for stable map lookups. */
26
+ /** Normalizes a menu id for stable map lookup. */
27
27
  function normalizeMenuId(menuId) {
28
28
  return String(menuId || '').trim();
29
29
  }
30
- /** Allocates a stable synthetic id for a generated menu item. */
30
+ /** Allocates a stable synthetic id for generated menu entries. */
31
31
  function allocateSyntheticId(prefix) {
32
- nextSyntheticId += 1;
33
- return `${prefix}-${nextSyntheticId}`;
32
+ syntheticId += 1;
33
+ return `${prefix}-${syntheticId}`;
34
34
  }
35
- /** Returns a stored menu state or null if it does not exist. */
35
+ /** Returns the stored menu state for one id, if present. */
36
36
  function getStoredMenu(menuId) {
37
37
  return menus.get(normalizeMenuId(menuId)) || null;
38
38
  }
39
- /** Inserts a menu id into the ordering list. */
39
+ /** Removes one menu id from the ordering list. */
40
+ function removeMenuId(menuId) {
41
+ const id = normalizeMenuId(menuId);
42
+ const index = menuOrder.indexOf(id);
43
+ if (index >= 0)
44
+ menuOrder.splice(index, 1);
45
+ }
46
+ /** Inserts one menu id into the ordering list. */
40
47
  function placeMenuId(menuId, options) {
41
48
  const id = normalizeMenuId(menuId);
42
- const existingIndex = menuOrder.indexOf(id);
43
- if (existingIndex >= 0) {
44
- menuOrder.splice(existingIndex, 1);
45
- }
49
+ removeMenuId(id);
46
50
  const beforeId = normalizeMenuId(options?.before || '');
47
51
  const afterId = normalizeMenuId(options?.after || '');
48
52
  if (afterId) {
@@ -61,7 +65,7 @@ function placeMenuId(menuId, options) {
61
65
  }
62
66
  menuOrder.push(id);
63
67
  }
64
- /** Ensures a menu record exists for the provided id. */
68
+ /** Ensures a menu record exists for one id. */
65
69
  function ensureStoredMenu(menuId, label, options) {
66
70
  const id = normalizeMenuId(menuId);
67
71
  const safeLabel = String(label || '').trim() || id;
@@ -69,46 +73,47 @@ function ensureStoredMenu(menuId, label, options) {
69
73
  if (!menu) {
70
74
  menu = { id, label: safeLabel, items: [] };
71
75
  menus.set(id, menu);
72
- placeMenuId(id, options);
73
- return menu;
74
76
  }
75
- menu.label = safeLabel;
77
+ else {
78
+ menu.label = safeLabel;
79
+ }
76
80
  placeMenuId(id, options);
77
81
  return menu;
78
82
  }
79
- /** Finds one menu item by action id. */
80
- function findMenuItem(menu, actionId) {
83
+ /** Finds a stored item by action id. */
84
+ function findStoredItem(menu, actionId) {
81
85
  const id = normalizeMenuId(actionId);
82
86
  return menu.items.find((item) => item.action === id) || null;
83
87
  }
84
- /** Inserts a menu item relative to before/after anchors when provided. */
88
+ /** Inserts a menu item at the requested position. */
85
89
  function insertMenuItem(menu, item, before, after) {
86
90
  const beforeId = normalizeMenuId(before || '');
87
91
  const afterId = normalizeMenuId(after || '');
88
- const insertAt = (index) => {
89
- const existingIndex = menu.items.findIndex((entry) => entry.id === item.id || entry.action === item.action);
90
- if (existingIndex >= 0) {
91
- menu.items.splice(existingIndex, 1);
92
- }
93
- menu.items.splice(index, 0, item);
92
+ const removeExisting = () => {
93
+ const index = menu.items.findIndex((entry) => entry.id === item.id || entry.action === item.action);
94
+ if (index >= 0)
95
+ menu.items.splice(index, 1);
94
96
  };
95
97
  if (beforeId) {
96
98
  const beforeIndex = menu.items.findIndex((entry) => entry.action === beforeId);
97
99
  if (beforeIndex >= 0) {
98
- insertAt(beforeIndex);
100
+ removeExisting();
101
+ menu.items.splice(beforeIndex, 0, item);
99
102
  return;
100
103
  }
101
104
  }
102
105
  if (afterId) {
103
106
  const afterIndex = menu.items.findIndex((entry) => entry.action === afterId);
104
107
  if (afterIndex >= 0) {
105
- insertAt(afterIndex + 1);
108
+ removeExisting();
109
+ menu.items.splice(afterIndex + 1, 0, item);
106
110
  return;
107
111
  }
108
112
  }
109
- insertAt(menu.items.length);
113
+ removeExisting();
114
+ menu.items.push(item);
110
115
  }
111
- /** Converts one stored menu item into a serializable plugin payload element. */
116
+ /** Converts one stored item into a serializable menu payload element. */
112
117
  function serializeMenuItem(item) {
113
118
  if (item.hidden)
114
119
  return null;
@@ -132,7 +137,7 @@ function serializeMenuItem(item) {
132
137
  label: item.label,
133
138
  };
134
139
  }
135
- /** Serializes the current menu registry for the host runtime. */
140
+ /** Serializes the local registry into plugin menu payloads. */
136
141
  function serializeMenus() {
137
142
  return menuOrder
138
143
  .map((menuId, index) => {
@@ -150,7 +155,7 @@ function serializeMenus() {
150
155
  })
151
156
  .filter((menu) => Boolean(menu));
152
157
  }
153
- /** Runs one registered action handler by id. */
158
+ /** Runs a registered action handler by id. */
154
159
  async function runRegisteredAction(actionId, ...args) {
155
160
  const id = String(actionId || '').trim();
156
161
  if (!id)
@@ -161,45 +166,33 @@ async function runRegisteredAction(actionId, ...args) {
161
166
  await handler(...args);
162
167
  return true;
163
168
  }
164
- /** Creates a stable, mutation-based handle for one menu id. */
169
+ /** Creates a stable handle for one stored menu. */
165
170
  function createMenuHandle(menuId) {
166
171
  const id = normalizeMenuId(menuId);
167
172
  return {
168
173
  id,
169
174
  addItem(item) {
170
- const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
171
175
  const label = String(item?.label || '').trim();
172
176
  const action = String(item?.action || '').trim();
173
177
  if (!label || !action)
174
178
  return;
175
- const entry = {
179
+ const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
180
+ insertMenuItem(menu, {
176
181
  kind: 'button',
177
182
  id: action,
178
183
  label,
179
184
  title: item.title,
180
185
  action,
181
- };
182
- insertMenuItem(menu, entry, item.before, item.after);
183
- const openvcs = getOpenVCS();
184
- if (!openvcs?.menus)
185
- return;
186
- openvcs.menus.getOrCreate(menu.id, menu.label);
187
- openvcs.menus.addMenuItem(menu.id, item);
186
+ }, item.before, item.after);
188
187
  },
189
188
  addSeparator(beforeAction) {
190
189
  const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
191
- const entry = {
190
+ insertMenuItem(menu, {
192
191
  kind: 'separator',
193
192
  id: allocateSyntheticId(`${menu.id}-separator`),
194
193
  label: 'Separator',
195
194
  content: '—',
196
- };
197
- insertMenuItem(menu, entry, beforeAction);
198
- const openvcs = getOpenVCS();
199
- if (!openvcs?.menus)
200
- return;
201
- openvcs.menus.getOrCreate(menu.id, menu.label);
202
- openvcs.menus.addMenuSeparator(menu.id, beforeAction);
195
+ }, beforeAction);
203
196
  },
204
197
  removeItem(actionId) {
205
198
  const menu = getStoredMenu(this.id);
@@ -207,37 +200,22 @@ function createMenuHandle(menuId) {
207
200
  return;
208
201
  const idToRemove = normalizeMenuId(actionId);
209
202
  menu.items = menu.items.filter((item) => item.action !== idToRemove);
210
- const openvcs = getOpenVCS();
211
- if (openvcs?.menus) {
212
- const handle = openvcs.menus.get(this.id);
213
- handle?.removeItem(idToRemove);
214
- }
215
203
  },
216
204
  hideItem(actionId) {
217
205
  const menu = getStoredMenu(this.id);
218
206
  if (!menu)
219
207
  return;
220
- const item = findMenuItem(menu, actionId);
208
+ const item = findStoredItem(menu, actionId);
221
209
  if (item)
222
210
  item.hidden = true;
223
- const openvcs = getOpenVCS();
224
- if (!openvcs?.menus)
225
- return;
226
- const handle = openvcs.menus.get(this.id);
227
- handle?.hideItem(actionId);
228
211
  },
229
212
  showItem(actionId) {
230
213
  const menu = getStoredMenu(this.id);
231
214
  if (!menu)
232
215
  return;
233
- const item = findMenuItem(menu, actionId);
216
+ const item = findStoredItem(menu, actionId);
234
217
  if (item)
235
218
  item.hidden = false;
236
- const openvcs = getOpenVCS();
237
- if (!openvcs?.menus)
238
- return;
239
- const handle = openvcs.menus.get(this.id);
240
- handle?.showItem(actionId);
241
219
  },
242
220
  };
243
221
  }
@@ -248,53 +226,41 @@ function getMenu(menuId) {
248
226
  return null;
249
227
  return createMenuHandle(stored.id);
250
228
  }
251
- /** Returns a menu by id, creating it when necessary. */
229
+ /** Returns a menu by id, creating it if needed. */
252
230
  function getOrCreateMenu(menuId, label) {
253
231
  const stored = ensureStoredMenu(menuId, label);
254
232
  return createMenuHandle(stored.id);
255
233
  }
256
- /** Creates a menu at a specific insertion point. */
234
+ /** Creates a menu at a specific position. */
257
235
  function createMenu(menuId, label, options) {
258
236
  const stored = ensureStoredMenu(menuId, label, options);
259
237
  return createMenuHandle(stored.id);
260
238
  }
261
- /** Adds one action item to a menu. */
239
+ /** Adds one item to a menu. */
262
240
  function addMenuItem(menuId, item) {
263
- const menu = getStoredMenu(menuId) || ensureStoredMenu(menuId, menuId);
264
- const handle = createMenuHandle(menu.id);
265
- handle.addItem(item);
241
+ createMenuHandle(menuId).addItem(item);
266
242
  }
267
- /** Adds one separator item to a menu. */
243
+ /** Adds one separator to a menu. */
268
244
  function addMenuSeparator(menuId, beforeAction) {
269
- const menu = getStoredMenu(menuId) || ensureStoredMenu(menuId, menuId);
270
- const handle = createMenuHandle(menu.id);
271
- handle.addSeparator(beforeAction);
245
+ createMenuHandle(menuId).addSeparator(beforeAction);
272
246
  }
273
- /** Removes one menu entirely. */
247
+ /** Removes one menu from the registry. */
274
248
  function removeMenu(menuId) {
275
249
  const id = normalizeMenuId(menuId);
276
250
  menus.delete(id);
277
- const index = menuOrder.indexOf(id);
278
- if (index >= 0)
279
- menuOrder.splice(index, 1);
280
- const openvcs = getOpenVCS();
281
- openvcs?.menus?.remove(id);
251
+ removeMenuId(id);
282
252
  }
283
- /** Hides one menu from the host UI. */
253
+ /** Hides one menu from the registry. */
284
254
  function hideMenu(menuId) {
285
255
  const menu = getStoredMenu(menuId);
286
256
  if (menu)
287
257
  menu.hidden = true;
288
- const openvcs = getOpenVCS();
289
- openvcs?.menus?.hide(normalizeMenuId(menuId));
290
258
  }
291
- /** Shows one previously hidden menu. */
259
+ /** Shows one menu from the registry. */
292
260
  function showMenu(menuId) {
293
261
  const menu = getStoredMenu(menuId);
294
262
  if (menu)
295
263
  menu.hidden = false;
296
- const openvcs = getOpenVCS();
297
- openvcs?.menus?.show(normalizeMenuId(menuId));
298
264
  }
299
265
  /** Registers an action handler by id. */
300
266
  function registerAction(id, handler) {
@@ -302,23 +268,21 @@ function registerAction(id, handler) {
302
268
  if (!key)
303
269
  return;
304
270
  actionHandlers.set(key, handler);
305
- const openvcs = getOpenVCS();
306
- openvcs?.registerAction(key, handler);
307
271
  }
308
- /** Invokes a host command when the host exposes a direct invoke helper. */
272
+ /** Invokes a host command when a host helper is available. */
309
273
  function invoke(cmd, args) {
310
274
  const openvcs = getOpenVCS();
311
275
  if (!openvcs) {
312
- return Promise.reject(new Error('OpenVCS not available'));
276
+ return Promise.reject(new Error('OpenVCS host is not available in this runtime'));
313
277
  }
314
278
  return openvcs.invoke(cmd, args);
315
279
  }
316
- /** Emits a user notification when the host exposes one. */
280
+ /** Emits a notification when the host helper is available. */
317
281
  function notify(msg) {
318
282
  const openvcs = getOpenVCS();
319
283
  openvcs?.notify(msg);
320
284
  }
321
- /** Builds the SDK plugin delegates contributed by the menu registry. */
285
+ /** Builds SDK delegates from the local menu/action registries. */
322
286
  function createMenuPluginDelegates() {
323
287
  return {
324
288
  async 'plugin.get_menus'() {
@@ -326,9 +290,9 @@ function createMenuPluginDelegates() {
326
290
  },
327
291
  async 'plugin.handle_action'(params) {
328
292
  const actionId = String(params?.action_id || '').trim();
329
- if (!actionId)
330
- return null;
331
- await runRegisteredAction(actionId);
293
+ if (actionId) {
294
+ await runRegisteredAction(actionId);
295
+ }
332
296
  return null;
333
297
  },
334
298
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openvcs/sdk",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "OpenVCS SDK CLI for plugin scaffolding and .ovcsp tar.gz packaging",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "homepage": "https://openvcs.app/",
@@ -11,22 +11,10 @@ import type {
11
11
  import type { PluginRuntimeContext } from './contracts.js';
12
12
 
13
13
  type MenubarMenuOptions = { before?: string; after?: string };
14
-
15
14
  type MenuEntryKind = 'button' | 'text' | 'separator';
16
15
 
17
16
  type OpenVCSGlobal = typeof globalThis & {
18
17
  OpenVCS?: {
19
- menus?: {
20
- get(menuId: string): unknown;
21
- getOrCreate(menuId: string, label: string): unknown;
22
- create(menuId: string, label: string, options?: MenubarMenuOptions): unknown;
23
- addMenuItem(menuId: string, item: MenubarItem): void;
24
- addMenuSeparator(menuId: string, beforeAction?: string): void;
25
- remove(menuId: string): void;
26
- hide(menuId: string): void;
27
- show(menuId: string): void;
28
- };
29
- registerAction(id: string, handler: (...args: unknown[]) => unknown): void;
30
18
  invoke<T = unknown>(cmd: string, args?: unknown): Promise<T>;
31
19
  notify(msg: string): void;
32
20
  };
@@ -66,36 +54,40 @@ interface SerializedMenuDefinition {
66
54
  const menus = new Map<string, StoredMenuState>();
67
55
  const menuOrder: string[] = [];
68
56
  const actionHandlers = new Map<string, (...args: unknown[]) => unknown>();
69
- let nextSyntheticId = 0;
57
+ let syntheticId = 0;
70
58
 
71
- /** Returns the OpenVCS global, when the host exposes one. */
59
+ /** Returns the host-side OpenVCS helper, when the environment provides one. */
72
60
  function getOpenVCS() {
73
61
  return (globalThis as OpenVCSGlobal).OpenVCS;
74
62
  }
75
63
 
76
- /** Normalizes menu ids for stable map lookups. */
64
+ /** Normalizes a menu id for stable map lookup. */
77
65
  function normalizeMenuId(menuId: string): string {
78
66
  return String(menuId || '').trim();
79
67
  }
80
68
 
81
- /** Allocates a stable synthetic id for a generated menu item. */
69
+ /** Allocates a stable synthetic id for generated menu entries. */
82
70
  function allocateSyntheticId(prefix: string): string {
83
- nextSyntheticId += 1;
84
- return `${prefix}-${nextSyntheticId}`;
71
+ syntheticId += 1;
72
+ return `${prefix}-${syntheticId}`;
85
73
  }
86
74
 
87
- /** Returns a stored menu state or null if it does not exist. */
75
+ /** Returns the stored menu state for one id, if present. */
88
76
  function getStoredMenu(menuId: string): StoredMenuState | null {
89
77
  return menus.get(normalizeMenuId(menuId)) || null;
90
78
  }
91
79
 
92
- /** Inserts a menu id into the ordering list. */
80
+ /** Removes one menu id from the ordering list. */
81
+ function removeMenuId(menuId: string): void {
82
+ const id = normalizeMenuId(menuId);
83
+ const index = menuOrder.indexOf(id);
84
+ if (index >= 0) menuOrder.splice(index, 1);
85
+ }
86
+
87
+ /** Inserts one menu id into the ordering list. */
93
88
  function placeMenuId(menuId: string, options?: MenubarMenuOptions): void {
94
89
  const id = normalizeMenuId(menuId);
95
- const existingIndex = menuOrder.indexOf(id);
96
- if (existingIndex >= 0) {
97
- menuOrder.splice(existingIndex, 1);
98
- }
90
+ removeMenuId(id);
99
91
 
100
92
  const beforeId = normalizeMenuId(options?.before || '');
101
93
  const afterId = normalizeMenuId(options?.after || '');
@@ -119,8 +111,12 @@ function placeMenuId(menuId: string, options?: MenubarMenuOptions): void {
119
111
  menuOrder.push(id);
120
112
  }
121
113
 
122
- /** Ensures a menu record exists for the provided id. */
123
- function ensureStoredMenu(menuId: string, label: string, options?: MenubarMenuOptions): StoredMenuState {
114
+ /** Ensures a menu record exists for one id. */
115
+ function ensureStoredMenu(
116
+ menuId: string,
117
+ label: string,
118
+ options?: MenubarMenuOptions,
119
+ ): StoredMenuState {
124
120
  const id = normalizeMenuId(menuId);
125
121
  const safeLabel = String(label || '').trim() || id;
126
122
  let menu = menus.get(id);
@@ -128,38 +124,35 @@ function ensureStoredMenu(menuId: string, label: string, options?: MenubarMenuOp
128
124
  if (!menu) {
129
125
  menu = { id, label: safeLabel, items: [] };
130
126
  menus.set(id, menu);
131
- placeMenuId(id, options);
132
- return menu;
127
+ } else {
128
+ menu.label = safeLabel;
133
129
  }
134
130
 
135
- menu.label = safeLabel;
136
131
  placeMenuId(id, options);
137
132
  return menu;
138
133
  }
139
134
 
140
- /** Finds one menu item by action id. */
141
- function findMenuItem(menu: StoredMenuState, actionId: string): StoredMenuItem | null {
135
+ /** Finds a stored item by action id. */
136
+ function findStoredItem(menu: StoredMenuState, actionId: string): StoredMenuItem | null {
142
137
  const id = normalizeMenuId(actionId);
143
138
  return menu.items.find((item) => item.action === id) || null;
144
139
  }
145
140
 
146
- /** Inserts a menu item relative to before/after anchors when provided. */
141
+ /** Inserts a menu item at the requested position. */
147
142
  function insertMenuItem(menu: StoredMenuState, item: StoredMenuItem, before?: string, after?: string): void {
148
143
  const beforeId = normalizeMenuId(before || '');
149
144
  const afterId = normalizeMenuId(after || '');
150
145
 
151
- const insertAt = (index: number) => {
152
- const existingIndex = menu.items.findIndex((entry) => entry.id === item.id || entry.action === item.action);
153
- if (existingIndex >= 0) {
154
- menu.items.splice(existingIndex, 1);
155
- }
156
- menu.items.splice(index, 0, item);
146
+ const removeExisting = () => {
147
+ const index = menu.items.findIndex((entry) => entry.id === item.id || entry.action === item.action);
148
+ if (index >= 0) menu.items.splice(index, 1);
157
149
  };
158
150
 
159
151
  if (beforeId) {
160
152
  const beforeIndex = menu.items.findIndex((entry) => entry.action === beforeId);
161
153
  if (beforeIndex >= 0) {
162
- insertAt(beforeIndex);
154
+ removeExisting();
155
+ menu.items.splice(beforeIndex, 0, item);
163
156
  return;
164
157
  }
165
158
  }
@@ -167,15 +160,17 @@ function insertMenuItem(menu: StoredMenuState, item: StoredMenuItem, before?: st
167
160
  if (afterId) {
168
161
  const afterIndex = menu.items.findIndex((entry) => entry.action === afterId);
169
162
  if (afterIndex >= 0) {
170
- insertAt(afterIndex + 1);
163
+ removeExisting();
164
+ menu.items.splice(afterIndex + 1, 0, item);
171
165
  return;
172
166
  }
173
167
  }
174
168
 
175
- insertAt(menu.items.length);
169
+ removeExisting();
170
+ menu.items.push(item);
176
171
  }
177
172
 
178
- /** Converts one stored menu item into a serializable plugin payload element. */
173
+ /** Converts one stored item into a serializable menu payload element. */
179
174
  function serializeMenuItem(item: StoredMenuItem): SerializedMenuItem | null {
180
175
  if (item.hidden) return null;
181
176
 
@@ -202,7 +197,7 @@ function serializeMenuItem(item: StoredMenuItem): SerializedMenuItem | null {
202
197
  };
203
198
  }
204
199
 
205
- /** Serializes the current menu registry for the host runtime. */
200
+ /** Serializes the local registry into plugin menu payloads. */
206
201
  function serializeMenus(): SerializedMenuDefinition[] {
207
202
  return menuOrder
208
203
  .map((menuId, index) => {
@@ -214,13 +209,13 @@ function serializeMenus(): SerializedMenuDefinition[] {
214
209
  order: index + 1,
215
210
  elements: menu.items
216
211
  .map((item) => serializeMenuItem(item))
217
- .filter((item): item is SerializedMenuItem => Boolean(item)),
212
+ .filter((item): item is SerializedMenuItem => Boolean(item)),
218
213
  };
219
214
  })
220
215
  .filter((menu): menu is SerializedMenuDefinition => Boolean(menu));
221
216
  }
222
217
 
223
- /** Runs one registered action handler by id. */
218
+ /** Runs a registered action handler by id. */
224
219
  export async function runRegisteredAction(actionId: string, ...args: unknown[]): Promise<boolean> {
225
220
  const id = String(actionId || '').trim();
226
221
  if (!id) return false;
@@ -241,80 +236,52 @@ export interface MenuHandle {
241
236
  showItem(actionId: string): void;
242
237
  }
243
238
 
244
- /** Creates a stable, mutation-based handle for one menu id. */
239
+ /** Creates a stable handle for one stored menu. */
245
240
  function createMenuHandle(menuId: string): MenuHandle {
246
241
  const id = normalizeMenuId(menuId);
247
242
 
248
243
  return {
249
244
  id,
250
245
  addItem(item: MenubarItem) {
251
- const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
252
246
  const label = String(item?.label || '').trim();
253
247
  const action = String(item?.action || '').trim();
254
248
  if (!label || !action) return;
255
249
 
256
- const entry: StoredMenuItem = {
250
+ const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
251
+ insertMenuItem(menu, {
257
252
  kind: 'button',
258
253
  id: action,
259
254
  label,
260
255
  title: item.title,
261
256
  action,
262
- };
263
- insertMenuItem(menu, entry, item.before, item.after);
264
-
265
- const openvcs = getOpenVCS();
266
- if (!openvcs?.menus) return;
267
- openvcs.menus.getOrCreate(menu.id, menu.label);
268
- openvcs.menus.addMenuItem(menu.id, item);
257
+ }, item.before, item.after);
269
258
  },
270
259
  addSeparator(beforeAction?: string) {
271
260
  const menu = getStoredMenu(this.id) || ensureStoredMenu(this.id, this.id);
272
- const entry: StoredMenuItem = {
261
+ insertMenuItem(menu, {
273
262
  kind: 'separator',
274
263
  id: allocateSyntheticId(`${menu.id}-separator`),
275
264
  label: 'Separator',
276
265
  content: '—',
277
- };
278
- insertMenuItem(menu, entry, beforeAction);
279
-
280
- const openvcs = getOpenVCS();
281
- if (!openvcs?.menus) return;
282
- openvcs.menus.getOrCreate(menu.id, menu.label);
283
- openvcs.menus.addMenuSeparator(menu.id, beforeAction);
266
+ }, beforeAction);
284
267
  },
285
268
  removeItem(actionId: string) {
286
269
  const menu = getStoredMenu(this.id);
287
270
  if (!menu) return;
288
271
  const idToRemove = normalizeMenuId(actionId);
289
272
  menu.items = menu.items.filter((item) => item.action !== idToRemove);
290
-
291
- const openvcs = getOpenVCS();
292
- if (openvcs?.menus) {
293
- const handle = openvcs.menus.get(this.id) as MenuHandle | null;
294
- handle?.removeItem(idToRemove);
295
- }
296
273
  },
297
274
  hideItem(actionId: string) {
298
275
  const menu = getStoredMenu(this.id);
299
276
  if (!menu) return;
300
- const item = findMenuItem(menu, actionId);
277
+ const item = findStoredItem(menu, actionId);
301
278
  if (item) item.hidden = true;
302
-
303
- const openvcs = getOpenVCS();
304
- if (!openvcs?.menus) return;
305
- const handle = openvcs.menus.get(this.id) as MenuHandle | null;
306
- handle?.hideItem(actionId);
307
279
  },
308
280
  showItem(actionId: string) {
309
281
  const menu = getStoredMenu(this.id);
310
282
  if (!menu) return;
311
- const item = findMenuItem(menu, actionId);
283
+ const item = findStoredItem(menu, actionId);
312
284
  if (item) item.hidden = false;
313
-
314
- const openvcs = getOpenVCS();
315
- if (!openvcs?.menus) return;
316
- const handle = openvcs.menus.get(this.id) as MenuHandle | null;
317
- handle?.showItem(actionId);
318
285
  },
319
286
  };
320
287
  }
@@ -326,59 +293,45 @@ export function getMenu(menuId: string): MenuHandle | null {
326
293
  return createMenuHandle(stored.id);
327
294
  }
328
295
 
329
- /** Returns a menu by id, creating it when necessary. */
296
+ /** Returns a menu by id, creating it if needed. */
330
297
  export function getOrCreateMenu(menuId: string, label: string): MenuHandle | null {
331
298
  const stored = ensureStoredMenu(menuId, label);
332
299
  return createMenuHandle(stored.id);
333
300
  }
334
301
 
335
- /** Creates a menu at a specific insertion point. */
302
+ /** Creates a menu at a specific position. */
336
303
  export function createMenu(menuId: string, label: string, options?: MenubarMenuOptions): MenuHandle | null {
337
304
  const stored = ensureStoredMenu(menuId, label, options);
338
305
  return createMenuHandle(stored.id);
339
306
  }
340
307
 
341
- /** Adds one action item to a menu. */
308
+ /** Adds one item to a menu. */
342
309
  export function addMenuItem(menuId: string, item: MenubarItem): void {
343
- const menu = getStoredMenu(menuId) || ensureStoredMenu(menuId, menuId);
344
- const handle = createMenuHandle(menu.id);
345
- handle.addItem(item);
310
+ createMenuHandle(menuId).addItem(item);
346
311
  }
347
312
 
348
- /** Adds one separator item to a menu. */
313
+ /** Adds one separator to a menu. */
349
314
  export function addMenuSeparator(menuId: string, beforeAction?: string): void {
350
- const menu = getStoredMenu(menuId) || ensureStoredMenu(menuId, menuId);
351
- const handle = createMenuHandle(menu.id);
352
- handle.addSeparator(beforeAction);
315
+ createMenuHandle(menuId).addSeparator(beforeAction);
353
316
  }
354
317
 
355
- /** Removes one menu entirely. */
318
+ /** Removes one menu from the registry. */
356
319
  export function removeMenu(menuId: string): void {
357
320
  const id = normalizeMenuId(menuId);
358
321
  menus.delete(id);
359
- const index = menuOrder.indexOf(id);
360
- if (index >= 0) menuOrder.splice(index, 1);
361
-
362
- const openvcs = getOpenVCS();
363
- openvcs?.menus?.remove(id);
322
+ removeMenuId(id);
364
323
  }
365
324
 
366
- /** Hides one menu from the host UI. */
325
+ /** Hides one menu from the registry. */
367
326
  export function hideMenu(menuId: string): void {
368
327
  const menu = getStoredMenu(menuId);
369
328
  if (menu) menu.hidden = true;
370
-
371
- const openvcs = getOpenVCS();
372
- openvcs?.menus?.hide(normalizeMenuId(menuId));
373
329
  }
374
330
 
375
- /** Shows one previously hidden menu. */
331
+ /** Shows one menu from the registry. */
376
332
  export function showMenu(menuId: string): void {
377
333
  const menu = getStoredMenu(menuId);
378
334
  if (menu) menu.hidden = false;
379
-
380
- const openvcs = getOpenVCS();
381
- openvcs?.menus?.show(normalizeMenuId(menuId));
382
335
  }
383
336
 
384
337
  /** Registers an action handler by id. */
@@ -386,27 +339,24 @@ export function registerAction(id: string, handler: (...args: unknown[]) => unkn
386
339
  const key = String(id || '').trim();
387
340
  if (!key) return;
388
341
  actionHandlers.set(key, handler);
389
-
390
- const openvcs = getOpenVCS();
391
- openvcs?.registerAction(key, handler);
392
342
  }
393
343
 
394
- /** Invokes a host command when the host exposes a direct invoke helper. */
344
+ /** Invokes a host command when a host helper is available. */
395
345
  export function invoke<T = unknown>(cmd: string, args?: unknown): Promise<T> {
396
346
  const openvcs = getOpenVCS();
397
347
  if (!openvcs) {
398
- return Promise.reject(new Error('OpenVCS not available'));
348
+ return Promise.reject(new Error('OpenVCS host is not available in this runtime'));
399
349
  }
400
350
  return openvcs.invoke(cmd, args);
401
351
  }
402
352
 
403
- /** Emits a user notification when the host exposes one. */
353
+ /** Emits a notification when the host helper is available. */
404
354
  export function notify(msg: string): void {
405
355
  const openvcs = getOpenVCS();
406
356
  openvcs?.notify(msg);
407
357
  }
408
358
 
409
- /** Builds the SDK plugin delegates contributed by the menu registry. */
359
+ /** Builds SDK delegates from the local menu/action registries. */
410
360
  export function createMenuPluginDelegates(): PluginDelegates<PluginRuntimeContext> {
411
361
  return {
412
362
  async 'plugin.get_menus'(): Promise<PluginMenuDefinition[]> {
@@ -414,8 +364,9 @@ export function createMenuPluginDelegates(): PluginDelegates<PluginRuntimeContex
414
364
  },
415
365
  async 'plugin.handle_action'(params: PluginHandleActionParams): Promise<null> {
416
366
  const actionId = String(params?.action_id || '').trim();
417
- if (!actionId) return null;
418
- await runRegisteredAction(actionId);
367
+ if (actionId) {
368
+ await runRegisteredAction(actionId);
369
+ }
419
370
  return null;
420
371
  },
421
372
  };