codexmate 0.1.10 → 0.1.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.
- package/README.md +0 -4
- package/README.vi.md +0 -4
- package/README.zh.md +0 -4
- package/cli/agents-files.js +27 -3
- package/cli/prompt-history.js +171 -0
- package/cli/system-prompt-files.js +20 -2
- package/cli.js +23 -1
- package/package.json +1 -1
- package/web-ui/app.js +15 -0
- package/web-ui/modules/app.methods.agents.mjs +90 -0
- package/web-ui/modules/app.methods.navigation.mjs +7 -1
- package/web-ui/modules/app.methods.session-actions.mjs +57 -0
- package/web-ui/modules/app.methods.system-prompt.mjs +81 -0
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +6 -0
- package/web-ui/modules/i18n/locales/en.mjs +9 -0
- package/web-ui/modules/i18n/locales/ja.mjs +9 -0
- package/web-ui/modules/i18n/locales/vi.mjs +9 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +9 -0
- package/web-ui/modules/i18n/locales/zh.mjs +9 -0
- package/web-ui/partials/index/layout-header.html +5 -0
- package/web-ui/partials/index/panel-config-claude.html +3 -3
- package/web-ui/partials/index/panel-config-codex.html +3 -3
- package/web-ui/partials/index/panel-config-kilocode.html +5 -5
- package/web-ui/partials/index/panel-config-openclaw.html +3 -3
- package/web-ui/partials/index/panel-config-opencode.html +5 -5
- package/web-ui/partials/index/panel-prompts.html +52 -0
- package/web-ui/partials/index/panel-sessions.html +12 -0
- package/web-ui/partials/index/panel-settings.html +35 -0
- package/web-ui/res/web-ui-render.precompiled.js +517 -186
- package/web-ui/styles/modals-core.css +86 -0
- package/web-ui/styles/sessions-preview.css +38 -0
- package/web-ui/styles/titles-cards.css +2 -2
package/README.md
CHANGED
|
@@ -184,10 +184,6 @@ Special thanks to all contributors for their contributions to Codex Mate ❤️
|
|
|
184
184
|
<img src="https://contrib.rocks/image?repo=SakuraByteCore/codexmate" />
|
|
185
185
|
</a>
|
|
186
186
|
|
|
187
|
-
## Star History
|
|
188
|
-
|
|
189
|
-
[](https://star-history.com/#SakuraByteCore/codexmate&Date)
|
|
190
|
-
|
|
191
187
|
## License
|
|
192
188
|
|
|
193
189
|
Apache-2.0
|
package/README.vi.md
CHANGED
|
@@ -167,10 +167,6 @@ Cảm ơn tất cả những người đóng góp cho Codex Mate ❤️
|
|
|
167
167
|
<img src="https://contrib.rocks/image?repo=SakuraByteCore/codexmate" alt="Contributors" />
|
|
168
168
|
</a>
|
|
169
169
|
|
|
170
|
-
## Lịch sử Star
|
|
171
|
-
|
|
172
|
-
[](https://star-history.com/#SakuraByteCore/codexmate&Date)
|
|
173
|
-
|
|
174
170
|
## Giấy phép
|
|
175
171
|
|
|
176
172
|
Apache-2.0
|
package/README.zh.md
CHANGED
|
@@ -181,10 +181,6 @@ flowchart TD
|
|
|
181
181
|
<img src="https://contrib.rocks/image?repo=SakuraByteCore/codexmate" />
|
|
182
182
|
</a>
|
|
183
183
|
|
|
184
|
-
## Star 历史
|
|
185
|
-
|
|
186
|
-
[](https://star-history.com/#SakuraByteCore/codexmate&Date)
|
|
187
|
-
|
|
188
184
|
## 开源协议
|
|
189
185
|
|
|
190
186
|
Apache-2.0
|
package/cli/agents-files.js
CHANGED
|
@@ -14,7 +14,8 @@ function createAgentsFileController(deps = {}) {
|
|
|
14
14
|
CLAUDE_DIR,
|
|
15
15
|
CLAUDE_MD_FILE_NAME,
|
|
16
16
|
readOpenclawAgentsFile,
|
|
17
|
-
readOpenclawWorkspaceFile
|
|
17
|
+
readOpenclawWorkspaceFile,
|
|
18
|
+
backupPromptBeforeWrite
|
|
18
19
|
} = deps;
|
|
19
20
|
|
|
20
21
|
if (!fs) throw new Error('createAgentsFileController 缺少 fs');
|
|
@@ -32,6 +33,7 @@ function createAgentsFileController(deps = {}) {
|
|
|
32
33
|
if (typeof CLAUDE_MD_FILE_NAME !== 'string' || !CLAUDE_MD_FILE_NAME) throw new Error('createAgentsFileController 缺少 CLAUDE_MD_FILE_NAME');
|
|
33
34
|
if (typeof readOpenclawAgentsFile !== 'function') throw new Error('createAgentsFileController 缺少 readOpenclawAgentsFile');
|
|
34
35
|
if (typeof readOpenclawWorkspaceFile !== 'function') throw new Error('createAgentsFileController 缺少 readOpenclawWorkspaceFile');
|
|
36
|
+
if (typeof backupPromptBeforeWrite !== 'function' && typeof backupPromptBeforeWrite !== 'undefined') throw new Error('createAgentsFileController 备份回调无效');
|
|
35
37
|
|
|
36
38
|
function resolveAgentsFilePath(params = {}) {
|
|
37
39
|
const baseDir = typeof params.baseDir === 'string' && params.baseDir.trim()
|
|
@@ -40,6 +42,12 @@ function createAgentsFileController(deps = {}) {
|
|
|
40
42
|
return path.join(baseDir, AGENTS_FILE_NAME);
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
function sanitizeHistoryId(raw) {
|
|
46
|
+
const safe = typeof raw === 'string' ? raw.trim() : '';
|
|
47
|
+
if (!safe) return 'global';
|
|
48
|
+
return safe.replace(/[^A-Za-z0-9_.-]/g, '_').slice(0, 64) || 'global';
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
function validateAgentsBaseDir(filePath) {
|
|
44
52
|
const dirPath = path.dirname(filePath);
|
|
45
53
|
try {
|
|
@@ -152,6 +160,13 @@ function createAgentsFileController(deps = {}) {
|
|
|
152
160
|
if (content.length > 2 * 1024 * 1024) {
|
|
153
161
|
return { error: 'content too large (max 2MB)' };
|
|
154
162
|
}
|
|
163
|
+
if (typeof backupPromptBeforeWrite === 'function') {
|
|
164
|
+
var bucket = resolved.isProject
|
|
165
|
+
? 'claude-project_' + sanitizeHistoryId(resolved.projectPath)
|
|
166
|
+
: 'claude-global';
|
|
167
|
+
backupPromptBeforeWrite(bucket, filePath);
|
|
168
|
+
resolved.historyBucket = bucket;
|
|
169
|
+
}
|
|
155
170
|
var lineEnding = params.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
156
171
|
var normalized = normalizeLineEnding(content, lineEnding);
|
|
157
172
|
var finalContent = ensureUtf8Bom(normalized);
|
|
@@ -159,6 +174,7 @@ function createAgentsFileController(deps = {}) {
|
|
|
159
174
|
ensureDir(path.dirname(filePath));
|
|
160
175
|
fs.writeFileSync(filePath, finalContent, 'utf-8');
|
|
161
176
|
var result = { success: true, path: filePath };
|
|
177
|
+
if (resolved.historyBucket) result.historyBucket = resolved.historyBucket;
|
|
162
178
|
if (resolved.isProject) {
|
|
163
179
|
result.projectPath = resolved.projectPath;
|
|
164
180
|
result.detectionSource = resolved.detectionSource;
|
|
@@ -218,13 +234,21 @@ function createAgentsFileController(deps = {}) {
|
|
|
218
234
|
if (content.length > 2 * 1024 * 1024) {
|
|
219
235
|
return { error: '内容过大(最大 2MB)' };
|
|
220
236
|
}
|
|
237
|
+
let agentsHistoryBucket = '';
|
|
238
|
+
if (typeof backupPromptBeforeWrite === 'function') {
|
|
239
|
+
const bucket = 'codex_' + sanitizeHistoryId(String(params.baseDir || '').trim() || 'global');
|
|
240
|
+
backupPromptBeforeWrite(bucket, filePath);
|
|
241
|
+
agentsHistoryBucket = bucket;
|
|
242
|
+
}
|
|
221
243
|
const lineEnding = params.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
222
|
-
|
|
244
|
+
var normalized = normalizeLineEnding(content, lineEnding);
|
|
223
245
|
const finalContent = ensureUtf8Bom(normalized);
|
|
224
246
|
|
|
225
247
|
try {
|
|
226
248
|
fs.writeFileSync(filePath, finalContent, 'utf-8');
|
|
227
|
-
|
|
249
|
+
const agentsResult = { success: true, path: filePath };
|
|
250
|
+
if (agentsHistoryBucket) agentsResult.historyBucket = agentsHistoryBucket;
|
|
251
|
+
return agentsResult;
|
|
228
252
|
} catch (e) {
|
|
229
253
|
return { error: `写入 AGENTS.md 失败: ${e.message}` };
|
|
230
254
|
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function createPromptHistoryController(deps = {}) {
|
|
4
|
+
const {
|
|
5
|
+
fs,
|
|
6
|
+
path,
|
|
7
|
+
CONFIG_DIR,
|
|
8
|
+
MAX_HISTORY = 20
|
|
9
|
+
} = deps;
|
|
10
|
+
|
|
11
|
+
if (!fs) throw new Error('createPromptHistoryController 缺少 fs');
|
|
12
|
+
if (!path) throw new Error('createPromptHistoryController 缺少 path');
|
|
13
|
+
if (typeof CONFIG_DIR !== 'string' || !CONFIG_DIR) throw new Error('createPromptHistoryController 缺少 CONFIG_DIR');
|
|
14
|
+
|
|
15
|
+
const HISTORY_ROOT = path.join(CONFIG_DIR, 'codexmate-prompt-history');
|
|
16
|
+
const STAMP_RE = /^(\d{8})-(\d{6})-(\d{3})\.bak$/;
|
|
17
|
+
|
|
18
|
+
function sanitizeBucket(bucket) {
|
|
19
|
+
const raw = typeof bucket === 'string' ? bucket.trim() : '';
|
|
20
|
+
if (!raw) throw new Error('prompt history bucket 不能为空');
|
|
21
|
+
const safe = raw.replace(/[^A-Za-z0-9_.-]/g, '_');
|
|
22
|
+
if (!safe) throw new Error('prompt history bucket 无效: ' + bucket);
|
|
23
|
+
return safe;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveBucketDir(bucket) {
|
|
27
|
+
const safe = sanitizeBucket(bucket);
|
|
28
|
+
return path.join(HISTORY_ROOT, safe);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildTimestamp(d = new Date()) {
|
|
32
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
33
|
+
const pad3 = (n) => String(n).padStart(3, '0');
|
|
34
|
+
return `${d.getFullYear()}${pad2(d.getMonth() + 1)}${pad2(d.getDate())}`
|
|
35
|
+
+ `-${pad2(d.getHours())}${pad2(d.getMinutes())}${pad2(d.getSeconds())}`
|
|
36
|
+
+ `-${pad3(d.getMilliseconds())}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseStamp(name) {
|
|
40
|
+
return STAMP_RE.test(name) ? name.slice(0, name.length - 4) : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ensureDirSync(dir) {
|
|
44
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function rmtreeSync(dir) {
|
|
48
|
+
try {
|
|
49
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
50
|
+
} catch (_) {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function backupPromptBeforeWrite(bucket, currentFilePath) {
|
|
54
|
+
const dir = resolveBucketDir(bucket);
|
|
55
|
+
const exists = fs.existsSync(currentFilePath);
|
|
56
|
+
if (!exists) return { backedUp: false, entries: listPromptHistory(bucket) };
|
|
57
|
+
let raw;
|
|
58
|
+
try {
|
|
59
|
+
raw = fs.readFileSync(currentFilePath, 'utf-8');
|
|
60
|
+
} catch (e) {
|
|
61
|
+
return { backedUp: false, error: '读取当前文件失败: ' + e.message };
|
|
62
|
+
}
|
|
63
|
+
ensureDirSync(dir);
|
|
64
|
+
const stamp = buildTimestamp();
|
|
65
|
+
const dest = path.join(dir, stamp + '.bak');
|
|
66
|
+
try {
|
|
67
|
+
fs.writeFileSync(dest, raw, 'utf-8');
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return { backedUp: false, error: '写入备份失败: ' + e.message };
|
|
70
|
+
}
|
|
71
|
+
trimHistory(dir);
|
|
72
|
+
return { backedUp: true, entries: listPromptHistory(bucket) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function listPromptHistory(bucket) {
|
|
76
|
+
const dir = resolveBucketDir(bucket);
|
|
77
|
+
if (!fs.existsSync(dir)) return [];
|
|
78
|
+
let names;
|
|
79
|
+
try {
|
|
80
|
+
names = fs.readdirSync(dir);
|
|
81
|
+
} catch (_) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const items = [];
|
|
85
|
+
for (const name of names) {
|
|
86
|
+
const stamp = parseStamp(name);
|
|
87
|
+
if (!stamp) continue;
|
|
88
|
+
let stat;
|
|
89
|
+
try {
|
|
90
|
+
stat = fs.statSync(path.join(dir, name));
|
|
91
|
+
} catch (_) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
items.push({
|
|
95
|
+
id: stamp,
|
|
96
|
+
bucket: sanitizeBucket(bucket),
|
|
97
|
+
size: Number(stat.size) || 0,
|
|
98
|
+
mtimeMs: Number(stat.mtimeMs) || 0
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
items.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
102
|
+
return items;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readPromptHistory(bucket, id) {
|
|
106
|
+
const dir = resolveBucketDir(bucket);
|
|
107
|
+
const stamp = typeof id === 'string' ? id.trim() : '';
|
|
108
|
+
const fullName = stamp + '.bak';
|
|
109
|
+
if (!stamp || !STAMP_RE.test(fullName)) {
|
|
110
|
+
return { error: 'history id invalid' };
|
|
111
|
+
}
|
|
112
|
+
const file = path.join(dir, fullName);
|
|
113
|
+
if (!fs.existsSync(file)) return { error: 'history entry 不存在' };
|
|
114
|
+
try {
|
|
115
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
116
|
+
return { id: stamp, content, bucket: sanitizeBucket(bucket) };
|
|
117
|
+
} catch (e) {
|
|
118
|
+
return { error: '读取 history 失败: ' + e.message };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function trimHistory(dir) {
|
|
123
|
+
let names;
|
|
124
|
+
try {
|
|
125
|
+
names = fs.readdirSync(dir);
|
|
126
|
+
} catch (_) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const stamped = [];
|
|
130
|
+
for (const name of names) {
|
|
131
|
+
const stamp = parseStamp(name);
|
|
132
|
+
if (!stamp) continue;
|
|
133
|
+
let stat;
|
|
134
|
+
try {
|
|
135
|
+
stat = fs.statSync(path.join(dir, name));
|
|
136
|
+
} catch (_) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
stamped.push({ name, mtimeMs: Number(stat.mtimeMs) || 0 });
|
|
140
|
+
}
|
|
141
|
+
stamped.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
142
|
+
if (stamped.length <= MAX_HISTORY) return;
|
|
143
|
+
for (let i = MAX_HISTORY; i < stamped.length; i++) {
|
|
144
|
+
try {
|
|
145
|
+
fs.unlinkSync(path.join(dir, stamped[i].name));
|
|
146
|
+
} catch (_) {}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function clearPromptHistory(bucket) {
|
|
151
|
+
if (!bucket) {
|
|
152
|
+
rmtreeSync(HISTORY_ROOT);
|
|
153
|
+
return { cleared: true };
|
|
154
|
+
}
|
|
155
|
+
const dir = resolveBucketDir(bucket);
|
|
156
|
+
rmtreeSync(dir);
|
|
157
|
+
return { cleared: true };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
sanitizeBucket,
|
|
162
|
+
backupPromptBeforeWrite,
|
|
163
|
+
listPromptHistory,
|
|
164
|
+
readPromptHistory,
|
|
165
|
+
clearPromptHistory
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
createPromptHistoryController
|
|
171
|
+
};
|
|
@@ -8,7 +8,8 @@ function createSystemPromptFileController(deps = {}) {
|
|
|
8
8
|
crypto,
|
|
9
9
|
buildLineDiff,
|
|
10
10
|
CONFIG_DIR,
|
|
11
|
-
PI_AGENT_DIR
|
|
11
|
+
PI_AGENT_DIR,
|
|
12
|
+
backupPromptBeforeWrite
|
|
12
13
|
} = deps;
|
|
13
14
|
|
|
14
15
|
if (!fs) throw new Error('createSystemPromptFileController 缺少 fs');
|
|
@@ -18,6 +19,7 @@ function createSystemPromptFileController(deps = {}) {
|
|
|
18
19
|
if (typeof buildLineDiff !== 'function') throw new Error('createSystemPromptFileController 缺少 buildLineDiff');
|
|
19
20
|
if (typeof CONFIG_DIR !== 'string' || !CONFIG_DIR) throw new Error('createSystemPromptFileController 缺少 CONFIG_DIR');
|
|
20
21
|
if (typeof PI_AGENT_DIR !== 'string' || !PI_AGENT_DIR) throw new Error('createSystemPromptFileController 缺少 PI_AGENT_DIR');
|
|
22
|
+
if (typeof backupPromptBeforeWrite !== 'function' && typeof backupPromptBeforeWrite !== 'undefined') throw new Error('createSystemPromptFileController 备份回调无效');
|
|
21
23
|
|
|
22
24
|
const MODES = {
|
|
23
25
|
system: 'SYSTEM.md',
|
|
@@ -34,6 +36,12 @@ function createSystemPromptFileController(deps = {}) {
|
|
|
34
36
|
return key;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
function sanitizeSystemHistoryId(raw) {
|
|
40
|
+
const safe = typeof raw === 'string' ? raw.trim() : '';
|
|
41
|
+
if (!safe) return 'global';
|
|
42
|
+
return safe.replace(/[^A-Za-z0-9_.-]/g, '_').slice(0, 64) || 'global';
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
function normalizeScope(scope) {
|
|
38
46
|
const key = typeof scope === 'string' ? scope.trim() : '';
|
|
39
47
|
if (!SCOPES.includes(key)) {
|
|
@@ -104,16 +112,26 @@ function createSystemPromptFileController(deps = {}) {
|
|
|
104
112
|
if (baseHash && baseHash !== current.hash) {
|
|
105
113
|
return { error: '文件已被外部修改,请重新加载后再保存' };
|
|
106
114
|
}
|
|
115
|
+
let sysHistoryBucket = '';
|
|
107
116
|
try {
|
|
108
117
|
const dir = path.dirname(target.path);
|
|
109
118
|
fs.mkdirSync(dir, { recursive: true });
|
|
119
|
+
if (typeof backupPromptBeforeWrite === 'function') {
|
|
120
|
+
const bucket = 'system_' + target.scope + '_' + (target.scope === 'project'
|
|
121
|
+
? sanitizeSystemHistoryId(path.dirname(target.path))
|
|
122
|
+
: 'global');
|
|
123
|
+
backupPromptBeforeWrite(bucket, target.path);
|
|
124
|
+
sysHistoryBucket = bucket;
|
|
125
|
+
}
|
|
110
126
|
const finalContent = content.endsWith('\n') ? content : content + '\n';
|
|
111
127
|
fs.writeFileSync(target.path, finalContent, { encoding: 'utf8', mode: 0o600 });
|
|
112
128
|
try { fs.chmodSync(target.path, 0o600); } catch (_) {}
|
|
113
129
|
} catch (e) {
|
|
114
130
|
return { error: '写入 system prompt 失败: ' + e.message };
|
|
115
131
|
}
|
|
116
|
-
|
|
132
|
+
const saved = readSystemPromptFilePath(target);
|
|
133
|
+
if (sysHistoryBucket) saved.historyBucket = sysHistoryBucket;
|
|
134
|
+
return saved;
|
|
117
135
|
}
|
|
118
136
|
|
|
119
137
|
function readSystemPromptFilePath(resolved) {
|
package/cli.js
CHANGED
|
@@ -108,6 +108,9 @@ const {
|
|
|
108
108
|
const {
|
|
109
109
|
createSystemPromptFileController
|
|
110
110
|
} = require('./cli/system-prompt-files');
|
|
111
|
+
const {
|
|
112
|
+
createPromptHistoryController
|
|
113
|
+
} = require('./cli/prompt-history');
|
|
111
114
|
const {
|
|
112
115
|
createArchiveHelperController
|
|
113
116
|
} = require('./cli/archive-helpers');
|
|
@@ -2057,6 +2060,17 @@ async function fetchProviderModels(providerName, overrides = {}) {
|
|
|
2057
2060
|
}
|
|
2058
2061
|
|
|
2059
2062
|
// buildAgentsDiff keeps the metaOnly optimization inside cli/agents-files.js.
|
|
2063
|
+
const {
|
|
2064
|
+
backupPromptBeforeWrite: historyBackup,
|
|
2065
|
+
listPromptHistory,
|
|
2066
|
+
readPromptHistory,
|
|
2067
|
+
clearPromptHistory
|
|
2068
|
+
} = createPromptHistoryController({
|
|
2069
|
+
fs,
|
|
2070
|
+
path,
|
|
2071
|
+
CONFIG_DIR
|
|
2072
|
+
});
|
|
2073
|
+
|
|
2060
2074
|
const {
|
|
2061
2075
|
resolveAgentsFilePath,
|
|
2062
2076
|
validateAgentsBaseDir,
|
|
@@ -2083,6 +2097,7 @@ const {
|
|
|
2083
2097
|
AGENTS_FILE_NAME,
|
|
2084
2098
|
CLAUDE_DIR,
|
|
2085
2099
|
CLAUDE_MD_FILE_NAME,
|
|
2100
|
+
backupPromptBeforeWrite: historyBackup,
|
|
2086
2101
|
readOpenclawAgentsFile() {
|
|
2087
2102
|
return readOpenclawAgentsFile(...arguments);
|
|
2088
2103
|
},
|
|
@@ -2102,7 +2117,8 @@ const {
|
|
|
2102
2117
|
crypto,
|
|
2103
2118
|
buildLineDiff,
|
|
2104
2119
|
CONFIG_DIR,
|
|
2105
|
-
PI_AGENT_DIR
|
|
2120
|
+
PI_AGENT_DIR,
|
|
2121
|
+
backupPromptBeforeWrite: historyBackup
|
|
2106
2122
|
});
|
|
2107
2123
|
|
|
2108
2124
|
const {
|
|
@@ -13378,6 +13394,12 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13378
13394
|
case 'preview-system-prompt-diff':
|
|
13379
13395
|
result = buildSystemPromptDiff(params || {});
|
|
13380
13396
|
break;
|
|
13397
|
+
case 'list-prompt-history':
|
|
13398
|
+
result = listPromptHistory((params && params.bucket) || '');
|
|
13399
|
+
break;
|
|
13400
|
+
case 'get-prompt-history':
|
|
13401
|
+
result = readPromptHistory((params && params.bucket) || '', (params && params.id) || '');
|
|
13402
|
+
break;
|
|
13381
13403
|
case 'switch':
|
|
13382
13404
|
case 'use':
|
|
13383
13405
|
case 'add':
|
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -74,6 +74,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
74
74
|
showAgentsModal: false,
|
|
75
75
|
promptsSubTab: 'codex',
|
|
76
76
|
projectClaudeMdPath: '',
|
|
77
|
+
promptHistoryVisible: false,
|
|
78
|
+
promptHistoryLoading: false,
|
|
79
|
+
promptHistoryBucket: '',
|
|
80
|
+
promptHistoryItems: [],
|
|
81
|
+
promptHistoryPreviewId: '',
|
|
82
|
+
promptHistoryPreviewContent: '',
|
|
83
|
+
promptHistoryError: '',
|
|
77
84
|
promptPresets: [],
|
|
78
85
|
selectedPromptPresetId: '',
|
|
79
86
|
promptPresetNameDraft: '',
|
|
@@ -130,6 +137,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
130
137
|
configTemplateDiffFingerprint: '',
|
|
131
138
|
_configTemplateDiffPreviewRequestToken: null,
|
|
132
139
|
configTemplateDiffConfirmEnabled: true,
|
|
140
|
+
configModeVisibility: { codex: true, claude: true, openclaw: true, opencode: true, kilocode: true },
|
|
133
141
|
codexApplying: false,
|
|
134
142
|
_pendingCodexApplyOptions: null,
|
|
135
143
|
agentsContent: '',
|
|
@@ -161,6 +169,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
161
169
|
sysPromptPath: '',
|
|
162
170
|
sysPromptExists: false,
|
|
163
171
|
sysPromptHash: '',
|
|
172
|
+
sysHistoryBucket: '',
|
|
173
|
+
sysHistoryVisible: false,
|
|
174
|
+
sysHistoryLoading: false,
|
|
175
|
+
sysHistoryItems: [],
|
|
176
|
+
sysHistoryPreviewId: '',
|
|
177
|
+
sysHistoryPreviewContent: '',
|
|
178
|
+
sysHistoryError: '',
|
|
164
179
|
sysPromptLoading: false,
|
|
165
180
|
sysPromptSaving: false,
|
|
166
181
|
sysPromptDiffVisible: false,
|
|
@@ -20,6 +20,22 @@ function isValidOpenclawWorkspaceFileName(fileName) {
|
|
|
20
20
|
return true;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function sanitizePromptHistoryId(raw) {
|
|
24
|
+
const safe = typeof raw === 'string' ? raw.trim() : '';
|
|
25
|
+
if (!safe) return 'global';
|
|
26
|
+
const replaced = safe.replace(/[^A-Za-z0-9_.-]/g, '_');
|
|
27
|
+
return (replaced || 'global').slice(0, 64);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveAgentsHistoryBucket(instance) {
|
|
31
|
+
const subTab = typeof instance.promptsSubTab === 'string' ? instance.promptsSubTab : 'codex';
|
|
32
|
+
if (subTab === 'claude-project') {
|
|
33
|
+
const projectPath = (instance.projectClaudeMdPath || '').trim();
|
|
34
|
+
return 'claude-project_' + sanitizePromptHistoryId(projectPath || 'global');
|
|
35
|
+
}
|
|
36
|
+
return 'codex_global';
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
export function createAgentsMethods(options = {}) {
|
|
24
40
|
const {
|
|
25
41
|
api,
|
|
@@ -681,6 +697,9 @@ export function createAgentsMethods(options = {}) {
|
|
|
681
697
|
this.showMessage(res.error, 'error');
|
|
682
698
|
return;
|
|
683
699
|
}
|
|
700
|
+
if (typeof res.historyBucket === 'string' && res.historyBucket) {
|
|
701
|
+
this.promptHistoryBucket = res.historyBucket;
|
|
702
|
+
}
|
|
684
703
|
const successLabel = this.agentsContext === 'openclaw-workspace'
|
|
685
704
|
? this.t('toast.agents.saved.workspace', { name: this.agentsWorkspaceFileName || '' }).replace(/:\s*$/, '')
|
|
686
705
|
: (this.agentsContext === 'claude-project'
|
|
@@ -884,6 +903,77 @@ export function createAgentsMethods(options = {}) {
|
|
|
884
903
|
await this.persistPromptPresets();
|
|
885
904
|
this.showMessage(this.t('prompts.presets.toast.deleted'), 'success');
|
|
886
905
|
},
|
|
906
|
+
async openPromptHistory() {
|
|
907
|
+
if (this.promptHistoryLoading) return;
|
|
908
|
+
const bucket = this.promptHistoryBucket || resolveAgentsHistoryBucket(this);
|
|
909
|
+
this.promptHistoryVisible = true;
|
|
910
|
+
this.promptHistoryError = '';
|
|
911
|
+
await this.loadPromptHistory(bucket);
|
|
912
|
+
},
|
|
913
|
+
|
|
914
|
+
async loadPromptHistory(bucketArg) {
|
|
915
|
+
if (this.promptHistoryLoading) return;
|
|
916
|
+
const bucket = typeof bucketArg === 'string' && bucketArg.trim()
|
|
917
|
+
? bucketArg.trim()
|
|
918
|
+
: (this.promptHistoryBucket || resolveAgentsHistoryBucket(this));
|
|
919
|
+
this.promptHistoryBucket = bucket;
|
|
920
|
+
this.promptHistoryLoading = true;
|
|
921
|
+
this.promptHistoryError = '';
|
|
922
|
+
this.promptHistoryItems = [];
|
|
923
|
+
this.promptHistoryPreviewId = '';
|
|
924
|
+
this.promptHistoryPreviewContent = '';
|
|
925
|
+
try {
|
|
926
|
+
const res = await api('list-prompt-history', { bucket });
|
|
927
|
+
if (res && res.error) {
|
|
928
|
+
this.promptHistoryError = res.error;
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
this.promptHistoryItems = Array.isArray(res) ? res : [];
|
|
932
|
+
} catch (e) {
|
|
933
|
+
this.promptHistoryError = this.t('toast.load.fail');
|
|
934
|
+
} finally {
|
|
935
|
+
this.promptHistoryLoading = false;
|
|
936
|
+
}
|
|
937
|
+
},
|
|
938
|
+
|
|
939
|
+
closePromptHistory() {
|
|
940
|
+
this.promptHistoryVisible = false;
|
|
941
|
+
this.promptHistoryPreviewId = '';
|
|
942
|
+
this.promptHistoryPreviewContent = '';
|
|
943
|
+
this.promptHistoryError = '';
|
|
944
|
+
},
|
|
945
|
+
|
|
946
|
+
async viewPromptHistoryItem(item) {
|
|
947
|
+
if (!item || !item.id) return;
|
|
948
|
+
if (this.promptHistoryLoading) return;
|
|
949
|
+
if (this.promptHistoryPreviewId === item.id && this.promptHistoryPreviewContent) return;
|
|
950
|
+
this.promptHistoryPreviewId = item.id;
|
|
951
|
+
this.promptHistoryPreviewContent = '';
|
|
952
|
+
try {
|
|
953
|
+
const res = await api('get-prompt-history', { bucket: this.promptHistoryBucket, id: item.id });
|
|
954
|
+
if (res && res.error) {
|
|
955
|
+
this.promptHistoryError = res.error;
|
|
956
|
+
this.promptHistoryPreviewId = '';
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
this.promptHistoryPreviewContent = typeof res.content === 'string' ? res.content : '';
|
|
960
|
+
} catch (e) {
|
|
961
|
+
this.promptHistoryError = this.t('toast.load.fail');
|
|
962
|
+
this.promptHistoryPreviewId = '';
|
|
963
|
+
}
|
|
964
|
+
},
|
|
965
|
+
|
|
966
|
+
applyPromptHistoryToEditor() {
|
|
967
|
+
if (!this.promptHistoryPreviewContent) return;
|
|
968
|
+
if (this.agentsSaving || this.agentsDiffVisible) return;
|
|
969
|
+
this.agentsContent = this.promptHistoryPreviewContent;
|
|
970
|
+
if (typeof this.onAgentsContentInput === 'function') this.onAgentsContentInput();
|
|
971
|
+
this.promptHistoryVisible = false;
|
|
972
|
+
this.promptHistoryPreviewId = '';
|
|
973
|
+
this.promptHistoryPreviewContent = '';
|
|
974
|
+
this.showMessage(this.t('toast.history.restored'), 'success');
|
|
975
|
+
},
|
|
976
|
+
|
|
887
977
|
switchPromptsSubTab(subTab) {
|
|
888
978
|
const normalized = subTab === 'claude-project' || subTab === 'system' ? subTab : 'codex';
|
|
889
979
|
if (normalized === 'claude-project' && !this.projectPathOptions.length && !this.projectPathOptionsLoading) {
|
|
@@ -145,7 +145,13 @@
|
|
|
145
145
|
if (typeof this.ensureMainTabSwitchState === 'function') {
|
|
146
146
|
this.ensureMainTabSwitchState().pendingConfigMode = '';
|
|
147
147
|
}
|
|
148
|
-
|
|
148
|
+
const resolvedMode = configModeSet.has(normalizedMode) ? normalizedMode : 'codex';
|
|
149
|
+
if (typeof this.isConfigModeVisible === 'function' && !this.isConfigModeVisible(resolvedMode)) {
|
|
150
|
+
const fallback = ['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].find(m => this.isConfigModeVisible(m)) || 'codex';
|
|
151
|
+
this.configMode = configModeSet.has(fallback) ? fallback : 'codex';
|
|
152
|
+
} else {
|
|
153
|
+
this.configMode = resolvedMode;
|
|
154
|
+
}
|
|
149
155
|
if (this.mainTab === 'config') {
|
|
150
156
|
if (this.configMode === 'kilocode' && typeof this.loadKilocodeConfig === 'function') {
|
|
151
157
|
this.loadKilocodeConfig();
|
|
@@ -357,6 +357,42 @@ export function createSessionActionMethods(options = {}) {
|
|
|
357
357
|
}
|
|
358
358
|
},
|
|
359
359
|
|
|
360
|
+
normalizeConfigModeVisibility(value) {
|
|
361
|
+
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
362
|
+
const result = {};
|
|
363
|
+
for (const mode of ['codex', 'claude', 'openclaw', 'opencode', 'kilocode']) {
|
|
364
|
+
result[mode] = typeof source[mode] === 'boolean' ? source[mode] : true;
|
|
365
|
+
}
|
|
366
|
+
return result;
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
isConfigModeVisible(mode) {
|
|
370
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
371
|
+
if (!this.configModeVisibility) return true;
|
|
372
|
+
return this.configModeVisibility[normalizedMode] !== false;
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
setConfigModeVisibility(mode, visible) {
|
|
376
|
+
const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
|
|
377
|
+
if (!normalizedMode || !['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].includes(normalizedMode)) return;
|
|
378
|
+
const visibility = this.configModeVisibility || { codex: true, claude: true, openclaw: true, opencode: true, kilocode: true };
|
|
379
|
+
const next = { ...visibility, [normalizedMode]: !!visible };
|
|
380
|
+
const visibleCount = Object.values(next).filter(Boolean).length;
|
|
381
|
+
if (visibleCount === 0) {
|
|
382
|
+
this.showMessage(typeof this.t === 'function' ? this.t('settings.configTabs.minRequired') : 'At least one tab must remain visible', 'info');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
this.configModeVisibility = next;
|
|
386
|
+
if (typeof this.persistWebUiPreferences === 'function') {
|
|
387
|
+
this.persistWebUiPreferences({ configModeVisibility: next });
|
|
388
|
+
}
|
|
389
|
+
if (this.mainTab === 'config' && this.configMode === normalizedMode && !next[normalizedMode]) {
|
|
390
|
+
const fallback = ['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].find(m => next[m] !== false);
|
|
391
|
+
if (fallback) this.switchConfigMode(fallback);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
|
|
360
396
|
getShareCommandPrefixInvocation() {
|
|
361
397
|
const prefix = this.normalizeShareCommandPrefix(this.shareCommandPrefix);
|
|
362
398
|
return prefix === 'codexmate' ? 'codexmate' : 'npm start --';
|
|
@@ -393,6 +429,27 @@ export function createSessionActionMethods(options = {}) {
|
|
|
393
429
|
}
|
|
394
430
|
},
|
|
395
431
|
|
|
432
|
+
async copySessionMessage(msg) {
|
|
433
|
+
const text = msg && typeof msg.text === 'string' ? msg.text : '';
|
|
434
|
+
if (!text) {
|
|
435
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.empty') : 'Nothing to copy', 'info');
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const ok = this.fallbackCopyText(text);
|
|
439
|
+
if (ok) {
|
|
440
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
445
|
+
await navigator.clipboard.writeText(text);
|
|
446
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.ok') : 'Copied', 'success');
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
} catch (_) {}
|
|
450
|
+
this.showMessage(typeof this.t === 'function' ? this.t('toast.copy.fail') : 'Copy failed', 'error');
|
|
451
|
+
},
|
|
452
|
+
|
|
396
453
|
copyAgentsContent() {
|
|
397
454
|
const text = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
398
455
|
if (!text) {
|