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 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. Only fire when you're idle they never interrupt active work:
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 # 2h · scan sessions → extract long-term facts + topic tags
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
- sleep mode → memory consolidation
323
- (background, automatic)
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-mode memory triggers.
328
- - **Distillation** (`scripts/distill.js`): On each launch, silently analyzes your recent messages and updates your profile.
329
- - **Memory Extract** (`scripts/memory-extract.js`): Triggered on sleep mode. Extracts long-term facts and session topic tags from completed conversations.
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 triggers on sleep mode (30-min idle), summaries trigger once per idle session.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metame-cli",
3
- "version": "1.4.13",
3
+ "version": "1.4.15",
4
4
  "description": "The Cognitive Profile Layer for Claude Code. Knows how you think, not just what you said.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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
- return { ok: true, data: { cwd: agentCwd } };
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 (skill && PINNED_SKILL_SESSIONS.has(skill)) {
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
- // Auto-attach to most recent Claude session (unified session management)
498
- const recent = listRecentSessions(1);
499
- if (recent.length > 0 && recent[0].sessionId && recent[0].projectPath) {
500
- const target = recent[0];
501
- const state = loadState();
502
- state.sessions[chatId] = {
503
- id: target.sessionId,
504
- cwd: target.projectPath,
505
- started: true,
506
- };
507
- saveState(state);
508
- session = state.sessions[chatId];
509
- log('INFO', `Auto-attached ${chatId} to recent session: ${target.sessionId.slice(0, 8)} (${path.basename(target.projectPath)})`);
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
- session = createSession(chatId);
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(/(?:~\/|\/)[^\s,。;;!!??"“”'‘’`]+/);
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, '请补充工作目录,例如:`给这个群创建一个 Agent,目录是 ~/projects/foo`');
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,