@velvetmonkey/flywheel-memory 2.4.2 → 2.4.3

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 (2) hide show
  1. package/dist/index.js +275 -60
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -8392,6 +8392,155 @@ function createStepTracker() {
8392
8392
  }
8393
8393
  };
8394
8394
  }
8395
+ function compactStep(step) {
8396
+ const out = step.output ?? {};
8397
+ let summary;
8398
+ switch (step.name) {
8399
+ case "entity_scan":
8400
+ summary = {
8401
+ entity_count: asNum(out.entity_count),
8402
+ added_count: asArrayLen(out.added),
8403
+ removed_count: asArrayLen(out.removed),
8404
+ alias_change_count: asArrayLen(out.alias_changes),
8405
+ category_change_count: asArrayLen(out.category_changes)
8406
+ };
8407
+ break;
8408
+ case "hub_scores":
8409
+ summary = {
8410
+ updated: asNum(out.updated),
8411
+ diff_count: asArrayLen(out.diffs)
8412
+ };
8413
+ break;
8414
+ case "forward_links":
8415
+ summary = {
8416
+ total_resolved: asNum(out.total_resolved),
8417
+ total_dead: asNum(out.total_dead),
8418
+ new_dead_count: asArrayLen(out.new_dead_links),
8419
+ diff_count: asArrayLen(out.link_diffs)
8420
+ };
8421
+ break;
8422
+ case "wikilink_check":
8423
+ summary = {
8424
+ tracked_count: asArrayLen(out.tracked),
8425
+ mention_count: asArrayLen(out.mentions)
8426
+ };
8427
+ break;
8428
+ case "prospect_scan": {
8429
+ const prospects = Array.isArray(out.prospects) ? out.prospects : [];
8430
+ summary = {
8431
+ implicit_count: prospects.reduce((s, p) => s + (Array.isArray(p.implicit) ? p.implicit.length : 0), 0),
8432
+ dead_match_count: prospects.reduce((s, p) => s + (Array.isArray(p.deadLinkMatches) ? p.deadLinkMatches.length : 0), 0)
8433
+ };
8434
+ break;
8435
+ }
8436
+ case "suggestion_scoring":
8437
+ summary = { scored_files: asNum(out.scored_files) };
8438
+ break;
8439
+ case "implicit_feedback":
8440
+ summary = {
8441
+ removal_count: asArrayLen(out.removals),
8442
+ addition_count: asArrayLen(out.additions),
8443
+ suppressed_count: asArrayLen(out.newly_suppressed)
8444
+ };
8445
+ break;
8446
+ case "note_embeddings":
8447
+ summary = { updated: asNum(out.updated), removed: asNum(out.removed) };
8448
+ break;
8449
+ case "entity_embeddings":
8450
+ summary = { updated: asNum(out.updated) };
8451
+ break;
8452
+ case "fts5_incremental":
8453
+ summary = { updated: asNum(out.updated), removed: asNum(out.removed) };
8454
+ break;
8455
+ case "index_rebuild":
8456
+ summary = {
8457
+ note_count: asNum(out.note_count),
8458
+ entity_count: asNum(out.entity_count),
8459
+ tag_count: asNum(out.tag_count)
8460
+ };
8461
+ break;
8462
+ case "index_cache":
8463
+ summary = { saved: asBool(out.saved) };
8464
+ break;
8465
+ case "task_cache":
8466
+ summary = { updated: asNum(out.updated), removed: asNum(out.removed) };
8467
+ break;
8468
+ case "tag_scan":
8469
+ summary = { added_count: asNum(out.total_added), removed_count: asNum(out.total_removed) };
8470
+ break;
8471
+ case "drain_proactive_queue":
8472
+ summary = {
8473
+ total_applied: asNum(out.total_applied),
8474
+ expired: asNum(out.expired),
8475
+ skipped_mtime: asNum(out.skipped_mtime),
8476
+ skipped_daily_cap: asNum(out.skipped_daily_cap)
8477
+ };
8478
+ break;
8479
+ case "proactive_enqueue":
8480
+ summary = { enqueued: asNum(out.enqueued), total_candidates: asNum(out.total_candidates) };
8481
+ break;
8482
+ case "recency":
8483
+ case "cooccurrence":
8484
+ case "edge_weights":
8485
+ summary = { rebuilt: asBool(out.rebuilt) };
8486
+ break;
8487
+ case "incremental_recency":
8488
+ summary = { entities_updated: asNum(out.entities_updated) };
8489
+ break;
8490
+ case "corrections":
8491
+ summary = { processed: asNum(out.processed) };
8492
+ break;
8493
+ case "retrieval_cooccurrence":
8494
+ summary = { pairs_inserted: asNum(out.pairs_inserted) };
8495
+ break;
8496
+ case "integrity_check":
8497
+ case "maintenance":
8498
+ summary = {
8499
+ ...out.skipped != null ? { skipped: asBool(out.skipped) } : {},
8500
+ ...typeof out.reason === "string" ? { reason: out.reason } : {}
8501
+ };
8502
+ break;
8503
+ case "note_moves":
8504
+ summary = { count: asNum(out.count ?? (Array.isArray(out.renames) ? out.renames.length : 0)) };
8505
+ break;
8506
+ default:
8507
+ summary = {};
8508
+ for (const [k, v] of Object.entries(out)) {
8509
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "string") {
8510
+ summary[k] = v;
8511
+ }
8512
+ }
8513
+ break;
8514
+ }
8515
+ return {
8516
+ name: step.name,
8517
+ duration_ms: step.duration_ms,
8518
+ ...step.skipped ? { skipped: true, skip_reason: step.skip_reason } : {},
8519
+ summary
8520
+ };
8521
+ }
8522
+ function compactPipelineRun(event) {
8523
+ const paths = event.changed_paths ?? [];
8524
+ return {
8525
+ timestamp: event.timestamp,
8526
+ trigger: event.trigger,
8527
+ duration_ms: event.duration_ms,
8528
+ files_changed: event.files_changed,
8529
+ changed_paths_total: event.files_changed ?? paths.length,
8530
+ changed_paths_sample: paths.slice(0, 3),
8531
+ step_count: event.steps?.length ?? 0,
8532
+ steps: (event.steps ?? []).map(compactStep)
8533
+ };
8534
+ }
8535
+ function asNum(v) {
8536
+ return typeof v === "number" ? v : 0;
8537
+ }
8538
+ function asBool(v) {
8539
+ return typeof v === "boolean" ? v : false;
8540
+ }
8541
+ function asArrayLen(v) {
8542
+ return Array.isArray(v) ? v.length : 0;
8543
+ }
8395
8544
  function recordIndexEvent(stateDb2, event) {
8396
8545
  stateDb2.db.prepare(
8397
8546
  `INSERT INTO index_events (timestamp, trigger, duration_ms, success, note_count, files_changed, changed_paths, error, steps)
@@ -11407,7 +11556,7 @@ function getSessionHistory(stateDb2, sessionId) {
11407
11556
  }));
11408
11557
  }
11409
11558
  function getSessionDetail(stateDb2, sessionId, options = {}) {
11410
- const { include_children = true, limit = 200 } = options;
11559
+ const { include_children = true, limit = 50 } = options;
11411
11560
  const rows = include_children ? stateDb2.db.prepare(`
11412
11561
  SELECT * FROM tool_invocations
11413
11562
  WHERE session_id = ? OR session_id LIKE ?
@@ -13330,7 +13479,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
13330
13479
  async ({ query, where, has_tag, has_any_tag, has_all_tags, include_children, folder, title_contains, modified_after, modified_before, sort_by, order, prefix, limit: requestedLimit, detail_count: requestedDetailCount, context_note, consumer }) => {
13331
13480
  requireIndex();
13332
13481
  const limit = Math.min(requestedLimit ?? 10, MAX_LIMIT);
13333
- const detailN = requestedDetailCount ?? 5;
13482
+ const enrichN = Math.min(requestedDetailCount ?? 5, limit);
13483
+ const expandN = Math.min(enrichN, 8);
13334
13484
  const index = getIndex();
13335
13485
  const vaultPath2 = getVaultPath();
13336
13486
  if (prefix && query) {
@@ -13384,7 +13534,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
13384
13534
  const limitedNotes = matchingNotes.slice(0, limit);
13385
13535
  const stateDb2 = getStateDb4();
13386
13536
  const notes = limitedNotes.map(
13387
- (note, i) => (i < detailN ? enrichResult : enrichResultLight)({ path: note.path, title: note.title }, index, stateDb2)
13537
+ (note, i) => (i < enrichN ? enrichResult : enrichResultLight)({ path: note.path, title: note.title }, index, stateDb2)
13388
13538
  );
13389
13539
  return { content: [{ type: "text", text: JSON.stringify({
13390
13540
  total_matches: totalMatches,
@@ -13519,7 +13669,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
13519
13669
  await enhanceSnippets(results2, query, vaultPath2);
13520
13670
  if (consumer === "llm") {
13521
13671
  applySandwichOrdering(results2);
13522
- await expandToSections(results2, index, vaultPath2, detailN);
13672
+ await expandToSections(results2, index, vaultPath2, expandN);
13523
13673
  stripInternalFields(results2);
13524
13674
  }
13525
13675
  const entitySection2 = await entitySectionPromise;
@@ -13567,7 +13717,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
13567
13717
  await enhanceSnippets(results2, query, vaultPath2);
13568
13718
  if (consumer === "llm") {
13569
13719
  applySandwichOrdering(results2);
13570
- await expandToSections(results2, index, vaultPath2, detailN);
13720
+ await expandToSections(results2, index, vaultPath2, expandN);
13571
13721
  stripInternalFields(results2);
13572
13722
  }
13573
13723
  const entitySection2 = await entitySectionPromise;
@@ -13594,7 +13744,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb4) {
13594
13744
  await enhanceSnippets(results, query, vaultPath2);
13595
13745
  if (consumer === "llm") {
13596
13746
  applySandwichOrdering(results);
13597
- await expandToSections(results, index, vaultPath2, detailN);
13747
+ await expandToSections(results, index, vaultPath2, expandN);
13598
13748
  stripInternalFields(results);
13599
13749
  }
13600
13750
  const entitySection = await entitySectionPromise;
@@ -14533,13 +14683,28 @@ function registerGraphExportTools(server2, getIndex, getVaultPath, getStateDb4)
14533
14683
  include_cooccurrence: z3.boolean().default(true).describe("Include co-occurrence edges between entities"),
14534
14684
  min_edge_weight: z3.number().default(0).describe("Minimum edge weight threshold (filters weighted edges)"),
14535
14685
  center_entity: z3.string().optional().describe("Center the export on this entity (ego network). Only includes nodes within `depth` hops."),
14536
- depth: z3.number().default(1).describe("Hops from center_entity to include (default 1). Ignored without center_entity.")
14686
+ depth: z3.number().default(1).describe("Hops from center_entity to include (default 1). Ignored without center_entity."),
14687
+ max_nodes: z3.number().default(500).describe("Maximum nodes in export when no center_entity. Pass higher for full vault exports.")
14537
14688
  },
14538
- async ({ format, include_cooccurrence, min_edge_weight, center_entity, depth }) => {
14689
+ async ({ format, include_cooccurrence, min_edge_weight, center_entity, depth, max_nodes }) => {
14539
14690
  requireIndex();
14540
14691
  const index = getIndex();
14541
14692
  const stateDb2 = getStateDb4?.() ?? null;
14542
14693
  const data = buildGraphData(index, stateDb2, { include_cooccurrence, min_edge_weight, center_entity, depth });
14694
+ if (!center_entity && data.nodes.length > max_nodes) {
14695
+ return {
14696
+ content: [{
14697
+ type: "text",
14698
+ text: JSON.stringify({
14699
+ error: `Graph has ${data.nodes.length} nodes and ${data.edges.length} edges, exceeding max_nodes=${max_nodes}. Use center_entity to scope, or pass a higher max_nodes.`,
14700
+ node_count: data.nodes.length,
14701
+ edge_count: data.edges.length,
14702
+ max_nodes
14703
+ })
14704
+ }],
14705
+ isError: true
14706
+ };
14707
+ }
14543
14708
  let output;
14544
14709
  if (format === "json") {
14545
14710
  output = JSON.stringify(data, null, 2);
@@ -15497,31 +15662,33 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15497
15662
  trigger: z5.string(),
15498
15663
  duration_ms: z5.number(),
15499
15664
  files_changed: z5.number().nullable(),
15500
- changed_paths: z5.array(z5.string()).nullable(),
15665
+ changed_paths_total: z5.number().describe("Total number of changed files"),
15666
+ changed_paths_sample: z5.array(z5.string()).describe("Up to 3 sample changed paths"),
15667
+ step_count: z5.number().describe("Number of pipeline steps"),
15501
15668
  steps: z5.array(z5.object({
15502
15669
  name: z5.string(),
15503
15670
  duration_ms: z5.number(),
15504
- input: z5.record(z5.unknown()),
15505
- output: z5.record(z5.unknown()),
15506
15671
  skipped: z5.boolean().optional(),
15507
- skip_reason: z5.string().optional()
15508
- }))
15509
- }).optional().describe("Most recent watcher pipeline run with per-step timing"),
15672
+ skip_reason: z5.string().optional(),
15673
+ summary: z5.record(z5.union([z5.number(), z5.boolean(), z5.string()]))
15674
+ })).optional().describe("Compact step summaries (full mode only)")
15675
+ }).optional().describe("Most recent watcher pipeline run"),
15510
15676
  recent_pipelines: z5.array(z5.object({
15511
15677
  timestamp: z5.number(),
15512
15678
  trigger: z5.string(),
15513
15679
  duration_ms: z5.number(),
15514
15680
  files_changed: z5.number().nullable(),
15515
- changed_paths: z5.array(z5.string()).nullable(),
15681
+ changed_paths_total: z5.number(),
15682
+ changed_paths_sample: z5.array(z5.string()),
15683
+ step_count: z5.number(),
15516
15684
  steps: z5.array(z5.object({
15517
15685
  name: z5.string(),
15518
15686
  duration_ms: z5.number(),
15519
- input: z5.record(z5.unknown()),
15520
- output: z5.record(z5.unknown()),
15521
15687
  skipped: z5.boolean().optional(),
15522
- skip_reason: z5.string().optional()
15688
+ skip_reason: z5.string().optional(),
15689
+ summary: z5.record(z5.union([z5.number(), z5.boolean(), z5.string()]))
15523
15690
  }))
15524
- })).optional().describe("Up to 5 most recent pipeline runs with steps data"),
15691
+ })).optional().describe("Up to 5 most recent pipeline runs with compact step summaries (full mode only)"),
15525
15692
  fts5_ready: z5.boolean().describe("Whether the FTS5 keyword search index is ready"),
15526
15693
  fts5_building: z5.boolean().describe("Whether the FTS5 keyword search index is currently building"),
15527
15694
  embeddings_building: z5.boolean().describe("Whether semantic embeddings are currently building"),
@@ -15718,14 +15885,13 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15718
15885
  try {
15719
15886
  const evt = getRecentPipelineEvent(stateDb2);
15720
15887
  if (evt && evt.steps && evt.steps.length > 0) {
15721
- lastPipeline = {
15722
- timestamp: evt.timestamp,
15723
- trigger: evt.trigger,
15724
- duration_ms: evt.duration_ms,
15725
- files_changed: evt.files_changed,
15726
- changed_paths: evt.changed_paths,
15727
- steps: evt.steps
15728
- };
15888
+ const compact = compactPipelineRun(evt);
15889
+ if (isFull) {
15890
+ lastPipeline = compact;
15891
+ } else {
15892
+ const { steps: _steps, ...metadataOnly } = compact;
15893
+ lastPipeline = metadataOnly;
15894
+ }
15729
15895
  }
15730
15896
  } catch {
15731
15897
  }
@@ -15733,14 +15899,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15733
15899
  try {
15734
15900
  const events = getRecentIndexEvents(stateDb2, 10).filter((e) => e.steps && e.steps.length > 0).slice(0, 5);
15735
15901
  if (events.length > 0) {
15736
- recentPipelines = events.map((e) => ({
15737
- timestamp: e.timestamp,
15738
- trigger: e.trigger,
15739
- duration_ms: e.duration_ms,
15740
- files_changed: e.files_changed,
15741
- changed_paths: e.changed_paths,
15742
- steps: e.steps
15743
- }));
15902
+ recentPipelines = events.map((e) => compactPipelineRun(e));
15744
15903
  }
15745
15904
  } catch {
15746
15905
  }
@@ -15903,13 +16062,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig2 = () =>
15903
16062
  if (stateDb2) {
15904
16063
  try {
15905
16064
  const events = getRecentIndexEvents(stateDb2, 10).filter((e) => e.steps && e.steps.length > 0).slice(0, 5);
15906
- output.recent_runs = events.map((e) => ({
15907
- timestamp: e.timestamp,
15908
- trigger: e.trigger,
15909
- duration_ms: e.duration_ms,
15910
- files_changed: e.files_changed,
15911
- steps: e.steps
15912
- }));
16065
+ output.recent_runs = events.map((e) => compactPipelineRun(e));
15913
16066
  } catch {
15914
16067
  }
15915
16068
  }
@@ -17137,7 +17290,7 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
17137
17290
  description: "Use when listing all linkable entities grouped by category. Produces the full entity index from the state database with names, aliases, hub scores, and categories. Returns an array of entity profiles. Does not search note content \u2014 only returns entity metadata from the index.",
17138
17291
  inputSchema: {
17139
17292
  category: z6.string().optional().describe('Filter to a specific category (e.g. "people", "technologies")'),
17140
- limit: z6.coerce.number().default(2e3).describe("Maximum entities per category")
17293
+ limit: z6.coerce.number().default(200).describe("Maximum entities per category (default 200; pass higher for full hydration)")
17141
17294
  }
17142
17295
  },
