dsh-session-recall 0.6.0 → 0.7.0

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/README.md CHANGED
@@ -21,7 +21,7 @@ If you need agent memory orchestration, use a memory framework; if you need boun
21
21
  | Capability focus | Memory frameworks | Generic transcript search | `dsh-session-recall` |
22
22
  |---|---|---|---|
23
23
  | Retrieval target | Derived memory objects | Varies by implementation | **Original session transcript events** |
24
- | Scope control | Framework-specific | Often coarse | **cwd-scoped default + explicit `all_projects` gate** |
24
+ | Scope control | Framework-specific | Often coarse | **cwd-scoped default + explicit `all_projects`, `since_days`, `tools`, `errors_only` gate** |
25
25
  | CJK behavior | Framework-specific | Often tokenizer-limited | **FTS + CJK zero-hit substring fallback** |
26
26
  | Output contract | Usually framework-native | Varies | **Typed `recall` result with stable fields/hints** |
27
27
 
package/lib/index.d.ts CHANGED
@@ -136,6 +136,12 @@ interface RecallArgs {
136
136
  all_projects?: boolean;
137
137
  limit?: number;
138
138
  cursor?: string;
139
+ /** Only return sessions/entries newer than this many days. */
140
+ since_days?: number;
141
+ /** Only return tool-related events whose tool name matches one of these (substring, case-insensitive). */
142
+ tools?: readonly string[];
143
+ /** Only return failed tool calls (tool results carrying an error). */
144
+ errors_only?: boolean;
139
145
  }
140
146
  //#endregion
141
147
  //#region src/rank.d.ts
