metame-cli 1.4.13 → 1.4.15
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 +9 -9
- package/package.json +1 -1
- package/scripts/daemon-agent-commands.js +27 -3
- package/scripts/daemon-claude-engine.js +116 -15
- package/scripts/daemon-command-router.js +27 -6
- package/scripts/daemon.js +12 -8
package/README.md
CHANGED
|
@@ -105,11 +105,11 @@ Built into the daemon. Runs every 60 seconds regardless of what's in your config
|
|
|
105
105
|
- Detects when you go idle → generates session continuity summaries
|
|
106
106
|
|
|
107
107
|
**Layer 1 — System Evolution (built-in defaults)**
|
|
108
|
-
Three tasks shipped out of the box.
|
|
108
|
+
Three tasks shipped out of the box. They are precondition-gated and run only when useful:
|
|
109
109
|
|
|
110
110
|
```yaml
|
|
111
111
|
- cognitive-distill # 4h · has signals? → distill preferences into profile
|
|
112
|
-
- memory-extract #
|
|
112
|
+
- memory-extract # 4h · scan sessions → extract long-term facts + topic tags
|
|
113
113
|
- skill-evolve # 6h · has signals? → evolve skills from task outcomes
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -150,7 +150,7 @@ Chain skills into multi-step workflows — research → write → publish — fu
|
|
|
150
150
|
prompt: "Publish it"
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
Task options: `require_idle` (defer when you're active), `precondition` (shell guard — skip if false, zero tokens), `notify` (push result to phone), `model`, `cwd`, `allowedTools`, `timeout`.
|
|
153
|
+
Task options: `require_idle` (defer when you're active, retry on next heartbeat tick), `precondition` (shell guard — skip if false, zero tokens), `notify` (push result to phone), `model`, `cwd`, `allowedTools`, `timeout`.
|
|
154
154
|
|
|
155
155
|
### 5. Skills That Evolve Themselves
|
|
156
156
|
|
|
@@ -319,14 +319,14 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
|
|
|
319
319
|
│ (memory layer) ← NEW │
|
|
320
320
|
└──────────────────────────────┘
|
|
321
321
|
↑
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
idle mode → summaries + background memory tasks
|
|
323
|
+
(automatic, precondition-gated)
|
|
324
324
|
```
|
|
325
325
|
|
|
326
326
|
- **Profile** (`~/.claude_profile.yaml`): Your cognitive fingerprint. Injected into every Claude session via `CLAUDE.md`.
|
|
327
|
-
- **Daemon** (`scripts/daemon.js`): Background process handling Telegram/Feishu messages, heartbeat tasks, Unix socket dispatch, and sleep
|
|
328
|
-
- **Distillation** (`scripts/distill.js`):
|
|
329
|
-
- **Memory Extract** (`scripts/memory-extract.js`):
|
|
327
|
+
- **Daemon** (`scripts/daemon.js`): Background process handling Telegram/Feishu messages, heartbeat tasks, Unix socket dispatch, and idle/sleep transitions.
|
|
328
|
+
- **Distillation** (`scripts/distill.js`): Heartbeat task (default 4h, signal-gated) that updates your profile.
|
|
329
|
+
- **Memory Extract** (`scripts/memory-extract.js`): Heartbeat task (default 4h, idle-gated) that extracts long-term facts and session topic tags.
|
|
330
330
|
- **Session Summarize** (`scripts/session-summarize.js`): Generates a 2-4 sentence summary for idle sessions. Injected as context when resuming after a 2h+ gap.
|
|
331
331
|
|
|
332
332
|
## Security
|
|
@@ -349,7 +349,7 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
|
|
|
349
349
|
| Session summary (per session) | ~400–900 tokens input + ≤250 tokens output (Haiku) |
|
|
350
350
|
| Mobile commands (`/stop`, `/list`, `/undo`) | 0 tokens |
|
|
351
351
|
|
|
352
|
-
> Both memory consolidation and session summarization run in the background via Haiku (`--model haiku`). Input is capped by code: skeleton text ≤ 3,000 chars, summary output ≤ 500 chars. Neither runs per-message — memory consolidation
|
|
352
|
+
> Both memory consolidation and session summarization run in the background via Haiku (`--model haiku`). Input is capped by code: skeleton text ≤ 3,000 chars, summary output ≤ 500 chars. Neither runs per-message — memory consolidation follows heartbeat schedule with idle/precondition guards, and summaries trigger once per idle session on sleep-mode transitions.
|
|
353
353
|
|
|
354
354
|
## Plugin
|
|
355
355
|
|
package/package.json
CHANGED
|
@@ -23,6 +23,7 @@ function createAgentCommandHandler(deps) {
|
|
|
23
23
|
doBindAgent,
|
|
24
24
|
mergeAgentRole,
|
|
25
25
|
agentTools,
|
|
26
|
+
attachOrCreateSession,
|
|
26
27
|
agentFlowTtlMs,
|
|
27
28
|
agentBindTtlMs,
|
|
28
29
|
} = deps;
|
|
@@ -99,13 +100,30 @@ function createAgentCommandHandler(deps) {
|
|
|
99
100
|
const icon = p.icon || '🤖';
|
|
100
101
|
const action = res.data.isNewProject ? '绑定成功' : '重新绑定';
|
|
101
102
|
const displayCwd = String(res.data.cwd || '').replace(HOME, '~');
|
|
103
|
+
if (res.data.cwd && typeof attachOrCreateSession === 'function') {
|
|
104
|
+
attachOrCreateSession(chatId, normalizeCwd(res.data.cwd), p.name || agentName || res.data.projectKey || '');
|
|
105
|
+
}
|
|
102
106
|
await bot.sendMessage(chatId, `${icon} ${p.name || agentName} ${action}\n目录: ${displayCwd}`);
|
|
103
107
|
return { ok: true, data: res.data };
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
// Backward-compatible fallback
|
|
107
|
-
await doBindAgent(bot, chatId, agentName, agentCwd);
|
|
108
|
-
|
|
111
|
+
const fallback = await doBindAgent(bot, chatId, agentName, agentCwd);
|
|
112
|
+
if (!fallback || fallback.ok === false) {
|
|
113
|
+
return { ok: false, error: (fallback && fallback.error) || 'bind failed' };
|
|
114
|
+
}
|
|
115
|
+
const fallbackCwd = (fallback.data && fallback.data.cwd) || agentCwd;
|
|
116
|
+
if (fallbackCwd && typeof attachOrCreateSession === 'function') {
|
|
117
|
+
attachOrCreateSession(chatId, normalizeCwd(fallbackCwd), agentName || '');
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
data: {
|
|
122
|
+
cwd: fallbackCwd,
|
|
123
|
+
projectKey: fallback && fallback.data ? fallback.data.projectKey : null,
|
|
124
|
+
project: fallback && fallback.data ? fallback.data.project : null,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
109
127
|
}
|
|
110
128
|
|
|
111
129
|
async function editRoleViaUnifiedApi(workspaceDir, deltaText) {
|
|
@@ -121,7 +139,10 @@ function createAgentCommandHandler(deps) {
|
|
|
121
139
|
if (agentTools && typeof agentTools.createNewWorkspaceAgent === 'function') {
|
|
122
140
|
return agentTools.createNewWorkspaceAgent(name, dir, roleDesc, chatId);
|
|
123
141
|
}
|
|
124
|
-
await doBindAgent({ sendMessage: async () => {} }, chatId, name, dir);
|
|
142
|
+
const bound = await doBindAgent({ sendMessage: async () => {} }, chatId, name, dir);
|
|
143
|
+
if (!bound || bound.ok === false) {
|
|
144
|
+
return { ok: false, error: (bound && bound.error) || 'bind failed' };
|
|
145
|
+
}
|
|
125
146
|
const merged = await mergeAgentRole(dir, roleDesc);
|
|
126
147
|
if (merged.error) return { ok: false, error: merged.error };
|
|
127
148
|
return { ok: true, data: { cwd: dir, project: { name }, role: merged } };
|
|
@@ -252,6 +273,9 @@ function createAgentCommandHandler(deps) {
|
|
|
252
273
|
await bot.sendMessage(chatId, `❌ 创建 Agent 失败: ${created.error}`);
|
|
253
274
|
return true;
|
|
254
275
|
}
|
|
276
|
+
if (created.data && created.data.cwd && typeof attachOrCreateSession === 'function') {
|
|
277
|
+
attachOrCreateSession(chatId, normalizeCwd(created.data.cwd), name || '');
|
|
278
|
+
}
|
|
255
279
|
const roleInfo = created.data.role || {};
|
|
256
280
|
if (roleInfo.skipped) {
|
|
257
281
|
await bot.sendMessage(chatId, '✅ Agent 创建成功');
|
|
@@ -25,6 +25,7 @@ function createClaudeEngine(deps) {
|
|
|
25
25
|
normalizeCwd,
|
|
26
26
|
isContentFile,
|
|
27
27
|
sendFileButtons,
|
|
28
|
+
findSessionFile,
|
|
28
29
|
listRecentSessions,
|
|
29
30
|
getSession,
|
|
30
31
|
createSession,
|
|
@@ -38,6 +39,70 @@ function createClaudeEngine(deps) {
|
|
|
38
39
|
statusThrottleMs = 3000,
|
|
39
40
|
fallbackThrottleMs = 8000,
|
|
40
41
|
} = deps;
|
|
42
|
+
const SESSION_CWD_VALIDATION_TTL_MS = 30 * 1000;
|
|
43
|
+
const _sessionCwdValidationCache = new Map(); // key: `${sessionId}@@${cwd}` -> { inCwd, ts }
|
|
44
|
+
|
|
45
|
+
function decodeProjectDirName(dirName) {
|
|
46
|
+
const raw = String(dirName || '');
|
|
47
|
+
if (!raw) return '';
|
|
48
|
+
if (raw.startsWith('-')) return '/' + raw.slice(1).replace(/-/g, '/');
|
|
49
|
+
return raw.replace(/-/g, '/');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function cacheSessionCwdValidation(cacheKey, inCwd) {
|
|
53
|
+
_sessionCwdValidationCache.set(cacheKey, { inCwd: !!inCwd, ts: Date.now() });
|
|
54
|
+
if (_sessionCwdValidationCache.size > 512) {
|
|
55
|
+
const firstKey = _sessionCwdValidationCache.keys().next().value;
|
|
56
|
+
if (firstKey) _sessionCwdValidationCache.delete(firstKey);
|
|
57
|
+
}
|
|
58
|
+
return !!inCwd;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isSessionInCwd(sessionId, cwd) {
|
|
62
|
+
const safeSessionId = String(sessionId || '').trim();
|
|
63
|
+
if (!safeSessionId || !cwd) return false;
|
|
64
|
+
|
|
65
|
+
const normCwd = normalizeCwd(cwd);
|
|
66
|
+
const cacheKey = `${safeSessionId}@@${normCwd}`;
|
|
67
|
+
const cached = _sessionCwdValidationCache.get(cacheKey);
|
|
68
|
+
if (cached && (Date.now() - cached.ts) < SESSION_CWD_VALIDATION_TTL_MS) {
|
|
69
|
+
return !!cached.inCwd;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// Fast path: locate the exact session file, then validate its indexed projectPath.
|
|
74
|
+
if (typeof findSessionFile === 'function') {
|
|
75
|
+
const sessionFile = findSessionFile(safeSessionId);
|
|
76
|
+
if (!sessionFile) return cacheSessionCwdValidation(cacheKey, false);
|
|
77
|
+
|
|
78
|
+
const projectDir = path.dirname(sessionFile);
|
|
79
|
+
const indexFile = path.join(projectDir, 'sessions-index.json');
|
|
80
|
+
if (fs.existsSync(indexFile)) {
|
|
81
|
+
const data = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|
82
|
+
const entries = Array.isArray(data && data.entries) ? data.entries : [];
|
|
83
|
+
const entry = entries.find(e => e && e.sessionId === safeSessionId);
|
|
84
|
+
if (entry && entry.projectPath) {
|
|
85
|
+
return cacheSessionCwdValidation(cacheKey, normalizeCwd(entry.projectPath) === normCwd);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Fallback: infer from encoded Claude project folder name.
|
|
90
|
+
const inferredPath = decodeProjectDirName(path.basename(projectDir));
|
|
91
|
+
if (inferredPath) {
|
|
92
|
+
return cacheSessionCwdValidation(cacheKey, normalizeCwd(inferredPath) === normCwd);
|
|
93
|
+
}
|
|
94
|
+
return cacheSessionCwdValidation(cacheKey, false);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Ultimate fallback (legacy path): scoped scan in target cwd.
|
|
98
|
+
const recentInCwd = listRecentSessions(1000, normCwd);
|
|
99
|
+
const existsInCwd = recentInCwd.some(s => s.sessionId === safeSessionId);
|
|
100
|
+
return cacheSessionCwdValidation(cacheKey, existsInCwd);
|
|
101
|
+
} catch {
|
|
102
|
+
// Conservative fallback: if validation infra fails, avoid false positives by preserving current session.
|
|
103
|
+
return cacheSessionCwdValidation(cacheKey, true);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
41
106
|
|
|
42
107
|
/**
|
|
43
108
|
* Parse [[FILE:...]] markers from Claude output.
|
|
@@ -467,13 +532,19 @@ Reply with ONLY the name, nothing else. Examples: 插件开发, API重构, Bug
|
|
|
467
532
|
// Skill routing: detect skill first, then decide session
|
|
468
533
|
// BUT: if agent was explicitly addressed by nickname, don't let skill routing hijack the session
|
|
469
534
|
const skill = agentMatch ? null : routeSkill(prompt);
|
|
535
|
+
const chatIdStr = String(chatId);
|
|
536
|
+
const chatAgentMap = { ...(config.telegram ? config.telegram.chat_agent_map : {}), ...(config.feishu ? config.feishu.chat_agent_map : {}) };
|
|
537
|
+
const boundProjectKey = chatAgentMap[chatIdStr] || (chatIdStr.startsWith('_agent_') ? chatIdStr.slice(7) : null);
|
|
538
|
+
const boundProject = boundProjectKey && config.projects ? config.projects[boundProjectKey] : null;
|
|
539
|
+
const boundCwd = (boundProject && boundProject.cwd) ? normalizeCwd(boundProject.cwd) : null;
|
|
470
540
|
|
|
471
541
|
// Skills with dedicated pinned sessions (reused across days, no re-injection needed)
|
|
472
542
|
const PINNED_SKILL_SESSIONS = new Set(['macos-mail-calendar', 'skill-manager']);
|
|
543
|
+
const usePinnedSkillSession = !!(skill && PINNED_SKILL_SESSIONS.has(skill));
|
|
473
544
|
|
|
474
545
|
let session = getSession(chatId);
|
|
475
546
|
|
|
476
|
-
if (
|
|
547
|
+
if (usePinnedSkillSession) {
|
|
477
548
|
// Use a dedicated long-lived session per skill
|
|
478
549
|
const state = loadState();
|
|
479
550
|
if (!state.pinned_sessions) state.pinned_sessions = {};
|
|
@@ -494,21 +565,51 @@ Reply with ONLY the name, nothing else. Examples: 插件开发, API重构, Bug
|
|
|
494
565
|
log('INFO', `Pinned session created for skill ${skill}: ${session.id.slice(0, 8)}`);
|
|
495
566
|
}
|
|
496
567
|
} else if (!session) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
568
|
+
if (boundCwd) {
|
|
569
|
+
// Agent-bound chats must stay in their own workspace: never attach to another project's session.
|
|
570
|
+
const recentInBound = listRecentSessions(1, boundCwd);
|
|
571
|
+
if (recentInBound.length > 0 && recentInBound[0].sessionId) {
|
|
572
|
+
const target = recentInBound[0];
|
|
573
|
+
const state = loadState();
|
|
574
|
+
state.sessions[chatId] = {
|
|
575
|
+
id: target.sessionId,
|
|
576
|
+
cwd: boundCwd,
|
|
577
|
+
started: true,
|
|
578
|
+
};
|
|
579
|
+
saveState(state);
|
|
580
|
+
session = state.sessions[chatId];
|
|
581
|
+
log('INFO', `Auto-attached ${chatId} to bound-session: ${target.sessionId.slice(0, 8)} (${path.basename(boundCwd)})`);
|
|
582
|
+
} else {
|
|
583
|
+
session = createSession(chatId, boundCwd, boundProject && boundProject.name ? boundProject.name : '');
|
|
584
|
+
log('INFO', `Created fresh session for bound workspace: ${path.basename(boundCwd)}`);
|
|
585
|
+
}
|
|
510
586
|
} else {
|
|
511
|
-
|
|
587
|
+
// Non-bound chats keep legacy behavior: attach global recent, else create.
|
|
588
|
+
const recent = listRecentSessions(1);
|
|
589
|
+
if (recent.length > 0 && recent[0].sessionId && recent[0].projectPath) {
|
|
590
|
+
const target = recent[0];
|
|
591
|
+
const state = loadState();
|
|
592
|
+
state.sessions[chatId] = {
|
|
593
|
+
id: target.sessionId,
|
|
594
|
+
cwd: target.projectPath,
|
|
595
|
+
started: true,
|
|
596
|
+
};
|
|
597
|
+
saveState(state);
|
|
598
|
+
session = state.sessions[chatId];
|
|
599
|
+
log('INFO', `Auto-attached ${chatId} to recent session: ${target.sessionId.slice(0, 8)} (${path.basename(target.projectPath)})`);
|
|
600
|
+
} else {
|
|
601
|
+
session = createSession(chatId);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Safety guard: prevent stale state from resuming another workspace's session.
|
|
607
|
+
if (!usePinnedSkillSession && session && session.started && session.id && session.id !== '__continue__' && session.cwd) {
|
|
608
|
+
const sessionCwd = normalizeCwd(session.cwd);
|
|
609
|
+
const existsInCwd = isSessionInCwd(session.id, sessionCwd);
|
|
610
|
+
if (!existsInCwd) {
|
|
611
|
+
log('WARN', `Session mismatch detected for ${chatId}: ${session.id.slice(0, 8)} not found in ${sessionCwd}; creating fresh session`);
|
|
612
|
+
session = createSession(chatId, sessionCwd, boundProject && boundProject.name ? boundProject.name : '');
|
|
512
613
|
}
|
|
513
614
|
}
|
|
514
615
|
|
|
@@ -6,6 +6,7 @@ function createCommandRouter(deps) {
|
|
|
6
6
|
loadConfig,
|
|
7
7
|
checkBudget,
|
|
8
8
|
checkCooldown,
|
|
9
|
+
resetCooldown,
|
|
9
10
|
routeAgent,
|
|
10
11
|
normalizeCwd,
|
|
11
12
|
attachOrCreateSession,
|
|
@@ -58,7 +59,7 @@ function createCommandRouter(deps) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
function extractPathFromText(input) {
|
|
61
|
-
const m = String(input || '').match(/(
|
|
62
|
+
const m = String(input || '').match(/(?:~\/|\/|\.\/|\.\.\/)[^\s,。;;!!??"“”'‘’`]+/);
|
|
62
63
|
if (!m) return '';
|
|
63
64
|
return m[0].replace(/[,。;;!!??]+$/, '');
|
|
64
65
|
}
|
|
@@ -100,6 +101,18 @@ function createCommandRouter(deps) {
|
|
|
100
101
|
return '';
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
function isLikelyDirectAgentAction(input) {
|
|
105
|
+
const text = String(input || '').trim();
|
|
106
|
+
return /^(?:请|帮我|麻烦|给我|给这个群|给当前群|在这个群|把这个群|把当前群|将这个群|这个群|当前群|本群|群里|我想|我要|我需要|创建|新建|新增|搞一个|加一个|create|bind|绑定|列出|查看|显示|有哪些|解绑|取消绑定|断开绑定|修改|调整)/i.test(text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function looksLikeAgentIssueReport(input) {
|
|
110
|
+
const text = String(input || '').trim();
|
|
111
|
+
const hasIssueWords = /(用户反馈|反馈|报错|bug|问题|故障|异常|修复|改一下|修一下|任务|工单|代码)/i.test(text);
|
|
112
|
+
const hasAgentWords = /(agent|智能体|session|会话|目录|工作区|绑定|切换)/i.test(text);
|
|
113
|
+
return hasIssueWords && hasAgentWords;
|
|
114
|
+
}
|
|
115
|
+
|
|
103
116
|
function projectNameFromResult(data, fallbackName) {
|
|
104
117
|
if (data && data.project && data.project.name) return data.project.name;
|
|
105
118
|
if (data && data.projectKey) return data.projectKey;
|
|
@@ -122,6 +135,11 @@ function createCommandRouter(deps) {
|
|
|
122
135
|
if (hasFreshPendingFlow(key) || hasFreshPendingFlow(key + ':edit')) return false;
|
|
123
136
|
const input = text.trim();
|
|
124
137
|
if (!input) return false;
|
|
138
|
+
const directAction = isLikelyDirectAgentAction(input);
|
|
139
|
+
const issueReport = looksLikeAgentIssueReport(input);
|
|
140
|
+
if (issueReport && !directAction) return false;
|
|
141
|
+
const workspaceDir = extractPathFromText(input);
|
|
142
|
+
const hasWorkspacePath = !!workspaceDir;
|
|
125
143
|
|
|
126
144
|
const hasAgentContext = /(agent|智能体|工作区|人设|绑定|当前群|这个群|chat|workspace)/i.test(input);
|
|
127
145
|
const wantsList = /(列出|查看|显示|有哪些|list|show)/i.test(input) && /(agent|智能体|工作区|绑定)/i.test(input);
|
|
@@ -130,10 +148,10 @@ function createCommandRouter(deps) {
|
|
|
130
148
|
((/(角色|职责|人设)/i.test(input) && /(改|修改|调整|更新|变成|改成|改为)/i.test(input)) ||
|
|
131
149
|
/(把这个agent|把当前agent|当前群.*角色|当前群.*职责)/i.test(input));
|
|
132
150
|
const wantsCreate =
|
|
133
|
-
(/(创建|新建|新增|搞一个|加一个|create)/i.test(input) && /(agent|智能体|人设|工作区)/i.test(input));
|
|
151
|
+
(/(创建|新建|新增|搞一个|加一个|create)/i.test(input) && /(agent|智能体|人设|工作区)/i.test(input) && (directAction || hasWorkspacePath));
|
|
134
152
|
const wantsBind =
|
|
135
153
|
!wantsCreate &&
|
|
136
|
-
(/(绑定|bind)/i.test(input) && hasAgentContext);
|
|
154
|
+
(/(绑定|bind)/i.test(input) && hasAgentContext && (directAction || hasWorkspacePath));
|
|
137
155
|
|
|
138
156
|
if (!wantsList && !wantsUnbind && !wantsEditRole && !wantsCreate && !wantsBind) {
|
|
139
157
|
return false;
|
|
@@ -194,9 +212,12 @@ function createCommandRouter(deps) {
|
|
|
194
212
|
}
|
|
195
213
|
|
|
196
214
|
if (wantsCreate) {
|
|
197
|
-
const workspaceDir = extractPathFromText(input);
|
|
198
215
|
if (!workspaceDir) {
|
|
199
|
-
await bot.sendMessage(chatId,
|
|
216
|
+
await bot.sendMessage(chatId, [
|
|
217
|
+
'我可以帮你创建并绑定 Agent,还差一个工作目录。',
|
|
218
|
+
'例如:`给这个群创建一个 Agent,目录是 ~/projects/foo`',
|
|
219
|
+
'也可以直接回我一个路径(`~/`、`/`、`./`、`../` 开头都行)。',
|
|
220
|
+
].join('\n'));
|
|
200
221
|
return true;
|
|
201
222
|
}
|
|
202
223
|
const agentName = deriveAgentName(input, workspaceDir);
|
|
@@ -214,7 +235,6 @@ function createCommandRouter(deps) {
|
|
|
214
235
|
}
|
|
215
236
|
|
|
216
237
|
if (wantsBind) {
|
|
217
|
-
const workspaceDir = extractPathFromText(input);
|
|
218
238
|
const agentName = deriveAgentName(input, workspaceDir);
|
|
219
239
|
const res = await agentTools.bindAgentToChat(chatId, agentName, workspaceDir || null);
|
|
220
240
|
if (!res.ok) {
|
|
@@ -357,6 +377,7 @@ function createCommandRouter(deps) {
|
|
|
357
377
|
if (msgs.length === 0) return;
|
|
358
378
|
const combined = msgs.join('\n');
|
|
359
379
|
log('INFO', `Processing ${msgs.length} queued message(s) for ${chatId}`);
|
|
380
|
+
resetCooldown(chatId); // queued msgs already waited, skip cooldown
|
|
360
381
|
try {
|
|
361
382
|
await handleCommand(bot, chatId, combined, config, executeTaskByName);
|
|
362
383
|
} catch (e) {
|
package/scripts/daemon.js
CHANGED
|
@@ -728,6 +728,10 @@ function checkCooldown(chatId) {
|
|
|
728
728
|
return { ok: true };
|
|
729
729
|
}
|
|
730
730
|
|
|
731
|
+
function resetCooldown(chatId) {
|
|
732
|
+
delete _lastClaudeCall[chatId];
|
|
733
|
+
}
|
|
734
|
+
|
|
731
735
|
// Path shortener — imported from ./utils
|
|
732
736
|
const { shortenPath, expandPath } = createPathMap();
|
|
733
737
|
const {
|
|
@@ -753,14 +757,6 @@ function attachOrCreateSession(chatId, projCwd, name) {
|
|
|
753
757
|
const state = loadState();
|
|
754
758
|
// Virtual agent chatIds (_agent_*) always get a fresh one-shot session.
|
|
755
759
|
// They must not resume real sessions, to avoid concurrency conflicts.
|
|
756
|
-
if (!String(chatId).startsWith('_agent_')) {
|
|
757
|
-
const recent = listRecentSessions(1, projCwd);
|
|
758
|
-
if (recent.length > 0 && recent[0].sessionId) {
|
|
759
|
-
state.sessions[chatId] = { id: recent[0].sessionId, cwd: projCwd, started: true };
|
|
760
|
-
saveState(state);
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
760
|
const newSess = createSession(chatId, projCwd, name || '');
|
|
765
761
|
state.sessions[chatId] = { id: newSess.id, cwd: projCwd, started: false };
|
|
766
762
|
saveState(state);
|
|
@@ -867,8 +863,13 @@ async function doBindAgent(bot, chatId, agentName, agentCwd) {
|
|
|
867
863
|
} else {
|
|
868
864
|
await bot.sendMessage(chatId, `${icon} ${agentName} ${action}\n目录: ${displayCwd}`);
|
|
869
865
|
}
|
|
866
|
+
return {
|
|
867
|
+
ok: true,
|
|
868
|
+
data: { projectKey, cwd: agentCwd, isNewProject: isNew, project: cfg.projects[projectKey] },
|
|
869
|
+
};
|
|
870
870
|
} catch (e) {
|
|
871
871
|
await bot.sendMessage(chatId, `❌ 绑定失败: ${e.message}`);
|
|
872
|
+
return { ok: false, error: e.message };
|
|
872
873
|
}
|
|
873
874
|
}
|
|
874
875
|
|
|
@@ -1086,6 +1087,7 @@ const { spawnClaudeAsync, askClaude } = createClaudeEngine({
|
|
|
1086
1087
|
normalizeCwd,
|
|
1087
1088
|
isContentFile,
|
|
1088
1089
|
sendFileButtons,
|
|
1090
|
+
findSessionFile,
|
|
1089
1091
|
listRecentSessions,
|
|
1090
1092
|
getSession,
|
|
1091
1093
|
createSession,
|
|
@@ -1152,6 +1154,7 @@ const { handleAgentCommand } = createAgentCommandHandler({
|
|
|
1152
1154
|
doBindAgent,
|
|
1153
1155
|
mergeAgentRole,
|
|
1154
1156
|
agentTools,
|
|
1157
|
+
attachOrCreateSession,
|
|
1155
1158
|
agentFlowTtlMs: getAgentFlowTtlMs,
|
|
1156
1159
|
agentBindTtlMs: getAgentBindTtlMs,
|
|
1157
1160
|
});
|
|
@@ -1207,6 +1210,7 @@ const { handleCommand } = createCommandRouter({
|
|
|
1207
1210
|
loadConfig,
|
|
1208
1211
|
checkBudget,
|
|
1209
1212
|
checkCooldown,
|
|
1213
|
+
resetCooldown,
|
|
1210
1214
|
routeAgent,
|
|
1211
1215
|
normalizeCwd,
|
|
1212
1216
|
attachOrCreateSession,
|