metame-cli 1.5.11 → 1.5.12
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/index.js +64 -7
- package/package.json +3 -2
- package/scripts/daemon-agent-commands.js +6 -2
- package/scripts/daemon-bridges.js +23 -9
- package/scripts/daemon-claude-engine.js +87 -28
- package/scripts/daemon-command-router.js +16 -0
- package/scripts/daemon-command-session-route.js +3 -1
- package/scripts/daemon-engine-runtime.js +1 -5
- package/scripts/daemon-message-pipeline.js +113 -44
- package/scripts/daemon-reactive-lifecycle.js +405 -9
- package/scripts/daemon-session-commands.js +3 -2
- package/scripts/daemon-session-store.js +82 -27
- package/scripts/daemon-team-dispatch.js +21 -5
- package/scripts/daemon-utils.js +3 -1
- package/scripts/daemon.js +1 -0
- package/scripts/docs/file-transfer.md +1 -0
- package/scripts/hooks/intent-file-transfer.js +2 -1
- package/scripts/hooks/intent-perpetual.js +109 -0
- package/scripts/hooks/intent-research.js +112 -0
- package/scripts/intent-registry.js +4 -0
- package/scripts/ops-mission-queue.js +258 -0
- package/scripts/ops-verifier.js +197 -0
- package/skills/agent-browser/SKILL.md +153 -0
- package/skills/agent-reach/SKILL.md +66 -0
- package/skills/agent-reach/evolution.json +13 -0
- package/skills/deep-research/SKILL.md +77 -0
- package/skills/find-skills/SKILL.md +133 -0
- package/skills/heartbeat-task-manager/SKILL.md +63 -0
- package/skills/macos-local-orchestrator/SKILL.md +192 -0
- package/skills/macos-local-orchestrator/agents/openai.yaml +4 -0
- package/skills/macos-local-orchestrator/references/tooling-landscape.md +70 -0
- package/skills/macos-mail-calendar/SKILL.md +394 -0
- package/skills/mcp-installer/SKILL.md +138 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/README.md +72 -0
- package/skills/skill-creator/SKILL.md +96 -0
- package/skills/skill-creator/evolution.json +6 -0
- package/skills/skill-creator/references/creation-guide.md +116 -0
- package/skills/skill-creator/references/evolution-guide.md +74 -0
- package/skills/skill-creator/references/output-patterns.md +82 -0
- package/skills/skill-creator/references/workflows.md +28 -0
- package/skills/skill-creator/scripts/align_all.py +32 -0
- package/skills/skill-creator/scripts/auto_evolve_hook.js +247 -0
- package/skills/skill-creator/scripts/init_skill.py +303 -0
- package/skills/skill-creator/scripts/merge_evolution.py +70 -0
- package/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/setup.py +141 -0
- package/skills/skill-creator/scripts/smart_stitch.py +82 -0
- package/skills/skill-manager/SKILL.md +112 -0
- package/skills/skill-manager/scripts/delete_skill.py +31 -0
- package/skills/skill-manager/scripts/list_skills.py +61 -0
- package/skills/skill-manager/scripts/scan_and_check.py +125 -0
- package/skills/skill-manager/scripts/sync_index.py +144 -0
- package/skills/skill-manager/scripts/update_helper.py +39 -0
package/index.js
CHANGED
|
@@ -412,16 +412,21 @@ try {
|
|
|
412
412
|
}
|
|
413
413
|
} catch { /* non-fatal */ }
|
|
414
414
|
|
|
415
|
-
// Worktree guard:
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
const
|
|
419
|
-
|
|
415
|
+
// Worktree guard: worktrees must NEVER deploy to ~/.metame/ — they are isolated sandboxes.
|
|
416
|
+
// Detection: git worktrees have a .git FILE (pointing to main repo), not a .git DIRECTORY.
|
|
417
|
+
// This is reliable regardless of worktree path conventions.
|
|
418
|
+
const _dotGitPath = path.join(__dirname, '.git');
|
|
419
|
+
const _isInWorktree = (() => {
|
|
420
|
+
try {
|
|
421
|
+
const stat = fs.statSync(_dotGitPath);
|
|
422
|
+
return stat.isFile(); // .git is a file → worktree; directory → main repo; missing → npm install
|
|
423
|
+
} catch { return false; }
|
|
424
|
+
})();
|
|
420
425
|
if (_isInWorktree) {
|
|
421
426
|
console.error(`\n${icon("stop")} ACTION BLOCKED: Worktree Deploy Prevented`);
|
|
422
|
-
console.error(` You are running from a worktree (${path.basename(__dirname)}).`);
|
|
427
|
+
console.error(` You are running from a git worktree (${path.basename(__dirname)}).`);
|
|
423
428
|
console.error(' Deploying from a worktree would overwrite production daemon code.');
|
|
424
|
-
console.error('
|
|
429
|
+
console.error(' Commit your changes, then deploy from the main repo.\n');
|
|
425
430
|
process.exit(1);
|
|
426
431
|
}
|
|
427
432
|
|
|
@@ -695,6 +700,58 @@ function ensureHookInstalled() {
|
|
|
695
700
|
|
|
696
701
|
ensureHookInstalled();
|
|
697
702
|
|
|
703
|
+
// ---------------------------------------------------------
|
|
704
|
+
// 1.6a AUTO-ENABLE BUNDLED PLUGINS
|
|
705
|
+
// ---------------------------------------------------------
|
|
706
|
+
function ensurePluginsEnabled() {
|
|
707
|
+
try {
|
|
708
|
+
let settings = {};
|
|
709
|
+
if (fs.existsSync(CLAUDE_SETTINGS)) {
|
|
710
|
+
settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS, 'utf8'));
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
714
|
+
if (!settings.extraKnownMarketplaces) settings.extraKnownMarketplaces = {};
|
|
715
|
+
|
|
716
|
+
const bundledPlugins = {
|
|
717
|
+
'example-skills@anthropic-agent-skills': true,
|
|
718
|
+
'ralph-loop@claude-plugins-official': true,
|
|
719
|
+
'planning-with-files@planning-with-files': true,
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
const bundledMarketplaces = {
|
|
723
|
+
'planning-with-files': {
|
|
724
|
+
source: { source: 'github', repo: 'OthmanAdi/planning-with-files' },
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
let modified = false;
|
|
729
|
+
|
|
730
|
+
for (const [key, val] of Object.entries(bundledPlugins)) {
|
|
731
|
+
if (!(key in settings.enabledPlugins)) {
|
|
732
|
+
settings.enabledPlugins[key] = val;
|
|
733
|
+
modified = true;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
for (const [key, val] of Object.entries(bundledMarketplaces)) {
|
|
738
|
+
if (!(key in settings.extraKnownMarketplaces)) {
|
|
739
|
+
settings.extraKnownMarketplaces[key] = val;
|
|
740
|
+
modified = true;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (modified) {
|
|
745
|
+
fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2), 'utf8');
|
|
746
|
+
console.log(`${icon("brain")} MetaMe: Bundled plugins enabled.`);
|
|
747
|
+
}
|
|
748
|
+
} catch {
|
|
749
|
+
// Non-fatal
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
ensurePluginsEnabled();
|
|
754
|
+
|
|
698
755
|
// ---------------------------------------------------------
|
|
699
756
|
// 1.6b LOCAL ACTIVITY HEARTBEAT
|
|
700
757
|
// ---------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.12",
|
|
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": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"!scripts/test_daemon.js",
|
|
14
14
|
"!scripts/hooks/test-*.js",
|
|
15
15
|
"!scripts/daemon.yaml",
|
|
16
|
-
"!scripts/daemon.yaml.bak"
|
|
16
|
+
"!scripts/daemon.yaml.bak",
|
|
17
|
+
"skills/"
|
|
17
18
|
],
|
|
18
19
|
"scripts": {
|
|
19
20
|
"test": "node --test scripts/*.test.js",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { normalizeEngineName: _normalizeEngine } = require('./daemon-utils');
|
|
4
|
+
|
|
3
5
|
function createAgentCommandHandler(deps) {
|
|
4
6
|
const {
|
|
5
7
|
fs,
|
|
@@ -34,11 +36,11 @@ function createAgentCommandHandler(deps) {
|
|
|
34
36
|
agentFlowTtlMs,
|
|
35
37
|
agentBindTtlMs,
|
|
36
38
|
getDefaultEngine = () => 'claude',
|
|
39
|
+
log = () => {},
|
|
37
40
|
} = deps;
|
|
38
41
|
|
|
39
42
|
function normalizeEngineName(name) {
|
|
40
|
-
|
|
41
|
-
return n === 'codex' ? 'codex' : getDefaultEngine();
|
|
43
|
+
return _normalizeEngine(name, getDefaultEngine);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
function inferStoredEngine(rawSession) {
|
|
@@ -358,7 +360,9 @@ function createAgentCommandHandler(deps) {
|
|
|
358
360
|
const curSession = getSession(route.sessionChatId) || getSession(chatId);
|
|
359
361
|
const curCwd = route.cwd || (curSession ? curSession.cwd : null);
|
|
360
362
|
const currentEngine = getCurrentEngine(chatId);
|
|
363
|
+
log('DEBUG', `[/resume] chatId=${chatId} curCwd=${curCwd} engine=${currentEngine} route.sessionChatId=${route.sessionChatId}`);
|
|
361
364
|
const recentSessions = listRecentSessions(5, curCwd, currentEngine);
|
|
365
|
+
log('DEBUG', `[/resume] recentSessions=${recentSessions.length} ids=[${recentSessions.map(s=>s.sessionId.slice(0,8)).join(',')}]`);
|
|
362
366
|
const resumeChoices = buildResumeChoices({
|
|
363
367
|
recentSessions,
|
|
364
368
|
currentLogical,
|
|
@@ -767,6 +767,7 @@ function createBridgeStarter(deps) {
|
|
|
767
767
|
log('INFO', `Feishu message from ${chatId}: ${text.slice(0, 50)}`);
|
|
768
768
|
const parentId = extractFeishuReplyMessageId(event);
|
|
769
769
|
let _replyAgentKey = null;
|
|
770
|
+
let _replyMappingFound = false; // true = mapping exists (agentKey may be null = main)
|
|
770
771
|
// Load state once for the entire routing block
|
|
771
772
|
const _st = loadState();
|
|
772
773
|
if (parentId) {
|
|
@@ -775,6 +776,7 @@ function createBridgeStarter(deps) {
|
|
|
775
776
|
if (parentId) {
|
|
776
777
|
const mapped = _st.msg_sessions && _st.msg_sessions[parentId];
|
|
777
778
|
if (mapped) {
|
|
779
|
+
_replyMappingFound = true;
|
|
778
780
|
if (typeof restoreSessionFromReply === 'function') {
|
|
779
781
|
restoreSessionFromReply(chatId, mapped);
|
|
780
782
|
} else {
|
|
@@ -852,16 +854,28 @@ function createBridgeStarter(deps) {
|
|
|
852
854
|
// Bare /stop, no sticky set → fall through to handleCommand
|
|
853
855
|
}
|
|
854
856
|
|
|
855
|
-
// 0. Quoted reply → force route
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
857
|
+
// 0. Quoted reply → force route based on which agent sent the parent message.
|
|
858
|
+
// Cases:
|
|
859
|
+
// a) agentKey = known team member → route to that member (set sticky)
|
|
860
|
+
// b) agentKey = null, mapping found → user replied to main; clear sticky, route to main
|
|
861
|
+
// c) parentId present, no mapping → intent is explicit, avoid sticky; clear sticky, route to main
|
|
862
|
+
if (parentId) {
|
|
863
|
+
if (_replyAgentKey) {
|
|
864
|
+
const member = _boundProj.team.find(m => m.key === _replyAgentKey);
|
|
865
|
+
if (member) {
|
|
866
|
+
_setSticky(member.key);
|
|
867
|
+
log('INFO', `Quoted reply → force route to ${_replyAgentKey} (sticky set)`);
|
|
868
|
+
_dispatchToTeamMember(member, _boundProj, trimmedText, liveCfg, bot, chatId, executeTaskByName, acl);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
// agentKey set but not a current team member → fall through to main
|
|
872
|
+
log('INFO', `Quoted reply agentKey=${_replyAgentKey} not in team, routing to main`);
|
|
863
873
|
}
|
|
864
|
-
|
|
874
|
+
// Cases b & c: no agentKey (main agent) or stale/unknown agentKey
|
|
875
|
+
_clearSticky();
|
|
876
|
+
log('INFO', `Quoted reply → route to main (agentKey=${_replyAgentKey} mappingFound=${_replyMappingFound})`);
|
|
877
|
+
await pipeline.processMessage(chatId, trimmedText, { bot, config: liveCfg, executeTaskByName, senderId: acl.senderId, readOnly: acl.readOnly });
|
|
878
|
+
return;
|
|
865
879
|
}
|
|
866
880
|
// 1. Explicit nickname → route + set sticky
|
|
867
881
|
const teamMatch = _findTeamMember(trimmedText, _boundProj.team);
|
|
@@ -106,7 +106,15 @@ function createClaudeEngine(deps) {
|
|
|
106
106
|
}
|
|
107
107
|
// Card reuse for merge-pause: when a task is paused for message merging,
|
|
108
108
|
// save the statusMsgId so the next askClaude reuses the same card.
|
|
109
|
-
|
|
109
|
+
// Entries auto-expire via periodic sweep (60s) to prevent unbounded growth.
|
|
110
|
+
const _pausedCards = new Map(); // chatId -> { statusMsgId, cardHeader, savedAt }
|
|
111
|
+
const _PAUSED_CARD_TTL = 60000;
|
|
112
|
+
setInterval(() => {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
for (const [k, v] of _pausedCards) {
|
|
115
|
+
if (now - (v.savedAt || 0) > _PAUSED_CARD_TTL) _pausedCards.delete(k);
|
|
116
|
+
}
|
|
117
|
+
}, _PAUSED_CARD_TTL).unref();
|
|
110
118
|
|
|
111
119
|
let mentorEngine = null;
|
|
112
120
|
try { mentorEngine = require('./mentor-engine'); } catch { /* optional */ }
|
|
@@ -239,6 +247,7 @@ function createClaudeEngine(deps) {
|
|
|
239
247
|
&& !!error
|
|
240
248
|
&& (!output || !!errorCode)
|
|
241
249
|
&& failureKind !== 'user-stop'
|
|
250
|
+
&& failureKind !== 'merge-pause'
|
|
242
251
|
&& !!canRetry;
|
|
243
252
|
}
|
|
244
253
|
|
|
@@ -481,6 +490,13 @@ function createClaudeEngine(deps) {
|
|
|
481
490
|
retryPromptPrefix: '',
|
|
482
491
|
};
|
|
483
492
|
}
|
|
493
|
+
if (code === 'INTERRUPTED_MERGE_PAUSE' || lowered.includes('paused for merge')) {
|
|
494
|
+
return {
|
|
495
|
+
kind: 'merge-pause',
|
|
496
|
+
userMessage: '',
|
|
497
|
+
retryPromptPrefix: '',
|
|
498
|
+
};
|
|
499
|
+
}
|
|
484
500
|
const interrupted = (
|
|
485
501
|
lowered.includes('stopped by user')
|
|
486
502
|
|| lowered.includes('interrupted')
|
|
@@ -1275,9 +1291,9 @@ function createClaudeEngine(deps) {
|
|
|
1275
1291
|
};
|
|
1276
1292
|
const _ackBoundKey = _ackAgentMap[_ackChatIdStr] || projectKeyFromVirtualChatId(_ackChatIdStr);
|
|
1277
1293
|
const _ackBoundProj = _ackBoundKey && config.projects ? config.projects[_ackBoundKey] : null;
|
|
1278
|
-
// _ackCardHeader: non-null for
|
|
1279
|
-
let _ackCardHeader = (_ackBoundProj && _ackBoundProj.
|
|
1280
|
-
? { title: `${_ackBoundProj.icon} ${_ackBoundProj.name}`, color: _ackBoundProj.color || 'blue' }
|
|
1294
|
+
// _ackCardHeader: non-null for bound projects with a name; passed to editMessage to preserve header on streaming edits
|
|
1295
|
+
let _ackCardHeader = (_ackBoundProj && _ackBoundProj.name)
|
|
1296
|
+
? { title: `${_ackBoundProj.icon || '🤖'} ${_ackBoundProj.name}`, color: _ackBoundProj.color || 'blue' }
|
|
1281
1297
|
: null;
|
|
1282
1298
|
// Reuse card from a paused merge (same card, no new push)
|
|
1283
1299
|
const _pausedCard = _pausedCards.get(chatId);
|
|
@@ -1287,7 +1303,6 @@ function createClaudeEngine(deps) {
|
|
|
1287
1303
|
const cardAge = _pausedCard.savedAt ? Date.now() - _pausedCard.savedAt : 0;
|
|
1288
1304
|
if (cardAge > 30000) {
|
|
1289
1305
|
log('INFO', `[askClaude] Discarding stale paused card for ${chatId} (${Math.round(cardAge / 1000)}s old)`);
|
|
1290
|
-
if (_pausedCard.statusMsgId && bot.deleteMessage) bot.deleteMessage(chatId, _pausedCard.statusMsgId).catch(() => {});
|
|
1291
1306
|
} else {
|
|
1292
1307
|
statusMsgId = _pausedCard.statusMsgId;
|
|
1293
1308
|
if (_pausedCard.cardHeader) _ackCardHeader = _pausedCard.cardHeader;
|
|
@@ -1365,6 +1380,10 @@ function createClaudeEngine(deps) {
|
|
|
1365
1380
|
const sessionRaw = getSession(sessionChatId);
|
|
1366
1381
|
const boundCwd = (boundProject && boundProject.cwd) ? normalizeCwd(boundProject.cwd) : null;
|
|
1367
1382
|
const boundEngineName = (boundProject && boundProject.engine) ? normalizeEngineName(boundProject.engine) : getDefaultEngine();
|
|
1383
|
+
// effectiveCwd: single source of truth for this request's working directory.
|
|
1384
|
+
// For bound projects, config always wins over stored session cwd.
|
|
1385
|
+
// Resolved once here; all downstream createSession/spawn calls use this.
|
|
1386
|
+
let effectiveCwd = boundCwd || null;
|
|
1368
1387
|
|
|
1369
1388
|
// Engine is determined from config only — bound agent config wins, then global default.
|
|
1370
1389
|
const engineName = normalizeEngineName(
|
|
@@ -1406,6 +1425,14 @@ function createClaudeEngine(deps) {
|
|
|
1406
1425
|
let session = resolveSessionForEngine(sessionChatId, engineName) || { cwd: boundCwd || HOME, engine: engineName, id: null, started: false };
|
|
1407
1426
|
session.engine = engineName; // keep local copy for Codex resume detection below
|
|
1408
1427
|
session.logicalChatId = sessionChatId;
|
|
1428
|
+
// Finalize effectiveCwd: bound config > stored session > HOME
|
|
1429
|
+
if (!effectiveCwd) effectiveCwd = (session && session.cwd) || HOME;
|
|
1430
|
+
// Correct stored cwd if it drifted from config (e.g., stale state from prior bug)
|
|
1431
|
+
if (session.cwd !== effectiveCwd) {
|
|
1432
|
+
log('WARN', `[SessionCwd] correcting session cwd for ${sessionChatId}: ${session.cwd || 'unknown'} -> ${effectiveCwd}`);
|
|
1433
|
+
session = { ...session, cwd: effectiveCwd };
|
|
1434
|
+
await patchSessionSerialized(sessionChatId, (cur) => ({ ...cur, cwd: effectiveCwd }));
|
|
1435
|
+
}
|
|
1409
1436
|
|
|
1410
1437
|
// Warm pool: check if a persistent process is available for this session (Claude only).
|
|
1411
1438
|
// Declared early so downstream logic can skip expensive operations when reusing warm process.
|
|
@@ -1425,7 +1452,7 @@ function createClaudeEngine(deps) {
|
|
|
1425
1452
|
}
|
|
1426
1453
|
session = createSession(
|
|
1427
1454
|
sessionChatId,
|
|
1428
|
-
|
|
1455
|
+
effectiveCwd,
|
|
1429
1456
|
boundProject && boundProject.name ? boundProject.name : '',
|
|
1430
1457
|
engineName,
|
|
1431
1458
|
engineName === 'codex' ? requestedCodexPermissionProfile : undefined
|
|
@@ -1492,7 +1519,7 @@ function createClaudeEngine(deps) {
|
|
|
1492
1519
|
const resumeInspection = inspectClaudeResumeSession(session, model);
|
|
1493
1520
|
if (resumeInspection.shouldResume === false) {
|
|
1494
1521
|
log('INFO', `[ModelPin] session ${session.id.slice(0, 8)} flagged as ${resumeInspection.reason}; starting fresh Claude session`);
|
|
1495
|
-
session = createSession(sessionChatId,
|
|
1522
|
+
session = createSession(sessionChatId, effectiveCwd, boundProject && boundProject.name ? boundProject.name : '', runtime.name);
|
|
1496
1523
|
} else if (resumeInspection.modelPin) {
|
|
1497
1524
|
if (resumeInspection.modelPin !== model) {
|
|
1498
1525
|
log('INFO', `[ModelPin] resuming ${session.id.slice(0, 8)} with original model ${resumeInspection.modelPin} (configured: ${model})`);
|
|
@@ -1546,6 +1573,8 @@ function createClaudeEngine(deps) {
|
|
|
1546
1573
|
const memory = require('./memory');
|
|
1547
1574
|
|
|
1548
1575
|
// L1: NOW.md per-agent whiteboard injection(按 projectKey 隔离,防并发冲突)
|
|
1576
|
+
// One-shot: inject once then clear, same pattern as compactContext.
|
|
1577
|
+
// Prevents re-injection on daemon restart or new session for the same chat.
|
|
1549
1578
|
if (!session.started) {
|
|
1550
1579
|
try {
|
|
1551
1580
|
const nowDir = path.join(HOME, '.metame', 'memory', 'now');
|
|
@@ -1555,6 +1584,8 @@ function createClaudeEngine(deps) {
|
|
|
1555
1584
|
const nowContent = fs.readFileSync(nowPath, 'utf8').trim();
|
|
1556
1585
|
if (nowContent) {
|
|
1557
1586
|
memoryHint += `\n\n[Current task context:\n${nowContent}]`;
|
|
1587
|
+
// Clear after injection to prevent re-triggering on next session start
|
|
1588
|
+
try { fs.writeFileSync(nowPath, '', 'utf8'); } catch { /* non-critical */ }
|
|
1558
1589
|
}
|
|
1559
1590
|
}
|
|
1560
1591
|
} catch { /* non-critical */ }
|
|
@@ -1648,10 +1679,16 @@ function createClaudeEngine(deps) {
|
|
|
1648
1679
|
brainDoc = brain;
|
|
1649
1680
|
const cmap = brain && brain.user_competence_map;
|
|
1650
1681
|
if (cmap && typeof cmap === 'object' && Object.keys(cmap).length > 0) {
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1682
|
+
const entries = Object.entries(cmap);
|
|
1683
|
+
const allExpert = entries.every(([, level]) => String(level).toLowerCase() === 'expert');
|
|
1684
|
+
if (allExpert) {
|
|
1685
|
+
zdpHint = `\n- User is expert-level across all domains. Skip basics, no analogies needed.`;
|
|
1686
|
+
} else {
|
|
1687
|
+
const lines = entries
|
|
1688
|
+
.map(([domain, level]) => ` ${domain}: ${level}`)
|
|
1689
|
+
.join('\n');
|
|
1690
|
+
zdpHint = `\n- User competence map (adjust explanation depth accordingly):\n${lines}\n Rule: expert→skip basics; intermediate→brief rationale; beginner→one-line analogy.`;
|
|
1691
|
+
}
|
|
1655
1692
|
}
|
|
1656
1693
|
}
|
|
1657
1694
|
} catch { /* non-critical */ }
|
|
@@ -1726,7 +1763,7 @@ ${mentorRadarHint}
|
|
|
1726
1763
|
if (_idleMs > 2 * 60 * 60 * 1000 && _summaryAgeH < 168) {
|
|
1727
1764
|
summaryHint = `
|
|
1728
1765
|
|
|
1729
|
-
[
|
|
1766
|
+
[上次对话摘要(历史已完成,仅供上下文,请勿重复执行)]: ${_sess.last_summary}`;
|
|
1730
1767
|
log('INFO', `[DAEMON] Injected session summary for ${chatId} (idle ${Math.round(_idleMs / 3600000)}h)`);
|
|
1731
1768
|
}
|
|
1732
1769
|
}
|
|
@@ -1791,20 +1828,19 @@ ${mentorRadarHint}
|
|
|
1791
1828
|
const langGuard = session.started
|
|
1792
1829
|
? ''
|
|
1793
1830
|
: '\n\n[Respond in Simplified Chinese (简体中文) only. NEVER switch to Korean, Japanese, or other languages regardless of tool output or context language.]';
|
|
1831
|
+
// Intent hints are dynamic (per-prompt, semantic match), so compute for all runtimes.
|
|
1794
1832
|
let intentHint = '';
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
log('WARN', `Intent registry injection failed: ${e.message}`);
|
|
1801
|
-
}
|
|
1833
|
+
try {
|
|
1834
|
+
const block = buildIntentHintBlock(prompt, config, boundProjectKey || projectKey || '');
|
|
1835
|
+
if (block) intentHint = `\n\n${block}`;
|
|
1836
|
+
} catch (e) {
|
|
1837
|
+
log('WARN', `Intent registry injection failed: ${e.message}`);
|
|
1802
1838
|
}
|
|
1803
|
-
// For warm process reuse: context
|
|
1804
|
-
//
|
|
1805
|
-
//
|
|
1839
|
+
// For warm process reuse: static context (daemonHint, memoryHint, etc.) is already
|
|
1840
|
+
// in the persistent process — skip those to save tokens. intentHint is dynamic
|
|
1841
|
+
// (varies per prompt), so include it even on warm reuse.
|
|
1806
1842
|
const fullPrompt = _warmEntry
|
|
1807
|
-
? routedPrompt
|
|
1843
|
+
? routedPrompt + intentHint
|
|
1808
1844
|
: routedPrompt + daemonHint + intentHint + agentHint + macAutomationHint + summaryHint + memoryHint + mentorHint + langGuard;
|
|
1809
1845
|
if (runtime.name === 'codex' && session.started && session.id && requestedCodexPermissionProfile) {
|
|
1810
1846
|
const actualPermissionProfile = getActualCodexPermissionProfile(session);
|
|
@@ -1941,17 +1977,24 @@ ${mentorRadarHint}
|
|
|
1941
1977
|
...(runtime.name === 'codex' ? { runtimeSessionObserved: true } : {}),
|
|
1942
1978
|
...(runtime.name === 'codex' ? actualPermissionProfile : {}),
|
|
1943
1979
|
};
|
|
1944
|
-
return { ...cur, cwd:
|
|
1980
|
+
return { ...cur, cwd: effectiveCwd || cur.cwd || HOME, engines };
|
|
1945
1981
|
});
|
|
1946
1982
|
if (runtime.name === 'codex' && wasStarted && prevSessionId && prevSessionId !== safeNextId && prevSessionId !== '__continue__') {
|
|
1947
1983
|
log('WARN', `Codex thread migrated for ${chatId}: ${prevSessionId.slice(0, 8)} -> ${safeNextId.slice(0, 8)}`);
|
|
1948
1984
|
}
|
|
1985
|
+
// Keep card header in sync with the real session ID reported by the engine
|
|
1986
|
+
if (_ackCardHeader && _ackCardHeader._baseTitle) {
|
|
1987
|
+
_ackCardHeader = { ..._ackCardHeader, title: `${_ackCardHeader._baseTitle}(${safeNextId.slice(0, 8)})` };
|
|
1988
|
+
}
|
|
1949
1989
|
};
|
|
1950
1990
|
|
|
1951
1991
|
// Check if user cancelled during pre-spawn phase (sentinel was marked aborted)
|
|
1952
1992
|
// Stamp session ID on card header so user can track session continuity
|
|
1993
|
+
if (_ackCardHeader) {
|
|
1994
|
+
_ackCardHeader._baseTitle = _ackCardHeader.title; // preserve original title for onSession updates
|
|
1995
|
+
}
|
|
1953
1996
|
if (session && session.id && _ackCardHeader) {
|
|
1954
|
-
_ackCardHeader = { ..._ackCardHeader, title: `${_ackCardHeader.
|
|
1997
|
+
_ackCardHeader = { ..._ackCardHeader, title: `${_ackCardHeader._baseTitle}(${session.id.slice(0, 8)})` };
|
|
1955
1998
|
}
|
|
1956
1999
|
|
|
1957
2000
|
const _preSentinel = activeProcesses.get(chatId);
|
|
@@ -2018,7 +2061,7 @@ ${mentorRadarHint}
|
|
|
2018
2061
|
);
|
|
2019
2062
|
session = createSession(
|
|
2020
2063
|
sessionChatId,
|
|
2021
|
-
|
|
2064
|
+
effectiveCwd,
|
|
2022
2065
|
boundProject && boundProject.name ? boundProject.name : '',
|
|
2023
2066
|
'codex',
|
|
2024
2067
|
requestedCodexPermissionProfile
|
|
@@ -2095,7 +2138,7 @@ ${mentorRadarHint}
|
|
|
2095
2138
|
if (resumeFailure.kind !== 'interrupted') {
|
|
2096
2139
|
session = createSession(
|
|
2097
2140
|
sessionChatId,
|
|
2098
|
-
|
|
2141
|
+
effectiveCwd,
|
|
2099
2142
|
boundProject && boundProject.name ? boundProject.name : '',
|
|
2100
2143
|
'codex',
|
|
2101
2144
|
requestedCodexPermissionProfile
|
|
@@ -2203,6 +2246,16 @@ ${mentorRadarHint}
|
|
|
2203
2246
|
return { ok: true };
|
|
2204
2247
|
}
|
|
2205
2248
|
|
|
2249
|
+
// Merge-pause with partial output: save card for reuse, discard partial output
|
|
2250
|
+
if (output && errorCode === 'INTERRUPTED_MERGE_PAUSE') {
|
|
2251
|
+
if (statusMsgId) {
|
|
2252
|
+
_pausedCards.set(chatId, { statusMsgId, cardHeader: _ackCardHeader, savedAt: Date.now() });
|
|
2253
|
+
if (bot.editMessage) bot.editMessage(chatId, statusMsgId, '⏸ 合并中…', _ackCardHeader).catch(() => {});
|
|
2254
|
+
log('INFO', `[askClaude] Merge-pause with partial output, saved card ${statusMsgId} for ${chatId}`);
|
|
2255
|
+
}
|
|
2256
|
+
return { ok: false, error: 'Paused for merge', errorCode };
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2206
2259
|
if (output) {
|
|
2207
2260
|
if (runtime.name === 'codex') {
|
|
2208
2261
|
_codexResumeRetryTs.delete(getCodexResumeRetryKey(chatId, 'interrupted'));
|
|
@@ -2378,6 +2431,12 @@ ${mentorRadarHint}
|
|
|
2378
2431
|
} catch { /* non-critical — memory module may not be available */ }
|
|
2379
2432
|
});
|
|
2380
2433
|
}
|
|
2434
|
+
// Speculatively save card for pipeline post-resume flush reuse.
|
|
2435
|
+
// If no follow-up arrives, the card expires (30s TTL in _pausedCards consumer).
|
|
2436
|
+
const _replyMsgId = replyMsg && replyMsg.message_id;
|
|
2437
|
+
if (_replyMsgId && _ackCardHeader) {
|
|
2438
|
+
_pausedCards.set(chatId, { statusMsgId: _replyMsgId, cardHeader: _ackCardHeader, savedAt: Date.now() });
|
|
2439
|
+
}
|
|
2381
2440
|
return { ok: !timedOut };
|
|
2382
2441
|
} else {
|
|
2383
2442
|
const errMsg = error || 'Unknown error';
|
|
@@ -2403,7 +2462,7 @@ ${mentorRadarHint}
|
|
|
2403
2462
|
if (runtime.name === 'claude' && _isSessionResumeFail) {
|
|
2404
2463
|
const _reason = errMsg.includes('already in use') ? 'locked' : _isThinkingSignatureError ? 'thinking-signature-invalid' : 'not found';
|
|
2405
2464
|
log('WARN', `Session ${session.id} unusable (${_reason}), creating new`);
|
|
2406
|
-
session = createSession(sessionChatId,
|
|
2465
|
+
session = createSession(sessionChatId, effectiveCwd, '', runtime.name);
|
|
2407
2466
|
|
|
2408
2467
|
const retryArgs = runtime.buildArgs({
|
|
2409
2468
|
model,
|
|
@@ -643,6 +643,19 @@ function createCommandRouter(deps) {
|
|
|
643
643
|
return;
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
+
// /btw — quick side question (read-only, concise, bypasses cooldown)
|
|
647
|
+
if (/^\/btw(\s|$)/i.test(text)) {
|
|
648
|
+
const btwQuestion = text.replace(/^\/btw\s*/i, '').trim();
|
|
649
|
+
if (!btwQuestion) {
|
|
650
|
+
await bot.sendMessage(chatId, '用法: /btw <问题>\n快速提问,不影响主会话节奏');
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const btwPrompt = `[Side question — answer concisely from existing context, no need for tools]\n\n${btwQuestion}`;
|
|
654
|
+
resetCooldown(chatId);
|
|
655
|
+
await askClaude(bot, chatId, btwPrompt, config, true, senderId);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
646
659
|
if (text.startsWith('/')) {
|
|
647
660
|
const currentModel = (config.daemon && config.daemon.model) || 'opus';
|
|
648
661
|
const currentProvider = providerMod ? providerMod.getActiveName() : 'anthropic';
|
|
@@ -664,6 +677,9 @@ function createCommandRouter(deps) {
|
|
|
664
677
|
'/agent reset — 重置当前 Agent 角色',
|
|
665
678
|
'/agent soul [repair] — 查看/修复 Agent Soul 身份层',
|
|
666
679
|
'',
|
|
680
|
+
'💬 快捷:',
|
|
681
|
+
'/btw <问题> — 快速旁白提问(只读,不打断主任务)',
|
|
682
|
+
'',
|
|
667
683
|
'📂 Session 管理:',
|
|
668
684
|
'/new [path] [name] — 新建会话',
|
|
669
685
|
'/sessions — 浏览所有最近会话',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { normalizeEngineName: _normalizeEngine } = require('./daemon-utils');
|
|
4
|
+
|
|
3
5
|
function createCommandSessionResolver(deps) {
|
|
4
6
|
const {
|
|
5
7
|
path,
|
|
@@ -11,7 +13,7 @@ function createCommandSessionResolver(deps) {
|
|
|
11
13
|
} = deps;
|
|
12
14
|
|
|
13
15
|
function normalizeEngineName(name) {
|
|
14
|
-
return
|
|
16
|
+
return _normalizeEngine(name, getDefaultEngine);
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
function inferStoredEngine(rawSession) {
|
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { execSync } = require('child_process');
|
|
7
|
+
const { normalizeEngineName } = require('./daemon-utils');
|
|
7
8
|
|
|
8
9
|
const CODEX_TOOL_MAP = Object.freeze({
|
|
9
10
|
command_execution: 'Bash',
|
|
@@ -14,11 +15,6 @@ const CODEX_TOOL_MAP = Object.freeze({
|
|
|
14
15
|
web_fetch: 'WebFetch',
|
|
15
16
|
});
|
|
16
17
|
|
|
17
|
-
function normalizeEngineName(name) {
|
|
18
|
-
const text = String(name || '').trim().toLowerCase();
|
|
19
|
-
return text === 'codex' ? 'codex' : 'claude';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
18
|
function resolveBinary(engineName, deps = {}) {
|
|
23
19
|
const engine = normalizeEngineName(engineName);
|
|
24
20
|
const home = deps.HOME || os.homedir();
|