17143
17296
  async ({
@@ -17292,10 +17445,11 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17292
17445
  description: "Use after search identifies a note you need detail on. Produces heading tree, frontmatter, tags, word count, backlink and outlink counts, and optionally full section content. Returns enriched note structure with entity metadata when available. Does not search \u2014 requires an exact path from a prior search result.",
17293
17446
  inputSchema: {
17294
17447
  path: z7.string().describe("Path to the note"),
17295
- include_content: z7.boolean().default(true).describe("Include the text content under each top-level section. Set false to get structure only.")
17448
+ include_content: z7.boolean().default(false).describe("Include the text content under each top-level section. Default false for structure only."),
17449
+ max_content_chars: z7.number().default(2e4).describe("Max total chars of section content to include. Sections are truncated at paragraph boundaries.")
17296
17450
  }
17297
17451
  },
17298
- async ({ path: path40, include_content }) => {
17452
+ async ({ path: path40, include_content, max_content_chars }) => {
17299
17453
  const index = getIndex();
17300
17454
  const vaultPath2 = getVaultPath();
17301
17455
  const result = await getNoteStructure(index, path40, vaultPath2);
@@ -17304,11 +17458,26 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17304
17458
  content: [{ type: "text", text: JSON.stringify({ error: "Note not found", path: path40 }, null, 2) }]
17305
17459
  };
17306
17460
  }
17461
+ let totalChars = 0;
17462
+ let truncated = false;
17307
17463
  if (include_content) {
17308
17464
  for (const section of result.sections) {
17465
+ if (totalChars >= max_content_chars) {
17466
+ truncated = true;
17467
+ break;
17468
+ }
17309
17469
  const sectionResult = await getSectionContent(index, path40, section.heading.text, vaultPath2, true);
17310
17470
  if (sectionResult) {
17311
- section.content = sectionResult.content;
17471
+ let content = sectionResult.content;
17472
+ const remaining = max_content_chars - totalChars;
17473
+ if (content.length > remaining) {
17474
+ const sliced = content.slice(0, remaining);
17475
+ const lastBreak = sliced.lastIndexOf("\n\n");
17476
+ content = lastBreak > 0 ? sliced.slice(0, lastBreak) : sliced;
17477
+ truncated = true;
17478
+ }
17479
+ section.content = content;
17480
+ totalChars += content.length;
17312
17481
  }
17313
17482
  }
17314
17483
  }
@@ -17335,6 +17504,10 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17335
17504
  } catch {
17336
17505
  }
