metame-cli 1.4.34 → 1.5.1
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 +136 -94
- package/index.js +312 -57
- package/package.json +8 -4
- package/scripts/agent-layer.js +320 -0
- package/scripts/daemon-admin-commands.js +328 -28
- package/scripts/daemon-agent-commands.js +145 -6
- package/scripts/daemon-agent-tools.js +163 -7
- package/scripts/daemon-bridges.js +110 -20
- package/scripts/daemon-checkpoints.js +36 -7
- package/scripts/daemon-claude-engine.js +849 -358
- package/scripts/daemon-command-router.js +31 -10
- package/scripts/daemon-default.yaml +28 -4
- package/scripts/daemon-engine-runtime.js +328 -0
- package/scripts/daemon-exec-commands.js +15 -7
- package/scripts/daemon-notify.js +37 -1
- package/scripts/daemon-ops-commands.js +8 -6
- package/scripts/daemon-runtime-lifecycle.js +129 -5
- package/scripts/daemon-session-commands.js +60 -25
- package/scripts/daemon-session-store.js +121 -13
- package/scripts/daemon-task-scheduler.js +129 -49
- package/scripts/daemon-user-acl.js +35 -9
- package/scripts/daemon.js +268 -33
- package/scripts/distill.js +327 -18
- package/scripts/docs/agent-guide.md +12 -0
- package/scripts/docs/maintenance-manual.md +155 -0
- package/scripts/docs/pointer-map.md +110 -0
- package/scripts/feishu-adapter.js +42 -13
- package/scripts/hooks/stop-session-capture.js +243 -0
- package/scripts/memory-extract.js +105 -6
- package/scripts/memory-nightly-reflect.js +199 -11
- package/scripts/memory.js +134 -3
- package/scripts/mentor-engine.js +405 -0
- package/scripts/platform.js +24 -0
- package/scripts/providers.js +182 -22
- package/scripts/schema.js +12 -0
- package/scripts/session-analytics.js +245 -12
- package/scripts/skill-changelog.js +245 -0
- package/scripts/skill-evolution.js +288 -5
- package/scripts/telegram-adapter.js +12 -8
- package/scripts/usage-classifier.js +1 -1
- package/scripts/daemon-admin-commands.test.js +0 -333
- package/scripts/daemon-task-envelope.test.js +0 -59
- package/scripts/daemon-task-scheduler.test.js +0 -106
- package/scripts/reliability-core.test.js +0 -280
- package/scripts/skill-evolution.test.js +0 -113
- package/scripts/task-board.test.js +0 -83
- package/scripts/test_daemon.js +0 -1407
- package/scripts/utils.test.js +0 -192
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_COOLDOWN_MS = 30 * 60 * 1000;
|
|
8
|
+
const FATIGUE_COOLDOWN_MS = 60 * 60 * 1000;
|
|
9
|
+
|
|
10
|
+
function runtimeFilePath() {
|
|
11
|
+
const override = String(process.env.METAME_MENTOR_RUNTIME || '').trim();
|
|
12
|
+
if (override) return override;
|
|
13
|
+
return path.join(os.homedir(), '.metame', 'mentor_runtime.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function defaultRuntime() {
|
|
17
|
+
return {
|
|
18
|
+
emotion_breaker_until: null,
|
|
19
|
+
debts: [],
|
|
20
|
+
last_fatigue_alert: null,
|
|
21
|
+
last_pattern_check: null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function safeNow(nowMs) {
|
|
26
|
+
return Number.isFinite(nowMs) ? nowMs : Date.now();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ensureParentDir(file) {
|
|
30
|
+
const dir = path.dirname(file);
|
|
31
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function loadRuntime() {
|
|
35
|
+
const file = runtimeFilePath();
|
|
36
|
+
try {
|
|
37
|
+
if (!fs.existsSync(file)) return defaultRuntime();
|
|
38
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
39
|
+
return {
|
|
40
|
+
...defaultRuntime(),
|
|
41
|
+
...(data && typeof data === 'object' ? data : {}),
|
|
42
|
+
debts: Array.isArray(data && data.debts) ? data.debts : [],
|
|
43
|
+
};
|
|
44
|
+
} catch {
|
|
45
|
+
return defaultRuntime();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function saveRuntime(runtime) {
|
|
50
|
+
const file = runtimeFilePath();
|
|
51
|
+
ensureParentDir(file);
|
|
52
|
+
const tmp = `${file}.tmp.${process.pid}.${Date.now()}`;
|
|
53
|
+
fs.writeFileSync(tmp, JSON.stringify(runtime, null, 2), 'utf8');
|
|
54
|
+
fs.renameSync(tmp, file);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeText(input) {
|
|
58
|
+
return String(input || '').trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function tokenize(text) {
|
|
62
|
+
const input = normalizeText(text).toLowerCase();
|
|
63
|
+
if (!input) return [];
|
|
64
|
+
const out = [];
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const push = (t) => {
|
|
67
|
+
const v = String(t || '').trim();
|
|
68
|
+
if (!v || seen.has(v)) return;
|
|
69
|
+
seen.add(v);
|
|
70
|
+
out.push(v);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const ascii = input.match(/[a-z0-9_./-]{2,}/g) || [];
|
|
74
|
+
for (const t of ascii) push(t);
|
|
75
|
+
|
|
76
|
+
const hanRuns = input.match(/[\u4e00-\u9fff]{2,}/g) || [];
|
|
77
|
+
for (const run of hanRuns) {
|
|
78
|
+
if (run.length === 2) push(run);
|
|
79
|
+
else {
|
|
80
|
+
for (let i = 0; i < run.length - 1; i++) push(run.slice(i, i + 2));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function overlapRatio(a, b) {
|
|
87
|
+
const sa = new Set(Array.isArray(a) ? a : []);
|
|
88
|
+
const sb = new Set(Array.isArray(b) ? b : []);
|
|
89
|
+
if (!sa.size || !sb.size) return 0;
|
|
90
|
+
let common = 0;
|
|
91
|
+
for (const x of sa) if (sb.has(x)) common++;
|
|
92
|
+
const base = Math.min(sa.size, sb.size);
|
|
93
|
+
return base > 0 ? common / base : 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function endOfTodayMs(nowMs) {
|
|
97
|
+
const d = new Date(safeNow(nowMs));
|
|
98
|
+
d.setHours(23, 59, 59, 999);
|
|
99
|
+
return d.getTime();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function resolveMode(config = {}) {
|
|
103
|
+
const mode = String(config.mode || '').toLowerCase().trim();
|
|
104
|
+
if (mode === 'gentle' || mode === 'active' || mode === 'intense') return mode;
|
|
105
|
+
const level = Number(config.friction_level);
|
|
106
|
+
if (Number.isFinite(level)) {
|
|
107
|
+
if (level >= 8) return 'intense';
|
|
108
|
+
if (level >= 4) return 'active';
|
|
109
|
+
}
|
|
110
|
+
return 'gentle';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function checkEmotionBreaker(userMessage, config = {}, nowMs = Date.now()) {
|
|
114
|
+
const text = normalizeText(userMessage);
|
|
115
|
+
const runtime = loadRuntime();
|
|
116
|
+
const now = safeNow(nowMs);
|
|
117
|
+
const until = Number(runtime.emotion_breaker_until || 0);
|
|
118
|
+
|
|
119
|
+
if (until > now) {
|
|
120
|
+
return {
|
|
121
|
+
tripped: true,
|
|
122
|
+
reason: 'cooldown_active',
|
|
123
|
+
response: '已暂停导师模式,先专注把问题解决。',
|
|
124
|
+
remaining_ms: until - now,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const baseRe = /[操草靠妈tmd]|fuck|shit|wtf|!!{2,}|??{2,}|急|崩|炸|烦死/i;
|
|
129
|
+
const extras = Array.isArray(config.emotion_keywords_extra) ? config.emotion_keywords_extra : [];
|
|
130
|
+
const hitExtra = extras.find(k => k && text.toLowerCase().includes(String(k).toLowerCase()));
|
|
131
|
+
const hit = baseRe.test(text) || !!hitExtra;
|
|
132
|
+
if (!hit) return { tripped: false };
|
|
133
|
+
|
|
134
|
+
runtime.emotion_breaker_until = now + DEFAULT_COOLDOWN_MS;
|
|
135
|
+
saveRuntime(runtime);
|
|
136
|
+
return {
|
|
137
|
+
tripped: true,
|
|
138
|
+
reason: hitExtra ? `keyword:${hitExtra}` : 'emotion_keyword',
|
|
139
|
+
response: '已暂停导师模式,先专注把问题解决。',
|
|
140
|
+
remaining_ms: DEFAULT_COOLDOWN_MS,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function computeZone(skeleton = {}) {
|
|
145
|
+
const toolErrors = Number(skeleton.tool_error_count || 0);
|
|
146
|
+
const retries = Number(skeleton.retry_sequences || 0);
|
|
147
|
+
const repetition = Number(skeleton.semantic_repetition || 0);
|
|
148
|
+
const durationMin = Number(skeleton.duration_min || 0);
|
|
149
|
+
const toolCalls = Number(skeleton.total_tool_calls || 0);
|
|
150
|
+
const avgPause = Number(skeleton.avg_pause_sec || 0);
|
|
151
|
+
const recovered = !!skeleton.error_recovered;
|
|
152
|
+
|
|
153
|
+
let panicScore = 0;
|
|
154
|
+
if (toolErrors >= 3) panicScore++;
|
|
155
|
+
if (retries >= 6) panicScore++;
|
|
156
|
+
if (repetition >= 0.6) panicScore++;
|
|
157
|
+
if (durationMin >= 75 && toolErrors >= 1) panicScore++;
|
|
158
|
+
if (avgPause >= 180) panicScore++;
|
|
159
|
+
|
|
160
|
+
let comfortScore = 0;
|
|
161
|
+
if (toolErrors === 0) comfortScore++;
|
|
162
|
+
if (retries <= 2) comfortScore++;
|
|
163
|
+
if (repetition < 0.35) comfortScore++;
|
|
164
|
+
if (toolCalls >= 3 && durationMin <= 45) comfortScore++;
|
|
165
|
+
if (recovered && toolErrors <= 1) comfortScore++;
|
|
166
|
+
|
|
167
|
+
let zone = 'stretch';
|
|
168
|
+
let dominant = Math.max(panicScore, comfortScore);
|
|
169
|
+
if (panicScore >= 2) zone = 'panic';
|
|
170
|
+
else if (comfortScore >= 3 && panicScore === 0) zone = 'comfort';
|
|
171
|
+
|
|
172
|
+
const confidence = Math.min(0.95, 0.6 + dominant * 0.08);
|
|
173
|
+
return {
|
|
174
|
+
zone,
|
|
175
|
+
confidence: Number(confidence.toFixed(2)),
|
|
176
|
+
signals: {
|
|
177
|
+
tool_error_count: toolErrors,
|
|
178
|
+
retry_sequences: retries,
|
|
179
|
+
semantic_repetition: repetition,
|
|
180
|
+
duration_min: durationMin,
|
|
181
|
+
tool_calls: toolCalls,
|
|
182
|
+
avg_pause_sec: avgPause,
|
|
183
|
+
error_recovered: recovered,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function registerDebt(projectId, topic, codeLineCount, nowMs = Date.now()) {
|
|
189
|
+
const pid = normalizeText(projectId);
|
|
190
|
+
const lines = Number(codeLineCount || 0);
|
|
191
|
+
if (!pid || lines <= 30) return null;
|
|
192
|
+
|
|
193
|
+
const t = normalizeText(topic) || 'unknown-topic';
|
|
194
|
+
const now = safeNow(nowMs);
|
|
195
|
+
const runtime = loadRuntime();
|
|
196
|
+
const topicKeywords = tokenize(t).slice(0, 8);
|
|
197
|
+
|
|
198
|
+
const debt = {
|
|
199
|
+
project_id: pid,
|
|
200
|
+
topic: t,
|
|
201
|
+
topic_keywords: topicKeywords,
|
|
202
|
+
code_summary: `Generated ${lines} lines`,
|
|
203
|
+
recorded_at: now,
|
|
204
|
+
expires_at: endOfTodayMs(now),
|
|
205
|
+
};
|
|
206
|
+
runtime.debts.push(debt);
|
|
207
|
+
if (runtime.debts.length > 100) runtime.debts = runtime.debts.slice(-100);
|
|
208
|
+
saveRuntime(runtime);
|
|
209
|
+
return debt;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function collectDebt(projectId, currentTopic, nowMs = Date.now()) {
|
|
213
|
+
const pid = normalizeText(projectId);
|
|
214
|
+
if (!pid) return null;
|
|
215
|
+
const runtime = loadRuntime();
|
|
216
|
+
const now = safeNow(nowMs);
|
|
217
|
+
|
|
218
|
+
const valid = [];
|
|
219
|
+
let matched = null;
|
|
220
|
+
const currentKeywords = tokenize(currentTopic).slice(0, 12);
|
|
221
|
+
|
|
222
|
+
for (const debt of runtime.debts) {
|
|
223
|
+
if (!debt || typeof debt !== 'object') continue;
|
|
224
|
+
if (Number(debt.expires_at || 0) < now) continue;
|
|
225
|
+
|
|
226
|
+
if (!matched && debt.project_id === pid) {
|
|
227
|
+
const ratio = overlapRatio(currentKeywords, debt.topic_keywords || []);
|
|
228
|
+
if (ratio > 0.3) {
|
|
229
|
+
matched = {
|
|
230
|
+
...debt,
|
|
231
|
+
overlap_ratio: Number(ratio.toFixed(2)),
|
|
232
|
+
prompt: `刚才那段 ${debt.topic} 的代码,核心逻辑是什么?`,
|
|
233
|
+
};
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
valid.push(debt);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
runtime.debts = valid;
|
|
241
|
+
saveRuntime(runtime);
|
|
242
|
+
return matched;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function gcExpiredDebts(nowMs = Date.now()) {
|
|
246
|
+
const runtime = loadRuntime();
|
|
247
|
+
const now = safeNow(nowMs);
|
|
248
|
+
const before = runtime.debts.length;
|
|
249
|
+
runtime.debts = runtime.debts.filter(d => d && Number(d.expires_at || 0) >= now);
|
|
250
|
+
const removed = before - runtime.debts.length;
|
|
251
|
+
if (removed > 0) saveRuntime(runtime);
|
|
252
|
+
return { removed, remaining: runtime.debts.length };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function repetitionFromTexts(texts) {
|
|
256
|
+
if (!Array.isArray(texts) || texts.length < 3) return 0;
|
|
257
|
+
let maxOverlap = 0;
|
|
258
|
+
for (let i = 2; i < texts.length; i++) {
|
|
259
|
+
const a = new Set(tokenize(texts[i - 2]));
|
|
260
|
+
const b = new Set(tokenize(texts[i - 1]));
|
|
261
|
+
const c = new Set(tokenize(texts[i]));
|
|
262
|
+
const union = new Set([...a, ...b, ...c]);
|
|
263
|
+
if (!union.size) continue;
|
|
264
|
+
let common = 0;
|
|
265
|
+
for (const t of a) if (b.has(t) && c.has(t)) common++;
|
|
266
|
+
maxOverlap = Math.max(maxOverlap, common / union.size);
|
|
267
|
+
}
|
|
268
|
+
return Number(maxOverlap.toFixed(3));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function extractErrorClass(text) {
|
|
272
|
+
const src = normalizeText(text).toLowerCase();
|
|
273
|
+
if (!src) return '';
|
|
274
|
+
if (/(timeout|timed out|超时)/.test(src)) return 'timeout';
|
|
275
|
+
if (/(permission|eacces|denied|权限)/.test(src)) return 'permission';
|
|
276
|
+
if (/(not found|enoent|找不到)/.test(src)) return 'not_found';
|
|
277
|
+
if (/(typeerror|referenceerror|syntaxerror|报错|异常|error)/.test(src)) return 'runtime_error';
|
|
278
|
+
return '';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function detectPatterns(recentMessages, sessionStartTime, opts = {}) {
|
|
282
|
+
const now = safeNow(opts.nowMs);
|
|
283
|
+
const runtime = loadRuntime();
|
|
284
|
+
let dirty = false;
|
|
285
|
+
const prevPatternTs = Number(runtime.last_pattern_check || 0);
|
|
286
|
+
if (!prevPatternTs || (now - prevPatternTs) > 30000) {
|
|
287
|
+
runtime.last_pattern_check = now;
|
|
288
|
+
dirty = true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const normalized = (Array.isArray(recentMessages) ? recentMessages : [])
|
|
292
|
+
.map(m => (typeof m === 'string' ? { text: m } : (m && typeof m === 'object' ? m : null)))
|
|
293
|
+
.filter(Boolean);
|
|
294
|
+
|
|
295
|
+
const texts = normalized.map(m => normalizeText(m.text || m.message || ''));
|
|
296
|
+
const shortCount = texts.filter(t => t && t.length < 20).length;
|
|
297
|
+
const toolCalls = normalized.reduce((acc, m) => acc + (Number(m.tool_calls || m.toolCalls || 0) || 0), 0);
|
|
298
|
+
const errorClasses = normalized.map(m => extractErrorClass(m.text || m.message || '')).filter(Boolean);
|
|
299
|
+
const classCount = new Map();
|
|
300
|
+
for (const c of errorClasses) classCount.set(c, (classCount.get(c) || 0) + 1);
|
|
301
|
+
const repeatedError = [...classCount.values()].some(v => v >= 3);
|
|
302
|
+
const semanticRepetition = repetitionFromTexts(texts);
|
|
303
|
+
|
|
304
|
+
const autopilot = shortCount >= 3 && toolCalls >= 3 && errorClasses.length === 0;
|
|
305
|
+
const stuck = repeatedError && semanticRepetition > 0.6;
|
|
306
|
+
|
|
307
|
+
const sessionMs = Math.max(0, now - safeNow(new Date(sessionStartTime).getTime()));
|
|
308
|
+
const isFatiguedRaw = sessionMs > 90 * 60 * 1000;
|
|
309
|
+
const lastFatigue = Number(runtime.last_fatigue_alert || 0);
|
|
310
|
+
const fatigued = isFatiguedRaw && (!lastFatigue || (now - lastFatigue) > FATIGUE_COOLDOWN_MS);
|
|
311
|
+
if (fatigued) {
|
|
312
|
+
runtime.last_fatigue_alert = now;
|
|
313
|
+
dirty = true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let suggestion = '';
|
|
317
|
+
if (stuck) suggestion = '检测到你在反复遇到类似问题,建议先退一步梳理整体思路。';
|
|
318
|
+
else if (fatigued) suggestion = '你已连续工作较久,建议短暂休息后再继续。';
|
|
319
|
+
else if (autopilot) suggestion = '你在高效执行模式,建议确认一下当前方向是否仍然正确。';
|
|
320
|
+
|
|
321
|
+
if (dirty) saveRuntime(runtime);
|
|
322
|
+
return { autopilot, stuck, fatigued, suggestion, semantic_repetition: semanticRepetition };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function getRuntimeStatus(nowMs = Date.now()) {
|
|
326
|
+
const runtime = loadRuntime();
|
|
327
|
+
const now = safeNow(nowMs);
|
|
328
|
+
const until = Number(runtime.emotion_breaker_until || 0);
|
|
329
|
+
return {
|
|
330
|
+
debt_count: Array.isArray(runtime.debts) ? runtime.debts.length : 0,
|
|
331
|
+
cooldown_until: until || null,
|
|
332
|
+
cooldown_remaining_ms: until > now ? (until - now) : 0,
|
|
333
|
+
last_fatigue_alert: runtime.last_fatigue_alert || null,
|
|
334
|
+
last_pattern_check: runtime.last_pattern_check || null,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function shouldSkipByCompetence(profile, sessionState = {}) {
|
|
339
|
+
const map = profile && typeof profile.user_competence_map === 'object'
|
|
340
|
+
? profile.user_competence_map
|
|
341
|
+
: null;
|
|
342
|
+
if (!map) return false;
|
|
343
|
+
const text = `${sessionState.topic || ''} ${sessionState.currentTopic || ''} ${sessionState.lastUserMessage || ''}`.toLowerCase();
|
|
344
|
+
if (!text) return false;
|
|
345
|
+
for (const [domain, level] of Object.entries(map)) {
|
|
346
|
+
if (String(level || '').toLowerCase() !== 'expert') continue;
|
|
347
|
+
if (text.includes(String(domain || '').toLowerCase())) return true;
|
|
348
|
+
}
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function buildMentorPrompt(sessionState = {}, profile = {}, config = {}, nowMs = Date.now()) {
|
|
353
|
+
if (!config || config.enabled === false) return '';
|
|
354
|
+
const now = safeNow(nowMs);
|
|
355
|
+
|
|
356
|
+
const quietUntil = profile && profile.growth ? profile.growth.quiet_until : null;
|
|
357
|
+
const quietMs = quietUntil ? new Date(quietUntil).getTime() : 0;
|
|
358
|
+
if (quietMs && quietMs > now) return '';
|
|
359
|
+
if (shouldSkipByCompetence(profile, sessionState)) return '';
|
|
360
|
+
|
|
361
|
+
const mode = resolveMode(config);
|
|
362
|
+
const zone = sessionState.zone || computeZone(sessionState.skeleton || {}).zone;
|
|
363
|
+
const lines = [];
|
|
364
|
+
lines.push('[Mentor mode protocol - keep concise and practical:');
|
|
365
|
+
lines.push(`- mode=${mode}, zone=${zone}`);
|
|
366
|
+
|
|
367
|
+
if (mode === 'gentle') {
|
|
368
|
+
lines.push('- Give solution but include brief rationale so user can learn the "why".');
|
|
369
|
+
} else if (mode === 'active') {
|
|
370
|
+
lines.push('- Lead with the key concept/principle before the implementation.');
|
|
371
|
+
lines.push('- Add one-line "关键收获" at the end of your reply.');
|
|
372
|
+
} else {
|
|
373
|
+
lines.push('- Prefer scaffold/pseudocode first; avoid dumping full solution immediately.');
|
|
374
|
+
lines.push('- Apply knowledge firewall: do not fill user logic gaps with unstated assumptions.');
|
|
375
|
+
lines.push('- Guide via explanation structure, not by asking the user questions.');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (zone === 'comfort') lines.push('- Increase challenge slightly (new method or stronger abstraction).');
|
|
379
|
+
if (zone === 'panic') lines.push('- Reduce friction: provide step-by-step scaffold and reassurance.');
|
|
380
|
+
|
|
381
|
+
const pattern = detectPatterns(sessionState.recentMessages || [], sessionState.sessionStartTime || Date.now(), { nowMs: now });
|
|
382
|
+
if (pattern.suggestion) lines.push(`- Pattern nudge: ${pattern.suggestion}`);
|
|
383
|
+
|
|
384
|
+
lines.push(']');
|
|
385
|
+
return lines.join('\n');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
module.exports = {
|
|
389
|
+
checkEmotionBreaker,
|
|
390
|
+
buildMentorPrompt,
|
|
391
|
+
computeZone,
|
|
392
|
+
registerDebt,
|
|
393
|
+
collectDebt,
|
|
394
|
+
gcExpiredDebts,
|
|
395
|
+
detectPatterns,
|
|
396
|
+
getRuntimeStatus,
|
|
397
|
+
_private: {
|
|
398
|
+
runtimeFilePath,
|
|
399
|
+
loadRuntime,
|
|
400
|
+
saveRuntime,
|
|
401
|
+
tokenize,
|
|
402
|
+
overlapRatio,
|
|
403
|
+
repetitionFromTexts,
|
|
404
|
+
},
|
|
405
|
+
};
|
package/scripts/platform.js
CHANGED
|
@@ -78,6 +78,27 @@ function findProcessesByPattern(pattern) {
|
|
|
78
78
|
return pids;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Kill a process and its entire child tree.
|
|
83
|
+
* POSIX: process.kill(-pid, signal) to kill process group.
|
|
84
|
+
* Windows: taskkill /PID /T /F (tree kill).
|
|
85
|
+
* Returns true if kill was attempted, false if process not found.
|
|
86
|
+
*/
|
|
87
|
+
function killProcessTree(pid, signal = 'SIGTERM') {
|
|
88
|
+
if (!pid || pid <= 0) return false;
|
|
89
|
+
try {
|
|
90
|
+
if (IS_WIN) {
|
|
91
|
+
execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore', windowsHide: true, timeout: 5000 });
|
|
92
|
+
} else {
|
|
93
|
+
// Try process group first, fallback to single process
|
|
94
|
+
try { process.kill(-pid, signal); } catch { process.kill(pid, signal); }
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
81
102
|
/**
|
|
82
103
|
* Whether the socket path needs fs.unlinkSync before server.listen().
|
|
83
104
|
* Named Pipes on Windows are kernel-managed — no file to unlink.
|
|
@@ -121,6 +142,7 @@ const _icons = IS_WIN ? {
|
|
|
121
142
|
phone: '[TEL]',
|
|
122
143
|
feishu: '[FS]',
|
|
123
144
|
check: '[v]',
|
|
145
|
+
tool: '[T]',
|
|
124
146
|
} : {
|
|
125
147
|
ok: '\u2705', // ✅
|
|
126
148
|
fail: '\u274C', // ❌
|
|
@@ -149,6 +171,7 @@ const _icons = IS_WIN ? {
|
|
|
149
171
|
phone: '\uD83D\uDCF1', // 📱
|
|
150
172
|
feishu: '\uD83D\uDCD8', // 📘
|
|
151
173
|
check: '\u2714', // ✔
|
|
174
|
+
tool: '\uD83D\uDEE0\uFE0F', // 🛠️
|
|
152
175
|
};
|
|
153
176
|
|
|
154
177
|
/**
|
|
@@ -167,6 +190,7 @@ module.exports = {
|
|
|
167
190
|
socketPath,
|
|
168
191
|
sleepSync,
|
|
169
192
|
findProcessesByPattern,
|
|
193
|
+
killProcessTree,
|
|
170
194
|
needsSocketCleanup,
|
|
171
195
|
icon,
|
|
172
196
|
};
|