metame-cli 1.5.11 → 1.5.13

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.
Files changed (55) hide show
  1. package/index.js +64 -7
  2. package/package.json +3 -2
  3. package/scripts/daemon-agent-commands.js +6 -2
  4. package/scripts/daemon-bridges.js +23 -9
  5. package/scripts/daemon-claude-engine.js +81 -28
  6. package/scripts/daemon-command-router.js +16 -0
  7. package/scripts/daemon-command-session-route.js +3 -1
  8. package/scripts/daemon-engine-runtime.js +1 -5
  9. package/scripts/daemon-message-pipeline.js +113 -44
  10. package/scripts/daemon-reactive-lifecycle.js +405 -9
  11. package/scripts/daemon-session-commands.js +3 -2
  12. package/scripts/daemon-session-store.js +82 -27
  13. package/scripts/daemon-team-dispatch.js +21 -5
  14. package/scripts/daemon-utils.js +3 -1
  15. package/scripts/daemon.js +1 -0
  16. package/scripts/docs/file-transfer.md +1 -0
  17. package/scripts/hooks/intent-file-transfer.js +2 -1
  18. package/scripts/hooks/intent-perpetual.js +109 -0
  19. package/scripts/hooks/intent-research.js +112 -0
  20. package/scripts/intent-registry.js +4 -0
  21. package/scripts/ops-mission-queue.js +258 -0
  22. package/scripts/ops-verifier.js +197 -0
  23. package/skills/agent-browser/SKILL.md +153 -0
  24. package/skills/agent-reach/SKILL.md +66 -0
  25. package/skills/agent-reach/evolution.json +13 -0
  26. package/skills/deep-research/SKILL.md +77 -0
  27. package/skills/find-skills/SKILL.md +133 -0
  28. package/skills/heartbeat-task-manager/SKILL.md +63 -0
  29. package/skills/macos-local-orchestrator/SKILL.md +192 -0
  30. package/skills/macos-local-orchestrator/agents/openai.yaml +4 -0
  31. package/skills/macos-local-orchestrator/references/tooling-landscape.md +70 -0
  32. package/skills/macos-mail-calendar/SKILL.md +394 -0
  33. package/skills/mcp-installer/SKILL.md +138 -0
  34. package/skills/skill-creator/LICENSE.txt +202 -0
  35. package/skills/skill-creator/README.md +72 -0
  36. package/skills/skill-creator/SKILL.md +96 -0
  37. package/skills/skill-creator/evolution.json +6 -0
  38. package/skills/skill-creator/references/creation-guide.md +116 -0
  39. package/skills/skill-creator/references/evolution-guide.md +74 -0
  40. package/skills/skill-creator/references/output-patterns.md +82 -0
  41. package/skills/skill-creator/references/workflows.md +28 -0
  42. package/skills/skill-creator/scripts/align_all.py +32 -0
  43. package/skills/skill-creator/scripts/auto_evolve_hook.js +247 -0
  44. package/skills/skill-creator/scripts/init_skill.py +303 -0
  45. package/skills/skill-creator/scripts/merge_evolution.py +70 -0
  46. package/skills/skill-creator/scripts/package_skill.py +110 -0
  47. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  48. package/skills/skill-creator/scripts/setup.py +141 -0
  49. package/skills/skill-creator/scripts/smart_stitch.py +82 -0
  50. package/skills/skill-manager/SKILL.md +112 -0
  51. package/skills/skill-manager/scripts/delete_skill.py +31 -0
  52. package/skills/skill-manager/scripts/list_skills.py +61 -0
  53. package/skills/skill-manager/scripts/scan_and_check.py +125 -0
  54. package/skills/skill-manager/scripts/sync_index.py +144 -0
  55. package/skills/skill-manager/scripts/update_helper.py +39 -0
@@ -7,21 +7,22 @@
7
7
  *
8
8
  * Behavior:
9
9
  * 1. First message → process immediately
10
- * 2. Follow-up while busy → SIGINT pause, start collecting
11
- * 3. More follow-upskeep collecting (no timer yet)
12
- * 4. Paused task diesstart debounce timer (3s)
13
- * 5. Each new message resets the 3s debounce
14
- * 6. Debounce fires flush ALL collected messages as ONE prompt → ONE reply
15
- * 7. Messages during flush processing collect again, repeat cycle
10
+ * 2. Follow-up within merge window (15s) → SIGINT pause, start collecting
11
+ * 3. Follow-up outside merge window queue for independent processing
12
+ * 4. More follow-ups while collecting keep collecting (no timer yet)
13
+ * 5. Paused task dies start debounce timer (5s)
14
+ * 6. Each new message resets the 5s debounce
15
+ * 7. Debounce fires flush ALL collected messages as ONE prompt → ONE reply
16
+ * 8. Messages during flush → queue individually (each gets own card)
16
17
  *
