codexmate 0.1.20 → 0.1.22
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/README.md +1 -1
- package/README.vi.md +1 -1
- package/README.zh.md +1 -1
- package/cli/agents-files.js +46 -1
- package/cli.js +12 -2
- package/package.json +1 -1
- package/web-ui/app.js +14 -0
- package/web-ui/index.html +3 -0
- package/web-ui/logic.markdown-editor.mjs +77 -0
- package/web-ui/modules/app.computed.session.mjs +33 -13
- package/web-ui/modules/app.methods.agents.mjs +21 -3
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.prompts-editor.mjs +153 -0
- package/web-ui/modules/app.methods.session-actions.mjs +37 -4
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +1 -0
- package/web-ui/modules/i18n/locales/en.mjs +12 -0
- package/web-ui/modules/i18n/locales/ja.mjs +12 -0
- package/web-ui/modules/i18n/locales/vi.mjs +12 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +12 -0
- package/web-ui/modules/i18n/locales/zh.mjs +12 -0
- package/web-ui/partials/index/panel-prompts.html +62 -15
- package/web-ui/res/highlight.min.js +1244 -0
- package/web-ui/res/marked.min.js +6 -0
- package/web-ui/res/purify.min.js +3 -0
- package/web-ui/res/web-ui-render.precompiled.js +198 -21
- package/web-ui/styles/prompts-editor.css +320 -0
- package/web-ui/styles.css +1 -0
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
|
|
|
63
63
|
- **Pi Provider Config**: Manage Pi providers from the Web UI with lossless round-trip writes to `~/.pi/agent/models.json`, auto-fetched remote model lists, and one-click default provider/model switching via `~/.pi/agent/settings.json`.
|
|
64
64
|
- **Provider Health Cleanup**: Probe local Codex and Claude provider routes, surface failed configs in one modal, and bulk-clean selected broken providers without touching healthy or protected entries.
|
|
65
65
|
- **Skills Marketplace**: A local-first market to share and import skills between different agent apps.
|
|
66
|
-
- **Prompt File Editor**: Unified editor for global and project-level `CLAUDE.md` and `AGENTS.md` with auto-detection of project paths, plus a shared preset pool for reusable prompts.
|
|
66
|
+
- **Prompt File Editor**: Unified editor for global and project-level `CLAUDE.md` and `AGENTS.md` (including OpenCode's `~/.config/opencode/AGENTS.md`) with auto-detection of project paths, plus a shared preset pool for reusable prompts.
|
|
67
67
|
- **Task Orchestrator**: Plan and execute complex tasks with dependency tracking.
|
|
68
68
|
|
|
69
69
|
---
|
package/README.vi.md
CHANGED
|
@@ -69,7 +69,7 @@ Khác với các wrapper đơn giản, Codex Mate hoạt động như một **Ag
|
|
|
69
69
|
| **Bridge OpenAI** | ✅ | Chuyển đổi Codex Responses API sang định dạng OpenAI chuẩn |
|
|
70
70
|
| **Kiểm tra sức khỏe Provider** | ✅ | Kiểm tra route provider Codex/Claude, đánh dấu cấu hình lỗi và xóa hàng loạt các provider hỏng đã chọn một cách an toàn |
|
|
71
71
|
| **Mẫu Prompt** | ✅ | Plugin prompt tái sử dụng được với hỗ trợ biến |
|
|
72
|
-
| **Trình sửa tệp prompt** | ✅ | Chỉnh sửa CLAUDE.md / AGENTS.md và dùng kho preset chung. Áp dụng preset chỉ cập nhật editor; cần lưu thủ công để ghi vào tệp. |
|
|
72
|
+
| **Trình sửa tệp prompt** | ✅ | Chỉnh sửa CLAUDE.md / AGENTS.md (bao gồm `~/.config/opencode/AGENTS.md` của OpenCode) và dùng kho preset chung. Áp dụng preset chỉ cập nhật editor; cần lưu thủ công để ghi vào tệp. |
|
|
73
73
|
| **Tích hợp MCP** | ✅ | Expose tool và resource cục bộ qua MCP stdio |
|
|
74
74
|
| **Tự động cập nhật** | ✅ | Cập nhật nhanh qua `codexmate update` |
|
|
75
75
|
|
package/README.zh.md
CHANGED
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
- **Pi Provider 配置**:在 Web UI 中管理 Pi 供应商,写入 `~/.pi/agent/models.json` 时无损保留未知字段,支持自动拉取远端模型列表,并可一键切换 `~/.pi/agent/settings.json` 中的默认供应商/模型。
|
|
62
62
|
- **Provider 健康清理**:探测本地 Codex 与 Claude provider 路由,在同一个弹窗里集中展示失败配置,并批量清理已选坏配置,不影响正常或受保护项。
|
|
63
63
|
- **Skills 市场**:本地优先的市场,支持在不同的智能体应用之间共享和导入 Skills。
|
|
64
|
-
- **提示词文件编辑器**:统一编辑全局和项目级 `CLAUDE.md` 与 `AGENTS.md
|
|
64
|
+
- **提示词文件编辑器**:统一编辑全局和项目级 `CLAUDE.md` 与 `AGENTS.md`(含 OpenCode 的 `~/.config/opencode/AGENTS.md`),支持项目路径自动检测,并提供通用预设池复用 prompt。
|
|
65
65
|
- **任务编排器**:支持带有依赖跟踪的复杂任务规划与执行。
|
|
66
66
|
|
|
67
67
|
---
|
package/cli/agents-files.js
CHANGED
|
@@ -15,6 +15,7 @@ function createAgentsFileController(deps = {}) {
|
|
|
15
15
|
CLAUDE_MD_FILE_NAME,
|
|
16
16
|
readOpenclawAgentsFile,
|
|
17
17
|
readOpenclawWorkspaceFile,
|
|
18
|
+
OPENCODE_CONFIG_DIR,
|
|
18
19
|
backupPromptBeforeWrite
|
|
19
20
|
} = deps;
|
|
20
21
|
|
|
@@ -33,6 +34,7 @@ function createAgentsFileController(deps = {}) {
|
|
|
33
34
|
if (typeof CLAUDE_MD_FILE_NAME !== 'string' || !CLAUDE_MD_FILE_NAME) throw new Error('createAgentsFileController 缺少 CLAUDE_MD_FILE_NAME');
|
|
34
35
|
if (typeof readOpenclawAgentsFile !== 'function') throw new Error('createAgentsFileController 缺少 readOpenclawAgentsFile');
|
|
35
36
|
if (typeof readOpenclawWorkspaceFile !== 'function') throw new Error('createAgentsFileController 缺少 readOpenclawWorkspaceFile');
|
|
37
|
+
if (typeof OPENCODE_CONFIG_DIR !== 'string' || !OPENCODE_CONFIG_DIR) throw new Error('createAgentsFileController 缺少 OPENCODE_CONFIG_DIR');
|
|
36
38
|
if (typeof backupPromptBeforeWrite !== 'function' && typeof backupPromptBeforeWrite !== 'undefined') throw new Error('createAgentsFileController 备份回调无效');
|
|
37
39
|
|
|
38
40
|
function resolveAgentsFilePath(params = {}) {
|
|
@@ -236,7 +238,9 @@ function createAgentsFileController(deps = {}) {
|
|
|
236
238
|
}
|
|
237
239
|
let agentsHistoryBucket = '';
|
|
238
240
|
if (typeof backupPromptBeforeWrite === 'function') {
|
|
239
|
-
const
|
|
241
|
+
const explicitBucket = typeof params.historyBucket === 'string' ? params.historyBucket.trim() : '';
|
|
242
|
+
const bucket = explicitBucket
|
|
243
|
+
|| ('codex_' + sanitizeHistoryId(String(params.baseDir || '').trim() || 'global'));
|
|
240
244
|
backupPromptBeforeWrite(bucket, filePath);
|
|
241
245
|
agentsHistoryBucket = bucket;
|
|
242
246
|
}
|
|
@@ -254,6 +258,43 @@ function createAgentsFileController(deps = {}) {
|
|
|
254
258
|
}
|
|
255
259
|
}
|
|
256
260
|
|
|
261
|
+
|
|
262
|
+
function readOpencodeAgentsFile(params = {}) {
|
|
263
|
+
const baseDir = OPENCODE_CONFIG_DIR;
|
|
264
|
+
const filePath = path.join(baseDir, AGENTS_FILE_NAME);
|
|
265
|
+
const lineEndingFallback = os.EOL === '\r\n' ? '\r\n' : '\n';
|
|
266
|
+
|
|
267
|
+
if (!fs.existsSync(baseDir)) {
|
|
268
|
+
return {
|
|
269
|
+
exists: false,
|
|
270
|
+
path: filePath,
|
|
271
|
+
content: '',
|
|
272
|
+
lineEnding: lineEndingFallback,
|
|
273
|
+
configDir: baseDir,
|
|
274
|
+
baseDirMissing: true
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const readResult = readAgentsFile({ baseDir, metaOnly: !!params.metaOnly });
|
|
279
|
+
return {
|
|
280
|
+
...readResult,
|
|
281
|
+
configDir: baseDir
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function applyOpencodeAgentsFile(params = {}) {
|
|
286
|
+
const baseDir = OPENCODE_CONFIG_DIR;
|
|
287
|
+
ensureDir(baseDir);
|
|
288
|
+
const result = applyAgentsFile({
|
|
289
|
+
...params,
|
|
290
|
+
baseDir,
|
|
291
|
+
historyBucket: 'opencode_global'
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
...result,
|
|
295
|
+
configDir: baseDir
|
|
296
|
+
};
|
|
297
|
+
}
|
|
257
298
|
function normalizeDiffText(input) {
|
|
258
299
|
const safe = typeof input === 'string' ? input : '';
|
|
259
300
|
return normalizeLineEnding(stripUtf8Bom(safe), '\n');
|
|
@@ -273,6 +314,8 @@ function createAgentsFileController(deps = {}) {
|
|
|
273
314
|
readResult = readOpenclawAgentsFile({ metaOnly });
|
|
274
315
|
} else if (context === 'openclaw-workspace') {
|
|
275
316
|
readResult = readOpenclawWorkspaceFile({ ...params, metaOnly });
|
|
317
|
+
} else if (context === 'opencode') {
|
|
318
|
+
readResult = readOpencodeAgentsFile({ metaOnly });
|
|
276
319
|
} else if (context === 'codex') {
|
|
277
320
|
readResult = readAgentsFile({ ...params, metaOnly });
|
|
278
321
|
} else {
|
|
@@ -310,6 +353,8 @@ function createAgentsFileController(deps = {}) {
|
|
|
310
353
|
applyClaudeMdFile,
|
|
311
354
|
readAgentsFile,
|
|
312
355
|
applyAgentsFile,
|
|
356
|
+
readOpencodeAgentsFile,
|
|
357
|
+
applyOpencodeAgentsFile,
|
|
313
358
|
normalizeDiffText,
|
|
314
359
|
buildAgentsDiff
|
|
315
360
|
};
|
package/cli.js
CHANGED
|
@@ -1190,7 +1190,8 @@ function getApiToolConfigWriteTarget(action) {
|
|
|
1190
1190
|
]);
|
|
1191
1191
|
const opencodeWriteActions = new Set([
|
|
1192
1192
|
'apply-opencode-config',
|
|
1193
|
-
'update-opencode-selection'
|
|
1193
|
+
'update-opencode-selection',
|
|
1194
|
+
'apply-opencode-agents-file'
|
|
1194
1195
|
]);
|
|
1195
1196
|
const kilocodeWriteActions = new Set([
|
|
1196
1197
|
'apply-kilocode-config',
|
|
@@ -2372,6 +2373,8 @@ const {
|
|
|
2372
2373
|
applyClaudeMdFile,
|
|
2373
2374
|
readAgentsFile,
|
|
2374
2375
|
applyAgentsFile,
|
|
2376
|
+
readOpencodeAgentsFile,
|
|
2377
|
+
applyOpencodeAgentsFile,
|
|
2375
2378
|
normalizeDiffText,
|
|
2376
2379
|
buildAgentsDiff
|
|
2377
2380
|
} = createAgentsFileController({
|
|
@@ -2394,7 +2397,8 @@ const {
|
|
|
2394
2397
|
},
|
|
2395
2398
|
readOpenclawWorkspaceFile() {
|
|
2396
2399
|
return readOpenclawWorkspaceFile(...arguments);
|
|
2397
|
-
}
|
|
2400
|
+
},
|
|
2401
|
+
OPENCODE_CONFIG_DIR
|
|
2398
2402
|
});
|
|
2399
2403
|
|
|
2400
2404
|
const {
|
|
@@ -13694,6 +13698,12 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13694
13698
|
case 'get-openclaw-agents-file':
|
|
13695
13699
|
result = readOpenclawAgentsFile();
|
|
13696
13700
|
break;
|
|
13701
|
+
case 'get-opencode-agents-file':
|
|
13702
|
+
result = readOpencodeAgentsFile(params || {});
|
|
13703
|
+
break;
|
|
13704
|
+
case 'apply-opencode-agents-file':
|
|
13705
|
+
result = applyOpencodeAgentsFile(params || {});
|
|
13706
|
+
break;
|
|
13697
13707
|
case 'apply-openclaw-agents-file':
|
|
13698
13708
|
result = applyOpenclawAgentsFile(params || {});
|
|
13699
13709
|
break;
|
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -102,6 +102,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
102
102
|
showConfigTemplateModal: false,
|
|
103
103
|
showAgentsModal: false,
|
|
104
104
|
promptsSubTab: 'codex',
|
|
105
|
+
promptsPreviewEnabled: true,
|
|
106
|
+
promptsPreviewCollapsed: false,
|
|
107
|
+
promptsMobileView: 'edit',
|
|
108
|
+
promptsPreviewLibsMissing: false,
|
|
109
|
+
agentsHighlightHtml: '',
|
|
110
|
+
sysPromptHighlightHtml: '',
|
|
111
|
+
agentsPreviewHtml: '',
|
|
112
|
+
sysPromptPreviewHtml: '',
|
|
105
113
|
projectClaudeMdPath: '',
|
|
106
114
|
promptHistoryVisible: false,
|
|
107
115
|
promptHistoryLoading: false,
|
|
@@ -812,6 +820,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
812
820
|
if (typeof this.loadPromptsTabContent === 'function') this.loadPromptsTabContent();
|
|
813
821
|
}
|
|
814
822
|
},
|
|
823
|
+
agentsContent() {
|
|
824
|
+
if (typeof this.schedulePromptsEditorRefresh === 'function') this.schedulePromptsEditorRefresh('agents');
|
|
825
|
+
},
|
|
826
|
+
sysPromptContent() {
|
|
827
|
+
if (typeof this.schedulePromptsEditorRefresh === 'function') this.schedulePromptsEditorRefresh('sys');
|
|
828
|
+
},
|
|
815
829
|
projectClaudeMdPath(newPath) {
|
|
816
830
|
if (typeof this.persistWebUiPreferences === 'function') {
|
|
817
831
|
this.persistWebUiPreferences({ projectClaudeMdPath: newPath || '' });
|
package/web-ui/index.html
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
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
|
+
<script src="/res/marked.min.js"></script>
|
|
13
|
+
<script src="/res/purify.min.js"></script>
|
|
11
14
|
<link rel="stylesheet" href="/web-ui/styles.css">
|
|
12
15
|
</head>
|
|
13
16
|
<body>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure logic for the prompts-panel Markdown editor.
|
|
3
|
+
* No DOM access in this module so every function stays unit-testable.
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const HIGHLIGHT_MAX_CHARS = 200000;
|
|
14
|
+
const HTML_ESCAPE_MAP = {
|
|
15
|
+
'&': '&',
|
|
16
|
+
'<': '<',
|
|
17
|
+
'>': '>',
|
|
18
|
+
'"': '"',
|
|
19
|
+
"'": '''
|
|
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
|
+
/**
|
|
63
|
+
* 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.
|
|
66
|
+
*/
|
|
67
|
+
export function buildMarkdownPreviewHtml(text, marked, purify) {
|
|
68
|
+
const source = typeof text === 'string' ? text : '';
|
|
69
|
+
if (!source.trim()) {
|
|
70
|
+
return { empty: true, html: '' };
|
|
71
|
+
}
|
|
72
|
+
if (!marked || typeof marked.parse !== 'function' || !purify || typeof purify.sanitize !== 'function') {
|
|
73
|
+
return { unavailable: true, html: '' };
|
|
74
|
+
}
|
|
75
|
+
const rawHtml = marked.parse(source, { gfm: true, breaks: false });
|
|
76
|
+
return { html: purify.sanitize(String(rawHtml)) };
|
|
77
|
+
}
|
|
@@ -112,30 +112,39 @@ function extractWorkspaceArtifacts(line, target) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function buildWorkspaceBriefText(summary, labels = {}) {
|
|
115
|
+
function buildWorkspaceBriefText(summary, labels = {}, metricLabels = {}) {
|
|
116
116
|
if (!summary || !summary.available) return '';
|
|
117
117
|
const label = (key, fallback) => (labels && labels[key]) || fallback;
|
|
118
|
+
const metricLabel = (key, fallback) => (metricLabels && metricLabels[key]) || fallback;
|
|
118
119
|
const lines = [
|
|
119
120
|
`# ${label('title', 'Session workspace brief')}`,
|
|
120
121
|
'',
|
|
121
122
|
`${label('source', 'Source')}: ${summary.sourceLabel || summary.source || '-'}`,
|
|
122
123
|
`${label('messages', 'Messages')}: ${summary.messageCount}`,
|
|
124
|
+
`${metricLabel('userLabel', 'User')}: ${summary.roleCounts.user}`,
|
|
125
|
+
`${metricLabel('assistantLabel', 'Assistant')}: ${summary.roleCounts.assistant}`,
|
|
126
|
+
`${metricLabel('commandsLabel', 'Commands')}: ${summary.commands.length}`,
|
|
127
|
+
`${metricLabel('artifactsLabel', 'Artifacts')}: ${summary.files.length + summary.links.length}`,
|
|
128
|
+
`${metricLabel('risksLabel', 'Risks')}: ${summary.risks.length + summary.nextSteps.length}`,
|
|
123
129
|
`${label('path', 'Path')}: ${summary.cwd || '-'}`,
|
|
124
130
|
''
|
|
125
131
|
];
|
|
126
132
|
const sections = [
|
|
127
|
-
['signals', label('signals', 'Signals'), summary.signals],
|
|
128
|
-
['commands', label('commands', 'Reusable commands'), summary.commands],
|
|
129
|
-
['files', label('files', 'Files'), summary.files],
|
|
130
|
-
['links', label('links', 'Links'), summary.links],
|
|
131
|
-
['risks', label('risks', 'Risks / blockers'), summary.risks],
|
|
132
|
-
['nextSteps', label('nextSteps', 'Next steps'), summary.nextSteps]
|
|
133
|
+
['signals', label('signals', 'Signals'), summary.signals, label('noneSignals', 'No strong signals yet')],
|
|
134
|
+
['commands', label('commands', 'Reusable commands'), summary.commands, label('noneCommands', 'No command signals')],
|
|
135
|
+
['files', label('files', 'Files'), summary.files, label('noneFiles', 'No file signals')],
|
|
136
|
+
['links', label('links', 'Links'), summary.links, label('noneLinks', 'No link signals')],
|
|
137
|
+
['risks', label('risks', 'Risks / blockers'), summary.risks, label('noneRisks', 'No risks or todos detected')],
|
|
138
|
+
['nextSteps', label('nextSteps', 'Next steps'), summary.nextSteps, label('noneRisks', 'No risks or todos detected')]
|
|
133
139
|
];
|
|
134
|
-
for (const [, sectionTitle, items] of sections) {
|
|
135
|
-
if (!Array.isArray(items) || !items.length) continue;
|
|
140
|
+
for (const [, sectionTitle, items, emptyText] of sections) {
|
|
136
141
|
lines.push(`## ${sectionTitle}`);
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
if (Array.isArray(items) && items.length) {
|
|
143
|
+
for (const item of items) {
|
|
144
|
+
lines.push(`- ${item}`);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
lines.push(`- ${emptyText}`);
|
|
139
148
|
}
|
|
140
149
|
lines.push('');
|
|
141
150
|
}
|
|
@@ -231,7 +240,13 @@ export function buildSessionWorkspaceSummary(session, messages, options = {}) {
|
|
|
231
240
|
risks,
|
|
232
241
|
nextSteps
|
|
233
242
|
};
|
|
234
|
-
summary.briefText = buildWorkspaceBriefText(summary, options.briefLabels || {}
|
|
243
|
+
summary.briefText = buildWorkspaceBriefText(summary, options.briefLabels || {}, {
|
|
244
|
+
userLabel: options.userLabel,
|
|
245
|
+
assistantLabel: options.assistantLabel,
|
|
246
|
+
commandsLabel: options.commandsLabel,
|
|
247
|
+
artifactsLabel: options.artifactsLabel,
|
|
248
|
+
risksLabel: options.risksLabel
|
|
249
|
+
});
|
|
235
250
|
return summary;
|
|
236
251
|
}
|
|
237
252
|
|
|
@@ -474,7 +489,12 @@ export function createSessionComputed() {
|
|
|
474
489
|
files: t ? t('sessions.workspace.files') : 'Files',
|
|
475
490
|
links: t ? t('sessions.workspace.links') : 'Links',
|
|
476
491
|
risks: t ? t('sessions.workspace.risks') : 'Risks / blockers',
|
|
477
|
-
nextSteps: t ? t('sessions.workspace.nextSteps') : 'Next steps'
|
|
492
|
+
nextSteps: t ? t('sessions.workspace.nextSteps') : 'Next steps',
|
|
493
|
+
noneSignals: t ? t('sessions.workspace.empty') : 'No strong signals yet',
|
|
494
|
+
noneCommands: t ? t('sessions.workspace.noneCommands') : 'No command signals',
|
|
495
|
+
noneFiles: t ? t('sessions.workspace.noneFiles') : 'No file signals',
|
|
496
|
+
noneLinks: t ? t('sessions.workspace.noneLinks') : 'No link signals',
|
|
497
|
+
noneRisks: t ? t('sessions.workspace.noneRisks') : 'No risks or todos detected'
|
|
478
498
|
}
|
|
479
499
|
});
|
|
480
500
|
},
|
|
@@ -33,6 +33,9 @@ function resolveAgentsHistoryBucket(instance) {
|
|
|
33
33
|
const projectPath = (instance.projectClaudeMdPath || '').trim();
|
|
34
34
|
return 'claude-project_' + sanitizePromptHistoryId(projectPath || 'global');
|
|
35
35
|
}
|
|
36
|
+
if (subTab === 'opencode') {
|
|
37
|
+
return 'opencode_global';
|
|
38
|
+
}
|
|
36
39
|
return 'codex_global';
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -216,6 +219,13 @@ export function createAgentsMethods(options = {}) {
|
|
|
216
219
|
this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile', `Workspace / ${fileName}`, { fileName });
|
|
217
220
|
return;
|
|
218
221
|
}
|
|
222
|
+
if (context === 'opencode') {
|
|
223
|
+
this.agentsContext = 'opencode';
|
|
224
|
+
this.agentsWorkspaceFileName = '';
|
|
225
|
+
this.agentsModalTitle = tr('modal.agents.title.opencode', 'OpenCode AGENTS.md 编辑器');
|
|
226
|
+
this.agentsModalHint = tr('modal.agents.hint.opencode', '保存后会写入 ~/.config/opencode/AGENTS.md。');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
219
229
|
this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
|
|
220
230
|
if (this.agentsContext === 'openclaw') {
|
|
221
231
|
this.agentsModalTitle = tr('modal.agents.title.openclaw', 'OpenClaw AGENTS.md 编辑器');
|
|
@@ -700,6 +710,8 @@ export function createAgentsMethods(options = {}) {
|
|
|
700
710
|
}
|
|
701
711
|
} else if (this.agentsContext === 'openclaw') {
|
|
702
712
|
action = 'apply-openclaw-agents-file';
|
|
713
|
+
} else if (this.agentsContext === 'opencode') {
|
|
714
|
+
action = 'apply-opencode-agents-file';
|
|
703
715
|
} else if (this.agentsContext === 'openclaw-workspace') {
|
|
704
716
|
action = 'apply-openclaw-workspace-file';
|
|
705
717
|
params.fileName = this.agentsWorkspaceFileName;
|
|
@@ -716,7 +728,11 @@ export function createAgentsMethods(options = {}) {
|
|
|
716
728
|
? this.t('toast.agents.saved.workspace', { name: this.agentsWorkspaceFileName || '' }).replace(/:\s*$/, '')
|
|
717
729
|
: (this.agentsContext === 'claude-project'
|
|
718
730
|
? this.t('toast.agents.saved.claudeProject')
|
|
719
|
-
: (this.agentsContext === 'openclaw'
|
|
731
|
+
: (this.agentsContext === 'openclaw'
|
|
732
|
+
? this.t('toast.agents.saved.openclaw')
|
|
733
|
+
: (this.agentsContext === 'opencode'
|
|
734
|
+
? this.t('toast.agents.saved.opencode')
|
|
735
|
+
: this.t('toast.agents.saved.agents'))));
|
|
720
736
|
this.showMessage(successLabel, 'success');
|
|
721
737
|
if (this.mainTab === 'prompts') {
|
|
722
738
|
this.loadPromptsContent();
|
|
@@ -987,7 +1003,7 @@ export function createAgentsMethods(options = {}) {
|
|
|
987
1003
|
},
|
|
988
1004
|
|
|
989
1005
|
switchPromptsSubTab(subTab) {
|
|
990
|
-
const normalized = subTab === 'claude-project' || subTab === 'system' ? subTab : 'codex';
|
|
1006
|
+
const normalized = subTab === 'claude-project' || subTab === 'system' || subTab === 'opencode' ? subTab : 'codex';
|
|
991
1007
|
if (normalized === 'claude-project' && !this.projectPathOptions.length && !this.projectPathOptionsLoading) {
|
|
992
1008
|
this.loadProjectPathOptions();
|
|
993
1009
|
}
|
|
@@ -1035,6 +1051,8 @@ export function createAgentsMethods(options = {}) {
|
|
|
1035
1051
|
if (projectPath) {
|
|
1036
1052
|
rpcParams.baseDir = projectPath;
|
|
1037
1053
|
}
|
|
1054
|
+
} else if (subTab === 'opencode') {
|
|
1055
|
+
action = 'get-opencode-agents-file';
|
|
1038
1056
|
} else {
|
|
1039
1057
|
action = 'get-agents-file';
|
|
1040
1058
|
}
|
|
@@ -1051,7 +1069,7 @@ export function createAgentsMethods(options = {}) {
|
|
|
1051
1069
|
this.agentsPath = res.path || '';
|
|
1052
1070
|
this.agentsExists = !!res.exists;
|
|
1053
1071
|
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
1054
|
-
this.agentsContext = subTab === 'claude-project' ? 'claude-project' : 'codex';
|
|
1072
|
+
this.agentsContext = subTab === 'claude-project' ? 'claude-project' : (subTab === 'opencode' ? 'opencode' : 'codex');
|
|
1055
1073
|
} catch (e) {
|
|
1056
1074
|
if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
|
|
1057
1075
|
return;
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from './app.constants.mjs';
|
|
14
14
|
import { createAgentsMethods } from './app.methods.agents.mjs';
|
|
15
15
|
import { createSystemPromptMethods } from './app.methods.system-prompt.mjs';
|
|
16
|
+
import { createPromptsEditorMethods } from './app.methods.prompts-editor.mjs';
|
|
16
17
|
import { createClaudeConfigMethods } from './app.methods.claude-config.mjs';
|
|
17
18
|
import { createCodexConfigMethods } from './app.methods.codex-config.mjs';
|
|
18
19
|
import { createInstallMethods } from './app.methods.install.mjs';
|
|
@@ -51,6 +52,7 @@ import {
|
|
|
51
52
|
export function createAppMethods() {
|
|
52
53
|
return {
|
|
53
54
|
...createI18nMethods(),
|
|
55
|
+
...createPromptsEditorMethods(),
|
|
54
56
|
...createWebhookMethods(),
|
|
55
57
|
...createStartupClaudeMethods({
|
|
56
58
|
api,
|
|
@@ -0,0 +1,153 @@
|
|
|
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.
|
|
6
|
+
*
|
|
7
|
+
* State contract (declared in app.js data()):
|
|
8
|
+
* promptsPreviewEnabled, promptsPreviewCollapsed, promptsMobileView,
|
|
9
|
+
* promptsPreviewLibsMissing, agentsHighlightHtml, sysPromptHighlightHtml,
|
|
10
|
+
* agentsPreviewHtml, sysPromptPreviewHtml
|
|
11
|
+
* Template contract:
|
|
12
|
+
* refs promptsAgentsTextarea / promptsSysTextarea on the two textareas,
|
|
13
|
+
* refs promptsAgentsHighlight / promptsSysHighlight on the backdrop <pre>.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
buildMarkdownPreviewHtml,
|
|
18
|
+
highlightMarkdownText
|
|
19
|
+
} from '../logic.markdown-editor.mjs';
|
|
20
|
+
|
|
21
|
+
const PROMPTS_HIGHLIGHT_DEBOUNCE_MS = 150;
|
|
22
|
+
const PROMPTS_PREVIEW_DEBOUNCE_MS = 300;
|
|
23
|
+
|
|
24
|
+
const PROMPTS_EDITOR_META = {
|
|
25
|
+
agents: {
|
|
26
|
+
contentField: 'agentsContent',
|
|
27
|
+
highlightField: 'agentsHighlightHtml',
|
|
28
|
+
previewField: 'agentsPreviewHtml',
|
|
29
|
+
textareaRef: 'promptsAgentsTextarea',
|
|
30
|
+
highlightRef: 'promptsAgentsHighlight'
|
|
31
|
+
},
|
|
32
|
+
sys: {
|
|
33
|
+
contentField: 'sysPromptContent',
|
|
34
|
+
highlightField: 'sysPromptHighlightHtml',
|
|
35
|
+
previewField: 'sysPromptPreviewHtml',
|
|
36
|
+
textareaRef: 'promptsSysTextarea',
|
|
37
|
+
highlightRef: 'promptsSysHighlight'
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function resolvePromptsHljs() {
|
|
42
|
+
if (typeof window !== 'undefined' && window.hljs) {
|
|
43
|
+
return window.hljs;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolvePromptsPreviewLibs() {
|
|
49
|
+
if (typeof window === 'undefined') {
|
|
50
|
+
return { marked: null, purify: null };
|
|
51
|
+
}
|
|
52
|
+
return { marked: window.marked || null, purify: window.DOMPurify || null };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createPromptsEditorMethods() {
|
|
56
|
+
return {
|
|
57
|
+
promptsEditorMeta(key) {
|
|
58
|
+
return PROMPTS_EDITOR_META[key] || null;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
refreshPromptsHighlight(key) {
|
|
63
|
+
const meta = this.promptsEditorMeta(key);
|
|
64
|
+
if (!meta) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this[meta.highlightField] = highlightMarkdownText(this[meta.contentField], resolvePromptsHljs());
|
|
68
|
+
this.syncPromptsOverlayScroll(key);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
schedulePromptsHighlight(key) {
|
|
72
|
+
const meta = this.promptsEditorMeta(key);
|
|
73
|
+
if (!meta) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!this._promptsHighlightTimers) {
|
|
77
|
+
this._promptsHighlightTimers = {};
|
|
78
|
+
}
|
|
79
|
+
if (this._promptsHighlightTimers[key]) {
|
|
80
|
+
clearTimeout(this._promptsHighlightTimers[key]);
|
|
81
|
+
}
|
|
82
|
+
this._promptsHighlightTimers[key] = setTimeout(() => {
|
|
83
|
+
this._promptsHighlightTimers[key] = null;
|
|
84
|
+
this.refreshPromptsHighlight(key);
|
|
85
|
+
}, PROMPTS_HIGHLIGHT_DEBOUNCE_MS);
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
syncPromptsOverlayScroll(key) {
|
|
89
|
+
const meta = this.promptsEditorMeta(key);
|
|
90
|
+
if (!meta || typeof this.$refs !== 'object' || !this.$refs) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const textarea = this.$refs[meta.textareaRef];
|
|
94
|
+
const backdrop = this.$refs[meta.highlightRef];
|
|
95
|
+
if (!textarea || !backdrop) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
backdrop.scrollTop = textarea.scrollTop;
|
|
99
|
+
backdrop.scrollLeft = textarea.scrollLeft;
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
refreshPromptsPreview(key) {
|
|
103
|
+
const meta = this.promptsEditorMeta(key);
|
|
104
|
+
if (!meta) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!this.promptsPreviewEnabled) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const libs = resolvePromptsPreviewLibs();
|
|
111
|
+
const state = buildMarkdownPreviewHtml(this[meta.contentField], libs.marked, libs.purify);
|
|
112
|
+
if (state.unavailable) {
|
|
113
|
+
this.promptsPreviewLibsMissing = true;
|
|
114
|
+
this[meta.previewField] = '';
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.promptsPreviewLibsMissing = false;
|
|
118
|
+
this[meta.previewField] = state.html;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
schedulePromptsPreview(key) {
|
|
122
|
+
const meta = this.promptsEditorMeta(key);
|
|
123
|
+
if (!meta) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!this._promptsPreviewTimers) {
|
|
127
|
+
this._promptsPreviewTimers = {};
|
|
128
|
+
}
|
|
129
|
+
if (this._promptsPreviewTimers[key]) {
|
|
130
|
+
clearTimeout(this._promptsPreviewTimers[key]);
|
|
131
|
+
}
|
|
132
|
+
this._promptsPreviewTimers[key] = setTimeout(() => {
|
|
133
|
+
this._promptsPreviewTimers[key] = null;
|
|
134
|
+
this.refreshPromptsPreview(key);
|
|
135
|
+
}, PROMPTS_PREVIEW_DEBOUNCE_MS);
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
schedulePromptsEditorRefresh(key) {
|
|
139
|
+
this.schedulePromptsHighlight(key);
|
|
140
|
+
this.schedulePromptsPreview(key);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
setPromptsMobileView(view) {
|
|
144
|
+
if (view === 'edit' || view === 'preview') {
|
|
145
|
+
this.promptsMobileView = view;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
togglePromptsPreviewCollapsed() {
|
|
150
|
+
this.promptsPreviewCollapsed = !this.promptsPreviewCollapsed;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|