17337
17506
  }
17507
+ if (include_content) {
17508
+ enriched.truncated = truncated;
17509
+ enriched.returned_chars = totalChars;
17510
+ }
17338
17511
  return {
17339
17512
  content: [{ type: "text", text: JSON.stringify(enriched, null, 2) }]
17340
17513
  };
@@ -17348,10 +17521,11 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17348
17521
  inputSchema: {
17349
17522
  path: z7.string().describe("Path to the note"),
17350
17523
  heading: z7.string().describe("Heading text to find"),
17351
- include_subheadings: z7.boolean().default(true).describe("Include content under subheadings")
17524
+ include_subheadings: z7.boolean().default(true).describe("Include content under subheadings"),
17525
+ max_content_chars: z7.number().default(1e4).describe("Max chars of section content. Truncated at paragraph boundaries.")
17352
17526
  }
17353
17527
  },
17354
- async ({ path: path40, heading, include_subheadings }) => {
17528
+ async ({ path: path40, heading, include_subheadings, max_content_chars }) => {
17355
17529
  const index = getIndex();
17356
17530
  const vaultPath2 = getVaultPath();
17357
17531
  const result = await getSectionContent(index, path40, heading, vaultPath2, include_subheadings);
@@ -17364,8 +17538,15 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig2 = ()
17364
17538
  }, null, 2) }]