17
- * Priority messages (/stop, /quit, 停) bypass everything and execute immediately.
18
+ * Priority messages (/stop, /quit) bypass everything and execute immediately.
18
19
  *
19
20
  * Public API:
20
21
  * processMessage(chatId, text, ctx) — enqueue & serialize
21
22
  * isActive(chatId) — check if a message is being processed
22
23
  * interruptActive(chatId) — abort the active process
23
- * clearQueue(chatId) — drop pending messages + cancel collecting
24
- * getQueueLength(chatId) — number of pending messages
24
+ * clearQueue(chatId) — drop ALL pending state
25
+ * getQueueLength(chatId) — number of pending messages (all queues)
25
26
  */
26
27
 
27
28
  function createMessagePipeline(deps) {
@@ -35,16 +36,26 @@ function createMessagePipeline(deps) {
35
36
  // Per-chatId Promise chain tail — ensures serial execution
36
37
  const chains = new Map(); // chatId -> Promise
37
38
 
38
- // Track the original message text being processed (for merge context)
39
- const activeTexts = new Map(); // chatId -> string
39
+ // Track the original message text and start time (for merge window)
40
+ const activeTexts = new Map(); // chatId -> string
41
+ const activeStartTimes = new Map(); // chatId -> timestamp (ms)
40
42
 
41
- // Per-chatId burst collection state
43
+ // Per-chatId burst collection state (first-round merge only)
42
44
  const collecting = new Map(); // chatId -> { messages: string[], ctx, timer, chainDead: boolean }
43
45
 
44
- // chatIds where flush is processing — new messages go to collecting, not interrupt
46
+ // chatIds where flush is processing — new messages go to pendingQueue
45
47
  const resumed = new Set();
46
48
 
49
+ // Individual message queue — each item processed independently with its own card.
50
+ // Used for: post-resume messages, messages outside merge window.
51
+ const pendingQueue = new Map(); // chatId -> [{text, ctx}]
52
+
53
+ // chatIds currently being drained — prevents new messages from interrupting drain items
54
+ const draining = new Set();
55
+
47
56
  const DEBOUNCE_MS = 5000;
57
+ const MERGE_WINDOW_MS = 15000;
58
+ const SIGINT_ESCALATE_MS = 2000;
48
59
 
49
60
  // Messages that must bypass everything and execute immediately
50
61
  const STOP_RE = /^\/stop(\s|$)/i;
@@ -52,27 +63,37 @@ function createMessagePipeline(deps) {
52
63
 
53
64
  function _isPriorityMessage(text) {
54
65
  const trimmed = (text || '').trim();
55
- // Only hard-stop commands bypass. Natural language interrupts (等一下/hold on)
56
- // are handled by command-router and should NOT bypass collecting — they would
57
- // silently drop collected messages.
58
66
  return STOP_RE.test(trimmed) || QUIT_RE.test(trimmed);
59
67
  }
60
68
 
69
+ // ── Helpers ────────────────────────────────────────────────────
70
+
71
+ function _enqueue(chatId, text, ctx) {
72
+ if (!pendingQueue.has(chatId)) pendingQueue.set(chatId, []);
73
+ pendingQueue.get(chatId).push({ text, ctx });
74
+ }
75
+
76
+ function _clearAll(chatId) {
77
+ _cancelCollecting(chatId);
78
+ resumed.delete(chatId);
79
+ pendingQueue.delete(chatId);
80
+ draining.delete(chatId);
81
+ }
82
+
61
83
  // ── Core: process / collect / flush ──────────────────────────────
62
84
 
63
85
  function processMessage(chatId, text, ctx) {
64
86
  // Priority messages bypass everything
65
87
  if ((chains.has(chatId) || collecting.has(chatId)) && _isPriorityMessage(text)) {
66
88
  log('INFO', `Pipeline: priority bypass "${text.trim()}" for ${chatId}`);
67
- _cancelCollecting(chatId);
89
+ _clearAll(chatId);
68
90
  return _processOne(chatId, text, ctx);
69
91
  }
70
92
 
71
- // Currently collecting → accumulate
93
+ // Currently collecting (first-round merge) → accumulate
72
94
  if (collecting.has(chatId)) {
73
95
  const c = collecting.get(chatId);
74
96
  c.messages.push(text);
75
- // Only reset debounce if chain is already dead (timer active)
76
97
  if (c.chainDead) _resetDebounce(chatId, c);
77
98
  log('INFO', `Pipeline: collecting follow-up for ${chatId} (${c.messages.length} pending)`);
78
99
  return Promise.resolve();
@@ -81,45 +102,83 @@ function createMessagePipeline(deps) {
81
102
  // Pipeline idle → start processing
82
103
  if (!chains.has(chatId)) {
83
104
  activeTexts.set(chatId, text);
105
+ activeStartTimes.set(chatId, Date.now());
84
106
  const p = _processOne(chatId, text, ctx)
85
107
  .finally(() => {
86
108
  activeTexts.delete(chatId);
109
+ activeStartTimes.delete(chatId);
87
110
  chains.delete(chatId);
88
111
  resumed.delete(chatId);
89
- // If messages were collected during processing, start debounce now
112
+ // First-round merge: collected messages debounce → flush
90
113
  if (collecting.has(chatId)) {
91
114
  const c = collecting.get(chatId);
92
115
  c.chainDead = true;
93
116
  _resetDebounce(chatId, c);
94
117
  log('INFO', `Pipeline: chain ended, starting debounce for ${chatId} (${c.messages.length} collected)`);
118
+ return; // let debounce handle the rest; don't drain yet
95
119
  }
120
+ // Drain individually queued messages
121
+ _drainNext(chatId);
96
122
  });
97
123
  chains.set(chatId, p);
98
124
  return p;
99
125
  }
100
126
 
101
- // Pipeline busy + already flushed oncekeep collecting (don't interrupt again)
102
- if (resumed.has(chatId)) {
103
- _addToCollecting(chatId, text, ctx);
104
- log('INFO', `Pipeline: collecting (post-resume) for ${chatId}`);
127
+ // Pipeline busy + post-flush or drainingqueue individually
128
+ if (resumed.has(chatId) || draining.has(chatId)) {
129
+ _enqueue(chatId, text, ctx);
130
+ log('INFO', `Pipeline: queued independently for ${chatId} (${pendingQueue.get(chatId).length} queued)`);
131
+ return Promise.resolve();
132
+ }
133
+
134
+ // Pipeline busy, first follow-up — check merge window
135
+ const elapsed = Date.now() - (activeStartTimes.get(chatId) || 0);
136
+ if (elapsed > MERGE_WINDOW_MS) {
137
+ _enqueue(chatId, text, ctx);
138
+ log('INFO', `Pipeline: outside merge window (${Math.round(elapsed / 1000)}s) for ${chatId}, queued independently`);
105
139
  return Promise.resolve();
106
140
  }
107
141
 
108
- // Pipeline busy, first follow-up → interrupt and start collecting
142
+ // Within merge window → interrupt and start collecting
109
143
  return _startCollecting(chatId, text, ctx);
110
144
  }
111
145
 
112
146
  // ── Interrupt-Collect-Flush ────────────────────────────────────
113
147
 
148
+ /**
149
+ * Send SIGINT with escalation: SIGINT → SIGTERM → SIGKILL.
150
+ * Works cross-platform — on macOS SIGINT is enough; on Windows claude
151
+ * ignores SIGINT so we escalate after SIGINT_ESCALATE_MS.
152
+ */
153
+ function _killWithEscalation(child, chatId) {
154
+ try { process.kill(-child.pid, 'SIGINT'); } catch { try { child.kill('SIGINT'); } catch { /* */ } }
155
+ let sigkillTimer = null;
156
+ const escalateTimer = setTimeout(() => {
157
+ if (child.exitCode !== null || child.killed) return;
158
+ log('WARN', `Pipeline: SIGINT ineffective for ${chatId}, escalating to SIGTERM`);
159
+ try { process.kill(-child.pid, 'SIGTERM'); } catch { try { child.kill('SIGTERM'); } catch { /* */ } }
160
+ sigkillTimer = setTimeout(() => {
161
+ if (child.exitCode !== null || child.killed) return;
162
+ log('WARN', `Pipeline: SIGTERM ineffective for ${chatId}, escalating to SIGKILL`);
163
+ try { process.kill(-child.pid, 'SIGKILL'); } catch { try { child.kill('SIGKILL'); } catch { /* */ } }
164
+ }, SIGINT_ESCALATE_MS);
165
+ }, SIGINT_ESCALATE_MS);
166
+ child.once('close', () => {
167
+ clearTimeout(escalateTimer);
168
+ if (sigkillTimer) clearTimeout(sigkillTimer);
169
+ });
170
+ }
171
+
114
172
  /**
115
173
  * Gracefully pause the running task (SIGINT = ESC equivalent).
174
+ * Escalates to SIGTERM/SIGKILL if SIGINT is ineffective (e.g. claude on Windows).
116
175
  */
117
176
  function _pauseActive(chatId) {
118
177
  const proc = activeProcesses.get(chatId);
119
178
  if (proc && proc.child) {
120
179
  proc.aborted = true;
121
180
  proc.abortReason = 'merge-pause';
122
- try { process.kill(-proc.child.pid, 'SIGINT'); } catch { try { proc.child.kill('SIGINT'); } catch { /* */ } }
181
+ _killWithEscalation(proc.child, chatId);
123
182
  return true;
124
183
  }
125
184
  if (proc && proc.child === null) {
@@ -132,7 +191,6 @@ function createMessagePipeline(deps) {
132
191
 
133
192
  /**
134
193
  * Pause the running task and start collecting.
135
- * No debounce timer — timer starts only after chain dies (.finally).
136
194
  */
137
195
  function _startCollecting(chatId, text, ctx) {
138
196
  _pauseActive(chatId);
@@ -140,23 +198,11 @@ function createMessagePipeline(deps) {
140
198
  const msgs = originalText ? [originalText, text] : [text];
141
199
  const c = { messages: msgs, ctx, timer: null, chainDead: false };
142
200
  collecting.set(chatId, c);
143
- // NO timer here — wait for chain to die
144
201
  log('INFO', `Pipeline: paused & collecting for ${chatId} (original: "${(originalText || '').slice(0, 30)}")`);
145
202
  ctx.bot.sendMessage(chatId, '⏸ 已暂停,继续连发,我会合并后继续').catch(() => {});
146
203
  return Promise.resolve();
147
204
  }
148
205
 
149
- /**
150
- * Add a message to collecting (create if needed).
151
- */
152
- function _addToCollecting(chatId, text, ctx) {
153
- if (!collecting.has(chatId)) {
154
- collecting.set(chatId, { messages: [text], ctx, timer: null, chainDead: false });
155
- } else {
156
- collecting.get(chatId).messages.push(text);
157
- }
158
- }
159
-
160
206
  /**
161
207
  * Reset the debounce timer for burst collection.
162
208
  */
@@ -206,6 +252,30 @@ function createMessagePipeline(deps) {
206
252
  }
207
253
  }
208
254
 
255
+ /**
256
+ * Drain the next queued message for chatId. Each gets its own card.
257
+ * Uses the normal pipeline path — serialization is guaranteed because
258
+ * we only call this from .finally() when the chain is already cleared.
259
+ */
260
+ function _drainNext(chatId) {
261
+ const queue = pendingQueue.get(chatId);
262
+ if (!queue || queue.length === 0) {
263
+ pendingQueue.delete(chatId);
264
+ draining.delete(chatId);
265
+ return;
266
+ }
267
+ draining.add(chatId);
268
+ const { text, ctx } = queue.shift();
269
+ if (queue.length === 0) pendingQueue.delete(chatId);
270
+ log('INFO', `Pipeline: draining queued message for ${chatId} (${queue.length} remaining)`);
271
+ // processMessage will take the "idle" path and set up a new chain.
272
+ // That chain's .finally() will call _drainNext again for the next item.
273
+ const p = processMessage(chatId, text, ctx);
274
+ if (p && typeof p.catch === 'function') {
275
+ p.catch(err => log('ERROR', `Pipeline: drain error for ${chatId}: ${err.message}`));
276
+ }
277
+ }
278
+
209
279
  // ── Process one message ────────────────────────────────────────
210
280
 
211
281
  /**
@@ -232,8 +302,7 @@ function createMessagePipeline(deps) {
232
302
  const proc = activeProcesses.get(chatId);
233
303
  if (proc && proc.child) {
234
304
  proc.aborted = true;
235
- // SIGINT = graceful stop (like ESC), preserves session context for --resume
236
- try { process.kill(-proc.child.pid, 'SIGINT'); } catch { try { proc.child.kill('SIGINT'); } catch { /* */ } }
305
+ _killWithEscalation(proc.child, chatId);
237
306
  return true;
238
307
  }
239
308
  if (proc && proc.child === null) {
@@ -244,13 +313,13 @@ function createMessagePipeline(deps) {
244
313
  }
245
314
 
246
315
  function clearQueue(chatId) {
247
- _cancelCollecting(chatId);
248
- resumed.delete(chatId);
316
+ _clearAll(chatId);
249
317
  }
250
318
 
251
319
  function getQueueLength(chatId) {
252
320
  const c = collecting.get(chatId);
253
- return c ? c.messages.length : 0;
321
+ const q = pendingQueue.get(chatId);
322
+ return (c ? c.messages.length : 0) + (q ? q.length : 0);
254
323
  }
255
324
 
256
325
  return {