engrm 0.4.7 → 0.4.8

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.
@@ -457,12 +457,20 @@ function buildSessionContext(db, cwd, options = {}) {
457
457
  if (maxCount !== undefined) {
458
458
  const remaining = Math.max(0, maxCount - pinned.length - dedupedRecent.length);
459
459
  const all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
460
+ const recentPrompts2 = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
461
+ const recentToolEvents2 = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
462
+ const recentSessions2 = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
463
+ const projectTypeCounts2 = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
460
464
  return {
461
465
  project_name: projectName,
462
466
  canonical_id: canonicalId,
463
467
  observations: all.map(toContextObservation),
464
468
  session_count: all.length,
465
- total_active: totalActive
469
+ total_active: totalActive,
470
+ recentPrompts: recentPrompts2.length > 0 ? recentPrompts2 : undefined,
471
+ recentToolEvents: recentToolEvents2.length > 0 ? recentToolEvents2 : undefined,
472
+ recentSessions: recentSessions2.length > 0 ? recentSessions2 : undefined,
473
+ projectTypeCounts: projectTypeCounts2
466
474
  };
467
475
  }
468
476
  let remainingBudget = tokenBudget - 30;
@@ -485,6 +493,10 @@ function buildSessionContext(db, cwd, options = {}) {
485
493
  selected.push(obs);
486
494
  }
487
495
  const summaries = isNewProject ? [] : db.getRecentSummaries(projectId, 5);
496
+ const recentPrompts = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
497
+ const recentToolEvents = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
498
+ const recentSessions = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
499
+ const projectTypeCounts = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
488
500
  let securityFindings = [];
489
501
  if (!isNewProject) {
490
502
  try {
@@ -538,7 +550,11 @@ function buildSessionContext(db, cwd, options = {}) {
538
550
  summaries: summaries.length > 0 ? summaries : undefined,
539
551
  securityFindings: securityFindings.length > 0 ? securityFindings : undefined,
540
552
  recentProjects,
541
- staleDecisions
553
+ staleDecisions,
554
+ recentPrompts: recentPrompts.length > 0 ? recentPrompts : undefined,
555
+ recentToolEvents: recentToolEvents.length > 0 ? recentToolEvents : undefined,
556
+ recentSessions: recentSessions.length > 0 ? recentSessions : undefined,
557
+ projectTypeCounts
542
558
  };
543
559
  }
544
560
  function estimateObservationTokens(obs, index) {
@@ -551,7 +567,7 @@ function estimateObservationTokens(obs, index) {
551
567
  return titleCost + estimateTokens(detailText);
552
568
  }
553
569
  function formatContextForInjection(context) {
554
- if (context.observations.length === 0) {
570
+ if (context.observations.length === 0 && (!context.recentPrompts || context.recentPrompts.length === 0) && (!context.recentToolEvents || context.recentToolEvents.length === 0) && (!context.recentSessions || context.recentSessions.length === 0) && (!context.projectTypeCounts || Object.keys(context.projectTypeCounts).length === 0)) {
555
571
  return `Project: ${context.project_name} (no prior observations)`;
556
572
  }
557
573
  const DETAILED_COUNT = 5;
@@ -574,11 +590,43 @@ function formatContextForInjection(context) {
574
590
  lines.push(`${context.session_count} relevant observation(s) from prior sessions:`);
575
591
  lines.push("");
576
592
  }
593
+ if (context.recentPrompts && context.recentPrompts.length > 0) {
594
+ lines.push("## Recent Requests");
595
+ for (const prompt of context.recentPrompts.slice(0, 5)) {
596
+ const label = prompt.prompt_number > 0 ? `#${prompt.prompt_number}` : new Date(prompt.created_at_epoch * 1000).toISOString().split("T")[0];
597
+ lines.push(`- ${label}: ${truncateText(prompt.prompt.replace(/\s+/g, " "), 160)}`);
598
+ }
599
+ lines.push("");
600
+ }
601
+ if (context.recentToolEvents && context.recentToolEvents.length > 0) {
602
+ lines.push("## Recent Tools");
603
+ for (const tool of context.recentToolEvents.slice(0, 5)) {
604
+ lines.push(`- ${tool.tool_name}: ${formatToolEventDetail(tool)}`);
605
+ }
606
+ lines.push("");
607
+ }
608
+ if (context.recentSessions && context.recentSessions.length > 0) {
609
+ lines.push("## Recent Sessions");
610
+ for (const session of context.recentSessions.slice(0, 4)) {
611
+ const summary = session.request ?? session.completed ?? "(no summary)";
612
+ lines.push(`- ${session.session_id}: ${truncateText(summary.replace(/\s+/g, " "), 140)} ` + `(prompts ${session.prompt_count}, tools ${session.tool_event_count}, obs ${session.observation_count})`);
613
+ }
614
+ lines.push("");
615
+ }
616
+ if (context.projectTypeCounts && Object.keys(context.projectTypeCounts).length > 0) {
617
+ const topTypes = Object.entries(context.projectTypeCounts).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 5).map(([type, count]) => `${type} ${count}`).join(" · ");
618
+ if (topTypes) {
619
+ lines.push(`## Project Signals`);
620
+ lines.push(`Top memory types: ${topTypes}`);
621
+ lines.push("");
622
+ }
623
+ }
577
624
  for (let i = 0;i < context.observations.length; i++) {
578
625
  const obs = context.observations[i];
579
626
  const date = obs.created_at.split("T")[0];
580
627
  const fromLabel = obs.source_project ? ` [from: ${obs.source_project}]` : "";
581
- lines.push(`- **[${obs.type}]** ${obs.title} (${date}, q=${obs.quality.toFixed(1)})${fromLabel}`);
628
+ const fileLabel = formatObservationFiles(obs);
629
+ lines.push(`- **#${obs.id} [${obs.type}]** ${obs.title} (${date}, q=${obs.quality.toFixed(1)})${fromLabel}${fileLabel}`);
582
630
  if (i < DETAILED_COUNT) {
583
631
  const detail = formatObservationDetailFromContext(obs);
584
632
  if (detail) {
@@ -715,11 +763,55 @@ function toContextObservation(obs) {
715
763
  title: obs.title,
716
764
  narrative: obs.narrative,
717
765
  facts: obs.facts,
766
+ files_read: obs.files_read,
767
+ files_modified: obs.files_modified,
718
768
  quality: obs.quality,
719
769
  created_at: obs.created_at,
720
770
  ...obs._source_project ? { source_project: obs._source_project } : {}
721
771
  };
722
772
  }
773
+ function formatObservationFiles(obs) {
774
+ const modified = parseJsonStringArray(obs.files_modified);
775
+ if (modified.length > 0) {
776
+ return ` · files: ${truncateText(modified.slice(0, 2).join(", "), 60)}`;
777
+ }
778
+ const read = parseJsonStringArray(obs.files_read);
779
+ if (read.length > 0) {
780
+ return ` · read: ${truncateText(read.slice(0, 2).join(", "), 60)}`;
781
+ }
782
+ return "";
783
+ }
784
+ function parseJsonStringArray(value) {
785
+ if (!value)
786
+ return [];
787
+ try {
788
+ const parsed = JSON.parse(value);
789
+ if (!Array.isArray(parsed))
790
+ return [];
791
+ return parsed.filter((item) => typeof item === "string" && item.trim().length > 0);
792
+ } catch {
793
+ return [];
794
+ }
795
+ }
796
+ function formatToolEventDetail(tool) {
797
+ const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
798
+ return truncateText(detail || "recent tool execution", 160);
799
+ }
800
+ function getProjectTypeCounts(db, projectId, userId) {
801
+ const visibilityClause = userId ? " AND (sensitivity != 'personal' OR user_id = ?)" : "";
802
+ const rows = db.db.query(`SELECT type, COUNT(*) as count
803
+ FROM observations
804
+ WHERE project_id = ?
805
+ AND lifecycle IN ('active', 'aging', 'pinned')
806
+ AND superseded_by IS NULL
807
+ ${visibilityClause}
808
+ GROUP BY type`).all(projectId, ...userId ? [userId] : []);
809
+ const counts = {};
810
+ for (const row of rows) {
811
+ counts[row.type] = row.count;
812
+ }
813
+ return counts;
814
+ }
723
815
 
724
816
  // src/telemetry/stack-detect.ts
725
817
  import { existsSync as existsSync2 } from "node:fs";
@@ -1168,6 +1260,21 @@ function asTeams(value, fallback) {
1168
1260
  }
1169
1261
 
1170
1262
  // src/sync/auth.ts
1263
+ var LEGACY_PUBLIC_HOSTS = new Set(["www.candengo.com", "candengo.com"]);
1264
+ function normalizeBaseUrl(url) {
1265
+ const trimmed = url.trim();
1266
+ if (!trimmed)
1267
+ return trimmed;
1268
+ try {
1269
+ const parsed = new URL(trimmed);
1270
+ if (LEGACY_PUBLIC_HOSTS.has(parsed.hostname)) {
1271
+ parsed.hostname = "engrm.dev";
1272
+ }
1273
+ return parsed.toString().replace(/\/$/, "");
1274
+ } catch {
1275
+ return trimmed.replace(/\/$/, "");
1276
+ }
1277
+ }
1171
1278
  function getApiKey(config) {
1172
1279
  const envKey = process.env.ENGRM_TOKEN;
1173
1280
  if (envKey && envKey.startsWith("cvk_"))
@@ -1179,7 +1286,7 @@ function getApiKey(config) {
1179
1286
  }
1180
1287
  function getBaseUrl(config) {
1181
1288
  if (config.candengo_url && config.candengo_url.length > 0) {
1182
- return config.candengo_url;
1289
+ return normalizeBaseUrl(config.candengo_url);
1183
1290
  }
1184
1291
  return null;
1185
1292
  }
@@ -1809,6 +1916,64 @@ var MIGRATIONS = [
1809
1916
  );
1810
1917
  INSERT INTO observations_fts(observations_fts) VALUES('rebuild');
1811
1918
  `
1919
+ },
1920
+ {
1921
+ version: 9,
1922
+ description: "Add first-class user prompt capture",
1923
+ sql: `
1924
+ CREATE TABLE IF NOT EXISTS user_prompts (
1925
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1926
+ session_id TEXT NOT NULL,
1927
+ project_id INTEGER REFERENCES projects(id),
1928
+ prompt_number INTEGER NOT NULL,
1929
+ prompt TEXT NOT NULL,
1930
+ prompt_hash TEXT NOT NULL,
1931
+ cwd TEXT,
1932
+ user_id TEXT NOT NULL,
1933
+ device_id TEXT NOT NULL,
1934
+ agent TEXT DEFAULT 'claude-code',
1935
+ created_at_epoch INTEGER NOT NULL,
1936
+ UNIQUE(session_id, prompt_number)
1937
+ );
1938
+
1939
+ CREATE INDEX IF NOT EXISTS idx_user_prompts_session
1940
+ ON user_prompts(session_id, prompt_number DESC);
1941
+ CREATE INDEX IF NOT EXISTS idx_user_prompts_project
1942
+ ON user_prompts(project_id, created_at_epoch DESC);
1943
+ CREATE INDEX IF NOT EXISTS idx_user_prompts_created
1944
+ ON user_prompts(created_at_epoch DESC);
1945
+ CREATE INDEX IF NOT EXISTS idx_user_prompts_hash
1946
+ ON user_prompts(prompt_hash);
1947
+ `
1948
+ },
1949
+ {
1950
+ version: 10,
1951
+ description: "Add first-class tool event chronology",
1952
+ sql: `
1953
+ CREATE TABLE IF NOT EXISTS tool_events (
1954
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1955
+ session_id TEXT NOT NULL,
1956
+ project_id INTEGER REFERENCES projects(id),
1957
+ tool_name TEXT NOT NULL,
1958
+ tool_input_json TEXT,
1959
+ tool_response_preview TEXT,
1960
+ file_path TEXT,
1961
+ command TEXT,
1962
+ user_id TEXT NOT NULL,
1963
+ device_id TEXT NOT NULL,
1964
+ agent TEXT DEFAULT 'claude-code',
1965
+ created_at_epoch INTEGER NOT NULL
1966
+ );
1967
+
1968
+ CREATE INDEX IF NOT EXISTS idx_tool_events_session
1969
+ ON tool_events(session_id, created_at_epoch DESC, id DESC);
1970
+ CREATE INDEX IF NOT EXISTS idx_tool_events_project
1971
+ ON tool_events(project_id, created_at_epoch DESC, id DESC);
1972
+ CREATE INDEX IF NOT EXISTS idx_tool_events_tool_name
1973
+ ON tool_events(tool_name, created_at_epoch DESC);
1974
+ CREATE INDEX IF NOT EXISTS idx_tool_events_created
1975
+ ON tool_events(created_at_epoch DESC, id DESC);
1976
+ `
1812
1977
  }
1813
1978
  ];
1814
1979
  function isVecExtensionLoaded(db) {
@@ -1893,6 +2058,7 @@ function ensureObservationTypes(db) {
1893
2058
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
1894
2059
 
1895
2060
  // src/storage/sqlite.ts
2061
+ import { createHash as createHash3 } from "node:crypto";
1896
2062
  var IS_BUN = typeof globalThis.Bun !== "undefined";
1897
2063
  function openDatabase(dbPath) {
1898
2064
  if (IS_BUN) {
@@ -2156,6 +2322,110 @@ class MemDatabase {
2156
2322
  const now = Math.floor(Date.now() / 1000);
2157
2323
  this.db.query("UPDATE sessions SET status = 'completed', completed_at_epoch = ? WHERE session_id = ?").run(now, sessionId);
2158
2324
  }
2325
+ getSessionById(sessionId) {
2326
+ return this.db.query("SELECT * FROM sessions WHERE session_id = ?").get(sessionId) ?? null;
2327
+ }
2328
+ getRecentSessions(projectId, limit = 10, userId) {
2329
+ const visibilityClause = userId ? " AND s.user_id = ?" : "";
2330
+ if (projectId !== null) {
2331
+ return this.db.query(`SELECT
2332
+ s.*,
2333
+ p.name AS project_name,
2334
+ ss.request AS request,
2335
+ ss.completed AS completed,
2336
+ (SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
2337
+ (SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
2338
+ FROM sessions s
2339
+ LEFT JOIN projects p ON p.id = s.project_id
2340
+ LEFT JOIN session_summaries ss ON ss.session_id = s.session_id
2341
+ WHERE s.project_id = ?${visibilityClause}
2342
+ ORDER BY COALESCE(s.completed_at_epoch, s.started_at_epoch, 0) DESC, s.id DESC
2343
+ LIMIT ?`).all(projectId, ...userId ? [userId] : [], limit);
2344
+ }
2345
+ return this.db.query(`SELECT
2346
+ s.*,
2347
+ p.name AS project_name,
2348
+ ss.request AS request,
2349
+ ss.completed AS completed,
2350
+ (SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
2351
+ (SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
2352
+ FROM sessions s
2353
+ LEFT JOIN projects p ON p.id = s.project_id
2354
+ LEFT JOIN session_summaries ss ON ss.session_id = s.session_id
2355
+ WHERE 1 = 1${visibilityClause}
2356
+ ORDER BY COALESCE(s.completed_at_epoch, s.started_at_epoch, 0) DESC, s.id DESC
2357
+ LIMIT ?`).all(...userId ? [userId] : [], limit);
2358
+ }
2359
+ insertUserPrompt(input) {
2360
+ const createdAt = input.created_at_epoch ?? Math.floor(Date.now() / 1000);
2361
+ const normalizedPrompt = input.prompt.trim();
2362
+ const promptHash = hashPrompt(normalizedPrompt);
2363
+ const latest = this.db.query(`SELECT * FROM user_prompts
2364
+ WHERE session_id = ?
2365
+ ORDER BY prompt_number DESC
2366
+ LIMIT 1`).get(input.session_id);
2367
+ if (latest && latest.prompt_hash === promptHash) {
2368
+ return latest;
2369
+ }
2370
+ const promptNumber = (latest?.prompt_number ?? 0) + 1;
2371
+ const result = this.db.query(`INSERT INTO user_prompts (
2372
+ session_id, project_id, prompt_number, prompt, prompt_hash, cwd,
2373
+ user_id, device_id, agent, created_at_epoch
2374
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.session_id, input.project_id, promptNumber, normalizedPrompt, promptHash, input.cwd ?? null, input.user_id, input.device_id, input.agent ?? "claude-code", createdAt);
2375
+ return this.getUserPromptById(Number(result.lastInsertRowid));
2376
+ }
2377
+ getUserPromptById(id) {
2378
+ return this.db.query("SELECT * FROM user_prompts WHERE id = ?").get(id) ?? null;
2379
+ }
2380
+ getRecentUserPrompts(projectId, limit = 10, userId) {
2381
+ const visibilityClause = userId ? " AND user_id = ?" : "";
2382
+ if (projectId !== null) {
2383
+ return this.db.query(`SELECT * FROM user_prompts
2384
+ WHERE project_id = ?${visibilityClause}
2385
+ ORDER BY created_at_epoch DESC, prompt_number DESC
2386
+ LIMIT ?`).all(projectId, ...userId ? [userId] : [], limit);
2387
+ }
2388
+ return this.db.query(`SELECT * FROM user_prompts
2389
+ WHERE 1 = 1${visibilityClause}
2390
+ ORDER BY created_at_epoch DESC, prompt_number DESC
2391
+ LIMIT ?`).all(...userId ? [userId] : [], limit);
2392
+ }
2393
+ getSessionUserPrompts(sessionId, limit = 20) {
2394
+ return this.db.query(`SELECT * FROM user_prompts
2395
+ WHERE session_id = ?
2396
+ ORDER BY prompt_number ASC
2397
+ LIMIT ?`).all(sessionId, limit);
2398
+ }
2399
+ insertToolEvent(input) {
2400
+ const createdAt = input.created_at_epoch ?? Math.floor(Date.now() / 1000);
2401
+ const result = this.db.query(`INSERT INTO tool_events (
2402
+ session_id, project_id, tool_name, tool_input_json, tool_response_preview,
2403
+ file_path, command, user_id, device_id, agent, created_at_epoch
2404
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.session_id, input.project_id, input.tool_name, input.tool_input_json ?? null, input.tool_response_preview ?? null, input.file_path ?? null, input.command ?? null, input.user_id, input.device_id, input.agent ?? "claude-code", createdAt);
2405
+ return this.getToolEventById(Number(result.lastInsertRowid));
2406
+ }
2407
+ getToolEventById(id) {
2408
+ return this.db.query("SELECT * FROM tool_events WHERE id = ?").get(id) ?? null;
2409
+ }
2410
+ getSessionToolEvents(sessionId, limit = 20) {
2411
+ return this.db.query(`SELECT * FROM tool_events
2412
+ WHERE session_id = ?
2413
+ ORDER BY created_at_epoch ASC, id ASC
2414
+ LIMIT ?`).all(sessionId, limit);
2415
+ }
2416
+ getRecentToolEvents(projectId, limit = 20, userId) {
2417
+ const visibilityClause = userId ? " AND user_id = ?" : "";
2418
+ if (projectId !== null) {
2419
+ return this.db.query(`SELECT * FROM tool_events
2420
+ WHERE project_id = ?${visibilityClause}
2421
+ ORDER BY created_at_epoch DESC, id DESC
2422
+ LIMIT ?`).all(projectId, ...userId ? [userId] : [], limit);
2423
+ }
2424
+ return this.db.query(`SELECT * FROM tool_events
2425
+ WHERE 1 = 1${visibilityClause}
2426
+ ORDER BY created_at_epoch DESC, id DESC
2427
+ LIMIT ?`).all(...userId ? [userId] : [], limit);
2428
+ }
2159
2429
  addToOutbox(recordType, recordId) {
2160
2430
  const now = Math.floor(Date.now() / 1000);
2161
2431
  this.db.query(`INSERT INTO sync_outbox (record_type, record_id, created_at_epoch)
@@ -2326,6 +2596,9 @@ class MemDatabase {
2326
2596
  this.db.query("INSERT OR REPLACE INTO packs_installed (name, installed_at, observation_count) VALUES (?, ?, ?)").run(name, now, observationCount);
2327
2597
  }
2328
2598
  }
2599
+ function hashPrompt(prompt) {
2600
+ return createHash3("sha256").update(prompt).digest("hex");
2601
+ }
2329
2602
 
2330
2603
  // src/hooks/common.ts
2331
2604
  var c = {
@@ -2491,6 +2764,15 @@ function formatSplashScreen(data) {
2491
2764
  if (data.available > 0) {
2492
2765
  statParts.push(`${c2.dim}${data.available.toLocaleString()} searchable${c2.reset}`);
2493
2766
  }
2767
+ if (data.context.recentSessions && data.context.recentSessions.length > 0) {
2768
+ statParts.push(`${c2.white}${data.context.recentSessions.length} sessions${c2.reset}`);
2769
+ }
2770
+ if (data.context.recentPrompts && data.context.recentPrompts.length > 0) {
2771
+ statParts.push(`${c2.magenta}${data.context.recentPrompts.length} requests${c2.reset}`);
2772
+ }
2773
+ if (data.context.recentToolEvents && data.context.recentToolEvents.length > 0) {
2774
+ statParts.push(`${c2.yellow}${data.context.recentToolEvents.length} tools${c2.reset}`);
2775
+ }
2494
2776
  if (data.synced > 0) {
2495
2777
  statParts.push(`${c2.cyan}${data.synced} synced${c2.reset}`);
2496
2778
  }
@@ -2523,9 +2805,13 @@ function formatVisibleStartupBrief(context) {
2523
2805
  const lines = [];
2524
2806
  const latest = pickBestSummary(context);
2525
2807
  const observationFallbacks = buildObservationFallbacks(context);
2808
+ const promptFallback = buildPromptFallback(context);
2809
+ const toolFallbacks = buildToolFallbacks(context);
2810
+ const sessionFallbacks = buildSessionFallbacks(context);
2811
+ const projectSignals = buildProjectSignalLine(context);
2526
2812
  if (latest) {
2527
2813
  const sections = [
2528
- ["Request", chooseRequest(latest.request, observationFallbacks.request), 1],
2814
+ ["Request", chooseRequest(latest.request, promptFallback ?? observationFallbacks.request), 1],
2529
2815
  ["Investigated", chooseSection(latest.investigated, observationFallbacks.investigated, "Investigated"), 2],
2530
2816
  ["Learned", latest.learned, 2],
2531
2817
  ["Completed", chooseSection(latest.completed, observationFallbacks.completed, "Completed"), 2],
@@ -2540,6 +2826,25 @@ function formatVisibleStartupBrief(context) {
2540
2826
  }
2541
2827
  }
2542
2828
  }
2829
+ } else if (promptFallback) {
2830
+ lines.push(`${c2.cyan}Request:${c2.reset}`);
2831
+ lines.push(` - ${truncateInline(promptFallback, 140)}`);
2832
+ if (toolFallbacks.length > 0) {
2833
+ lines.push(`${c2.cyan}Recent Tools:${c2.reset}`);
2834
+ for (const item of toolFallbacks) {
2835
+ lines.push(` - ${truncateInline(item, 140)}`);
2836
+ }
2837
+ }
2838
+ }
2839
+ if (sessionFallbacks.length > 0) {
2840
+ lines.push(`${c2.cyan}Recent Sessions:${c2.reset}`);
2841
+ for (const item of sessionFallbacks) {
2842
+ lines.push(` - ${truncateInline(item, 140)}`);
2843
+ }
2844
+ }
2845
+ if (projectSignals) {
2846
+ lines.push(`${c2.cyan}Project Signals:${c2.reset}`);
2847
+ lines.push(` - ${truncateInline(projectSignals, 140)}`);
2543
2848
  }
2544
2849
  const stale = pickRelevantStaleDecision(context, latest);
2545
2850
  if (stale) {
@@ -2553,6 +2858,32 @@ function formatVisibleStartupBrief(context) {
2553
2858
  }
2554
2859
  return lines.slice(0, 10);
2555
2860
  }
2861
+ function buildPromptFallback(context) {
2862
+ const latest = context.recentPrompts?.[0];
2863
+ if (!latest?.prompt)
2864
+ return null;
2865
+ return latest.prompt.replace(/\s+/g, " ").trim();
2866
+ }
2867
+ function buildToolFallbacks(context) {
2868
+ return (context.recentToolEvents ?? []).slice(0, 3).map((tool) => {
2869
+ const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
2870
+ return `${tool.tool_name}: ${detail}`.trim();
2871
+ }).filter((item) => item.length > 0);
2872
+ }
2873
+ function buildSessionFallbacks(context) {
2874
+ return (context.recentSessions ?? []).slice(0, 2).map((session) => {
2875
+ const summary = session.request ?? session.completed ?? "";
2876
+ if (!summary)
2877
+ return "";
2878
+ return `${session.session_id}: ${summary}`;
2879
+ }).filter((item) => item.length > 0);
2880
+ }
2881
+ function buildProjectSignalLine(context) {
2882
+ if (!context.projectTypeCounts)
2883
+ return null;
2884
+ const top = Object.entries(context.projectTypeCounts).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 4).map(([type, count]) => `${type} ${count}`).join("; ");
2885
+ return top || null;
2886
+ }
2556
2887
  function toSplashLines(value, maxItems) {
2557
2888
  if (!value)
2558
2889
  return [];