17365
17539
  };
17366
17540
  }
17541
+ let truncated = false;
17542
+ if (result.content.length > max_content_chars) {
17543
+ const sliced = result.content.slice(0, max_content_chars);
17544
+ const lastBreak = sliced.lastIndexOf("\n\n");
17545
+ result.content = lastBreak > 0 ? sliced.slice(0, lastBreak) : sliced;
17546
+ truncated = true;
17547
+ }
17367
17548
  return {
17368
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
17549
+ content: [{ type: "text", text: JSON.stringify({ ...result, truncated }, null, 2) }]
17369
17550
  };
17370
17551
  }
17371
17552
  );
@@ -24150,18 +24331,27 @@ function registerBriefTools(server2, getStateDb4) {
24150
24331
  if (args.max_tokens) {
24151
24332
  let totalTokens2 = 0;
24152
24333
  sections.sort((a, b) => a.priority - b.priority);
24334
+ const kept = [];
24153
24335
  for (const section of sections) {
24154
- totalTokens2 += section.estimated_tokens;
24155
- if (totalTokens2 > args.max_tokens) {
24156
- if (Array.isArray(section.content)) {
24157
- const remaining = Math.max(0, args.max_tokens - (totalTokens2 - section.estimated_tokens));
24158
- const itemTokens = section.estimated_tokens / Math.max(1, section.content.length);
24159
- const keepCount = Math.max(1, Math.floor(remaining / itemTokens));
24160
- section.content = section.content.slice(0, keepCount);
24161
- section.estimated_tokens = estimateTokens2(section.content);
24162
- }
24336
+ if (totalTokens2 >= args.max_tokens) {
24337
+ break;
24338
+ }
24339
+ if (totalTokens2 + section.estimated_tokens <= args.max_tokens) {
24340
+ totalTokens2 += section.estimated_tokens;
24341
+ kept.push(section);
24342
+ } else if (Array.isArray(section.content)) {
24343
+ const remaining = args.max_tokens - totalTokens2;
24344
+ const itemTokens = section.estimated_tokens / Math.max(1, section.content.length);
24345
+ const keepCount = Math.max(1, Math.floor(remaining / itemTokens));
24346
+ section.content = section.content.slice(0, keepCount);
24347
+ section.estimated_tokens = estimateTokens2(section.content);
24348
+ totalTokens2 += section.estimated_tokens;
24349
+ kept.push(section);
24350
+ break;
24163
24351
  }
24164
24352
  }
24353
+ sections.length = 0;
24354
+ sections.push(...kept);
24165
24355
  }
24166
24356
  const response = {};
24167
24357
  let totalTokens = 0;
@@ -24672,7 +24862,32 @@ function registerMetricsTools(server2, getIndex, getStateDb4) {
24672
24862
  const recentEvents = getRecentIndexEvents(stateDb2, eventLimit ?? 20);
24673
24863
  result = {
24674
24864
  mode: "index_activity",
24675
- index_activity: { summary, recent_events: recentEvents }
24865
+ index_activity: {
24866
+ summary,
24867
+ recent_events: recentEvents.map((e) => {
24868
+ const base = {
24869
+ id: e.id,
24870
+ timestamp: e.timestamp,
24871
+ trigger: e.trigger,
24872
+ duration_ms: e.duration_ms,
24873
+ success: e.success,
24874
+ note_count: e.note_count,
24875
+ files_changed: e.files_changed,
24876
+ error: e.error
24877
+ };
24878
+ if (e.steps) {
24879
+ const compact = compactPipelineRun(e);
24880
+ return {
24881
+ ...base,
24882
+ changed_paths_total: compact.changed_paths_total,
24883
+ changed_paths_sample: compact.changed_paths_sample,
24884
+ step_count: compact.step_count,
24885
+ steps: compact.steps
24886
+ };
24887
+ }
24888
+ return base;
24889
+ })
24890
+ }
24676
24891
  };
24677
24892
  break;
24678
24893
  }
@@ -25930,7 +26145,7 @@ function registerSessionHistoryTools(server2, getStateDb4) {
25930
26145
  {
25931
26146
  session_id: z36.string().optional().describe("Session ID for detail view. Omit for recent sessions list."),
25932
26147
  include_children: z36.boolean().optional().describe("Include child sessions (default: true)"),
25933
- limit: z36.number().min(1).max(500).optional().describe("Max invocations to return in detail view (default: 200)")
26148
+ limit: z36.number().min(1).max(500).optional().describe("Max invocations to return in detail view (default: 50)")
25934
26149
  },
25935
26150
  async (args) => {
25936
26151
  const stateDb2 = getStateDb4();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velvetmonkey/flywheel-memory",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "description": "MCP tools that search, write, and auto-link your Obsidian vault — and learn from your edits.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@huggingface/transformers": "^3.8.1",
57
57
  "@modelcontextprotocol/sdk": "^1.25.1",
58
- "@velvetmonkey/vault-core": "^2.4.2",
58
+ "@velvetmonkey/vault-core": "^2.4.3",
59
59
  "better-sqlite3": "^12.0.0",
60
60
  "chokidar": "^4.0.0",
61
61
  "gray-matter": "^4.0.3",