metame-cli 1.5.21 → 1.5.23
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 +3 -2
- package/index.js +25 -24
- package/package.json +1 -1
- package/scripts/agent-intent-shared.js +111 -0
- package/scripts/daemon-agent-commands.js +160 -354
- package/scripts/daemon-agent-intent.js +282 -0
- package/scripts/daemon-agent-lifecycle.js +243 -0
- package/scripts/daemon-agent-workflow.js +295 -0
- package/scripts/daemon-bridges.js +18 -5
- package/scripts/daemon-claude-engine.js +74 -75
- package/scripts/daemon-command-router.js +24 -294
- package/scripts/daemon-prompt-context.js +127 -0
- package/scripts/daemon-reactive-lifecycle.js +138 -6
- package/scripts/daemon-session-commands.js +16 -2
- package/scripts/daemon-session-store.js +104 -0
- package/scripts/daemon-team-workflow.js +146 -0
- package/scripts/daemon.js +14 -3
- package/scripts/docs/hook-config.md +41 -21
- package/scripts/docs/maintenance-manual.md +2 -2
- package/scripts/docs/orphan-files-review.md +1 -1
- package/scripts/docs/pointer-map.md +2 -2
- package/scripts/hooks/intent-agent-capability.js +51 -0
- package/scripts/hooks/intent-doc-router.js +23 -11
- package/scripts/hooks/intent-memory-recall.js +1 -3
- package/scripts/hooks/intent-team-dispatch.js +1 -1
- package/scripts/intent-registry.js +78 -14
- package/scripts/ops-mission-queue.js +101 -36
- package/scripts/ops-reactive-bootstrap.js +86 -0
- package/scripts/resolve-yaml.js +3 -0
- package/scripts/runtime-bootstrap.js +77 -0
- package/scripts/hooks/intent-engine.js +0 -75
- package/scripts/hooks/team-context.js +0 -143
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function buildBoundSessionChatId(projectKey) {
|
|
4
|
+
const key = String(projectKey || '').trim();
|
|
5
|
+
return key ? `_bound_${key}` : '';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getBoundProject(chatId, cfg) {
|
|
9
|
+
const agentMap = {
|
|
10
|
+
...(cfg && cfg.telegram ? cfg.telegram.chat_agent_map : {}),
|
|
11
|
+
...(cfg && cfg.feishu ? cfg.feishu.chat_agent_map : {}),
|
|
12
|
+
};
|
|
13
|
+
const boundKey = agentMap[String(chatId)];
|
|
14
|
+
const boundProj = boundKey && cfg && cfg.projects && cfg.projects[boundKey];
|
|
15
|
+
return { boundKey: boundKey || null, boundProj: boundProj || null };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getLatestActivationForChat(chatId, pendingActivations) {
|
|
19
|
+
if (!pendingActivations || pendingActivations.size === 0) return null;
|
|
20
|
+
const cid = String(chatId);
|
|
21
|
+
let latest = null;
|
|
22
|
+
for (const rec of pendingActivations.values()) {
|
|
23
|
+
if (rec.createdByChatId === cid) continue;
|
|
24
|
+
if (!latest || rec.createdAt > latest.createdAt) latest = rec;
|
|
25
|
+
}
|
|
26
|
+
return latest;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function listUnboundProjects(cfg) {
|
|
30
|
+
const allBoundKeys = new Set(Object.values({
|
|
31
|
+
...(cfg && cfg.telegram ? cfg.telegram.chat_agent_map : {}),
|
|
32
|
+
...(cfg && cfg.feishu ? cfg.feishu.chat_agent_map : {}),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
return Object.entries((cfg && cfg.projects) || {})
|
|
36
|
+
.filter(([key, p]) => p && p.cwd && !allBoundKeys.has(key))
|
|
37
|
+
.map(([key, p]) => ({ key, name: p.name || key, cwd: p.cwd, icon: p.icon || '🤖' }));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function attachBoundSession({
|
|
41
|
+
attachOrCreateSession,
|
|
42
|
+
projectKey,
|
|
43
|
+
chatId,
|
|
44
|
+
cwd,
|
|
45
|
+
name,
|
|
46
|
+
engine,
|
|
47
|
+
normalizeCwd,
|
|
48
|
+
getDefaultEngine,
|
|
49
|
+
}) {
|
|
50
|
+
if (!cwd || typeof attachOrCreateSession !== 'function') return;
|
|
51
|
+
const sessionChatId = projectKey ? buildBoundSessionChatId(projectKey) : String(chatId);
|
|
52
|
+
attachOrCreateSession(
|
|
53
|
+
sessionChatId,
|
|
54
|
+
normalizeCwd(cwd),
|
|
55
|
+
name || projectKey || '',
|
|
56
|
+
engine || getDefaultEngine()
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function bindAgentToChat({
|
|
61
|
+
agentTools,
|
|
62
|
+
doBindAgent,
|
|
63
|
+
bot,
|
|
64
|
+
chatId,
|
|
65
|
+
agentName,
|
|
66
|
+
agentCwd,
|
|
67
|
+
HOME,
|
|
68
|
+
attachOrCreateSession,
|
|
69
|
+
normalizeCwd,
|
|
70
|
+
getDefaultEngine,
|
|
71
|
+
announce = true,
|
|
72
|
+
}) {
|
|
73
|
+
if (agentTools && typeof agentTools.bindAgentToChat === 'function') {
|
|
74
|
+
const res = await agentTools.bindAgentToChat(chatId, agentName, agentCwd);
|
|
75
|
+
if (!res.ok) {
|
|
76
|
+
await bot.sendMessage(chatId, `❌ 绑定失败: ${res.error}`);
|
|
77
|
+
return { ok: false };
|
|
78
|
+
}
|
|
79
|
+
const p = res.data.project || {};
|
|
80
|
+
const icon = p.icon || '🤖';
|
|
81
|
+
const action = res.data.isNewProject ? '绑定成功' : '重新绑定';
|
|
82
|
+
const displayCwd = String(res.data.cwd || '').replace(HOME, '~');
|
|
83
|
+
attachBoundSession({
|
|
84
|
+
attachOrCreateSession,
|
|
85
|
+
projectKey: res.data.projectKey,
|
|
86
|
+
chatId,
|
|
87
|
+
cwd: res.data.cwd,
|
|
88
|
+
name: p.name || agentName || res.data.projectKey || '',
|
|
89
|
+
engine: p.engine,
|
|
90
|
+
normalizeCwd,
|
|
91
|
+
getDefaultEngine,
|
|
92
|
+
});
|
|
93
|
+
if (announce) {
|
|
94
|
+
await bot.sendMessage(chatId, `${icon} ${p.name || agentName} ${action}\n目录: ${displayCwd}`);
|
|
95
|
+
}
|
|
96
|
+
return { ok: true, data: res.data };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const fallback = await doBindAgent(bot, chatId, agentName, agentCwd);
|
|
100
|
+
if (!fallback || fallback.ok === false) {
|
|
101
|
+
return { ok: false, error: (fallback && fallback.error) || 'bind failed' };
|
|
102
|
+
}
|
|
103
|
+
const fallbackCwd = (fallback.data && fallback.data.cwd) || agentCwd;
|
|
104
|
+
attachBoundSession({
|
|
105
|
+
attachOrCreateSession,
|
|
106
|
+
projectKey: fallback && fallback.data ? fallback.data.projectKey : null,
|
|
107
|
+
chatId,
|
|
108
|
+
cwd: fallbackCwd,
|
|
109
|
+
name: agentName || '',
|
|
110
|
+
engine: fallback && fallback.data && fallback.data.project ? fallback.data.project.engine : null,
|
|
111
|
+
normalizeCwd,
|
|
112
|
+
getDefaultEngine,
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
data: {
|
|
117
|
+
cwd: fallbackCwd,
|
|
118
|
+
projectKey: fallback && fallback.data ? fallback.data.projectKey : null,
|
|
119
|
+
project: fallback && fallback.data ? fallback.data.project : null,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function editAgentRole({ agentTools, mergeAgentRole, workspaceDir, deltaText }) {
|
|
125
|
+
if (agentTools && typeof agentTools.editAgentRoleDefinition === 'function') {
|
|
126
|
+
return agentTools.editAgentRoleDefinition(workspaceDir, deltaText);
|
|
127
|
+
}
|
|
128
|
+
const legacy = await mergeAgentRole(workspaceDir, deltaText);
|
|
129
|
+
if (legacy.error) return { ok: false, error: legacy.error };
|
|
130
|
+
return { ok: true, data: legacy };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function listAgents({ agentTools, chatId, loadConfig }) {
|
|
134
|
+
if (agentTools && typeof agentTools.listAllAgents === 'function') {
|
|
135
|
+
return agentTools.listAllAgents(chatId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const cfg = loadConfig();
|
|
139
|
+
const projects = cfg.projects || {};
|
|
140
|
+
const entries = Object.entries(projects)
|
|
141
|
+
.filter(([, p]) => p && p.cwd)
|
|
142
|
+
.map(([key, p]) => ({
|
|
143
|
+
key,
|
|
144
|
+
name: p.name || key,
|
|
145
|
+
cwd: p.cwd,
|
|
146
|
+
icon: p.icon || '🤖',
|
|
147
|
+
}));
|
|
148
|
+
const { boundKey } = getBoundProject(chatId, cfg);
|
|
149
|
+
return { ok: true, data: { agents: entries, boundKey } };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function unbindAgent({ agentTools, chatId, loadConfig, writeConfigSafe, backupConfig }) {
|
|
153
|
+
if (agentTools && typeof agentTools.unbindCurrentAgent === 'function') {
|
|
154
|
+
return agentTools.unbindCurrentAgent(chatId);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const cfg = loadConfig();
|
|
158
|
+
const isTg = typeof chatId === 'number';
|
|
159
|
+
const ak = isTg ? 'telegram' : 'feishu';
|
|
160
|
+
if (!cfg[ak]) cfg[ak] = {};
|
|
161
|
+
if (!cfg[ak].chat_agent_map) cfg[ak].chat_agent_map = {};
|
|
162
|
+
const old = cfg[ak].chat_agent_map[String(chatId)] || null;
|
|
163
|
+
if (old) {
|
|
164
|
+
delete cfg[ak].chat_agent_map[String(chatId)];
|
|
165
|
+
if (typeof writeConfigSafe === 'function') writeConfigSafe(cfg);
|
|
166
|
+
if (typeof backupConfig === 'function') backupConfig();
|
|
167
|
+
}
|
|
168
|
+
return { ok: true, data: { unbound: !!old, previousProjectKey: old } };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function createWorkspaceAgent({
|
|
172
|
+
agentTools,
|
|
173
|
+
chatId,
|
|
174
|
+
agentName,
|
|
175
|
+
workspaceDir,
|
|
176
|
+
roleDescription,
|
|
177
|
+
pendingActivations,
|
|
178
|
+
skipChatBinding = false,
|
|
179
|
+
engine = null,
|
|
180
|
+
attachOrCreateSession,
|
|
181
|
+
normalizeCwd,
|
|
182
|
+
getDefaultEngine,
|
|
183
|
+
legacyCreate,
|
|
184
|
+
}) {
|
|
185
|
+
let res;
|
|
186
|
+
if (agentTools && typeof agentTools.createNewWorkspaceAgent === 'function') {
|
|
187
|
+
res = await agentTools.createNewWorkspaceAgent(agentName, workspaceDir, roleDescription, chatId, {
|
|
188
|
+
skipChatBinding,
|
|
189
|
+
engine,
|
|
190
|
+
});
|
|
191
|
+
} else if (typeof legacyCreate === 'function') {
|
|
192
|
+
res = await legacyCreate();
|
|
193
|
+
} else {
|
|
194
|
+
res = { ok: false, error: 'agentTools.createNewWorkspaceAgent unavailable' };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!res.ok) return res;
|
|
198
|
+
|
|
199
|
+
const data = res.data || {};
|
|
200
|
+
if (skipChatBinding) {
|
|
201
|
+
if (data.projectKey && pendingActivations) {
|
|
202
|
+
pendingActivations.set(data.projectKey, {
|
|
203
|
+
agentKey: data.projectKey,
|
|
204
|
+
agentName: (data.project && data.project.name) || agentName || data.projectKey,
|
|
205
|
+
cwd: data.cwd,
|
|
206
|
+
createdByChatId: String(chatId),
|
|
207
|
+
createdAt: Date.now(),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return res;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
attachBoundSession({
|
|
214
|
+
attachOrCreateSession,
|
|
215
|
+
projectKey: data.projectKey,
|
|
216
|
+
chatId,
|
|
217
|
+
cwd: data.cwd,
|
|
218
|
+
name: (data.project && data.project.name) || agentName || data.projectKey || '',
|
|
219
|
+
engine: data.project && data.project.engine,
|
|
220
|
+
normalizeCwd,
|
|
221
|
+
getDefaultEngine,
|
|
222
|
+
});
|
|
223
|
+
return res;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function handleActivateCommand({
|
|
227
|
+
bot,
|
|
228
|
+
chatId,
|
|
229
|
+
loadConfig,
|
|
230
|
+
pendingActivations,
|
|
231
|
+
bindAgent,
|
|
232
|
+
}) {
|
|
233
|
+
const cfg = loadConfig();
|
|
234
|
+
const { boundKey } = getBoundProject(chatId, cfg);
|
|
235
|
+
if (boundKey) {
|
|
236
|
+
await bot.sendMessage(chatId, `此群已绑定到「${boundKey}」,无需激活。如需更换请先 /agent unbind`);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const activation = getLatestActivationForChat(chatId, pendingActivations);
|
|
241
|
+
if (!activation) {
|
|
242
|
+
if (pendingActivations) {
|
|
243
|
+
for (const rec of pendingActivations.values()) {
|
|
244
|
+
if (rec.createdByChatId === String(chatId)) {
|
|
245
|
+
await bot.sendMessage(
|
|
246
|
+
chatId,
|
|
247
|
+
`❌ 不能在创建来源群激活。\n请在你新建的目标群里发送 \`/activate\`\n\n或在任意群用: \`/agent bind ${rec.agentName} ${rec.cwd}\``
|
|
248
|
+
);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const unboundProjects = listUnboundProjects(cfg);
|
|
255
|
+
if (unboundProjects.length === 1) {
|
|
256
|
+
const proj = unboundProjects[0];
|
|
257
|
+
const bindRes = await bindAgent(proj.key, proj.cwd);
|
|
258
|
+
if (bindRes.ok && pendingActivations) pendingActivations.delete(proj.key);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (unboundProjects.length > 1) {
|
|
263
|
+
const lines = ['请选择要激活的 Agent:', ''];
|
|
264
|
+
for (const p of unboundProjects) {
|
|
265
|
+
lines.push(`${p.icon} ${p.name} → \`/agent bind ${p.key} ${p.cwd}\``);
|
|
266
|
+
}
|
|
267
|
+
lines.push('\n发送对应命令即可绑定此群。');
|
|
268
|
+
await bot.sendMessage(chatId, lines.join('\n'));
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
await bot.sendMessage(
|
|
273
|
+
chatId,
|
|
274
|
+
'没有待激活的 Agent。\n\n如果已创建过 Agent,直接用:\n`/agent bind <名称> <目录>`\n即可绑定,不需要重新创建。'
|
|
275
|
+
);
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const bindRes = await bindAgent(activation.agentName, activation.cwd);
|
|
280
|
+
if (bindRes.ok && pendingActivations) pendingActivations.delete(activation.agentKey);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
module.exports = {
|
|
285
|
+
getBoundProject,
|
|
286
|
+
getLatestActivationForChat,
|
|
287
|
+
listUnboundProjects,
|
|
288
|
+
buildBoundSessionChatId,
|
|
289
|
+
bindAgentToChat,
|
|
290
|
+
createWorkspaceAgent,
|
|
291
|
+
editAgentRole,
|
|
292
|
+
listAgents,
|
|
293
|
+
unbindAgent,
|
|
294
|
+
handleActivateCommand,
|
|
295
|
+
};
|
|
@@ -817,14 +817,14 @@ function createBridgeStarter(deps) {
|
|
|
817
817
|
let _replyMappingFound = false; // true = mapping exists (agentKey may be null = main)
|
|
818
818
|
// Load state once for the entire routing block
|
|
819
819
|
const _st = loadState();
|
|
820
|
+
const _parentMapping = parentId && _st.msg_sessions ? _st.msg_sessions[parentId] : null;
|
|
820
821
|
// Quoted reply = explicit parentId but NOT a topic thread (topics always carry parentId=root_id)
|
|
821
822
|
const _isQuotedReply = !!(parentId && !threadRootId);
|
|
822
|
-
if (
|
|
823
|
-
log('INFO', `Feishu reply metadata detected chat=${chatId} parentId=${parentId}`);
|
|
823
|
+
if (parentId) {
|
|
824
|
+
log('INFO', `Feishu reply metadata detected chat=${chatId} parentId=${parentId}${threadRootId ? ' topic=true' : ''}`);
|
|
824
825
|
}
|
|
825
|
-
// In topic mode, session continuity is handled by pipelineChatId — skip msg_sessions lookup
|
|
826
826
|
if (_isQuotedReply) {
|
|
827
|
-
const mapped =
|
|
827
|
+
const mapped = _parentMapping;
|
|
828
828
|
if (mapped) {
|
|
829
829
|
_replyMappingFound = true;
|
|
830
830
|
if (typeof restoreSessionFromReply === 'function') {
|
|
@@ -843,6 +843,10 @@ function createBridgeStarter(deps) {
|
|
|
843
843
|
} else {
|
|
844
844
|
log('INFO', `Feishu reply parentId=${parentId} had no msg_sessions mapping`);
|
|
845
845
|
}
|
|
846
|
+
} else if (threadRootId && _parentMapping) {
|
|
847
|
+
_replyMappingFound = true;
|
|
848
|
+
_replyAgentKey = _parentMapping.agentKey || null;
|
|
849
|
+
log('INFO', `Feishu topic inherited root mapping agentKey=${_replyAgentKey || 'main'} parentId=${parentId}`);
|
|
846
850
|
}
|
|
847
851
|
|
|
848
852
|
// Helper: set/clear sticky on shared state object and persist
|
|
@@ -857,13 +861,22 @@ function createBridgeStarter(deps) {
|
|
|
857
861
|
if (_st.team_sticky) delete _st.team_sticky[_chatKey];
|
|
858
862
|
saveState(_st);
|
|
859
863
|
};
|
|
860
|
-
|
|
864
|
+
let _stickyKey = (_st.team_sticky || {})[_chatKey] || null;
|
|
861
865
|
|
|
862
866
|
// Team group routing: if bound project has a team array, check message for member nickname
|
|
863
867
|
// Non-/stop slash commands bypass team routing → handled by main project
|
|
864
868
|
const { key: _boundKey, project: _boundProj } = _getBoundProject(chatId, liveCfg);
|
|
865
869
|
const _isTeamSlashCmd = trimmedText.startsWith('/') && !/^\/stop(\s|$)/i.test(trimmedText);
|
|
866
870
|
if (_boundProj && Array.isArray(_boundProj.team) && _boundProj.team.length > 0 && !_isTeamSlashCmd) {
|
|
871
|
+
if (threadRootId && !_stickyKey && _replyAgentKey) {
|
|
872
|
+
const _topicRootMember = _boundProj.team.find(m => m.key === _replyAgentKey);
|
|
873
|
+
if (_topicRootMember) {
|
|
874
|
+
_setSticky(_topicRootMember.key);
|
|
875
|
+
_stickyKey = _topicRootMember.key;
|
|
876
|
+
log('INFO', `Topic root mapping → sticky set: ${_chatKey.slice(-8)} → ${_topicRootMember.key}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
867
880
|
// ── /stop precise routing for team groups ──
|
|
868
881
|
const _stopMatch = trimmedText && trimmedText.match(/^\/stop(?:\s+(.+))?$/i);
|
|
869
882
|
if (_stopMatch) {
|
|
@@ -9,9 +9,17 @@ const {
|
|
|
9
9
|
ENGINE_MODEL_CONFIG,
|
|
10
10
|
_private: { resolveCodexPermissionProfile },
|
|
11
11
|
} = require('./daemon-engine-runtime');
|
|
12
|
-
const { buildIntentHintBlock } = require('./intent-registry');
|
|
13
12
|
const { rawChatId } = require('./core/thread-chat-id');
|
|
14
13
|
const { buildAgentContextForEngine, buildMemorySnapshotContent, refreshMemorySnapshot } = require('./agent-layer');
|
|
14
|
+
const {
|
|
15
|
+
adaptDaemonHintForEngine,
|
|
16
|
+
buildAgentHint,
|
|
17
|
+
buildDaemonHint,
|
|
18
|
+
buildMacAutomationHint,
|
|
19
|
+
buildLanguageGuard,
|
|
20
|
+
buildIntentHint,
|
|
21
|
+
composePrompt,
|
|
22
|
+
} = require('./daemon-prompt-context');
|
|
15
23
|
const { createPlatformSpawn, terminateChildProcess, stopStreamingLifecycle, abortStreamingChildLifecycle, setActiveChildProcess, clearActiveChildProcess, acquireStreamingChild, buildStreamingResult, resolveStreamingClosePayload, accumulateStreamingStderr, splitStreamingStdoutChunk, buildStreamFlushPayload, buildToolOverlayPayload, buildMilestoneOverlayPayload, finalizePersistentStreamingTurn, writeStreamingChildInput, parseStreamingEvents, applyStreamingMetadata, applyStreamingToolState, applyStreamingContentState, createStreamingWatchdog, runAsyncCommand } = require('./core/handoff');
|
|
16
24
|
|
|
17
25
|
/**
|
|
@@ -235,16 +243,6 @@ function createClaudeEngine(deps) {
|
|
|
235
243
|
return err.message || String(err);
|
|
236
244
|
}
|
|
237
245
|
|
|
238
|
-
function adaptDaemonHintForEngine(daemonHint, engineName) {
|
|
239
|
-
if (normalizeEngineName(engineName) === 'claude') return daemonHint;
|
|
240
|
-
let out = String(daemonHint || '');
|
|
241
|
-
// Keep this replacement conservative: only unwrap the known outer wrapper.
|
|
242
|
-
out = out.replace('[System hints - DO NOT mention these to user:', 'System hints (internal, do not mention to user):');
|
|
243
|
-
// The current daemonHint template ends with a single trailing `]`.
|
|
244
|
-
out = out.replace(/\]\s*$/, '');
|
|
245
|
-
return out;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
246
|
function getCodexPermissionProfile(readOnly, daemonCfg = {}, session = {}) {
|
|
249
247
|
return resolveCodexPermissionProfile({ readOnly, daemonCfg, session });
|
|
250
248
|
}
|
|
@@ -573,7 +571,12 @@ function createClaudeEngine(deps) {
|
|
|
573
571
|
|
|
574
572
|
function projectKeyFromVirtualChatId(chatId) {
|
|
575
573
|
const v = String(chatId || '');
|
|
576
|
-
if (v.startsWith('_agent_'))
|
|
574
|
+
if (v.startsWith('_agent_')) {
|
|
575
|
+
const rest = v.slice(7);
|
|
576
|
+
const scopeIdx = rest.indexOf('::');
|
|
577
|
+
const key = scopeIdx >= 0 ? rest.slice(0, scopeIdx) : rest;
|
|
578
|
+
return key || null;
|
|
579
|
+
}
|
|
577
580
|
if (v.startsWith('_scope_')) {
|
|
578
581
|
const idx = v.lastIndexOf('__');
|
|
579
582
|
if (idx > 7 && idx + 2 < v.length) return v.slice(idx + 2);
|
|
@@ -1441,20 +1444,15 @@ function createClaudeEngine(deps) {
|
|
|
1441
1444
|
}
|
|
1442
1445
|
}
|
|
1443
1446
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
).hint || '';
|
|
1454
|
-
} catch (e) {
|
|
1455
|
-
log('WARN', `Agent context injection failed: ${e.message}`);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1447
|
+
const agentHint = buildAgentHint({
|
|
1448
|
+
sessionStarted: session.started,
|
|
1449
|
+
boundProject,
|
|
1450
|
+
sessionCwd: session && session.cwd,
|
|
1451
|
+
engineName,
|
|
1452
|
+
HOME,
|
|
1453
|
+
buildAgentContextForEngine,
|
|
1454
|
+
log,
|
|
1455
|
+
});
|
|
1458
1456
|
|
|
1459
1457
|
// Memory & Knowledge Injection (RAG)
|
|
1460
1458
|
let memoryHint = '';
|
|
@@ -1628,41 +1626,28 @@ function createClaudeEngine(deps) {
|
|
|
1628
1626
|
|
|
1629
1627
|
// Inject daemon hints only on first message of a session
|
|
1630
1628
|
// Task-specific rules (3-4) are injected only when isTaskIntent() returns true (~250 token saving for casual chat)
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
Keep it under 200 words. Clear it when the task is fully complete by running: \`> ~/.metame/memory/now/${projectKey || 'default'}.md\`` : '';
|
|
1645
|
-
daemonHint = `\n\n[System hints - DO NOT mention these to user:
|
|
1646
|
-
1. Daemon config: The ONLY config is ~/.metame/daemon.yaml (never edit daemon-default.yaml). Auto-reloads on change.
|
|
1647
|
-
2. Explanation depth (ZPD):${zdpHint ? zdpHint : '\n- User competence map unavailable. Default to concise expert-first explanations unless the user asks for teaching mode.'}${reflectHint}${taskRules}]`;
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
daemonHint = adaptDaemonHintForEngine(daemonHint, runtime.name);
|
|
1629
|
+
const mentorRadarHint = (config && config.daemon && config.daemon.mentor && config.daemon.mentor.enabled)
|
|
1630
|
+
? '\n When you observe the user is clearly expert or beginner in a domain, note it in your response and suggest: "要不要把你的 {domain} 水平 ({level}) 记录到能力雷达?"'
|
|
1631
|
+
: '';
|
|
1632
|
+
const daemonHint = buildDaemonHint({
|
|
1633
|
+
sessionStarted: session.started,
|
|
1634
|
+
prompt,
|
|
1635
|
+
mentorRadarHint,
|
|
1636
|
+
zdpHint,
|
|
1637
|
+
reflectHint,
|
|
1638
|
+
projectKey,
|
|
1639
|
+
isTaskIntent,
|
|
1640
|
+
runtimeName: runtime.name,
|
|
1641
|
+
});
|
|
1651
1642
|
|
|
1652
1643
|
const routedPrompt = skill ? `/${skill} ${prompt}` : prompt;
|
|
1653
1644
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
2. Read/query actions can execute directly.
|
|
1661
|
-
3. Before any side-effect action (send email, create/delete/modify calendar event, delete/move files, app quit, system sleep), first show a short execution preview and require explicit user confirmation.
|
|
1662
|
-
4. Keep output concise: success/failure + key result only.
|
|
1663
|
-
5. If permission is missing, guide user to run /mac perms open then retry.
|
|
1664
|
-
6. Before executing high-risk or non-obvious Bash commands (rm, kill, git reset, overwrite configs), prepend a single-line [Why] explanation. Skip for routine commands (ls, cat, grep).]`;
|
|
1665
|
-
}
|
|
1645
|
+
const macAutomationHint = buildMacAutomationHint({
|
|
1646
|
+
processPlatform: process.platform,
|
|
1647
|
+
readOnly,
|
|
1648
|
+
prompt,
|
|
1649
|
+
isMacAutomationIntent,
|
|
1650
|
+
});
|
|
1666
1651
|
|
|
1667
1652
|
// P2-B: inject session summary when resuming after a 2h+ gap
|
|
1668
1653
|
let summaryHint = '';
|
|
@@ -1738,23 +1723,29 @@ ${mentorRadarHint}
|
|
|
1738
1723
|
// Language guard: only inject on first message of a new session to avoid
|
|
1739
1724
|
// linearly growing token cost on every turn in long conversations.
|
|
1740
1725
|
// Claude Code preserves session context, so the guard persists after initial injection.
|
|
1741
|
-
const langGuard = session.started
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
} catch (e) {
|
|
1750
|
-
log('WARN', `Intent registry injection failed: ${e.message}`);
|
|
1751
|
-
}
|
|
1726
|
+
const langGuard = buildLanguageGuard(session.started);
|
|
1727
|
+
const intentHint = buildIntentHint({
|
|
1728
|
+
prompt,
|
|
1729
|
+
config,
|
|
1730
|
+
boundProjectKey,
|
|
1731
|
+
projectKey,
|
|
1732
|
+
log,
|
|
1733
|
+
});
|
|
1752
1734
|
// For warm process reuse: static context (daemonHint, memoryHint, etc.) is already
|
|
1753
1735
|
// in the persistent process — skip those to save tokens. intentHint is dynamic
|
|
1754
1736
|
// (varies per prompt), so include it even on warm reuse.
|
|
1755
|
-
const fullPrompt =
|
|
1756
|
-
|
|
1757
|
-
:
|
|
1737
|
+
const fullPrompt = composePrompt({
|
|
1738
|
+
routedPrompt,
|
|
1739
|
+
warmEntry: _warmEntry,
|
|
1740
|
+
intentHint,
|
|
1741
|
+
daemonHint,
|
|
1742
|
+
agentHint,
|
|
1743
|
+
macAutomationHint,
|
|
1744
|
+
summaryHint,
|
|
1745
|
+
memoryHint,
|
|
1746
|
+
mentorHint,
|
|
1747
|
+
langGuard,
|
|
1748
|
+
});
|
|
1758
1749
|
if (runtime.name === 'codex' && session.started && session.id && requestedCodexPermissionProfile) {
|
|
1759
1750
|
const actualPermissionProfile = getActualCodexPermissionProfile(session);
|
|
1760
1751
|
if (codexNeedsFallbackForRequestedPermissions(actualPermissionProfile, requestedCodexPermissionProfile)) {
|
|
@@ -2146,14 +2137,14 @@ ${mentorRadarHint}
|
|
|
2146
2137
|
const allProjects = (config && config.projects) || {};
|
|
2147
2138
|
const names = dispatchedTargets.map(k => (allProjects[k] && allProjects[k].name) || k).join('、');
|
|
2148
2139
|
const doneMsg = await bot.sendMessage(chatId, `✉️ 已转达给 ${names},处理中…`);
|
|
2149
|
-
if (doneMsg && doneMsg.message_id && session) trackMsgSession(doneMsg.message_id, session,
|
|
2140
|
+
if (doneMsg && doneMsg.message_id && session) trackMsgSession(doneMsg.message_id, session, projectKeyFromVirtualChatId(chatId));
|
|
2150
2141
|
const wasNew = !session.started;
|
|
2151
2142
|
if (wasNew) markSessionStarted(sessionChatId, engineName);
|
|
2152
2143
|
return { ok: true };
|
|
2153
2144
|
}
|
|
2154
2145
|
const filesDesc = files && files.length > 0 ? `\n修改了 ${files.length} 个文件` : '';
|
|
2155
2146
|
const doneMsg = await bot.sendMessage(chatId, `✅ 完成${filesDesc}`);
|
|
2156
|
-
if (doneMsg && doneMsg.message_id && session) trackMsgSession(doneMsg.message_id, session,
|
|
2147
|
+
if (doneMsg && doneMsg.message_id && session) trackMsgSession(doneMsg.message_id, session, projectKeyFromVirtualChatId(chatId));
|
|
2157
2148
|
const wasNew = !session.started;
|
|
2158
2149
|
if (wasNew) markSessionStarted(sessionChatId, engineName);
|
|
2159
2150
|
return { ok: true };
|
|
@@ -2217,6 +2208,10 @@ ${mentorRadarHint}
|
|
|
2217
2208
|
cleanOutput = `⚠️ **任务超时,以下是已完成的部分结果:**\n\n${cleanOutput}`;
|
|
2218
2209
|
}
|
|
2219
2210
|
|
|
2211
|
+
if (typeof bot.notifyFinalOutput === 'function') {
|
|
2212
|
+
try { await bot.notifyFinalOutput(cleanOutput); } catch { /* non-critical */ }
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2220
2215
|
// Match current session to a project for colored card display.
|
|
2221
2216
|
// Prefer the bound project (known by virtual chatId or chat_agent_map) — avoids ambiguity
|
|
2222
2217
|
// when multiple projects share the same cwd (e.g. team members with parent project cwd).
|
|
@@ -2291,7 +2286,7 @@ ${mentorRadarHint}
|
|
|
2291
2286
|
log('ERROR', `sendMessage fallback also failed: ${e2.message}`);
|
|
2292
2287
|
}
|
|
2293
2288
|
}
|
|
2294
|
-
const trackedAgentKey =
|
|
2289
|
+
const trackedAgentKey = projectKeyFromVirtualChatId(chatId);
|
|
2295
2290
|
if (replyMsg && replyMsg.message_id && session) {
|
|
2296
2291
|
if (runtime.name === 'codex' && session.runtimeSessionObserved === false) {
|
|
2297
2292
|
trackMsgSession(replyMsg.message_id, session, trackedAgentKey, { routeOnly: true });
|
|
@@ -2408,6 +2403,9 @@ ${mentorRadarHint}
|
|
|
2408
2403
|
if (retry.output) {
|
|
2409
2404
|
markSessionStarted(sessionChatId, runtime.name);
|
|
2410
2405
|
const { markedFiles: retryMarked, cleanOutput: retryClean } = parseFileMarkers(retry.output);
|
|
2406
|
+
if (typeof bot.notifyFinalOutput === 'function') {
|
|
2407
|
+
try { await bot.notifyFinalOutput(retryClean); } catch { /* non-critical */ }
|
|
2408
|
+
}
|
|
2411
2409
|
await bot.sendMarkdown(chatId, retryClean);
|
|
2412
2410
|
await sendFileButtons(bot, chatId, mergeFileCollections(retryMarked, retry.files));
|
|
2413
2411
|
return { ok: true };
|
|
@@ -2485,6 +2483,7 @@ ${mentorRadarHint}
|
|
|
2485
2483
|
codexApprovalPrivilegeRank,
|
|
2486
2484
|
codexNeedsFallbackForRequestedPermissions,
|
|
2487
2485
|
buildCodexFallbackBridgePrompt,
|
|
2486
|
+
projectKeyFromVirtualChatId,
|
|
2488
2487
|
},
|
|
2489
2488
|
};
|
|
2490
2489
|
}
|