package/lib/index.js CHANGED
@@ -297,6 +297,34 @@ function cjkZeroHitHint(query, zeroHits, enabled) {
297
297
  * calling agent's own project cwd unless the model explicitly widens the
298
298
  * scope and the deployment allows it.
299
299
  */
300
+ /** Whether a best-match event type is a tool result (vs user/assistant message). */
301
+ function isToolResultType(type) {
302
+ return type === "tool/result" || type.startsWith("tool/");
303
+ }
304
+ /** Apply the dimensional post-filters (sinceDays / tools / errorsOnly) to recall items. */
305
+ function applyDimensionFilters(items, args, now = Date.now()) {
306
+ let out = [...items];
307
+ if (args.since_days !== void 0 && args.since_days > 0) {
308
+ const cutoff = now - args.since_days * 864e5;
309
+ out = out.filter((item) => {
310
+ return Math.max(item.createdAt ?? 0, item.bestMatch?.time ?? 0) >= cutoff;
311
+ });
312
+ }
313
+ if (args.tools !== void 0 && args.tools.length > 0) {
314
+ const needles = args.tools.map((t) => t.toLowerCase());
315
+ out = out.filter((item) => {
316
+ if (!isToolResultType(item.bestMatch?.type ?? "")) return false;
317
+ const snippet = (item.bestMatch?.snippet ?? "").toLowerCase();
318
+ return needles.some((n) => snippet.includes(n));
319
+ });
320
+ }
321
+ if (args.errors_only === true) out = out.filter((item) => {
322
+ if ((item.bestMatch?.type ?? "") !== "tool/result") return false;
323
+ const snippet = item.bestMatch?.snippet ?? "";
324
+ return snippet.includes("[error]") || /\berror\b|\bfailed\b/i.test(snippet);
325
+ });
326
+ return out;
327
+ }
300
328
  /** Walk a lineage trace and collect every session id in the tree. */
301
329
  function collectLineageIds(trace) {
302
330
  const ids = /* @__PURE__ */ new Set();
@@ -317,7 +345,7 @@ const RECALL_TOOL_DESCRIPTION = [
317
345
  "Matches whole words/phrases for English and code identifiers; a zero-hit Chinese (CJK) query automatically falls back to a substring scan in which every whitespace-separated term must match. Returns the best-matching event snippet per session plus the session id.",
318
346
  "Then use the read tool on files, or ask the user, to go deeper — this tool only points at history, it does not resume sessions.",
319
347
  "To save a full evidence report of a hit session, call the transcript_export tool (from dsh-session-export) with its sessionId when available.",
320
- "Scoping: by default only sessions started in the current project directory; pass all_projects=true to search everywhere (the deployment may ignore it or require user approval).",
348
+ "Scoping: by default only sessions started in the current project directory; pass all_projects=true to search everywhere (the deployment may ignore it or require user approval). Narrow further with since_days (only sessions newer than N days), tools (only tool events whose tool name matches, e.g. tools=[\"bash\"]), or errors_only=true (only failed tool calls).",
321
349
  "When the deployment enables redaction, secret-looking text in snippets appears as [REDACTED] or a #hash marker — treat it as removed; do not try to reconstruct or echo it.",
322
350
  "The first search after startup may be slow while the index builds."
323
351
  ].join(" ");
@@ -600,6 +628,19 @@ function createRecallTool(config, engine, approver) {
600
628
  cursor: {
601
629
  type: "string",
602
630
  description: "Opaque continuation cursor from a previous recall result with hasMore=true."
631
+ },
632
+ since_days: {
633
+ type: "integer",
634
+ description: "Only return sessions newer than this many days (0 = no time filter)."
635
+ },
636
+ tools: {
637
+ type: "array",
638
+ items: { type: "string" },
639
+ description: "Only return tool events whose tool name contains one of these substrings (case-insensitive), e.g. [\"bash\",\"read\"]."
640
+ },
641
+ errors_only: {
642
+ type: "boolean",
643
+ description: "Only return failed tool calls (tool results carrying an error)."
603
644
  }
604
645
  },
605
646
  output: {
@@ -675,7 +716,7 @@ function createRecallTool(config, engine, approver) {
675
716
  redacted: 0,
676
717
  diagnostics: null
677
718
  };
678
- let items = eventItems(page, sessionId);
719
+ let items = applyDimensionFilters(eventItems(page, sessionId), args);
679
720
  let hint = null;
680
721
  let diagnostics = {
681
722
  source: "fts",
@@ -690,7 +731,7 @@ function createRecallTool(config, engine, approver) {
690
731
  scanBudget: cfg.cjkFallbackScanMax,
691
732
  ranked: false
692
733
  };
693
- if (docs.length > 0) items = docs.slice(0, limit).map((doc) => ({
734
+ if (docs.length > 0) items = applyDimensionFilters(docs.slice(0, limit).map((doc) => ({
694
735
  sessionId,
695
736
  id8: id8(sessionId),
696
737
  title: null,
@@ -704,7 +745,7 @@ function createRecallTool(config, engine, approver) {
704
745
  time: doc.time,
705
746
  snippet: snippetAround(doc.text, highlight, CJK_SNIPPET_CHARS)
706
747
  }
707
- }));
748
+ })), args);
708
749
  hint = items.length > 0 ? cjkFallbackHint(items.length, cfg.cjkHint) : cjkZeroHitHint(query, true, cfg.cjkHint);
709
750
  } else if (items.length === 0) hint = cjkZeroHitHint(query, true, cfg.cjkHint);
710
751
  const red = applyRedaction(items);
@@ -738,6 +779,7 @@ function createRecallTool(config, engine, approver) {
738
779
  const ranked = rankingActive(rankingOptions);
739
780
  let items = rankItems(toItems(page.items, titles).filter((item) => cwdAllowed(item.cwd, cfg)), rankingOptions);
740
781
  if (allowedIds != null) items = items.filter((item) => allowedIds.has(item.sessionId));
782
+ items = applyDimensionFilters(items, args);
741
783
  let hint = null;
742
784
  let fallbackRan = false;
743
785
  let diagnostics = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-session-recall",
3
3
  "description": "Deterministic cross-session transcript retrieval for DeepSeek Harness: the model-facing `recall` tool searches past session logs with explicit scope control",
4
- "version": "0.6.0",
4
+ "version": "0.7.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },