@unblocklabs/unblock-memory 0.7.3 → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { DatabaseSync } from "node:sqlite";
3
- import { ACTIVE_EVENT_COUNT_SQL, ACTIVE_EVENT_ROWS_SQL, assertAgentTranscriptSchema } from "./agent-transcript.js";
3
+ import { AgentTranscriptReader } from "./agent-transcript.js";
4
4
  import { messageText } from "./whisperer-context.js";
5
5
  import { conversationUserText } from "./response-text.js";
6
6
  import { prepareQueryConversation, QUERY_CONTRACT, QueryInputBudgetError } from "./query-contract.js";
@@ -133,12 +133,13 @@ export function trainingExamples(rows, renew) {
133
133
  /** Only active events; no Markdown projections, archived branches, or tool bodies. */
134
134
  export class TrainingTranscriptReader {
135
135
  #db;
136
+ #transcripts;
136
137
  #lineage;
137
138
  constructor(path, agentId) {
138
139
  this.#db = new DatabaseSync(path, { readOnly: true });
139
140
  try {
140
141
  this.#db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000");
141
- assertAgentTranscriptSchema(this.#db, agentId, "Unsupported training transcript schema or agent");
142
+ this.#transcripts = new AgentTranscriptReader(this.#db, agentId, "Unsupported training transcript schema or agent");
142
143
  const columns = this.#db.prepare("PRAGMA table_info(session_windows)").all().map(c => c.name);
143
144
  this.#lineage = ["parent_session_key", "spawned_by", "plugin_owner_id", "hook_external_content_source"].every(c => columns.includes(c));
144
145
  }
@@ -161,11 +162,12 @@ export class TrainingTranscriptReader {
161
162
  /:(?:cron|subagent|heartbeat|hook)(?::|$)/i.test(String(session.session_key)) ||
162
163
  session.parent_session_key || session.spawned_by || session.plugin_owner_id || session.hook_external_content_source)
163
164
  return null;
164
- const size = this.#db.prepare(ACTIVE_EVENT_COUNT_SQL).get(sessionId);
165
- if (Number(size.n) > 50_000 || Number(size.bytes) > 32_000_000)
165
+ const snapshot = this.#transcripts.read(sessionId, { maxEvents: 50_000, maxBytes: 32_000_000 });
166
+ if (snapshot.kind === "oversized")
166
167
  return { oversized: true };
167
- const rows = this.#db.prepare(ACTIVE_EVENT_ROWS_SQL).iterate(sessionId);
168
- return trainingExamples(rows, renew);
168
+ if (snapshot.kind !== "ready")
169
+ return { unavailable: true };
170
+ return trainingExamples(snapshot.rows, renew);
169
171
  }
170
172
  finally {
171
173
  this.#db.exec("COMMIT");
@@ -34,6 +34,7 @@ export declare function generateTrainingQueries(source: Source, store: TrainingS
34
34
  sessions: number;
35
35
  excludedSessions: number;
36
36
  oversizedSessions: number;
37
+ unavailableSessions: number;
37
38
  eligible: number;
38
39
  users: number;
39
40
  filtered: number;
@@ -79,6 +80,7 @@ export declare function evaluateTrainingQueries(source: Source, store: TrainingS
79
80
  sessions: number;
80
81
  excludedSessions: number;
81
82
  oversizedSessions: number;
83
+ unavailableSessions: number;
82
84
  eligible: number;
83
85
  users: number;
84
86
  filtered: number;
@@ -13,6 +13,7 @@ export declare function collectTraining(source: Source, store?: TrainingStore, o
13
13
  sessions: number;
14
14
  excludedSessions: number;
15
15
  oversizedSessions: number;
16
+ unavailableSessions: number;
16
17
  eligible: number;
17
18
  users: number;
18
19
  filtered: number;
@@ -39,6 +40,7 @@ export declare function runTraining(source: Source, store: TrainingStore, config
39
40
  sessions: number;
40
41
  excludedSessions: number;
41
42
  oversizedSessions: number;
43
+ unavailableSessions: number;
42
44
  eligible: number;
43
45
  users: number;
44
46
  filtered: number;
@@ -5,7 +5,7 @@ import { judgeTrainingInput, TRAINING_GATE_QUESTIONS } from "./training-gate.js"
5
5
  export function collectTraining(source, store, options = {}) {
6
6
  const since = options.since ?? 0, until = options.until ?? Number.MAX_SAFE_INTEGER;
7
7
  const reader = new TrainingTranscriptReader(source.databasePath, source.agentId);
8
- const result = { sessions: 0, excludedSessions: 0, oversizedSessions: 0, eligible: 0,
8
+ const result = { sessions: 0, excludedSessions: 0, oversizedSessions: 0, unavailableSessions: 0, eligible: 0,
9
9
  users: 0, filtered: 0, oversized: 0, unanswered: 0, added: 0, changed: 0, unchanged: 0, retired: 0,
10
10
  review: [] };
11
11
  try {
@@ -14,6 +14,11 @@ export function collectTraining(source, store, options = {}) {
14
14
  store?.renew();
15
15
  const extracted = reader.read(id, () => store?.renew());
16
16
  result.sessions++;
17
+ if (extracted && "unavailable" in extracted) {
18
+ result.unavailableSessions++;
19
+ store?.unavailable(id);
20
+ continue;
21
+ }
17
22
  if (extracted && "oversized" in extracted) {
18
23
  result.oversizedSessions++;
19
24
  store?.unavailable(id);
@@ -11,7 +11,8 @@ settings; a rotated credential file is reread without a restart.
11
11
 
12
12
  | Feature | Required settings/dependencies | TypeSafe disabled / no key | Provider or unreadable-key failure |
13
13
  | --- | --- | --- | --- |
14
- | Ordinary search/get | Installed/enabled memory slot; configured corpora | Unchanged local retrieval | Unchanged; its own indexing/embedding errors still matter |
14
+ | `memory_search` | Installed/enabled memory slot; configured corpora; TypeSafe key | Explicit unavailable error | Successful judged siblings returned with a partial warning; total failure is an error; no vector fallback |
15
+ | `memory_get` | Installed/enabled memory slot; configured corpora | Unchanged local indexed reads | Unchanged; its own indexing errors still matter |
15
16
  | Skill Whisperer | `skillWhisperer.enabled`, skills corpus, host hooks | Best local vector candidate meeting `minScore` | Exclude failed candidates; successful siblings may qualify; no vector fallback |
16
17
  | Memory Whisperer | `memoryWhisperer.enabled`, explicit approved corpora, host hooks | No hints | Exclude failed candidates; recall/deadline failures still suppress the turn |
17
18
  | Complementary hints | Enabled Memory Whisperer + `complementaryHints` | No additional judgment; base hints also require a key | Keep baseline hints unless the total deadline expires |
@@ -202,7 +203,7 @@ contract; these tables explain their effects.
202
203
  | `corpora[].kind` | Required for explicit entries | `files`, `sessions`, or `skills` |
203
204
  | `corpora[].paths` | Required for files/skills | Nonempty exact-file/directory/glob list; workspace-relative, absolute or `~/`; directory means recursive Markdown; does not grant host write trust |
204
205
  | `corpora[sessions].chatTypes` | `['channel','group']` | Nonempty subset of channel/group/direct; DMs require `direct` |
205
- | `corpora[sessions].maxExpandedTokens` | `500` | 1–10,000; ordinary search uses full turn/message when it fits, otherwise preserves the matched chunk. Whisperer instead shares a fixed 1,200-character renderer with training. |
206
+ | `corpora[sessions].maxExpandedTokens` | `500` | 1–10,000; internal host vector retrieval expands a full turn/message when it fits. Public search and Whisperer share the fixed 1,200-character complete-passage renderer with training. |
206
207
  | `corpora[sessions].syncIntervalMinutes` | `60` | 0–1,440; zero manual-only; first scheduled sync after one interval; requires running Gateway |
207
208
  | `keepEmbeddingModelWarm` | `true` | Retain embedding model/context after first use; false allows five-minute idle disposal |
208
209
  | `analysis.executable` | Unset | Optional absolute local worker path; enables ability to recluster, not automatic scheduling |
@@ -78,7 +78,9 @@ bytes, so changed corpus content still invalidates caches.
78
78
 
79
79
  ## Input and historical boundaries
80
80
 
81
- Read original agent SQLite active-branch conversations (schema 17–19), including
81
+ Read original agent SQLite active-branch conversations through the shared read-only
82
+ adapter, including compressed payloads, while deferring cold or oversized sessions
83
+ without retiring their previously collected examples. This includes
82
84
  direct/group/channel chats, excluding cron, heartbeat, spawned/subagent, hook/plugin
83
85
  and untyped diagnostic sessions. Exclude explicit bots, synthetic messages,
84
86
  analysis/thinking, errors and tool payloads. Ordinary older users need not have
@@ -202,8 +202,8 @@ factual verification. Week buckets
202
202
  may be partial, and several exchanges in one session are not independent. Wilson
203
203
  intervals are descriptive, not calibrated confidence about overall agent ability.
204
204
 
205
- Each run selects at most 100 recent sessions for inference, each at most 2,000 active events/2M
206
- characters; episodes must fit 24K characters and six feedback messages without
205
+ Each run selects at most 100 recent sessions containing approved-sender messages for inference,
206
+ each at most 2,000 active events/2M decoded UTF-8 bytes; episodes must fit 24K characters and six feedback messages without
207
207
  truncating the answer. Coverage counts describe the scanned sessions; only episodes
208
208
  within `lookbackDays` are judged. Caps, failures and no-feedback cases remain visible.
209
209
  Saved sessions in the report window are also reconciled independently of that
package/docs/retrieval.md CHANGED
@@ -6,26 +6,31 @@
6
6
 
7
7
  `memory_search` searches every configured **non-skill** corpus for this agent by
8
8
  default. Select named corpora, or use `["all"]` alone. An empty list or unknown
9
- name is an error. Default: **5 results**, vector `minScore: 0.3`; configurable
10
- `maxResults` is 1–20 and `minScore` is 0–1. No TypeSafe, BM25, query expansion
11
- or reranker runs in this tool.
9
+ name is an error. Supply `bm25Query` (distinctive keywords/names) and
10
+ `vectorQuery` (a natural-language question describing the information needed).
11
+ Each lane retrieves ten candidates; complete passages are deduplicated and
12
+ independently judged by TypeSafe against `vectorQuery`. No query generation or
13
+ recall gate runs. Default: **5 results**, `minUsefulness: 0.7`;
14
+ `maxResults` is 1–20 and `minUsefulness` is 0–1. The old `query` and vector
15
+ `minScore` parameters are removed, not silently reinterpreted.
12
16
 
13
17
  Example tool input (the `memory` corpus exists by default):
14
18
 
15
19
  ```json
16
- { "query": "Who approved the staging rollout?", "corpora": ["memory"], "maxResults": 5 }
20
+ { "bm25Query": "staging rollout approval", "vectorQuery": "Who approved the staging rollout?", "corpora": ["memory"], "maxResults": 5 }
17
21
  ```
18
22
 
19
23
  Results use compact JSON and carry `path`, `startLine`, `endLine`, `snippet`,
20
- `corpus`, and `score`/`vectorScore` rounded to hundredths. Ranking and threshold
24
+ `corpus`, and a usefulness `score` rounded to hundredths. Ranking and threshold
21
25
  filtering still use full precision. The constant `source` and top-level `provider`
22
26
  fields are omitted; `path` plus line numbers replace the redundant `citation`.
23
27
  Session hits also carry session metadata and, when available,
24
28
  `messageTimestamp`: the original timestamp text (including timezone) of the message
25
29
  containing the matched chunk. It stays tied to that message even when the excerpt
26
30
  expands to the surrounding turn. Missing timestamps are omitted, not replaced by
27
- session start time. Vector similarity is a retrieval signal, not confidence in
28
- the truth of a claim.
31
+ session start time. Usefulness is not confidence in the truth of a claim.
32
+ Passages retain complete evidence within 1,200 characters; oversized matches
33
+ are omitted, never trimmed into a misleading prefix.
29
34
 
30
35
  Session `snippet` values are arrays of messages, in source order:
31
36
 
@@ -61,56 +66,40 @@ Search snippets are leads. Inspect attribution, qualifications, dates and adjace
61
66
  context before relying on a factual claim. An old plan is not proof it happened,
62
67
  and memory does not grant permission to act. No results may mean the wrong corpus,
63
68
  a high threshold, an unsynced session or an unavailable index—not absence of the fact.
69
+ Search requires enabled TypeSafe and a configured key. It sends the semantic
70
+ query, candidate excerpts, dates, corpus and source paths to TypeSafe. Missing
71
+ credentials and total judging failure include an `error` alongside empty `results`;
72
+ partial judging failure includes a `warning` and only successfully judged results.
73
+ Oversized queries are rejected before credentials, retrieval or provider calls.
64
74
  Search initialization errors include an `error` alongside empty `results`;
65
75
  other failures may surface as tool errors. Investigate them instead of reporting
66
76
  “nothing is remembered.”
77
+ `memory_diagnostics` exposes content-free manual-search measurements under
78
+ `whisperers.telemetry.operations.memorySearch`: end-to-end latency, retrieval and
79
+ judging time, candidate/result counts and outcomes (including partial failures).
80
+ These counters are separate from automatic `memoryWhisperer` and internal `vector`
81
+ retrieval; no query or evidence text is recorded.
67
82
 
68
83
  Corpora and session filters select evidence; they are not audience ACLs. Normal
69
84
  tools can access this agent's configured non-skill corpora, not just its current
70
85
  chat. Only index material suitable for the agent's tool callers. Per-feature
71
86
  TypeSafe allowlists do not restrict ordinary retrieval.
72
87
 
73
- ## QMD search modes
74
-
75
- The following describes QMD's TypeSafe-ranked `query` API (2.10+). Use the exact
76
- dependency in the installed plugin's package metadata when diagnosing an older
77
- installation.
78
-
79
- | Surface | Retrieval/ranking | Score / useful distinction |
80
- | --- | --- | --- |
81
- | Plugin `memory_search` | Direct vector search, expansion off | Vector similarity; ordinary agent recall |
82
- | QMD CLI `search` / SDK `searchLex` | Local BM25 | Normalized lexical relevance; useful for names, identifiers and exact phrases |
83
- | QMD CLI/SDK `vsearch` | Local vectors; standalone default includes local query expansion | Vector similarity; `--no-expand` / `expand:false` selects literal retrieval |
84
- | QMD CLI/MCP `query` / SDK `search` | Literal vectors + BM25, deduplicated source excerpts, independent TypeSafe usefulness ranking | Usefulness divided by 3, not cosine similarity; default limit 10/minScore 0 |
85
- | QMD `query --no-rerank` / SDK/MCP `rerank:false` | Local retrieval, best reciprocal retrieval-rank ordering | 1 / rank; explicitly skips remote scoring |
86
-
87
- Plain `query` takes `ceil(1.5 × limit)` candidates per backend, without local
88
- expansion or local reranking. Explicit `lex`/`vec`/`hyde` variants share the
89
- TypeSafe ranking policy by default. A supplied hypothetical `hyde` passage is
90
- a retrieval input, not evidence. Scores across modes are not interchangeable.
91
-
92
- **Migration:** Memory 0.3.22 removed the temporary `memory_xsearch` tool. QMD's
93
- `query` provides that hybrid retrieval/ranking functionality; there is no plugin
94
- `memory_query`, and `memory_search` did not become hybrid.
95
-
96
- Standalone QMD is a separate entry point. Its project/named/global index is not
97
- automatically this agent's `unblock-memory/index.sqlite`. Plugin corpus names
98
- also are not QMD collection IDs: each configured path maps to a `source-<hash>`
99
- collection, and a corpus can contain several. Establish the intended index,
100
- collection scope and installed QMD version before using CLI/MCP as an alternative.
101
- Do not run standalone collection/update/embed maintenance against a live
102
- plugin-managed index as a casual search fallback.
103
-
104
- QMD CLI/MCP read `TYPESAFE_API_KEY` or `TYPESAFE_API_KEY_FILE` from their own
105
- process; SDK callers can supply credentials. Plugin `typesafe.apiKeyFile` does
106
- not export a key to those processes. Ranked query sends the query, intent,
107
- selected excerpts, source paths and evaluation time to TypeSafe. Missing keys or
108
- scoring failures return errors, not vector fallback or a successful empty answer.
109
- Use an explicitly local mode when appropriate.
110
-
111
- See the [QMD guide](https://github.com/unblocklabs-ai/qmd#readme) for CLI/MCP syntax.
112
- Private skill frontmatter retrieval and response-audit lexical investigations are
113
- internal workflows, not extra public plugin search modes.
88
+ ## QMD entry points
89
+
90
+ `memory_search` is the agent-facing ranked search. The QMD CLI `query`,
91
+ `search` and `deep-search` alias, MCP `query` tool, and HTTP `/query` and
92
+ `/search` routes are removed in the companion QMD change. Existing installations
93
+ retain those commands until that QMD change is released and installed.
94
+
95
+ QMD remains the local indexing/retrieval backend. Its BM25/vector SDK primitives,
96
+ vector-only `vsearch`, indexed reads, collection maintenance and benchmark APIs
97
+ remain available. Private skill retrieval and response-audit lexical investigations
98
+ are internal workflows, not extra public plugin search tools.
99
+
100
+ Standalone QMD and the plugin need not use the same index. Do not run standalone
101
+ collection/update/embed maintenance against a live plugin-managed index as a
102
+ casual search fallback.
114
103
 
115
104
  ## Sessions
116
105
 
@@ -127,7 +116,8 @@ corpora searchable. Supported fields are `startedFrom` and `startedTo`
127
116
 
128
117
  ```json
129
118
  {
130
- "query": "deployment decision",
119
+ "bm25Query": "deployment decision",
120
+ "vectorQuery": "What deployment decision was made?",
131
121
  "sessionFilter": {
132
122
  "startedFrom": "2026-08-01T00:00:00Z",
133
123
  "provider": "slack",
@@ -155,13 +145,19 @@ fits. The default is `500`; the original semantic chunk is preserved when
155
145
  neither complete context fits, so expansion never clips the matched evidence.
156
146
  Run
157
147
  `memory_sync_sessions` to start a refresh, then use `memory_sync_status` to
158
- check its progress or result. The read-only adapter explicitly supports OpenClaw
159
- agent database schemas 17, 18, and 19 and validates its required columns before
160
- reading. Projections are private derived Markdown under the
148
+ check its progress or result. The shared read-only transcript adapter validates
149
+ agent identity and the storage capabilities it needs, rather than rejecting every
150
+ new schema number. It reads both text and bounded Zstd payloads (schema 23+),
151
+ counts decoded UTF-8 bytes for budgets, and preserves previous projections when
152
+ history is cold, oversized, or unreadable. Cold history is deferred until restored
153
+ by OpenClaw; the plugin never restores or migrates the source database. The public
154
+ full-transcript SDK reader currently permits restoration, while its read-only
155
+ catalog redacts and truncates text, so neither supplies this full-fidelity read-only
156
+ contract. Extraction retains its supported host cursor API. Projections are private derived Markdown under the
161
157
  agent's `unblock-memory/sessions` state directory and can be rebuilt from
162
158
  OpenClaw at any time. Their embedded text contains only `# Transcript` and
163
159
  role-labeled, timestamped speaker messages; filtering metadata remains in the
164
- session manifest. Projection v7 also retains message metadata and exact character
160
+ session manifest. Projection v8 retains message metadata and exact character
165
161
  boundaries there, without duplicating message bodies. Readers use those boundaries
166
162
  only when the projection hash matches the indexed document. Older/mismatched snapshots
167
163
  use a conservative heading parser that skips code fences and blockquotes; an unfenced
@@ -294,7 +290,9 @@ through BM25 and vectors respectively, retrieving ten candidates per backend acr
294
290
  the complete approved collection scope (not ten per collection). The lexical adapter
295
291
  uses literal OR keywords; quotation marks do not enable exact-phrase matching.
296
292
  API or generated-output failures skip the hint; there is no direct-vector fallback.
297
- TypeSafe evaluates one independent
293
+ After query generation Whisperer uses the same discovery, deduplication and
294
+ ranking core as `memory_search`, but judges against the bounded conversation
295
+ rather than the explicit tool's semantic query alone. TypeSafe evaluates one independent
298
296
  Noul question per candidate in its own concurrent HTTP request: would a careful
299
297
  assistant use a specific factual detail from the excerpt when answering the current
300
298
  request? Partial answers and concrete leads count; repeated facts, topic/name matches
@@ -309,16 +307,18 @@ Explicit configured thresholds are preserved. Evaluate it on your own conversati
309
307
  **Privacy and budgets:** training and runtime prepare the same visible conversation:
310
308
  at most 8,192 pinned-LFM tokens and 24,000 UTF-8 bytes after serialization. The whole
311
309
  current request and most recent whole messages are retained; an oversized current
312
- request skips automatic memory. The judge receives that conversation plus one complete
313
- 1,200-character excerpt, its corpus/source path, all represented message timestamps,
310
+ request skips automatic memory before query generation or the TypeSafe recall gate.
311
+ Diagnostics count `input_too_large`; completed logs report `outcome: "skipped"` and
312
+ `reason: "input_too_large"`, without recording the input. The judge receives that
313
+ conversation plus one complete 1,200-character excerpt, its corpus/source path, all represented message timestamps,
314
314
  and the request's evaluation time per HTTP request. A v2 query pair can therefore
315
315
  produce up to twenty passage requests before deduplication and eligibility filtering.
316
316
  Dates record when source messages were said, without inferring event dates.
317
317
  Session excerpts retain a complete turn or message when it fits,
318
318
  otherwise the complete matched chunk. Chunks exceeding the excerpt budget are
319
- skipped, never sliced. This shared Whisperer renderer uses the fixed 1,200-character
320
- budget independently of `sessions.maxExpandedTokens`; that setting still controls
321
- ordinary `memory_search` expansion, which is unchanged.
319
+ skipped, never sliced. Public `memory_search` and Whisperer use the same fixed
320
+ 1,200-character renderer, independently of `sessions.maxExpandedTokens`.
321
+ That setting controls only internal host-vector retrieval expansion.
322
322
  It does not fetch a complete historical transcript; the host may
323
323
  already have compacted the available context. System messages, thinking blocks,
324
324
  images, and tool-result messages are omitted;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "unblock-memory",
3
3
  "name": "Unblock Memory",
4
- "version": "0.7.3",
4
+ "version": "0.8.0",
5
5
  "description": "Indexes, retrieves, and analyzes configured workspace memory with existing QMD vectors.",
6
6
  "kind": "memory",
7
7
  "activation": { "onStartup": true },
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "typesafe.enabled": {
66
66
  "label": "TypeSafe Judgments",
67
- "help": "Shared provider gate for opted-in whisperers, people primer/save review, evidence/quality reviews and response audits. Does not enable features or approve corpora by itself. Ordinary search and People Whisperer injection stay local."
67
+ "help": "Shared provider gate for opted-in whisperers, people primer/save review, evidence/quality reviews and response audits. Does not enable features or approve corpora by itself. Memory search discovers locally and uses TypeSafe to rank usefulness; People Whisperer injection stays local."
68
68
  },
69
69
  "typesafe.apiKey": { "label": "TypeSafe API Key", "sensitive": true },
70
70
  "typesafe.timeoutMs": { "label": "TypeSafe Request Timeout", "help": "Per-request timeout, not the whole operation. People primer/dossier review use peoplePrimer.timeoutMs instead; Memory Whisperer has its own total deadline, and Skill Whisperer has a fixed 3-second total budget." },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unblocklabs/unblock-memory",
3
- "version": "0.7.3",
3
+ "version": "0.8.0",
4
4
  "description": "Workspace-native memory for OpenClaw, powered by QMD",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@huggingface/tokenizers": "0.2.0",
51
- "@unblocklabs/qmd": "https://github.com/unblocklabs-ai/qmd/releases/download/v2.11.1/unblocklabs-qmd-2.11.1.tgz",
51
+ "@unblocklabs/qmd": "https://github.com/unblocklabs-ai/qmd/releases/download/v3.0.0/unblocklabs-qmd-3.0.0.tgz",
52
52
  "chokidar": "5.0.0",
53
53
  "js-tiktoken": "1.0.21",
54
54
  "picomatch": "^4.0.5",