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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metame-cli",
3
- "version": "1.5.18",
3
+ "version": "1.5.19",
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": {
@@ -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 || (60 * 60 * 1000);
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 = setTimeout(() => killChild('ceiling'), HARD_CEILING_MS);
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: { idleMs: 10 * 60 * 1000, toolMs: 25 * 60 * 1000, ceilingMs: 60 * 60 * 1000 },
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: { idleMs: 5 * 60 * 1000, toolMs: 25 * 60 * 1000, ceilingMs: 60 * 60 * 1000 },
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(nullBot, dispatchChatId, prompt, config, null, null, dispatchReadOnly).catch(e => {
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();