metame-cli 1.5.18 → 1.5.19
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/package.json +1 -1
- package/scripts/daemon-claude-engine.js +22 -5
- package/scripts/daemon-command-router.js +4 -4
- package/scripts/daemon-engine-runtime.js +27 -2
- package/scripts/daemon-message-pipeline.js +2 -2
- package/scripts/daemon-reactive-lifecycle.js +35 -1
- package/scripts/daemon.js +16 -1
package/package.json
CHANGED
|
@@ -6,8 +6,8 @@ const {
|
|
|
6
6
|
createEngineRuntimeFactory,
|
|
7
7
|
normalizeEngineName,
|
|
8
8
|
resolveEngineModel,
|
|
9
|
+
_private: { resolveCodexPermissionProfile, resolveEngineTimeouts },
|
|
9
10
|
ENGINE_MODEL_CONFIG,
|
|
10
|
-
_private: { resolveCodexPermissionProfile },
|
|
11
11
|
} = require('./daemon-engine-runtime');
|
|
12
12
|
const { buildIntentHintBlock } = require('./intent-registry');
|
|
13
13
|
const { buildAgentContextForEngine, buildMemorySnapshotContent, refreshMemorySnapshot } = require('./agent-layer');
|
|
@@ -835,12 +835,14 @@ function createClaudeEngine(deps) {
|
|
|
835
835
|
|
|
836
836
|
if (chatId) {
|
|
837
837
|
activeProcesses.set(chatId, {
|
|
838
|
+
chatId,
|
|
838
839
|
child,
|
|
839
840
|
aborted: false,
|
|
840
841
|
abortReason: null,
|
|
841
842
|
startedAt: _spawnAt,
|
|
842
843
|
engine: rt.name,
|
|
843
844
|
killSignal: rt.killSignal || 'SIGTERM',
|
|
845
|
+
reactiveProjectKey: String(options && options.reactiveProjectKey || '').trim(),
|
|
844
846
|
});
|
|
845
847
|
saveActivePids();
|
|
846
848
|
}
|
|
@@ -871,10 +873,10 @@ function createClaudeEngine(deps) {
|
|
|
871
873
|
const toolUsageLog = [];
|
|
872
874
|
|
|
873
875
|
void timeoutMs;
|
|
874
|
-
const engineTimeouts = rt.timeouts || {};
|
|
876
|
+
const engineTimeouts = options.timeouts || rt.timeouts || {};
|
|
875
877
|
const IDLE_TIMEOUT_MS = engineTimeouts.idleMs || (5 * 60 * 1000);
|
|
876
878
|
const TOOL_EXEC_TIMEOUT_MS = engineTimeouts.toolMs || (25 * 60 * 1000);
|
|
877
|
-
const HARD_CEILING_MS = engineTimeouts.ceilingMs
|
|
879
|
+
const HARD_CEILING_MS = Number.isFinite(engineTimeouts.ceilingMs) ? engineTimeouts.ceilingMs : null;
|
|
878
880
|
const startTime = Date.now();
|
|
879
881
|
let waitingForTool = false;
|
|
880
882
|
|
|
@@ -892,7 +894,9 @@ function createClaudeEngine(deps) {
|
|
|
892
894
|
}
|
|
893
895
|
|
|
894
896
|
let idleTimer = setTimeout(() => killChild('idle'), IDLE_TIMEOUT_MS);
|
|
895
|
-
const ceilingTimer =
|
|
897
|
+
const ceilingTimer = HARD_CEILING_MS && HARD_CEILING_MS > 0
|
|
898
|
+
? setTimeout(() => killChild('ceiling'), HARD_CEILING_MS)
|
|
899
|
+
: null;
|
|
896
900
|
|
|
897
901
|
function resetIdleTimer() {
|
|
898
902
|
clearTimeout(idleTimer);
|
|
@@ -1248,7 +1252,7 @@ function createClaudeEngine(deps) {
|
|
|
1248
1252
|
return loadConfig();
|
|
1249
1253
|
}
|
|
1250
1254
|
|
|
1251
|
-
async function askClaude(bot, chatId, prompt, config, readOnly = false, senderId = null) {
|
|
1255
|
+
async function askClaude(bot, chatId, prompt, config, readOnly = false, senderId = null, meta = {}) {
|
|
1252
1256
|
const _t0 = Date.now();
|
|
1253
1257
|
log('INFO', `askClaude for ${chatId}: ${prompt.slice(0, 50)}`);
|
|
1254
1258
|
|
|
@@ -1261,12 +1265,14 @@ function createClaudeEngine(deps) {
|
|
|
1261
1265
|
try { process.kill(-_existing.child.pid, 'SIGTERM'); } catch { try { _existing.child.kill('SIGTERM'); } catch { /* */ } }
|
|
1262
1266
|
}
|
|
1263
1267
|
activeProcesses.set(chatId, {
|
|
1268
|
+
chatId,
|
|
1264
1269
|
child: null, // sentinel: no process yet
|
|
1265
1270
|
aborted: false,
|
|
1266
1271
|
abortReason: null,
|
|
1267
1272
|
startedAt: _t0,
|
|
1268
1273
|
engine: 'pending',
|
|
1269
1274
|
killSignal: 'SIGTERM',
|
|
1275
|
+
reactiveProjectKey: String(meta && meta.reactiveProjectKey || '').trim(),
|
|
1270
1276
|
});
|
|
1271
1277
|
|
|
1272
1278
|
// Track interaction time for idle/sleep detection
|
|
@@ -1391,6 +1397,7 @@ function createClaudeEngine(deps) {
|
|
|
1391
1397
|
(boundProject && boundProject.engine) || getDefaultEngine()
|
|
1392
1398
|
);
|
|
1393
1399
|
const runtime = getEngineRuntime(engineName);
|
|
1400
|
+
const executionTimeouts = resolveEngineTimeouts(engineName, { reactive: !!(meta && meta.reactive) });
|
|
1394
1401
|
const requestedCodexPermissionProfile = engineName === 'codex'
|
|
1395
1402
|
? getCodexPermissionProfile(readOnly, daemonCfg)
|
|
1396
1403
|
: null;
|
|
@@ -2021,6 +2028,8 @@ ${mentorRadarHint}
|
|
|
2021
2028
|
persistent: runtime.name === 'claude' && !!warmPool,
|
|
2022
2029
|
warmPool,
|
|
2023
2030
|
warmSessionKey: _warmSessionKey,
|
|
2031
|
+
reactiveProjectKey: String(meta && meta.reactiveProjectKey || '').trim(),
|
|
2032
|
+
timeouts: executionTimeouts,
|
|
2024
2033
|
},
|
|
2025
2034
|
));
|
|
2026
2035
|
|
|
@@ -2085,6 +2094,10 @@ ${mentorRadarHint}
|
|
|
2085
2094
|
normalizeSenderId(senderId),
|
|
2086
2095
|
runtime,
|
|
2087
2096
|
onSession,
|
|
2097
|
+
{
|
|
2098
|
+
reactiveProjectKey: String(meta && meta.reactiveProjectKey || '').trim(),
|
|
2099
|
+
timeouts: executionTimeouts,
|
|
2100
|
+
},
|
|
2088
2101
|
));
|
|
2089
2102
|
if (sessionId) await onSession(sessionId);
|
|
2090
2103
|
observedRuntimeProfile = getActualCodexPermissionProfile(sessionId ? { id: sessionId } : session);
|
|
@@ -2154,6 +2167,10 @@ ${mentorRadarHint}
|
|
|
2154
2167
|
normalizeSenderId(senderId),
|
|
2155
2168
|
runtime,
|
|
2156
2169
|
onSession,
|
|
2170
|
+
{
|
|
2171
|
+
reactiveProjectKey: String(meta && meta.reactiveProjectKey || '').trim(),
|
|
2172
|
+
timeouts: executionTimeouts,
|
|
2173
|
+
},
|
|
2157
2174
|
));
|
|
2158
2175
|
if (sessionId) await onSession(sessionId);
|
|
2159
2176
|
}
|
|
@@ -574,7 +574,7 @@ function createCommandRouter(deps) {
|
|
|
574
574
|
return false;
|
|
575
575
|
}
|
|
576
576
|
|
|
577
|
-
async function handleCommand(bot, chatId, text, config, executeTaskByName, senderId = null, readOnly = false) {
|
|
577
|
+
async function handleCommand(bot, chatId, text, config, executeTaskByName, senderId = null, readOnly = false, meta = {}) {
|
|
578
578
|
if (text && !text.startsWith('/chatid') && !text.startsWith('/myid')) log('INFO', `CMD [${String(chatId).slice(-8)}]: ${text.slice(0, 80)}`);
|
|
579
579
|
const state = loadState();
|
|
580
580
|
|
|
@@ -654,7 +654,7 @@ function createCommandRouter(deps) {
|
|
|
654
654
|
}
|
|
655
655
|
const btwPrompt = `[Side question — answer concisely from existing context, no need for tools]\n\n${btwQuestion}`;
|
|
656
656
|
resetCooldown(chatId);
|
|
657
|
-
await askClaude(bot, chatId, btwPrompt, config, true, senderId);
|
|
657
|
+
await askClaude(bot, chatId, btwPrompt, config, true, senderId, meta);
|
|
658
658
|
return;
|
|
659
659
|
}
|
|
660
660
|
|
|
@@ -727,7 +727,7 @@ function createCommandRouter(deps) {
|
|
|
727
727
|
if (handled) {
|
|
728
728
|
// /last attached the session — now send "继续" to actually resume the conversation
|
|
729
729
|
resetCooldown(chatId);
|
|
730
|
-
await askClaude(bot, chatId, '继续上面的工作', config, readOnly, senderId);
|
|
730
|
+
await askClaude(bot, chatId, '继续上面的工作', config, readOnly, senderId, meta);
|
|
731
731
|
return;
|
|
732
732
|
}
|
|
733
733
|
// No session found — fall through to normal askClaude
|
|
@@ -775,7 +775,7 @@ function createCommandRouter(deps) {
|
|
|
775
775
|
await bot.sendMessage(chatId, 'Daily token budget exceeded.');
|
|
776
776
|
return;
|
|
777
777
|
}
|
|
778
|
-
const claudeResult = await askClaude(bot, chatId, text, config, readOnly, senderId);
|
|
778
|
+
const claudeResult = await askClaude(bot, chatId, text, config, readOnly, senderId, meta);
|
|
779
779
|
const claudeFailed = !!(claudeResult && claudeResult.ok === false);
|
|
780
780
|
const claudeAborted = !!(claudeResult && claudeResult.error === 'Stopped by user');
|
|
781
781
|
if (claudeFailed && !claudeAborted && !macLocalFirst && macFallbackEnabled && allowLocalMacControl) {
|
|
@@ -15,6 +15,19 @@ const CODEX_TOOL_MAP = Object.freeze({
|
|
|
15
15
|
web_fetch: 'WebFetch',
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
const ENGINE_TIMEOUT_DEFAULTS = Object.freeze({
|
|
19
|
+
codex: Object.freeze({
|
|
20
|
+
idleMs: 10 * 60 * 1000,
|
|
21
|
+
toolMs: 25 * 60 * 1000,
|
|
22
|
+
ceilingMs: 60 * 60 * 1000,
|
|
23
|
+
}),
|
|
24
|
+
claude: Object.freeze({
|
|
25
|
+
idleMs: 5 * 60 * 1000,
|
|
26
|
+
toolMs: 25 * 60 * 1000,
|
|
27
|
+
ceilingMs: 60 * 60 * 1000,
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
|
|
18
31
|
function resolveBinary(engineName, deps = {}) {
|
|
19
32
|
const engine = normalizeEngineName(engineName);
|
|
20
33
|
const home = deps.HOME || os.homedir();
|
|
@@ -269,6 +282,16 @@ function parseCodexStreamEvent(line) {
|
|
|
269
282
|
return out;
|
|
270
283
|
}
|
|
271
284
|
|
|
285
|
+
function resolveEngineTimeouts(engineName, opts = {}) {
|
|
286
|
+
const engine = normalizeEngineName(engineName);
|
|
287
|
+
const base = ENGINE_TIMEOUT_DEFAULTS[engine] || ENGINE_TIMEOUT_DEFAULTS.claude;
|
|
288
|
+
if (!opts || !opts.reactive) return { ...base };
|
|
289
|
+
return {
|
|
290
|
+
...base,
|
|
291
|
+
ceilingMs: null,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
272
295
|
function buildClaudeArgs(options = {}) {
|
|
273
296
|
const { model = ENGINE_MODEL_CONFIG.claude.main, readOnly = false, session = {}, addDirs } = options;
|
|
274
297
|
const args = ['-p', '--model', model];
|
|
@@ -421,7 +444,7 @@ function createEngineRuntimeFactory(deps = {}) {
|
|
|
421
444
|
defaultModel: ENGINE_MODEL_CONFIG.codex.main,
|
|
422
445
|
stdinBehavior: 'write-and-close',
|
|
423
446
|
killSignal: 'SIGTERM',
|
|
424
|
-
timeouts:
|
|
447
|
+
timeouts: resolveEngineTimeouts('codex'),
|
|
425
448
|
buildArgs: buildCodexArgs,
|
|
426
449
|
buildEnv: ({ metameProject = '', metameSenderId = '' } = {}) => buildCodexEnv(process.env, { metameProject, metameSenderId }),
|
|
427
450
|
parseStreamEvent: parseCodexStreamEvent,
|
|
@@ -434,7 +457,7 @@ function createEngineRuntimeFactory(deps = {}) {
|
|
|
434
457
|
defaultModel: ENGINE_MODEL_CONFIG.claude.main,
|
|
435
458
|
stdinBehavior: 'write-and-close',
|
|
436
459
|
killSignal: 'SIGTERM',
|
|
437
|
-
timeouts:
|
|
460
|
+
timeouts: resolveEngineTimeouts('claude'),
|
|
438
461
|
buildArgs: buildClaudeArgs,
|
|
439
462
|
buildEnv: ({ metameProject = '', metameSenderId = '' } = {}) => ({
|
|
440
463
|
...(() => {
|
|
@@ -460,6 +483,7 @@ module.exports = {
|
|
|
460
483
|
ENGINE_DISTILL_MAP,
|
|
461
484
|
ENGINE_DEFAULT_MODEL,
|
|
462
485
|
_private: {
|
|
486
|
+
ENGINE_TIMEOUT_DEFAULTS,
|
|
463
487
|
classifyEngineError,
|
|
464
488
|
parseClaudeStreamEvent,
|
|
465
489
|
parseCodexStreamEvent,
|
|
@@ -472,5 +496,6 @@ module.exports = {
|
|
|
472
496
|
BUILTIN_CLAUDE_MODEL_VALUES,
|
|
473
497
|
normalizeClaudeModel,
|
|
474
498
|
looksLikeCodexModel,
|
|
499
|
+
resolveEngineTimeouts,
|
|
475
500
|
},
|
|
476
501
|
};
|
|
@@ -283,9 +283,9 @@ function createMessagePipeline(deps) {
|
|
|
283
283
|
*/
|
|
284
284
|
async function _processOne(chatId, text, ctx) {
|
|
285
285
|
if (resetCooldown) resetCooldown(chatId);
|
|
286
|
-
const { bot, config, executeTaskByName, senderId, readOnly } = ctx;
|
|
286
|
+
const { bot, config, executeTaskByName, senderId, readOnly, meta } = ctx;
|
|
287
287
|
try {
|
|
288
|
-
return await handleCommand(bot, chatId, text, config, executeTaskByName, senderId, readOnly);
|
|
288
|
+
return await handleCommand(bot, chatId, text, config, executeTaskByName, senderId, readOnly, meta || {});
|
|
289
289
|
} catch (err) {
|
|
290
290
|
log('ERROR', `Pipeline: error processing message for ${chatId}: ${err.message}`);
|
|
291
291
|
return { ok: false, error: err.message };
|
|
@@ -87,6 +87,33 @@ function setReactiveStatus(state, projectKey, status, reason) {
|
|
|
87
87
|
rs.updated_at = new Date().toISOString();
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
function isReactiveExecutionActive(projectKey, config, deps) {
|
|
91
|
+
const active = deps && deps.activeProcesses;
|
|
92
|
+
if (!active || typeof active.values !== 'function') return false;
|
|
93
|
+
const key = String(projectKey || '').trim();
|
|
94
|
+
if (!key) return false;
|
|
95
|
+
const parent = config && config.projects ? config.projects[key] : null;
|
|
96
|
+
const memberKeys = new Set(
|
|
97
|
+
Array.isArray(parent && parent.team)
|
|
98
|
+
? parent.team.map(member => String(member && member.key || '').trim()).filter(Boolean)
|
|
99
|
+
: []
|
|
100
|
+
);
|
|
101
|
+
for (const proc of active.values()) {
|
|
102
|
+
if (!proc || proc.aborted) continue;
|
|
103
|
+
const reactiveProjectKey = String(proc.reactiveProjectKey || '').trim();
|
|
104
|
+
if (reactiveProjectKey && reactiveProjectKey === key) return true;
|
|
105
|
+
const procChatId = String(proc.chatId || proc.logicalChatId || '').trim();
|
|
106
|
+
if (!procChatId) continue;
|
|
107
|
+
if (procChatId === `_agent_${key}`) return true;
|
|
108
|
+
if (procChatId.startsWith('_scope_') && procChatId.endsWith(`__${key}`)) return true;
|
|
109
|
+
for (const memberKey of memberKeys) {
|
|
110
|
+
if (procChatId === `_agent_${memberKey}`) return true;
|
|
111
|
+
if (procChatId.startsWith('_scope_') && procChatId.endsWith(`__${memberKey}`)) return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
90
117
|
/**
|
|
91
118
|
* Find the reactive parent project key for a given team member.
|
|
92
119
|
* Returns the parent key string, or null if not found.
|
|
@@ -457,6 +484,10 @@ function reconcilePerpetualProjects(config, deps) {
|
|
|
457
484
|
const staleThreshold = staleMinutes * 60 * 1000;
|
|
458
485
|
|
|
459
486
|
if (Date.now() - lastUpdate > staleThreshold) {
|
|
487
|
+
if (isReactiveExecutionActive(key, config, deps)) {
|
|
488
|
+
deps.log('INFO', `Reconcile: ${key} exceeds stale threshold but reactive execution is still active`);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
460
491
|
deps.log('WARN', `Reconcile: ${key} stuck since ${rs.updated_at}`);
|
|
461
492
|
setReactiveStatus(st, key, 'stale', 'no_activity');
|
|
462
493
|
deps.saveState(st);
|
|
@@ -925,6 +956,7 @@ function handleReactiveOutput(targetProject, output, config, deps) {
|
|
|
925
956
|
prompt: completionResult.nextMissionPrompt,
|
|
926
957
|
from: '_system',
|
|
927
958
|
_reactive: true,
|
|
959
|
+
_reactive_project: projectKey,
|
|
928
960
|
new_session: true,
|
|
929
961
|
}, config);
|
|
930
962
|
}
|
|
@@ -972,6 +1004,7 @@ function handleReactiveOutput(targetProject, output, config, deps) {
|
|
|
972
1004
|
prompt: d.prompt,
|
|
973
1005
|
from: projectKey,
|
|
974
1006
|
_reactive: true,
|
|
1007
|
+
_reactive_project: projectKey,
|
|
975
1008
|
new_session: true,
|
|
976
1009
|
}, config);
|
|
977
1010
|
}
|
|
@@ -1089,6 +1122,7 @@ function handleReactiveOutput(targetProject, output, config, deps) {
|
|
|
1089
1122
|
prompt: `[${targetProject} delivery]${verifierBlock}\n\n${summary}\n\nDecide next step. Use NEXT_DISPATCH or ${signal}.`,
|
|
1090
1123
|
from: targetProject,
|
|
1091
1124
|
_reactive: true,
|
|
1125
|
+
_reactive_project: parentKey,
|
|
1092
1126
|
new_session: true,
|
|
1093
1127
|
}, config);
|
|
1094
1128
|
}
|
|
@@ -1098,5 +1132,5 @@ module.exports = {
|
|
|
1098
1132
|
parseReactiveSignals,
|
|
1099
1133
|
reconcilePerpetualProjects,
|
|
1100
1134
|
replayEventLog,
|
|
1101
|
-
__test: { runProjectVerifier, readPhaseFromState, resolveProjectCwd, appendEvent, projectProgressTsv, generateStateFile, loadProjectManifest, resolveProjectScripts, parseEventLog, buildRunningMemory, scanRelevantArtifacts, buildWorkingMemory, persistMemoryFiles, extractInlineFacts, extractOutputSummary },
|
|
1135
|
+
__test: { runProjectVerifier, readPhaseFromState, resolveProjectCwd, appendEvent, projectProgressTsv, generateStateFile, loadProjectManifest, resolveProjectScripts, parseEventLog, buildRunningMemory, scanRelevantArtifacts, buildWorkingMemory, persistMemoryFiles, extractInlineFacts, extractOutputSummary, isReactiveExecutionActive },
|
|
1102
1136
|
};
|
package/scripts/daemon.js
CHANGED
|
@@ -943,6 +943,8 @@ function dispatchTask(targetProject, message, config, replyFn, streamOptions = n
|
|
|
943
943
|
payload,
|
|
944
944
|
callback: message.callback || false,
|
|
945
945
|
new_session: !!message.new_session,
|
|
946
|
+
reactive: !!message._reactive,
|
|
947
|
+
reactive_project_key: String(message._reactive_project || '').trim(),
|
|
946
948
|
chain: [...chain, message.from || 'unknown'],
|
|
947
949
|
task_id: envelope ? envelope.task_id : null,
|
|
948
950
|
scope_id: envelope ? envelope.scope_id : null,
|
|
@@ -1164,7 +1166,19 @@ function dispatchTask(targetProject, message, config, replyFn, streamOptions = n
|
|
|
1164
1166
|
taskBoard.markTaskStatus(envelope.task_id, 'running', { summary: `dispatched via ${sessionMode}` });
|
|
1165
1167
|
taskBoard.appendTaskEvent(envelope.task_id, 'task_started', targetProject, { session_mode: sessionMode });
|
|
1166
1168
|
}
|
|
1167
|
-
_handleCommand(
|
|
1169
|
+
_handleCommand(
|
|
1170
|
+
nullBot,
|
|
1171
|
+
dispatchChatId,
|
|
1172
|
+
prompt,
|
|
1173
|
+
config,
|
|
1174
|
+
null,
|
|
1175
|
+
null,
|
|
1176
|
+
dispatchReadOnly,
|
|
1177
|
+
{
|
|
1178
|
+
reactive: !!fullMsg.reactive,
|
|
1179
|
+
reactiveProjectKey: fullMsg.reactive_project_key || '',
|
|
1180
|
+
},
|
|
1181
|
+
).catch(e => {
|
|
1168
1182
|
log('ERROR', `Dispatch handleCommand failed for ${targetProject}: ${e.message}`);
|
|
1169
1183
|
if (envelope && taskBoard) {
|
|
1170
1184
|
taskBoard.markTaskStatus(envelope.task_id, 'failed', { last_error: e.message, summary: 'dispatch execution failed' });
|
|
@@ -1699,6 +1713,7 @@ function physiologicalHeartbeat(config) {
|
|
|
1699
1713
|
log,
|
|
1700
1714
|
loadState,
|
|
1701
1715
|
saveState,
|
|
1716
|
+
activeProcesses,
|
|
1702
1717
|
notifyUser: (msg) => {
|
|
1703
1718
|
try {
|
|
1704
1719
|
const cfg = loadConfig();
|