metame-cli 1.5.3 → 1.5.5
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 +60 -18
- package/index.js +352 -79
- package/package.json +2 -2
- package/scripts/agent-layer.js +4 -2
- package/scripts/bin/dispatch_to +178 -90
- package/scripts/daemon-admin-commands.js +353 -105
- package/scripts/daemon-agent-commands.js +434 -66
- package/scripts/daemon-bridges.js +477 -68
- package/scripts/daemon-claude-engine.js +1267 -674
- package/scripts/daemon-command-router.js +205 -27
- package/scripts/daemon-command-session-route.js +118 -0
- package/scripts/daemon-default.yaml +7 -0
- package/scripts/daemon-engine-runtime.js +96 -20
- package/scripts/daemon-exec-commands.js +108 -49
- package/scripts/daemon-file-browser.js +64 -7
- package/scripts/daemon-notify.js +18 -4
- package/scripts/daemon-ops-commands.js +16 -2
- package/scripts/daemon-remote-dispatch.js +55 -1
- package/scripts/daemon-runtime-lifecycle.js +87 -0
- package/scripts/daemon-session-commands.js +102 -45
- package/scripts/daemon-session-store.js +497 -66
- package/scripts/daemon-siri-bridge.js +234 -0
- package/scripts/daemon-siri-imessage.js +209 -0
- package/scripts/daemon-task-scheduler.js +10 -2
- package/scripts/daemon.js +697 -179
- package/scripts/daemon.yaml +7 -0
- package/scripts/docs/agent-guide.md +36 -3
- package/scripts/docs/hook-config.md +134 -0
- package/scripts/docs/maintenance-manual.md +162 -5
- package/scripts/docs/pointer-map.md +60 -5
- package/scripts/feishu-adapter.js +7 -15
- package/scripts/hooks/doc-router.js +29 -0
- package/scripts/hooks/hook-utils.js +61 -0
- package/scripts/hooks/intent-doc-router.js +54 -0
- package/scripts/hooks/intent-engine.js +72 -0
- package/scripts/hooks/intent-file-transfer.js +51 -0
- package/scripts/hooks/intent-memory-recall.js +35 -0
- package/scripts/hooks/intent-ops-assist.js +54 -0
- package/scripts/hooks/intent-task-create.js +35 -0
- package/scripts/hooks/intent-team-dispatch.js +106 -0
- package/scripts/hooks/team-context.js +143 -0
- package/scripts/intent-registry.js +59 -0
- package/scripts/memory-extract.js +59 -0
- package/scripts/memory-nightly-reflect.js +109 -43
- package/scripts/memory.js +55 -17
- package/scripts/mentor-engine.js +6 -0
- package/scripts/schema.js +1 -0
- package/scripts/self-reflect.js +110 -12
- package/scripts/session-analytics.js +160 -0
- package/scripts/signal-capture.js +1 -1
- package/scripts/team-dispatch.js +315 -0
|
@@ -7,6 +7,14 @@ const {
|
|
|
7
7
|
} = require('./usage-classifier');
|
|
8
8
|
const { IS_WIN } = require('./platform');
|
|
9
9
|
const { ENGINE_MODEL_CONFIG, resolveEngineModel } = require('./daemon-engine-runtime');
|
|
10
|
+
const { resolveProjectKey: _resolveProjectKey } = require('./team-dispatch');
|
|
11
|
+
const {
|
|
12
|
+
parseRemoteTargetRef,
|
|
13
|
+
getRemoteDispatchStatus,
|
|
14
|
+
generatePairCode,
|
|
15
|
+
isValidPairCode,
|
|
16
|
+
deriveSecretFromPairCode,
|
|
17
|
+
} = require('./daemon-remote-dispatch');
|
|
10
18
|
let mentorEngine = null;
|
|
11
19
|
try { mentorEngine = require('./mentor-engine'); } catch { /* optional */ }
|
|
12
20
|
|
|
@@ -39,26 +47,26 @@ function createAdminCommandHandler(deps) {
|
|
|
39
47
|
getDistillModel = () => 'haiku',
|
|
40
48
|
} = deps;
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// Return full path: parentKey/teamMemberKey
|
|
56
|
-
return `${key}/${member.key}`;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
50
|
+
// resolveProjectKey: imported from team-dispatch.js (shared with dispatch_to and daemon.js)
|
|
51
|
+
const resolveProjectKey = _resolveProjectKey;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a target name to { dispatchKey, projInfo }.
|
|
55
|
+
* resolveProjectKey returns 'parent/member' for team members; this splits
|
|
56
|
+
* it into the bare dispatch key and looks up the project/member config.
|
|
57
|
+
*/
|
|
58
|
+
function resolveDispatchTarget(targetName, projects) {
|
|
59
|
+
const resolved = resolveProjectKey(targetName, projects || {});
|
|
60
|
+
if (!resolved) return null;
|
|
61
|
+
if (!resolved.includes('/')) {
|
|
62
|
+
return { dispatchKey: resolved, projInfo: (projects || {})[resolved] || {} };
|
|
60
63
|
}
|
|
61
|
-
|
|
64
|
+
const [parentKey, memberKey] = resolved.split('/');
|
|
65
|
+
const parent = (projects || {})[parentKey] || {};
|
|
66
|
+
const member = Array.isArray(parent.team)
|
|
67
|
+
? (parent.team.find(m => m.key === memberKey) || {})
|
|
68
|
+
: {};
|
|
69
|
+
return { dispatchKey: memberKey, projInfo: member };
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
function resolveSenderKey(chatId, config) {
|
|
@@ -69,6 +77,14 @@ function createAdminCommandHandler(deps) {
|
|
|
69
77
|
return map[String(chatId)] || 'user';
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
function resolveBoundProjectKey(chatId, config) {
|
|
81
|
+
const map = {
|
|
82
|
+
...(config && config.feishu ? config.feishu.chat_agent_map : {}),
|
|
83
|
+
...(config && config.telegram ? config.telegram.chat_agent_map : {}),
|
|
84
|
+
};
|
|
85
|
+
return map[String(chatId)] || '';
|
|
86
|
+
}
|
|
87
|
+
|
|
72
88
|
function popFlag(input, flagName) {
|
|
73
89
|
const src = String(input || '');
|
|
74
90
|
const re = new RegExp(`(?:^|\\s)--${flagName}\\s+(\\S+)`, 'i');
|
|
@@ -97,6 +113,87 @@ function createAdminCommandHandler(deps) {
|
|
|
97
113
|
};
|
|
98
114
|
}
|
|
99
115
|
|
|
116
|
+
function isLikelyTeamTaskResumeIntent(text) {
|
|
117
|
+
const src = String(text || '').trim();
|
|
118
|
+
if (!src || src.startsWith('/')) return false;
|
|
119
|
+
if (src.length < 4 || src.length > 120) return false;
|
|
120
|
+
if (/(?:新建|创建|查看|列出|列表|详情|状态|有哪些|teamtask\s*$|\/teamtask)/i.test(src)) return false;
|
|
121
|
+
return /(?:继续(?:做|改|修)?|接着(?:做|改|修)?|续上|接续|返工|复工|再修(?:一下)?|再改(?:一下)?).*(?:上次|那个|这单|这个任务|任务|TeamTask|team task|工单)?|(?:上次|那个|这单|这个任务).*(?:继续|接着|返工|复工|再修|再改)/i.test(src);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function listAutoResumeCandidates(chatId, senderKey, config) {
|
|
125
|
+
if (!taskBoard || typeof taskBoard.listRecentTasks !== 'function') return [];
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
const chatKey = String(chatId);
|
|
128
|
+
const recent = taskBoard.listRecentTasks(12, null, 'team');
|
|
129
|
+
return recent.filter((task) => {
|
|
130
|
+
if (!task || task.task_kind !== 'team') return false;
|
|
131
|
+
if (!config.projects || !config.projects[task.to_agent]) return false;
|
|
132
|
+
const sourceChatId = String(task.inputs && task.inputs.source_chat_id || '').trim();
|
|
133
|
+
if (!sourceChatId || sourceChatId !== chatKey) return false;
|
|
134
|
+
const updatedAt = Date.parse(task.updated_at || task.created_at || '');
|
|
135
|
+
if (!Number.isFinite(updatedAt) || (now - updatedAt) > 12 * 3600_000) return false;
|
|
136
|
+
const participants = Array.isArray(task.participants) ? task.participants : [];
|
|
137
|
+
return task.from_agent === senderKey || participants.includes(senderKey);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildTeamTaskResumeEnvelope(task, targetKey, chatId, config) {
|
|
142
|
+
return taskEnvelope && taskEnvelope.normalizeTaskEnvelope
|
|
143
|
+
? taskEnvelope.normalizeTaskEnvelope({
|
|
144
|
+
...task,
|
|
145
|
+
status: 'queued',
|
|
146
|
+
updated_at: new Date().toISOString(),
|
|
147
|
+
task_kind: 'team',
|
|
148
|
+
participants: taskBoard.listScopeParticipants(task.scope_id || task.task_id),
|
|
149
|
+
}, {
|
|
150
|
+
from_agent: task.from_agent || resolveSenderKey(chatId, config),
|
|
151
|
+
to_agent: targetKey,
|
|
152
|
+
scope_id: task.scope_id || task.task_id,
|
|
153
|
+
})
|
|
154
|
+
: {
|
|
155
|
+
task_id: task.task_id,
|
|
156
|
+
scope_id: task.scope_id || task.task_id,
|
|
157
|
+
from_agent: task.from_agent || resolveSenderKey(chatId, config),
|
|
158
|
+
to_agent: targetKey,
|
|
159
|
+
participants: taskBoard.listScopeParticipants(task.scope_id || task.task_id),
|
|
160
|
+
goal: task.goal,
|
|
161
|
+
definition_of_done: task.definition_of_done || [],
|
|
162
|
+
inputs: task.inputs || {},
|
|
163
|
+
artifacts: task.artifacts || [],
|
|
164
|
+
owned_paths: task.owned_paths || [],
|
|
165
|
+
priority: task.priority || 'normal',
|
|
166
|
+
status: 'queued',
|
|
167
|
+
task_kind: 'team',
|
|
168
|
+
created_at: task.created_at,
|
|
169
|
+
updated_at: new Date().toISOString(),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function dispatchTeamTaskResume(task, chatId, config, senderId = null) {
|
|
174
|
+
const targetKey = task.to_agent;
|
|
175
|
+
if (!config.projects || !config.projects[targetKey]) {
|
|
176
|
+
return { success: false, error: `target_missing:${targetKey}` };
|
|
177
|
+
}
|
|
178
|
+
const envelope = buildTeamTaskResumeEnvelope(task, targetKey, chatId, config);
|
|
179
|
+
const result = dispatchTask(targetKey, {
|
|
180
|
+
from: envelope.from_agent || 'user',
|
|
181
|
+
type: 'task',
|
|
182
|
+
priority: envelope.priority || 'normal',
|
|
183
|
+
payload: {
|
|
184
|
+
title: envelope.goal.slice(0, 60),
|
|
185
|
+
prompt: envelope.goal,
|
|
186
|
+
task_envelope: envelope,
|
|
187
|
+
},
|
|
188
|
+
callback: false,
|
|
189
|
+
new_session: false,
|
|
190
|
+
source_chat_id: String(chatId),
|
|
191
|
+
source_sender_key: envelope.from_agent || resolveSenderKey(chatId, config),
|
|
192
|
+
source_sender_id: String(senderId || '').trim() || '',
|
|
193
|
+
}, config);
|
|
194
|
+
return { success: !!(result && result.success), result, envelope, targetKey };
|
|
195
|
+
}
|
|
196
|
+
|
|
100
197
|
function formatTaskSchedule(task) {
|
|
101
198
|
const at = typeof task.at === 'string' ? task.at.trim() : '';
|
|
102
199
|
if (at) {
|
|
@@ -165,8 +262,30 @@ function createAdminCommandHandler(deps) {
|
|
|
165
262
|
}
|
|
166
263
|
}
|
|
167
264
|
|
|
265
|
+
async function sendLocalDispatchReceipt(bot, chatId, targetKey, projInfo, result, preview) {
|
|
266
|
+
if (!result || !result.success) return;
|
|
267
|
+
const icon = projInfo && projInfo.icon ? projInfo.icon : '🤖';
|
|
268
|
+
const name = projInfo && projInfo.name ? projInfo.name : targetKey;
|
|
269
|
+
const lines = [
|
|
270
|
+
'📮 Dispatch 回执',
|
|
271
|
+
'',
|
|
272
|
+
`状态: ${icon} ${name} 已接收并入队`,
|
|
273
|
+
];
|
|
274
|
+
if (result.id) lines.push(`编号: ${result.id}`);
|
|
275
|
+
if (preview) lines.push(`摘要: ${String(preview).slice(0, 120)}`);
|
|
276
|
+
if (result.task_id) {
|
|
277
|
+
lines.push('');
|
|
278
|
+
lines.push(`TeamTask: ${result.task_id}`);
|
|
279
|
+
if (result.scope_id && result.scope_id !== result.task_id) {
|
|
280
|
+
lines.push(`Scope: ${result.scope_id}`);
|
|
281
|
+
}
|
|
282
|
+
lines.push(`如需复工,请使用: /TeamTask resume ${result.task_id}`);
|
|
283
|
+
}
|
|
284
|
+
await bot.sendMessage(chatId, lines.join('\n'));
|
|
285
|
+
}
|
|
286
|
+
|
|
168
287
|
async function handleAdminCommand(ctx) {
|
|
169
|
-
const { bot, chatId, text } = ctx;
|
|
288
|
+
const { bot, chatId, text, senderId = null } = ctx;
|
|
170
289
|
const state = ctx.state || {};
|
|
171
290
|
let config = ctx.config || {};
|
|
172
291
|
|
|
@@ -366,6 +485,39 @@ function createAdminCommandHandler(deps) {
|
|
|
366
485
|
return { handled: true, config };
|
|
367
486
|
}
|
|
368
487
|
|
|
488
|
+
if (isLikelyTeamTaskResumeIntent(text)) {
|
|
489
|
+
const senderKey = resolveSenderKey(chatId, config);
|
|
490
|
+
const candidates = listAutoResumeCandidates(chatId, senderKey, config);
|
|
491
|
+
if (candidates.length === 1) {
|
|
492
|
+
const task = candidates[0];
|
|
493
|
+
const resumed = dispatchTeamTaskResume(task, chatId, config, senderId);
|
|
494
|
+
if (resumed.success) {
|
|
495
|
+
if (taskBoard && typeof taskBoard.appendTaskEvent === 'function') {
|
|
496
|
+
taskBoard.appendTaskEvent(task.task_id, 'task_resume_requested', String(chatId), { by: String(chatId), source: 'nl_auto_resume' });
|
|
497
|
+
}
|
|
498
|
+
await bot.sendMessage(chatId, [
|
|
499
|
+
`🔄 已自动续跑最近的 TeamTask: ${task.task_id}`,
|
|
500
|
+
`目标: ${task.to_agent}`,
|
|
501
|
+
`意图: ${text.trim().slice(0, 80)}`,
|
|
502
|
+
'回执会在目标端真正接收后返回。',
|
|
503
|
+
].join('\n'));
|
|
504
|
+
await sendLocalDispatchReceipt(bot, chatId, resumed.targetKey, config.projects[resumed.targetKey], resumed.result, resumed.envelope.goal);
|
|
505
|
+
return { handled: true, config };
|
|
506
|
+
}
|
|
507
|
+
await bot.sendMessage(chatId, `❌ 自动续跑失败: ${resumed.result && resumed.result.error ? resumed.result.error : 'unknown_error'}`);
|
|
508
|
+
return { handled: true, config };
|
|
509
|
+
}
|
|
510
|
+
if (candidates.length > 1) {
|
|
511
|
+
const lines = ['⚠️ 检测到你可能想复工 TeamTask,但最近有多条候选任务:'];
|
|
512
|
+
for (const task of candidates.slice(0, 3)) {
|
|
513
|
+
lines.push(`- ${task.task_id} [${task.status}] ${task.goal.slice(0, 50)}`);
|
|
514
|
+
}
|
|
515
|
+
lines.push('请直接回复更明确一点,或使用 /TeamTask 查看后再选择。');
|
|
516
|
+
await bot.sendMessage(chatId, lines.join('\n'));
|
|
517
|
+
return { handled: true, config };
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
369
521
|
// /TeamTask — create/list/detail/resume team collaboration tasks
|
|
370
522
|
const teamTaskCmdMatch = text.match(/^\/teamtask(?:\s+([\s\S]+))?$/i);
|
|
371
523
|
if (teamTaskCmdMatch) {
|
|
@@ -430,13 +582,18 @@ function createAdminCommandHandler(deps) {
|
|
|
430
582
|
task_envelope: envelope,
|
|
431
583
|
},
|
|
432
584
|
callback: false,
|
|
585
|
+
source_chat_id: String(chatId),
|
|
586
|
+
source_sender_key: senderKey,
|
|
587
|
+
source_sender_id: String(senderId || '').trim() || '',
|
|
433
588
|
}, config);
|
|
434
589
|
if (result.success) {
|
|
435
590
|
await bot.sendMessage(chatId, [
|
|
436
|
-
`✅ 已创建 TeamTask
|
|
591
|
+
`✅ 已创建 TeamTask 并提交派发: ${envelope.task_id}`,
|
|
437
592
|
`Scope: ${envelope.scope_id || envelope.task_id}`,
|
|
593
|
+
'回执会在目标端真正接收后返回。',
|
|
438
594
|
`查看: /TeamTask ${envelope.task_id}`,
|
|
439
595
|
].join('\n'));
|
|
596
|
+
await sendLocalDispatchReceipt(bot, chatId, targetKey, config.projects[targetKey], result, goal);
|
|
440
597
|
} else {
|
|
441
598
|
await bot.sendMessage(chatId, `❌ 创建 TeamTask 失败: ${result.error}`);
|
|
442
599
|
}
|
|
@@ -476,52 +633,13 @@ function createAdminCommandHandler(deps) {
|
|
|
476
633
|
await bot.sendMessage(chatId, `❌ 目标 agent 不存在: ${targetKey}`);
|
|
477
634
|
return { handled: true, config };
|
|
478
635
|
}
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
...task,
|
|
482
|
-
status: 'queued',
|
|
483
|
-
updated_at: new Date().toISOString(),
|
|
484
|
-
task_kind: 'team',
|
|
485
|
-
participants: taskBoard.listScopeParticipants(task.scope_id || task.task_id),
|
|
486
|
-
}, {
|
|
487
|
-
from_agent: task.from_agent || resolveSenderKey(chatId, config),
|
|
488
|
-
to_agent: targetKey,
|
|
489
|
-
scope_id: task.scope_id || task.task_id,
|
|
490
|
-
})
|
|
491
|
-
: {
|
|
492
|
-
task_id: task.task_id,
|
|
493
|
-
scope_id: task.scope_id || task.task_id,
|
|
494
|
-
from_agent: task.from_agent || resolveSenderKey(chatId, config),
|
|
495
|
-
to_agent: targetKey,
|
|
496
|
-
participants: taskBoard.listScopeParticipants(task.scope_id || task.task_id),
|
|
497
|
-
goal: task.goal,
|
|
498
|
-
definition_of_done: task.definition_of_done || [],
|
|
499
|
-
inputs: task.inputs || {},
|
|
500
|
-
artifacts: task.artifacts || [],
|
|
501
|
-
owned_paths: task.owned_paths || [],
|
|
502
|
-
priority: task.priority || 'normal',
|
|
503
|
-
status: 'queued',
|
|
504
|
-
task_kind: 'team',
|
|
505
|
-
created_at: task.created_at,
|
|
506
|
-
updated_at: new Date().toISOString(),
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
const result = dispatchTask(targetKey, {
|
|
510
|
-
from: envelope.from_agent || 'user',
|
|
511
|
-
type: 'task',
|
|
512
|
-
priority: envelope.priority || 'normal',
|
|
513
|
-
payload: {
|
|
514
|
-
title: envelope.goal.slice(0, 60),
|
|
515
|
-
prompt: envelope.goal,
|
|
516
|
-
task_envelope: envelope,
|
|
517
|
-
},
|
|
518
|
-
callback: false,
|
|
519
|
-
new_session: false,
|
|
520
|
-
}, config);
|
|
636
|
+
const resumed = dispatchTeamTaskResume(task, chatId, config, senderId);
|
|
637
|
+
const { result, envelope } = resumed;
|
|
521
638
|
|
|
522
639
|
if (result.success) {
|
|
523
640
|
taskBoard.appendTaskEvent(task.task_id, 'task_resume_requested', String(chatId), { by: String(chatId) });
|
|
524
|
-
await bot.sendMessage(chatId, `✅ 已续跑 TeamTask: ${task.task_id}
|
|
641
|
+
await bot.sendMessage(chatId, `✅ 已续跑 TeamTask: ${task.task_id}\n回执会在目标端真正接收后返回。`);
|
|
642
|
+
await sendLocalDispatchReceipt(bot, chatId, targetKey, config.projects[targetKey], result, envelope.goal);
|
|
525
643
|
} else {
|
|
526
644
|
await bot.sendMessage(chatId, `❌ 续跑失败: ${result.error}`);
|
|
527
645
|
}
|
|
@@ -622,23 +740,126 @@ function createAdminCommandHandler(deps) {
|
|
|
622
740
|
return { handled: true, config };
|
|
623
741
|
}
|
|
624
742
|
|
|
743
|
+
// /dispatch peers — show remote dispatch config
|
|
744
|
+
if (args === 'peers') {
|
|
745
|
+
const rd = getRemoteDispatchStatus(config);
|
|
746
|
+
if (!rd) {
|
|
747
|
+
await bot.sendMessage(chatId, '📡 远端 Dispatch 未配置\n\n在 daemon.yaml 中设置 feishu.remote_dispatch 启用。');
|
|
748
|
+
return { handled: true, config };
|
|
749
|
+
}
|
|
750
|
+
let msg = `📡 远端 Dispatch 配置\n─────────────\nself: ${rd.selfPeer}\nrelay chat: ${rd.chatId}\nmode: pair code\nsecret: ${rd.hasSecret ? 'configured' : 'missing'}\n\n远端成员:\n`;
|
|
751
|
+
let hasRemote = false;
|
|
752
|
+
for (const [key, proj] of Object.entries(config.projects || {})) {
|
|
753
|
+
if (!Array.isArray(proj.team)) continue;
|
|
754
|
+
for (const m of proj.team) {
|
|
755
|
+
if (m.peer) {
|
|
756
|
+
hasRemote = true;
|
|
757
|
+
msg += `- ${m.icon || '🤖'} ${m.name || m.key} → peer:${m.peer} (${key}/${m.key})\n`;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (!hasRemote) msg += '(无远端成员)\n';
|
|
762
|
+
await bot.sendMessage(chatId, msg.trim());
|
|
763
|
+
return { handled: true, config };
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (args === 'code') {
|
|
767
|
+
const rd = getRemoteDispatchStatus(config);
|
|
768
|
+
if (!rd) {
|
|
769
|
+
await bot.sendMessage(chatId, '📡 远端 Dispatch 未配置\n\n在 daemon.yaml 中设置 feishu.remote_dispatch 启用。');
|
|
770
|
+
return { handled: true, config };
|
|
771
|
+
}
|
|
772
|
+
const code = generatePairCode();
|
|
773
|
+
const secret = deriveSecretFromPairCode(code, rd.chatId);
|
|
774
|
+
backupConfig();
|
|
775
|
+
const cfg = yaml.load(fs.readFileSync(CONFIG_FILE, 'utf8')) || {};
|
|
776
|
+
if (!cfg.feishu) cfg.feishu = {};
|
|
777
|
+
if (!cfg.feishu.remote_dispatch) cfg.feishu.remote_dispatch = {};
|
|
778
|
+
cfg.feishu.remote_dispatch.secret = secret;
|
|
779
|
+
writeConfigSafe(cfg);
|
|
780
|
+
config = loadConfig();
|
|
781
|
+
await bot.sendMessage(chatId, `🔐 配对码已生成\n\n配对码: ${code}\n\n把这 6 位码发到另一台设备执行:\n/dispatch pair ${code}`);
|
|
782
|
+
return { handled: true, config };
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const pairMatch = args.match(/^pair\s+(\d{6})$/);
|
|
786
|
+
if (pairMatch) {
|
|
787
|
+
const rd = getRemoteDispatchStatus(config);
|
|
788
|
+
if (!rd) {
|
|
789
|
+
await bot.sendMessage(chatId, '📡 远端 Dispatch 未配置\n\n在 daemon.yaml 中设置 feishu.remote_dispatch 启用。');
|
|
790
|
+
return { handled: true, config };
|
|
791
|
+
}
|
|
792
|
+
const code = pairMatch[1];
|
|
793
|
+
if (!isValidPairCode(code)) {
|
|
794
|
+
await bot.sendMessage(chatId, '❌ 配对码必须是 6 位数字');
|
|
795
|
+
return { handled: true, config };
|
|
796
|
+
}
|
|
797
|
+
const secret = deriveSecretFromPairCode(code, rd.chatId);
|
|
798
|
+
backupConfig();
|
|
799
|
+
const cfg = yaml.load(fs.readFileSync(CONFIG_FILE, 'utf8')) || {};
|
|
800
|
+
if (!cfg.feishu) cfg.feishu = {};
|
|
801
|
+
if (!cfg.feishu.remote_dispatch) cfg.feishu.remote_dispatch = {};
|
|
802
|
+
cfg.feishu.remote_dispatch.secret = secret;
|
|
803
|
+
writeConfigSafe(cfg);
|
|
804
|
+
config = loadConfig();
|
|
805
|
+
await bot.sendMessage(chatId, `✅ 配对码已写入\n\n当前设备: ${rd.selfPeer}\nrelay chat: ${rd.chatId}\n现在可以测试 /dispatch to <peer:project> ...`);
|
|
806
|
+
return { handled: true, config };
|
|
807
|
+
}
|
|
808
|
+
|
|
625
809
|
// /dispatch to <agent> <prompt>
|
|
626
810
|
const toMatch = args.match(/^to\s+(\S+)\s+(.+)$/s);
|
|
627
811
|
if (toMatch) {
|
|
628
812
|
const targetName = toMatch[1];
|
|
629
813
|
const prompt = toMatch[2].trim();
|
|
814
|
+
const senderKey = resolveSenderKey(chatId, config);
|
|
630
815
|
|
|
631
|
-
//
|
|
632
|
-
const
|
|
633
|
-
if (
|
|
634
|
-
|
|
816
|
+
// Check for remote target (peer:project format)
|
|
817
|
+
const remoteTarget = parseRemoteTargetRef(targetName);
|
|
818
|
+
if (remoteTarget && deps.sendRemoteDispatch) {
|
|
819
|
+
const res = await deps.sendRemoteDispatch({
|
|
820
|
+
type: 'task',
|
|
821
|
+
to_peer: remoteTarget.peer,
|
|
822
|
+
target_project: remoteTarget.project,
|
|
823
|
+
prompt,
|
|
824
|
+
source_chat_id: String(chatId),
|
|
825
|
+
source_sender_key: senderKey,
|
|
826
|
+
source_sender_id: String(senderId || '').trim() || '',
|
|
827
|
+
}, config);
|
|
828
|
+
if (res.success) {
|
|
829
|
+
await bot.sendMessage(chatId, `📡 已发送给 ${remoteTarget.peer}:${remoteTarget.project}`);
|
|
830
|
+
} else {
|
|
831
|
+
await bot.sendMessage(chatId, `❌ 远端派发失败: ${res.error}`);
|
|
832
|
+
}
|
|
635
833
|
return { handled: true, config };
|
|
636
834
|
}
|
|
637
835
|
|
|
638
|
-
//
|
|
639
|
-
const
|
|
836
|
+
// Resolve target by project key or nickname (handles team members via compound key)
|
|
837
|
+
const resolved = resolveDispatchTarget(targetName, config.projects || {});
|
|
838
|
+
if (!resolved) {
|
|
839
|
+
await bot.sendMessage(chatId, `未找到 agent: ${targetName}\n可用: ${Object.keys(config.projects || {}).join(', ')}`);
|
|
840
|
+
return { handled: true, config };
|
|
841
|
+
}
|
|
842
|
+
const { dispatchKey: targetKey, projInfo } = resolved;
|
|
843
|
+
|
|
844
|
+
// Check if resolved target is a remote team member
|
|
845
|
+
if (projInfo.peer && deps.sendRemoteDispatch) {
|
|
846
|
+
const res = await deps.sendRemoteDispatch({
|
|
847
|
+
type: 'task',
|
|
848
|
+
to_peer: projInfo.peer,
|
|
849
|
+
target_project: targetKey,
|
|
850
|
+
prompt,
|
|
851
|
+
source_chat_id: String(chatId),
|
|
852
|
+
source_sender_key: senderKey,
|
|
853
|
+
source_sender_id: String(senderId || '').trim() || '',
|
|
854
|
+
}, config);
|
|
855
|
+
if (res.success) {
|
|
856
|
+
await bot.sendMessage(chatId, `📡 已发送给 ${projInfo.icon || '🤖'} ${projInfo.name || targetKey} (${projInfo.peer})`);
|
|
857
|
+
} else {
|
|
858
|
+
await bot.sendMessage(chatId, `❌ 远端派发失败: ${res.error}`);
|
|
859
|
+
}
|
|
860
|
+
return { handled: true, config };
|
|
861
|
+
}
|
|
640
862
|
|
|
641
|
-
const projInfo = config.projects[targetKey] || {};
|
|
642
863
|
// Find the target project's own Feishu chat (reverse lookup of chat_agent_map)
|
|
643
864
|
const feishuChatAgentMap = (config.feishu && config.feishu.chat_agent_map) || {};
|
|
644
865
|
const targetChatId = Object.entries(feishuChatAgentMap).find(([, v]) => v === targetKey)?.[0] || null;
|
|
@@ -659,10 +880,14 @@ function createAdminCommandHandler(deps) {
|
|
|
659
880
|
priority: 'normal',
|
|
660
881
|
payload: { title: prompt.slice(0, 60), prompt },
|
|
661
882
|
callback: false,
|
|
883
|
+
source_chat_id: String(chatId),
|
|
884
|
+
source_sender_key: senderKey,
|
|
885
|
+
source_sender_id: String(senderId || '').trim() || '',
|
|
662
886
|
}, config, replyFn, dispatchStreamOptions);
|
|
663
887
|
|
|
664
888
|
if (result.success) {
|
|
665
|
-
await bot.sendMessage(chatId, `✅
|
|
889
|
+
await bot.sendMessage(chatId, `✅ 已提交派发给 ${projInfo.name || targetName},等待回执…`);
|
|
890
|
+
await sendLocalDispatchReceipt(bot, chatId, targetKey, projInfo, result, prompt);
|
|
666
891
|
} else {
|
|
667
892
|
await bot.sendMessage(chatId, `❌ 派发失败: ${result.error}`);
|
|
668
893
|
}
|
|
@@ -673,7 +898,11 @@ function createAdminCommandHandler(deps) {
|
|
|
673
898
|
'用法:',
|
|
674
899
|
'/dispatch status — 查看状态',
|
|
675
900
|
'/dispatch log — 查看记录',
|
|
901
|
+
'/dispatch peers — 查看远端配置',
|
|
902
|
+
'/dispatch code — 生成 6 位配对码并写入本机',
|
|
903
|
+
'/dispatch pair <123456> — 输入 6 位配对码写入本机',
|
|
676
904
|
'/dispatch to <agent> <任务内容> — 直接跨 agent 派发',
|
|
905
|
+
'/dispatch to <peer:project> <任务内容> — 跨设备派发',
|
|
677
906
|
'/TeamTask create <agent> <目标> [--scope <id>] [--parent <id>] — 创建/续接 TeamTask',
|
|
678
907
|
'/TeamTask — 查看 TeamTask 列表',
|
|
679
908
|
].join('\n'));
|
|
@@ -691,42 +920,30 @@ function createAdminCommandHandler(deps) {
|
|
|
691
920
|
const targetName = msgMatch[1];
|
|
692
921
|
const message = msgMatch[2].trim();
|
|
693
922
|
|
|
694
|
-
// Resolve target
|
|
695
|
-
let targetKey = null;
|
|
923
|
+
// Resolve target by nickname or key (handles team members via compound key)
|
|
696
924
|
const senderKey = resolveSenderKey(chatId, config);
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
// Check if sender has a team
|
|
700
|
-
if (senderProj && Array.isArray(senderProj.team)) {
|
|
701
|
-
for (const member of senderProj.team) {
|
|
702
|
-
const nicks = Array.isArray(member.nicknames) ? member.nicknames : [];
|
|
703
|
-
if (member.key === targetName || nicks.some(n => n === targetName)) {
|
|
704
|
-
targetKey = member.key;
|
|
705
|
-
break;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
// Fall back to project lookup
|
|
710
|
-
if (!targetKey) {
|
|
711
|
-
targetKey = resolveProjectKey(targetName, config.projects || {});
|
|
712
|
-
}
|
|
925
|
+
const resolved = resolveDispatchTarget(targetName, config.projects || {});
|
|
713
926
|
|
|
714
|
-
if (!
|
|
927
|
+
if (!resolved) {
|
|
715
928
|
await bot.sendMessage(chatId, `未找到 agent: ${targetName}`);
|
|
716
929
|
return { handled: true, config };
|
|
717
930
|
}
|
|
931
|
+
const { dispatchKey: targetKey, projInfo: toProj } = resolved;
|
|
718
932
|
|
|
719
|
-
const toProj = config.projects[targetKey] || {};
|
|
720
933
|
const result = dispatchTask(targetKey, {
|
|
721
934
|
from: senderKey,
|
|
722
935
|
type: 'message',
|
|
723
936
|
priority: 'normal',
|
|
724
937
|
payload: { title: 'team message', prompt: `[来自团队的消息]\n\n${message}` },
|
|
725
938
|
callback: false,
|
|
939
|
+
source_chat_id: String(chatId),
|
|
940
|
+
source_sender_key: senderKey,
|
|
941
|
+
source_sender_id: String(senderId || '').trim() || '',
|
|
726
942
|
}, config, null, null);
|
|
727
943
|
|
|
728
944
|
if (result.success) {
|
|
729
945
|
await bot.sendMessage(chatId, `📬 已发送消息给 ${toProj.icon || '🤖'} ${toProj.name || targetKey}`);
|
|
946
|
+
await sendLocalDispatchReceipt(bot, chatId, targetKey, toProj, result, message);
|
|
730
947
|
} else {
|
|
731
948
|
await bot.sendMessage(chatId, `❌ 发送失败: ${result.error}`);
|
|
732
949
|
}
|
|
@@ -917,7 +1134,7 @@ function createAdminCommandHandler(deps) {
|
|
|
917
1134
|
const arg = text.slice('/mentor'.length).trim();
|
|
918
1135
|
|
|
919
1136
|
if (!arg || arg === 'status') {
|
|
920
|
-
const status = mentorEngine && typeof mentorEngine.getRuntimeStatus === 'function'
|
|
1137
|
+
const status = mentorCfg.enabled && mentorEngine && typeof mentorEngine.getRuntimeStatus === 'function'
|
|
921
1138
|
? mentorEngine.getRuntimeStatus()
|
|
922
1139
|
: { debt_count: 0, cooldown_remaining_ms: 0 };
|
|
923
1140
|
const mode = String(mentorCfg.mode || modeFromLevel(mentorCfg.friction_level));
|
|
@@ -937,6 +1154,9 @@ function createAdminCommandHandler(deps) {
|
|
|
937
1154
|
|
|
938
1155
|
if (arg === 'on' || arg === 'off') {
|
|
939
1156
|
mentorCfg.enabled = arg === 'on';
|
|
1157
|
+
if (!mentorCfg.enabled && mentorEngine && typeof mentorEngine.clearRuntime === 'function') {
|
|
1158
|
+
mentorEngine.clearRuntime();
|
|
1159
|
+
}
|
|
940
1160
|
writeConfigSafe(cfg);
|
|
941
1161
|
config = loadConfig();
|
|
942
1162
|
await bot.sendMessage(chatId, mentorCfg.enabled
|
|
@@ -1239,21 +1459,30 @@ function createAdminCommandHandler(deps) {
|
|
|
1239
1459
|
// Switching engine auto-syncs: distill model + preferred provider (if available)
|
|
1240
1460
|
if (text === '/engine' || text.startsWith('/engine ')) {
|
|
1241
1461
|
const arg = text.slice('/engine'.length).trim().toLowerCase();
|
|
1462
|
+
const boundProjectKey = resolveBoundProjectKey(chatId, config);
|
|
1463
|
+
const boundProject = boundProjectKey && config && config.projects ? config.projects[boundProjectKey] : null;
|
|
1242
1464
|
if (!arg) {
|
|
1243
|
-
const cur = getDefaultEngine();
|
|
1244
|
-
const
|
|
1245
|
-
const
|
|
1465
|
+
const cur = boundProject && boundProject.engine ? String(boundProject.engine).trim().toLowerCase() : getDefaultEngine();
|
|
1466
|
+
const safeCur = cur === 'codex' ? 'codex' : 'claude';
|
|
1467
|
+
const curEngineCfg = ENGINE_MODEL_CONFIG[safeCur] || ENGINE_MODEL_CONFIG.claude;
|
|
1468
|
+
const activeProvider = (safeCur === 'claude' && providerMod)
|
|
1246
1469
|
? providerMod.getActiveName()
|
|
1247
1470
|
: curEngineCfg.provider;
|
|
1248
1471
|
const distill = getDistillModel();
|
|
1249
1472
|
const daemonCfg = config.daemon || {};
|
|
1250
|
-
const currentModel = resolveEngineModel(
|
|
1473
|
+
const currentModel = resolveEngineModel(safeCur, daemonCfg, boundProject && boundProject.model);
|
|
1474
|
+
const scopeLine = boundProjectKey
|
|
1475
|
+
? `📍 当前 chat 绑定 Agent: ${boundProjectKey}`
|
|
1476
|
+
: `📍 当前 chat 使用全局默认引擎`;
|
|
1251
1477
|
await bot.sendMessage(chatId, [
|
|
1252
|
-
`🔧 引擎: ${
|
|
1478
|
+
`🔧 引擎: ${safeCur} | Provider: ${activeProvider}`,
|
|
1253
1479
|
`🤖 会话模型: ${currentModel} | 后台轻量: ${distill}`,
|
|
1480
|
+
scopeLine,
|
|
1254
1481
|
'',
|
|
1255
1482
|
'用法: /engine claude 或 /engine codex',
|
|
1256
|
-
|
|
1483
|
+
boundProjectKey
|
|
1484
|
+
? '当前 chat 已绑定 Agent;切换时会同步更新该 Agent 的 engine/model'
|
|
1485
|
+
: '切换引擎将自动同步 distill model 和首选 provider',
|
|
1257
1486
|
].join('\n'));
|
|
1258
1487
|
return { handled: true, config };
|
|
1259
1488
|
}
|
|
@@ -1266,13 +1495,29 @@ function createAdminCommandHandler(deps) {
|
|
|
1266
1495
|
|
|
1267
1496
|
setDefaultEngine(arg); // syncs distill model + providerMod.setEngine (no longer resets session model)
|
|
1268
1497
|
const distill = getDistillModel();
|
|
1269
|
-
|
|
1498
|
+
let freshCfg = loadConfig();
|
|
1499
|
+
if (boundProjectKey && freshCfg && freshCfg.projects && freshCfg.projects[boundProjectKey]) {
|
|
1500
|
+
const nextCfg = JSON.parse(JSON.stringify(freshCfg));
|
|
1501
|
+
nextCfg.projects[boundProjectKey].engine = arg;
|
|
1502
|
+
nextCfg.projects[boundProjectKey].model = resolveEngineModel(arg, nextCfg.daemon || {});
|
|
1503
|
+
writeConfigSafe(nextCfg);
|
|
1504
|
+
freshCfg = loadConfig();
|
|
1505
|
+
}
|
|
1270
1506
|
const freshDaemon = freshCfg.daemon || {};
|
|
1271
|
-
const syncedModel = resolveEngineModel(
|
|
1507
|
+
const syncedModel = resolveEngineModel(
|
|
1508
|
+
arg,
|
|
1509
|
+
freshDaemon,
|
|
1510
|
+
boundProjectKey && freshCfg.projects && freshCfg.projects[boundProjectKey]
|
|
1511
|
+
? freshCfg.projects[boundProjectKey].model
|
|
1512
|
+
: ''
|
|
1513
|
+
);
|
|
1272
1514
|
|
|
1273
|
-
// Auto-switch provider
|
|
1515
|
+
// Auto-switch provider only for Claude-compatible routing.
|
|
1516
|
+
// Codex auth is handled by `codex login` / `OPENAI_API_KEY`, not providers.yaml.
|
|
1274
1517
|
let providerNote = '';
|
|
1275
|
-
if (
|
|
1518
|
+
if (arg === 'codex') {
|
|
1519
|
+
providerNote = '\n🔌 Codex 认证: 使用 `codex login` 或 OPENAI_API_KEY(/provider 不参与 Codex 路由)';
|
|
1520
|
+
} else if (providerMod && preferredProvider) {
|
|
1276
1521
|
try {
|
|
1277
1522
|
providerMod.setActive(preferredProvider);
|
|
1278
1523
|
providerNote = `\n🔌 Provider 已同步: ${preferredProvider}`;
|
|
@@ -1283,8 +1528,11 @@ function createAdminCommandHandler(deps) {
|
|
|
1283
1528
|
}
|
|
1284
1529
|
}
|
|
1285
1530
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1531
|
+
const scopeNote = boundProjectKey
|
|
1532
|
+
? `\n📍 已同步当前 Agent: ${boundProjectKey}`
|
|
1533
|
+
: '';
|
|
1534
|
+
await bot.sendMessage(chatId, `✅ 引擎已切换: ${arg}\n🤖 会话模型: ${syncedModel}\n🧪 后台轻量模型: ${distill}${scopeNote}${providerNote}`);
|
|
1535
|
+
return { handled: true, config: freshCfg };
|
|
1288
1536
|
}
|
|
1289
1537
|
|
|
1290
1538
|
// /distill-model [name] — show or update distill model
|