clementine-agent 1.18.58 → 1.18.59

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.
@@ -81,17 +81,13 @@ export async function runAgentHeartbeat(opts) {
81
81
  allowedTools: [],
82
82
  abortSignal: opts.abortSignal,
83
83
  });
84
- // Mirror the heartbeat into transcripts so dedup + recall work.
85
- // Skip pure __NOTHING__ outputs since they carry no information.
86
- const text = result.text?.trim() ?? '';
87
- if (opts.memoryStore && text && text !== '__NOTHING__') {
88
- try {
89
- opts.memoryStore.saveTurn(sessionKey, 'heartbeat', text, opts.model ?? MODELS.haiku);
90
- }
91
- catch {
92
- /* non-fatal */
93
- }
94
- }
84
+ // Heartbeat output is NOT mirrored to transcripts. Heartbeats fire
85
+ // up to 28x/day per agent and most output is low-value (status
86
+ // pings, dedup'd reminders). The heartbeat dedup that prior versions
87
+ // wanted recall for actually lives in the prompt itself (the
88
+ // dedupContext block + the __NOTHING__ sentinel), not in DB queries.
89
+ // Saving rows here just polluted FTS and the dashboard memory panel
90
+ // for no recall benefit.
95
91
  return result;
96
92
  }
97
93
  //# sourceMappingURL=run-agent-heartbeat.js.map
@@ -7,6 +7,8 @@ import { type RunAgentResult } from './run-agent.js';
7
7
  * the full assistant graph. */
8
8
  export interface TeamTaskPostHooks {
9
9
  triggerMemoryExtractionPostExchange: (userMessage: string, assistantResponse: string, sessionKey?: string, profile?: AgentProfile) => Promise<void>;
10
+ triggerSkillExtractionFromExecution: (source: 'unleashed' | 'cron' | 'chat', jobName: string, prompt: string, output: string, durationMs: number, agentSlug?: string) => Promise<void>;
11
+ triggerCronReflection: (jobName: string, jobPrompt: string, deliverable: string, successCriteria?: string[]) => Promise<void>;
10
12
  }
11
13
  export interface RunAgentTeamTaskOptions {
12
14
  fromName: string;
@@ -56,6 +56,7 @@ export async function runAgentTeamTask(opts) {
56
56
  promptChars: builtPrompt.length,
57
57
  }, 'runAgentTeamTask: dispatching to runAgent');
58
58
  const sessionKey = `team-task:${opts.fromSlug}->${opts.profile.slug}`;
59
+ const startedAt = Date.now();
59
60
  const result = await runAgent(builtPrompt, {
60
61
  sessionKey,
61
62
  source: 'team-task',
@@ -82,14 +83,23 @@ export async function runAgentTeamTask(opts) {
82
83
  /* non-fatal */
83
84
  }
84
85
  }
85
- // Auto-memory extraction distill any new facts the recipient
86
- // learned during the task into their MEMORY.md. Fire-and-forget,
87
- // scoped to the recipient's profile so writes route to
88
- // agents/<slug>/MEMORY.md, not the global one.
86
+ // Post-task hooks: memory + skill extraction + reflection. All
87
+ // fire-and-forget. Mirrors the cron wrapper's three-hook pattern.
88
+ // Team tasks often produce repeatable procedures (e.g. "draft a
89
+ // follow-up email after a discovery call") and reflection grades
90
+ // whether the response actually fulfilled the request.
89
91
  if (opts.postTaskHooks && result.text?.trim()) {
92
+ const durationMs = Date.now() - startedAt;
90
93
  opts.postTaskHooks
91
94
  .triggerMemoryExtractionPostExchange(opts.content, result.text, sessionKey, opts.profile)
92
95
  .catch(err => logger.debug({ err, fromSlug: opts.fromSlug, toSlug: opts.profile.slug }, 'runAgentTeamTask: memory extraction failed (non-fatal)'));
96
+ opts.postTaskHooks
97
+ .triggerSkillExtractionFromExecution('cron', // 'cron' covers autonomous-task skill source category
98
+ taskName, opts.content, result.text, durationMs, opts.profile.slug)
99
+ .catch(err => logger.debug({ err, fromSlug: opts.fromSlug, toSlug: opts.profile.slug }, 'runAgentTeamTask: skill extraction failed (non-fatal)'));
100
+ opts.postTaskHooks
101
+ .triggerCronReflection(taskName, opts.content, result.text)
102
+ .catch(err => logger.debug({ err, fromSlug: opts.fromSlug, toSlug: opts.profile.slug }, 'runAgentTeamTask: reflection failed (non-fatal)'));
93
103
  }
94
104
  return {
95
105
  ...result,
@@ -281,8 +281,48 @@ async function searchMemory(query, limit = 20, filters = {}) {
281
281
  WHERE ${where.join(' AND ')}
282
282
  ORDER BY ${orderBy}
283
283
  LIMIT ?`;
284
- const rows = db.prepare(sql).all(...params, limit);
285
- return { results: rows, dbExists: true };
284
+ const chunkRows = db.prepare(sql).all(...params, limit);
285
+ // Also surface transcripts from chat / cron / team-task. These
286
+ // are written by saveTurn and would otherwise be invisible to the
287
+ // main search panel (only the per-session viewer surfaced them).
288
+ // chunkType filter is chunk-only — if set, skip transcripts.
289
+ let transcriptRows = [];
290
+ if (words.length > 0 && !filters.chunkType && !filters.pinnedOnly) {
291
+ try {
292
+ const ftsQuery = words.map((w) => `"${w.replace(/"/g, '')}"`).join(' OR ');
293
+ const tWhere = ['transcripts_fts MATCH ?'];
294
+ const tParams = [ftsQuery];
295
+ if (filters.sinceDays && filters.sinceDays > 0) {
296
+ tWhere.push("t.created_at >= datetime('now', ?)");
297
+ tParams.push(`-${filters.sinceDays} days`);
298
+ }
299
+ const tSql = `SELECT t.id, t.session_key, t.role, t.content, t.model, t.created_at,
300
+ bm25(transcripts_fts) as score
301
+ FROM transcripts_fts f JOIN transcripts t ON t.id = f.rowid
302
+ WHERE ${tWhere.join(' AND ')}
303
+ ORDER BY bm25(transcripts_fts)
304
+ LIMIT ?`;
305
+ transcriptRows = db.prepare(tSql).all(...tParams, Math.min(limit, 10))
306
+ .map(r => ({
307
+ id: `transcript:${r.id}`,
308
+ source_file: `transcripts/${r.session_key}`,
309
+ section: `${r.role} @ ${r.created_at}`,
310
+ content: r.content,
311
+ chunk_type: 'transcript',
312
+ updated_at: r.created_at,
313
+ salience: 0,
314
+ pinned: 0,
315
+ score: r.score,
316
+ }));
317
+ }
318
+ catch { /* transcripts FTS may be empty/unavailable — non-fatal */ }
319
+ }
320
+ // Merge: transcripts interleaved by score with chunks. FTS bm25
321
+ // is comparable across both since they use the same tokenizer.
322
+ const merged = [...chunkRows, ...transcriptRows]
323
+ .sort((a, b) => Number(a.score ?? 0) - Number(b.score ?? 0))
324
+ .slice(0, limit);
325
+ return { results: merged, dbExists: true };
286
326
  }
287
327
  catch (err) {
288
328
  return { results: [], dbExists: true, error: String(err) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.58",
3
+ "version": "1.18.59",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",