codexmate 0.1.24 → 0.1.25

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 (33) hide show
  1. package/cli.js +4 -1
  2. package/package.json +1 -1
  3. package/plugins/prompt-templates/computed.mjs +1 -1
  4. package/plugins/prompt-templates/index.mjs +3 -3
  5. package/plugins/prompt-templates/manifest.mjs +5 -3
  6. package/plugins/prompt-templates/methods.mjs +9 -2
  7. package/plugins/registry.mjs +5 -2
  8. package/plugins/text-tools/computed.mjs +23 -0
  9. package/plugins/text-tools/index.mjs +14 -0
  10. package/plugins/text-tools/manifest.mjs +20 -0
  11. package/plugins/text-tools/methods.mjs +24 -0
  12. package/plugins/text-tools/overview.mjs +12 -0
  13. package/plugins/text-tools/ownership.mjs +7 -0
  14. package/web-ui/app.js +2 -2
  15. package/web-ui/index.html +0 -1
  16. package/web-ui/logic.markdown-editor.mjs +5 -57
  17. package/web-ui/modules/app.methods.navigation.mjs +79 -3
  18. package/web-ui/modules/app.methods.prompts-editor.mjs +6 -64
  19. package/web-ui/modules/app.methods.web-ui-preferences.mjs +7 -1
  20. package/web-ui/modules/i18n/locales/en.mjs +11 -0
  21. package/web-ui/modules/i18n/locales/ja.mjs +11 -0
  22. package/web-ui/modules/i18n/locales/vi.mjs +11 -0
  23. package/web-ui/modules/i18n/locales/zh-tw.mjs +11 -0
  24. package/web-ui/modules/i18n/locales/zh.mjs +11 -0
  25. package/web-ui/modules/plugins.computed.mjs +8 -2
  26. package/web-ui/modules/plugins.methods.mjs +8 -2
  27. package/web-ui/partials/index/layout-header.html +19 -4
  28. package/web-ui/partials/index/panel-plugins.html +45 -5
  29. package/web-ui/partials/index/panel-prompts.html +2 -17
  30. package/web-ui/res/web-ui-render.precompiled.js +104 -72
  31. package/web-ui/session-helpers.mjs +0 -1
  32. package/web-ui/source-bundle.cjs +1 -0
  33. package/web-ui/styles/prompts-editor.css +3 -66
package/cli.js CHANGED
@@ -1098,7 +1098,10 @@ function normalizeWebUiPreferences(value = {}) {
1098
1098
  skillsTargetApp: navigation.skillsTargetApp === 'claude' || navigation.skillsTargetApp === 'pi'
1099
1099
  ? navigation.skillsTargetApp
1100
1100
  : 'codex',
1101
- promptTemplatesMode: navigation.promptTemplatesMode === 'manage' ? 'manage' : 'compose'
1101
+ promptTemplatesMode: navigation.promptTemplatesMode === 'manage' ? 'manage' : 'compose',
1102
+ pluginsActiveId: typeof navigation.pluginsActiveId === 'string' && navigation.pluginsActiveId.trim()
1103
+ ? navigation.pluginsActiveId.trim()
1104
+ : 'prompt-templates'
1102
1105
  }
1103
1106
  };
1104
1107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -158,7 +158,7 @@ function localizeBuiltinPromptTemplate(item, t) {
158
158
 
159
159
  import { pluginsRegistry } from '../registry.mjs';
160
160
 
161
- export function createPluginsComputed() {
161
+ export function createPromptTemplatesComputed() {
162
162
  return {
163
163
  pluginsCatalog() {
164
164
  return pluginsRegistry
@@ -1,6 +1,6 @@
1
- export { createPluginsMethods } from './methods.mjs';
2
- export { createPluginsComputed } from './computed.mjs';
3
- export { pluginMeta } from './manifest.mjs';
1
+ export { createPromptTemplatesMethods as createPluginsMethods } from './methods.mjs';
2
+ export { createPromptTemplatesComputed as createPluginsComputed } from './computed.mjs';
3
+ export { promptTemplatesPluginMeta as pluginMeta } from './manifest.mjs';
4
4
  export {
5
5
  readPromptTemplatesFromStorage,
6
6
  persistPromptTemplatesToStorage,
@@ -1,6 +1,6 @@
1
1
  import { pluginOwnership } from './ownership.mjs';
2
2
 
3
- const baseMeta = {
3
+ const promptTemplatesBaseMeta = {
4
4
  id: 'prompt-templates',
5
5
  title: 'Prompt Templates',
6
6
  titleKey: 'plugins.catalog.promptTemplates.title',
@@ -11,8 +11,10 @@ const baseMeta = {
11
11
  tone: 'configured'
12
12
  };
13
13
 
14
- export const pluginMeta = {
15
- ...baseMeta,
14
+ export const promptTemplatesPluginMeta = {
15
+ ...promptTemplatesBaseMeta,
16
16
  createdBy: pluginOwnership && typeof pluginOwnership.createdBy === 'string' ? pluginOwnership.createdBy : '',
17
17
  maintainers: pluginOwnership && Array.isArray(pluginOwnership.maintainers) ? pluginOwnership.maintainers : []
18
18
  };
19
+
20
+ export { promptTemplatesPluginMeta as pluginMeta };
@@ -79,7 +79,7 @@ function normalizePromptTemplateDraft(draft) {
79
79
  };
80
80
  }
81
81
 
82
- export function createPluginsMethods() {
82
+ export function createPromptTemplatesMethods() {
83
83
  return {
84
84
  resetPromptComposerVarValues() {
85
85
  this.promptComposerVarValuesRaw = {};
@@ -280,8 +280,15 @@ export function createPluginsMethods() {
280
280
  selectPlugin(pluginId) {
281
281
  const id = typeof pluginId === 'string' ? pluginId.trim() : '';
282
282
  if (!id) return;
283
- if (!getPluginEntry(id)) return;
283
+ const entry = getPluginEntry(id);
284
+ if (!entry) return;
284
285
  this.pluginsActiveId = id;
286
+ if (typeof this.saveNavState === 'function') {
287
+ this.saveNavState();
288
+ }
289
+ if (typeof entry.loadOverview === 'function' && typeof this.loadPluginsOverview === 'function') {
290
+ void Promise.resolve(this.loadPluginsOverview({ silent: true })).catch(() => {});
291
+ }
285
292
  },
286
293
 
287
294
  async loadPluginsOverview(options = {}) {
@@ -1,8 +1,11 @@
1
- import { pluginMeta as promptTemplatesMeta } from './prompt-templates/manifest.mjs';
1
+ import { promptTemplatesPluginMeta } from './prompt-templates/manifest.mjs';
2
2
  import { loadPromptTemplatesOverview } from './prompt-templates/overview.mjs';
3
+ import { textToolsPluginMeta } from './text-tools/manifest.mjs';
4
+ import { loadTextToolsOverview } from './text-tools/overview.mjs';
3
5
 
4
6
  export const pluginsRegistry = [
5
- { id: promptTemplatesMeta.id, meta: promptTemplatesMeta, loadOverview: loadPromptTemplatesOverview }
7
+ { id: promptTemplatesPluginMeta.id, meta: promptTemplatesPluginMeta, loadOverview: loadPromptTemplatesOverview },
8
+ { id: textToolsPluginMeta.id, meta: textToolsPluginMeta, loadOverview: loadTextToolsOverview }
6
9
  ];
7
10
 
8
11
  export function getFirstPluginId() {
@@ -0,0 +1,23 @@
1
+ import { textTools, DEFAULT_TEXT_TOOL_ID } from './index.mjs';
2
+
3
+ export function createTextToolsComputed() {
4
+ return {
5
+ textToolsList() {
6
+ const t = typeof this.t === 'function' ? this.t : null;
7
+ return textTools.map((tool) => ({
8
+ id: tool.id,
9
+ labelKey: tool.labelKey,
10
+ label: t ? t(tool.labelKey) : tool.labelKey
11
+ }));
12
+ },
13
+
14
+ textToolsOutput() {
15
+ const activeId = typeof this.textToolsActiveToolId === 'string' ? this.textToolsActiveToolId.trim() : '';
16
+ const tool = textTools.find((item) => item && item.id === activeId)
17
+ || textTools.find((item) => item && item.id === DEFAULT_TEXT_TOOL_ID)
18
+ || null;
19
+ const input = typeof this.textToolsInput === 'string' ? this.textToolsInput : '';
20
+ return tool && typeof tool.run === 'function' ? tool.run(input) : '';
21
+ }
22
+ };
23
+ }
@@ -0,0 +1,14 @@
1
+ export const DEFAULT_TEXT_TOOL_ID = 'compress';
2
+
3
+ export function compressTextToSingleLine(input) {
4
+ if (typeof input !== 'string') return '';
5
+ return input.replace(/\s+/g, ' ').trim();
6
+ }
7
+
8
+ export const textTools = [
9
+ {
10
+ id: 'compress',
11
+ labelKey: 'plugins.textTools.tools.compress',
12
+ run: compressTextToSingleLine
13
+ }
14
+ ];
@@ -0,0 +1,20 @@
1
+ import { textToolsPluginOwnership } from './ownership.mjs';
2
+
3
+ const textToolsBaseMeta = {
4
+ id: 'text-tools',
5
+ title: 'Text Tools',
6
+ titleKey: 'plugins.catalog.textTools.title',
7
+ description: 'Lightweight in-browser text utilities; the first tool compresses text into a single line.',
8
+ descriptionKey: 'plugins.catalog.textTools.description',
9
+ statusLabel: 'standard',
10
+ statusLabelKey: 'plugins.status.standard',
11
+ tone: 'configured'
12
+ };
13
+
14
+ export const textToolsPluginMeta = {
15
+ ...textToolsBaseMeta,
16
+ createdBy: textToolsPluginOwnership && typeof textToolsPluginOwnership.createdBy === 'string' ? textToolsPluginOwnership.createdBy : '',
17
+ maintainers: textToolsPluginOwnership && Array.isArray(textToolsPluginOwnership.maintainers) ? textToolsPluginOwnership.maintainers : []
18
+ };
19
+
20
+ export { textToolsPluginMeta as pluginMeta };
@@ -0,0 +1,24 @@
1
+ export function createTextToolsMethods() {
2
+ return {
3
+ async copyTextToolsOutput() {
4
+ const text = typeof this.textToolsOutput === 'string' ? this.textToolsOutput.trim() : '';
5
+ if (!text) {
6
+ this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.empty') : 'Nothing to copy', 'info');
7
+ return;
8
+ }
9
+ try {
10
+ if (navigator.clipboard && window.isSecureContext) {
11
+ await navigator.clipboard.writeText(text);
12
+ this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
13
+ return;
14
+ }
15
+ } catch (_) {}
16
+ const ok = typeof this.fallbackCopyText === 'function' ? this.fallbackCopyText(text) : false;
17
+ if (ok) {
18
+ this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
19
+ return;
20
+ }
21
+ this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.fail') : 'Copy failed', 'error');
22
+ }
23
+ };
24
+ }
@@ -0,0 +1,12 @@
1
+ import { textTools, DEFAULT_TEXT_TOOL_ID } from './index.mjs';
2
+
3
+ export async function loadTextToolsOverview(ctx) {
4
+ const app = ctx && typeof ctx === 'object' ? ctx : {};
5
+ const activeId = typeof app.textToolsActiveToolId === 'string' ? app.textToolsActiveToolId.trim() : '';
6
+ const exists = textTools.some((tool) => tool && tool.id === activeId);
7
+ app.textToolsActiveToolId = exists ? activeId : DEFAULT_TEXT_TOOL_ID;
8
+ if (typeof app.textToolsInput !== 'string') {
9
+ app.textToolsInput = '';
10
+ }
11
+ return true;
12
+ }
@@ -0,0 +1,7 @@
1
+ export const textToolsPluginOwnership = {
2
+ pluginId: 'text-tools',
3
+ createdBy: 'ymkiux',
4
+ maintainers: ['ymkiux']
5
+ };
6
+
7
+ export { textToolsPluginOwnership as pluginOwnership };
package/web-ui/app.js CHANGED
@@ -111,8 +111,6 @@ document.addEventListener('DOMContentLoaded', () => {
111
111
  promptsPathReferencePaths: {},
112
112
  promptsPathReferenceLoading: false,
113
113
  promptsPathReferenceCacheKey: '',
114
- agentsHighlightHtml: '',
115
- sysPromptHighlightHtml: '',
116
114
  agentsPreviewHtml: '',
117
115
  sysPromptPreviewHtml: '',
118
116
  projectClaudeMdPath: '',
@@ -172,6 +170,8 @@ document.addEventListener('DOMContentLoaded', () => {
172
170
  pluginsActiveId: 'prompt-templates',
173
171
  pluginsLoading: false,
174
172
  pluginsError: '',
173
+ textToolsActiveToolId: '',
174
+ textToolsInput: '',
175
175
  promptTemplatesListRaw: [],
176
176
  promptTemplatesLoadedOnce: false,
177
177
  promptTemplatesKeyword: '',
package/web-ui/index.html CHANGED
@@ -8,7 +8,6 @@
8
8
  <link rel="apple-touch-icon" href="/res/logo-pack.webp">
9
9
  <script src="/res/vue.runtime.global.prod.js"></script>
10
10
  <script src="/res/json5.min.js"></script>
11
- <script src="/res/highlight.min.js"></script>
12
11
  <script src="/res/marked.min.js"></script>
13
12
  <script src="/res/purify.min.js"></script>
14
13
  <link rel="stylesheet" href="/web-ui/styles.css">
@@ -2,67 +2,15 @@
2
2
  * Pure logic for the prompts-panel Markdown editor.
3
3
  * No DOM access in this module so every function stays unit-testable.
4
4
  *
5
- * Syntax highlighting delegates to the vendored highlight.js UMD build
6
- * (web-ui/res/highlight.min.js, "markdown" grammar is included in the
7
- * common build) and the rendered highlight markup is layered behind a
8
- * transparent textarea. Preview rendering delegates to the vendored
9
- * marked + DOMPurify pair; raw markdown may embed arbitrary HTML, so
10
- * sanitize is mandatory before the result reaches v-html.
5
+ * Preview rendering delegates to the vendored marked + DOMPurify pair; raw
6
+ * markdown may embed arbitrary HTML, so sanitize is mandatory before the
7
+ * result reaches v-html.
11
8
  */
12
9
 
13
- const HIGHLIGHT_MAX_CHARS = 200000;
14
- const HTML_ESCAPE_MAP = {
15
- '&': '&amp;',
16
- '<': '&lt;',
17
- '>': '&gt;',
18
- '"': '&quot;',
19
- "'": '&#39;'
20
- };
21
-
22
-
23
- let hasWarnedHighlightFallback = false;
24
-
25
- export function escapeHtmlText(text) {
26
- return String(text === null || text === undefined ? '' : text)
27
- .replace(/[&<>"']/g, (ch) => HTML_ESCAPE_MAP[ch]);
28
- }
29
-
30
- function plainHighlightFallback(text) {
31
- if (!hasWarnedHighlightFallback) {
32
- hasWarnedHighlightFallback = true;
33
- // eslint-disable-next-line no-console
34
- console.warn('prompts editor: highlight.js unavailable, editor falls back to plain text rendering');
35
- }
36
- return escapeHtmlText(text) + '\n';
37
- }
38
-
39
- /**
40
- * Produce the highlighted HTML layer for a markdown source string.
41
- * highlight.js escapes HTML entities itself; the trailing newline keeps
42
- * the <pre> backdrop visually aligned with the textarea's final line.
43
- * Returns escaped plain text when hljs is missing or the input exceeds
44
- * the highlight budget (observable degradation, never a fake success).
45
- */
46
- export function highlightMarkdownText(text, hljs) {
47
- const source = typeof text === 'string' ? text : '';
48
- if (!hljs || typeof hljs.highlight !== 'function' || typeof hljs.getLanguage !== 'function' || !hljs.getLanguage('markdown')) {
49
- return plainHighlightFallback(source);
50
- }
51
- if (source.length > HIGHLIGHT_MAX_CHARS) {
52
- return plainHighlightFallback(source);
53
- }
54
- try {
55
- return hljs.highlight(source, { language: 'markdown', ignoreUnescapedHTML: true }).value + '\n';
56
- } catch (e) {
57
- return plainHighlightFallback(source);
58
- }
59
- }
60
-
61
-
62
10
  /**
63
11
  * Render a markdown preview. Returns a state object instead of raw HTML so
64
- * the UI can surface "libs missing" and "empty input" explicitly instead
65
- * of faking a successful render.
12
+ * the UI can surface "libs missing" and "empty input" explicitly instead of
13
+ * faking a successful render.
66
14
  */
67
15
  export function buildMarkdownPreviewHtml(text, marked, purify) {
68
16
  const source = typeof text === 'string' ? text : '';
@@ -110,12 +110,17 @@
110
110
  const settingsTab = typeof vm.settingsTab === 'string' ? vm.settingsTab.trim().toLowerCase() : 'general';
111
111
  const skillsTargetApp = typeof vm.skillsTargetApp === 'string' && (vm.skillsTargetApp === 'codex' || vm.skillsTargetApp === 'claude' || vm.skillsTargetApp === 'pi') ? vm.skillsTargetApp : 'codex';
112
112
  const promptTemplatesMode = typeof vm.promptTemplatesMode === 'string' && (vm.promptTemplatesMode === 'compose' || vm.promptTemplatesMode === 'manage') ? vm.promptTemplatesMode : 'compose';
113
+ const pluginsActiveIdSource = resolvedOverrides && typeof resolvedOverrides.pluginsActiveId === 'string'
114
+ ? resolvedOverrides.pluginsActiveId
115
+ : vm.pluginsActiveId;
116
+ const pluginsActiveId = typeof pluginsActiveIdSource === 'string' ? pluginsActiveIdSource.trim() : '';
113
117
  const snapshot = {
114
118
  settingsTab: settingsTab === 'data' ? 'data' : 'general',
115
119
  mainTab: resolveSelectableMainTab(mainTab),
116
120
  configMode: configModeSet && configModeSet.has(configMode) ? configMode : 'codex',
117
121
  skillsTargetApp,
118
- promptTemplatesMode
122
+ promptTemplatesMode,
123
+ pluginsActiveId: pluginsActiveId || 'prompt-templates'
119
124
  };
120
125
  if (typeof vm.persistWebUiPreferences === 'function') {
121
126
  vm.persistWebUiPreferences({ navigation: snapshot });
@@ -192,6 +197,7 @@
192
197
  intent: '',
193
198
  pendingTarget: '',
194
199
  pendingConfigMode: '',
200
+ pendingPluginsId: '',
195
201
  ticket: 0
196
202
  };
197
203
  return this.__mainTabSwitchState;
@@ -242,7 +248,7 @@
242
248
  if (!normalizedKind || !normalizedValue) {
243
249
  return;
244
250
  }
245
- const expectedIntent = normalizedKind === 'config' ? 'config' : normalizedValue;
251
+ const expectedIntent = normalizedKind === 'config' || normalizedKind === 'plugins' ? normalizedKind : normalizedValue;
246
252
  this.cancelTouchNavIntentReset();
247
253
  const token = (Number(this.__touchNavIntentResetToken) || 0) + 1;
248
254
  this.__touchNavIntentResetToken = token;
@@ -259,21 +265,26 @@
259
265
  this.clearMainTabSwitchIntent(expectedIntent);
260
266
  }, 1000);
261
267
  },
262
- applyImmediateNavIntent(tab, configMode = '') {
268
+ applyImmediateNavIntent(tab, configMode = '', pluginsId = '') {
263
269
  if (typeof document === 'undefined') return;
264
270
  const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
265
271
  if (!normalizedTab) return;
266
272
  const normalizedMode = typeof configMode === 'string' ? configMode.trim().toLowerCase() : '';
273
+ const normalizedPluginId = typeof pluginsId === 'string' ? pluginsId.trim().toLowerCase() : '';
267
274
  const domState = this.ensureImmediateNavDomState();
268
275
  const nodes = Array.isArray(domState.navNodes) ? domState.navNodes : [];
269
276
  for (const node of nodes) {
270
277
  if (!node || !node.classList) continue;
271
278
  const nodeTab = String(node.getAttribute('data-main-tab') || '').trim().toLowerCase();
272
279
  const nodeMode = String(node.getAttribute('data-config-mode') || '').trim().toLowerCase();
280
+ const nodePluginId = String(node.getAttribute('data-plugins-id') || '').trim().toLowerCase();
273
281
  let shouldActivate = nodeTab === normalizedTab;
274
282
  if (shouldActivate && normalizedTab === 'config') {
275
283
  shouldActivate = nodeMode ? nodeMode === normalizedMode : false;
276
284
  }
285
+ if (shouldActivate && normalizedTab === 'plugins') {
286
+ shouldActivate = nodePluginId ? nodePluginId === normalizedPluginId : !normalizedPluginId;
287
+ }
277
288
  node.classList.toggle('nav-intent-active', !!shouldActivate);
278
289
  node.classList.toggle('nav-intent-inactive', !shouldActivate);
279
290
  }
@@ -395,6 +406,67 @@
395
406
  if (this.consumePointerNavCommit('config', normalizedMode)) return;
396
407
  this.switchConfigMode(normalizedMode);
397
408
  },
409
+
410
+ switchPluginsTab(pluginId) {
411
+ const normalizedId = typeof pluginId === 'string' ? pluginId.trim().toLowerCase() : '';
412
+ if (!normalizedId) return;
413
+ if (typeof this.selectPlugin === 'function') {
414
+ this.selectPlugin(normalizedId);
415
+ } else {
416
+ this.pluginsActiveId = normalizedId;
417
+ }
418
+ this.switchMainTab('plugins');
419
+ },
420
+
421
+ onPluginsTabPointerDown(pluginId) {
422
+ const event = arguments.length > 1 ? arguments[1] : null;
423
+ if (event && typeof event.button === 'number' && event.button !== 0) {
424
+ return;
425
+ }
426
+ const normalizedId = typeof pluginId === 'string' ? pluginId.trim().toLowerCase() : '';
427
+ if (!normalizedId) return;
428
+ persistNavState(this, { mainTab: 'plugins', pluginsActiveId: normalizedId });
429
+ this.setMainTabSwitchIntent('plugins');
430
+ if (typeof this.ensureMainTabSwitchState === 'function') {
431
+ this.ensureMainTabSwitchState().pendingPluginsId = normalizedId;
432
+ }
433
+ this.applyImmediateNavIntent('plugins', '', normalizedId);
434
+ const shouldHideSessionPanel = this.mainTab === 'sessions';
435
+ this.setSessionPanelFastHidden(shouldHideSessionPanel);
436
+ const pointerType = event && typeof event.pointerType === 'string'
437
+ ? event.pointerType.trim().toLowerCase()
438
+ : '';
439
+ if (pointerType === 'touch') {
440
+ this.scheduleTouchNavIntentReset('plugins', normalizedId);
441
+ return;
442
+ }
443
+ this.recordPointerNavCommit('plugins', normalizedId);
444
+ this.switchPluginsTab(normalizedId);
445
+ },
446
+
447
+ onPluginsTabClick(pluginId) {
448
+ const normalizedId = typeof pluginId === 'string' ? pluginId.trim().toLowerCase() : '';
449
+ if (!normalizedId) return;
450
+ if (this.consumePointerNavCommit('plugins', normalizedId)) return;
451
+ this.switchPluginsTab(normalizedId);
452
+ },
453
+
454
+ isPluginsIdNavActive(pluginId) {
455
+ const normalizedId = typeof pluginId === 'string' ? pluginId.trim().toLowerCase() : '';
456
+ if (!normalizedId) return false;
457
+ if (!this.isMainTabNavActive('plugins')) return false;
458
+ const state = this.ensureMainTabSwitchState();
459
+ const pendingId = typeof state.pendingPluginsId === 'string'
460
+ ? state.pendingPluginsId.trim().toLowerCase()
461
+ : '';
462
+ if (state.intent === 'plugins' && pendingId) {
463
+ return pendingId === normalizedId;
464
+ }
465
+ const currentId = typeof this.pluginsActiveId === 'string'
466
+ ? this.pluginsActiveId.trim().toLowerCase()
467
+ : '';
468
+ return currentId === normalizedId;
469
+ },
398
470
  clearMainTabSwitchIntent(expectedTab = '') {
399
471
  const state = this.ensureMainTabSwitchState();
400
472
  if (expectedTab && state.intent && state.intent !== expectedTab) {
@@ -404,6 +476,7 @@
404
476
  state.intent = '';
405
477
  state.pendingTarget = '';
406
478
  state.pendingConfigMode = '';
479
+ state.pendingPluginsId = '';
407
480
  this.clearImmediateNavIntent();
408
481
  this.setSessionPanelFastHidden(false);
409
482
  },
@@ -463,6 +536,9 @@
463
536
  if (targetTab !== 'config') {
464
537
  switchState.pendingConfigMode = '';
465
538
  }
539
+ if (targetTab !== 'plugins') {
540
+ switchState.pendingPluginsId = '';
541
+ }
466
542
  if (targetTab === previousTab) {
467
543
  switchState.ticket += 1;
468
544
  switchState.pendingTarget = '';
@@ -1,25 +1,20 @@
1
1
  /**
2
- * Prompts-panel Markdown editor methods: overlay highlight sync,
3
- * mobile view switching, and the live split preview (marked + DOMPurify,
4
- * debounced). One implementation is shared by the agentsContent editor
5
- * and the sysPromptContent editor.
2
+ * Prompts-panel Markdown editor methods: mobile view switching and the
3
+ * live split preview (marked + DOMPurify, debounced). One implementation
4
+ * is shared by the agentsContent editor and the sysPromptContent editor.
6
5
  *
7
6
  * State contract (declared in app.js data()):
8
7
  * promptsPreviewEnabled, promptsPreviewCollapsed, promptsMobileView,
9
- * promptsPreviewLibsMissing, agentsHighlightHtml, sysPromptHighlightHtml,
10
- * agentsPreviewHtml, sysPromptPreviewHtml, agentsChangeAxis, sysChangeAxis,
8
+ * promptsPreviewLibsMissing, agentsPreviewHtml, sysPromptPreviewHtml,
9
+ * agentsChangeAxis, sysChangeAxis,
11
10
  * promptsPathReferencePaths, promptsPathReferenceLoading,
12
11
  * promptsPathReferenceCacheKey (path-reference dropdown)
13
12
  * Template contract:
14
13
  * refs promptsAgentsTextarea / promptsSysTextarea on the two textareas,
15
- * refs promptsAgentsHighlight / promptsSysHighlight on the backdrop <pre>,
16
14
  * refs promptsAgentsDiffView / promptsSysDiffView on the two diff row containers.
17
15
  */
18
16
 
19
- import {
20
- buildMarkdownPreviewHtml,
21
- highlightMarkdownText
22
- } from '../logic.markdown-editor.mjs';
17
+ import { buildMarkdownPreviewHtml } from '../logic.markdown-editor.mjs';
23
18
  import {
24
19
  buildPromptPathReferenceOptions as buildPromptPathReferenceOptionList,
25
20
  buildPromptPathReferenceSentence,
@@ -29,7 +24,6 @@ import {
29
24
  buildPromptsDiffAxis
30
25
  } from '../logic.prompts-change-axis.mjs';
31
26
 
32
- const PROMPTS_HIGHLIGHT_DEBOUNCE_MS = 150;
33
27
  const PROMPTS_PREVIEW_DEBOUNCE_MS = 300;
34
28
  const PROMPTS_AXIS_DEBOUNCE_MS = 300;
35
29
 
@@ -37,7 +31,6 @@ const PROMPTS_EDITOR_META = {
37
31
  agents: {
38
32
  contentField: 'agentsContent',
39
33
  originalField: 'agentsOriginalContent',
40
- highlightField: 'agentsHighlightHtml',
41
34
  previewField: 'agentsPreviewHtml',
42
35
  axisField: 'agentsChangeAxis',
43
36
  diffLinesField: 'agentsDiffLines',
@@ -48,13 +41,11 @@ const PROMPTS_EDITOR_META = {
48
41
  diffLoadingField: 'agentsDiffLoading',
49
42
  prepareDiffMethod: 'prepareAgentsDiff',
50
43
  textareaRef: 'promptsAgentsTextarea',
51
- highlightRef: 'promptsAgentsHighlight',
52
44
  diffViewRef: 'promptsAgentsDiffView'
53
45
  },
54
46
  sys: {
55
47
  contentField: 'sysPromptContent',
56
48
  originalField: 'sysPromptOriginalContent',
57
- highlightField: 'sysPromptHighlightHtml',
58
49
  previewField: 'sysPromptPreviewHtml',
59
50
  axisField: 'sysChangeAxis',
60
51
  diffLinesField: 'sysPromptDiffLines',
@@ -65,7 +56,6 @@ const PROMPTS_EDITOR_META = {
65
56
  diffLoadingField: 'sysPromptDiffLoading',
66
57
  prepareDiffMethod: 'prepareSysPromptDiff',
67
58
  textareaRef: 'promptsSysTextarea',
68
- highlightRef: 'promptsSysHighlight',
69
59
  diffViewRef: 'promptsSysDiffView'
70
60
  }
71
61
  };
@@ -74,13 +64,6 @@ function emptyPromptsChangeAxis() {
74
64
  return { mode: 'edit', ticks: [], truncated: false, totalLines: 0, added: 0, removed: 0 };
75
65
  }
76
66
 
77
- function resolvePromptsHljs() {
78
- if (typeof window !== 'undefined' && window.hljs) {
79
- return window.hljs;
80
- }
81
- return null;
82
- }
83
-
84
67
  function resolvePromptsPreviewLibs() {
85
68
  if (typeof window === 'undefined') {
86
69
  return { marked: null, purify: null };
@@ -97,46 +80,6 @@ export function createPromptsEditorMethods(options = {}) {
97
80
  },
98
81
 
99
82
 
100
- refreshPromptsHighlight(key) {
101
- const meta = this.promptsEditorMeta(key);
102
- if (!meta) {
103
- return;
104
- }
105
- this[meta.highlightField] = highlightMarkdownText(this[meta.contentField], resolvePromptsHljs());
106
- this.syncPromptsOverlayScroll(key);
107
- },
108
-
109
- schedulePromptsHighlight(key) {
110
- const meta = this.promptsEditorMeta(key);
111
- if (!meta) {
112
- return;
113
- }
114
- if (!this._promptsHighlightTimers) {
115
- this._promptsHighlightTimers = {};
116
- }
117
- if (this._promptsHighlightTimers[key]) {
118
- clearTimeout(this._promptsHighlightTimers[key]);
119
- }
120
- this._promptsHighlightTimers[key] = setTimeout(() => {
121
- this._promptsHighlightTimers[key] = null;
122
- this.refreshPromptsHighlight(key);
123
- }, PROMPTS_HIGHLIGHT_DEBOUNCE_MS);
124
- },
125
-
126
- syncPromptsOverlayScroll(key) {
127
- const meta = this.promptsEditorMeta(key);
128
- if (!meta || typeof this.$refs !== 'object' || !this.$refs) {
129
- return;
130
- }
131
- const textarea = this.$refs[meta.textareaRef];
132
- const backdrop = this.$refs[meta.highlightRef];
133
- if (!textarea || !backdrop) {
134
- return;
135
- }
136
- backdrop.scrollTop = textarea.scrollTop;
137
- backdrop.scrollLeft = textarea.scrollLeft;
138
- },
139
-
140
83
  refreshPromptsPreview(key) {
141
84
  const meta = this.promptsEditorMeta(key);
142
85
  if (!meta) {
@@ -174,7 +117,6 @@ export function createPromptsEditorMethods(options = {}) {
174
117
  },
175
118
 
176
119
  schedulePromptsEditorRefresh(key) {
177
- this.schedulePromptsHighlight(key);
178
120
  this.schedulePromptsPreview(key);
179
121
  this.schedulePromptsChangeAxis(key);
180
122
  },
@@ -114,7 +114,10 @@ function normalizeNavigationSnapshot(vm, source = {}) {
114
114
  : currentSkillsTargetApp,
115
115
  promptTemplatesMode: source.promptTemplatesMode === 'manage' || source.promptTemplatesMode === 'compose'
116
116
  ? source.promptTemplatesMode
117
- : currentPromptTemplatesMode
117
+ : currentPromptTemplatesMode,
118
+ pluginsActiveId: typeof source.pluginsActiveId === 'string'
119
+ ? source.pluginsActiveId
120
+ : (typeof vm.pluginsActiveId === 'string' ? vm.pluginsActiveId : '')
118
121
  };
119
122
  }
120
123
 
@@ -475,6 +478,9 @@ export function createWebUiPreferencesMethods(options = {}) {
475
478
  this.settingsTab = this.normalizeSettingsTab(nav.settingsTab);
476
479
  }
477
480
  if (typeof nav.configMode === 'string') this.configMode = nav.configMode;
481
+ if (typeof nav.pluginsActiveId === 'string' && nav.pluginsActiveId.trim()) {
482
+ this.pluginsActiveId = nav.pluginsActiveId.trim();
483
+ }
478
484
  if (typeof nav.mainTab === 'string') {
479
485
  if (typeof this.switchMainTab === 'function') {
480
486
  this.switchMainTab(nav.mainTab);
@@ -384,6 +384,17 @@ const en = Object.freeze({
384
384
  'plugins.catalog.promptTemplates.title': 'Prompt templates',
385
385
  'plugins.catalog.promptTemplates.description': 'Standardized, template-driven prompt tools with variables, copy, and export helpers.',
386
386
  'plugins.status.standard': 'standard',
387
+ 'plugins.catalog.textTools.title': 'Text tools',
388
+ 'plugins.catalog.textTools.description': 'A set of lightweight plain-text utilities; the first tool compresses multi-line text into a single line.',
389
+ 'plugins.textTools.title': 'Text tools',
390
+ 'plugins.textTools.tools.aria': 'Text tool selection',
391
+ 'plugins.textTools.tools.compress': 'Compress text',
392
+ 'plugins.textTools.compress.inputLabel': 'Input text',
393
+ 'plugins.textTools.compress.inputPlaceholder': 'Paste or type the text to compress…',
394
+ 'plugins.textTools.compress.inputAria': 'Text to compress',
395
+ 'plugins.textTools.compress.outputTitle': 'Compressed result',
396
+ 'plugins.textTools.compress.outputAria': 'Compressed text result',
397
+ 'plugins.textTools.compress.copy': 'Copy',
387
398
  'plugins.main.ariaWorkspace': 'Plugin workspace',
388
399
  'plugins.refresh': 'Refresh',
389
400
  'plugins.refreshing': 'Refreshing...',