@evandu/super-pi 0.1.84 → 0.1.85

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 (33) hide show
  1. package/agents/super-pi-knowledge/index.ts +40 -0
  2. package/assets/knowledge-model/README.md +37 -0
  3. package/assets/knowledge-model/Xenova/bge-small-zh-v1.5/config.json +31 -0
  4. package/assets/knowledge-model/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
  5. package/assets/knowledge-model/Xenova/bge-small-zh-v1.5/special_tokens_map.json +7 -0
  6. package/assets/knowledge-model/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
  7. package/assets/knowledge-model/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
  8. package/assets/knowledge-model/Xenova/bge-small-zh-v1.5/vocab.txt +21128 -0
  9. package/dist/.super-pi-check-inputs.json +4 -0
  10. package/dist/client/.super-pi-client-sources.json +8 -7
  11. package/dist/client/assets/index-B_jOwjyp.js +72 -0
  12. package/dist/client/assets/index-CNNGl4eF.css +1 -0
  13. package/dist/client/index.html +2 -2
  14. package/dist/server/application/run-service.d.ts +50 -11
  15. package/dist/server/application/run-service.js +206 -77
  16. package/dist/server/command-handler.js +16 -4
  17. package/dist/server/http-server.js +59 -1
  18. package/dist/server/index.js +7 -0
  19. package/dist/server/knowledge-vectors.d.ts +133 -0
  20. package/dist/server/knowledge-vectors.js +302 -0
  21. package/dist/server/knowledge.d.ts +263 -0
  22. package/dist/server/knowledge.js +743 -0
  23. package/dist/server/persistence/app-state-repository.d.ts +25 -1
  24. package/dist/server/persistence/sqlite-state-repository.js +47 -4
  25. package/dist/server/runtime.d.ts +48 -1
  26. package/dist/server/runtime.js +83 -7
  27. package/dist/server/server-context.d.ts +1 -0
  28. package/dist/server/store.d.ts +153 -68
  29. package/dist/server/store.js +405 -187
  30. package/dist/server/types.d.ts +97 -65
  31. package/package.json +163 -155
  32. package/dist/client/assets/index-8Pf3MG2d.js +0 -72
  33. package/dist/client/assets/index-Jnj15dhT.css +0 -1
@@ -0,0 +1,263 @@
1
+ import { VectorIndex } from "./knowledge-vectors.js";
2
+ import type { Store } from "./store.js";
3
+ import type { Conversation } from "./types.js";
4
+ /** One document as it lives on disk: the ORIGINAL text, never the chunks. */
5
+ export type KnowledgeDocument = {
6
+ id: string;
7
+ workspaceId: string;
8
+ title: string;
9
+ /** Where it came from - a workspace-relative path, or `pasted` for hand-entered text. */
10
+ source: string;
11
+ format: string;
12
+ characters: number;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ /**
16
+ * Content fingerprint, used to recognise an import the user already made.
17
+ *
18
+ * WHY IT IS STORED RATHER THAN COMPUTED ON COMPARISON. Deduplication runs inside `add`, which already
19
+ * reads every document in the workspace; hashing each one's full text there would be O(corpus) on
20
+ * every import. Storing it turns the check into one string comparison, and a document written by an
21
+ * older build simply hashes on read (`digestOf`) instead.
22
+ */
23
+ digest: string;
24
+ text: string;
25
+ };
26
+ /** A retrievable slice. `heading` is the nearest preceding markdown heading, so a hit can be cited. */
27
+ export type KnowledgeChunk = {
28
+ index: number;
29
+ heading: string;
30
+ text: string;
31
+ };
32
+ export type KnowledgeHit = {
33
+ documentId: string;
34
+ title: string;
35
+ source: string;
36
+ heading: string;
37
+ chunk: number;
38
+ score: number;
39
+ text: string;
40
+ };
41
+ /** 1 MB. Big enough for a design doc or a spec, small enough that chunking stays instant. */
42
+ export declare const MAX_DOCUMENT_BYTES: number;
43
+ /** A pasted document rides inside a JSON body, so the request cap leaves room for escaping overhead. */
44
+ export declare const MAX_KNOWLEDGE_REQUEST_BYTES: number;
45
+ /** Per workspace, across every document. A knowledge base is a curated asset, not a file server. */
46
+ export declare const MAX_WORKSPACE_BYTES: number;
47
+ export declare const DEFAULT_RESULT_LIMIT = 6;
48
+ export declare const MAX_RESULT_LIMIT = 20;
49
+ /** What one search may be asked for. `rerank` and `conversation` exist only for the agent's own calls. */
50
+ export type KnowledgeSearch = {
51
+ limit?: number;
52
+ /** Re-order with the session's model. Off for the settings preview, which must stay instant. */
53
+ rerank?: boolean;
54
+ /** Required by `rerank`: it answers with this conversation's model and workspace. */
55
+ conversation?: Conversation;
56
+ signal?: AbortSignal;
57
+ };
58
+ /**
59
+ * The slice of the runtime the reranker needs, declared structurally.
60
+ *
61
+ * Declared here rather than imported so this file does not depend on the spawn machinery that depends
62
+ * on it - `runtime.ts` imports `KNOWLEDGE_TOOL_NAME` from here, and a type-only back edge into that
63
+ * module would make the cycle visible in every import graph.
64
+ */
65
+ export type KnowledgeRuntime = {
66
+ answer(input: {
67
+ conversation: Conversation;
68
+ prompt: string;
69
+ cwd: string;
70
+ signal: AbortSignal;
71
+ knowledgeRerank?: boolean;
72
+ }): AsyncIterable<string>;
73
+ };
74
+ /**
75
+ * The first integer array in the model's answer, or null.
76
+ *
77
+ * WHY TOLERANT PARSING. The prompt asks for a bare JSON array, and a model that mostly complies will
78
+ * still sometimes wrap it in a sentence or a code fence. Recovering the array from that is worth doing;
79
+ * guessing when there is no array at all is not, so anything unparseable is null and the caller keeps
80
+ * the lexical order.
81
+ */
82
+ export declare function parseRanking(output: string): number[] | null;
83
+ /** Duplicated as a literal in `agents/super-pi-knowledge/index.ts`, the way `TASK_TOOL_NAME` is. */
84
+ export declare const KNOWLEDGE_TOOL_NAME = "knowledge_search";
85
+ /**
86
+ * Index terms for one string: CJK bigrams plus Latin words.
87
+ *
88
+ * WHY BIGRAMS AND NOT WORDS. Chinese has no spaces, so the word-list tokenizer the rest of this
89
+ * server uses (`ActiveMemory.search`) returns one enormous "word" for a whole sentence and matches
90
+ * nothing usefully. Character bigrams need no dictionary and no dependency, and they carry enough
91
+ * precision that 知识库 and 记忆库 share 识库 but not the whole run - which is what makes a two-term
92
+ * overlap meaningful rather than accidental.
93
+ */
94
+ export declare function tokenize(text: string): string[];
95
+ /**
96
+ * The longer-range channel: character TRIGRAMS, for the passage that repeats a phrase verbatim.
97
+ *
98
+ * WHY A SECOND GRANULARITY AND NOT JUST BIGRAMS. Bigrams answer "do these share any two-character
99
+ * run", which is high recall but low precision - two unrelated Chinese sentences collide constantly on
100
+ * a shared pair. Trigrams only fire on a real three-character overlap, so when the user's question
101
+ * quotes the document's own wording this channel separates it sharply from the rest of the corpus.
102
+ * It is scored on its own and fused with the bigram channel rather than merged into it, because the
103
+ * two answer different questions and a term that is strong for one is usually weak for the other.
104
+ */
105
+ export declare function tokenizePhrases(text: string): string[];
106
+ /** The identity of a document's CONTENT, independent of its name, its path and when it arrived. */
107
+ export declare function digestOf(text: string): string;
108
+ /**
109
+ * Splits a document into overlapping chunks, remembering the heading each one sits under.
110
+ *
111
+ * WHY OVERLAP. A passage that spans a boundary is the passage most likely to be the answer - the
112
+ * sentence that defines a term usually starts in one chunk and ends in the next - and a retrieval
113
+ * system that cut there would return a fragment ending mid-thought. The 150-character tail is
114
+ * repeated at the head of the next chunk to keep that sentence whole in at least one hit.
115
+ */
116
+ export declare function chunkDocument(text: string): KnowledgeChunk[];
117
+ export declare class KnowledgeBase {
118
+ readonly directory: string;
119
+ private store;
120
+ private runtime?;
121
+ private vectors?;
122
+ private capabilities;
123
+ private indexes;
124
+ private serial;
125
+ constructor(directory: string, store: Store, runtime?: KnowledgeRuntime | undefined, vectors?: VectorIndex | undefined);
126
+ /** `SUPER_PI_KNOWLEDGE_ENABLED=0` turns the whole feature off, the same way memory turns off. */
127
+ enabled(): boolean;
128
+ /** The opaque workspace identity on disk, encoded. Paths never come from the model or the request. */
129
+ private folder;
130
+ private file;
131
+ private requireWorkspace;
132
+ private locked;
133
+ /** Metadata only - the caller listing documents must never receive the full text of a workspace. */
134
+ private summarise;
135
+ private readAll;
136
+ private save;
137
+ private index;
138
+ /**
139
+ * Loads (or rebuilds) one workspace's index. Cache miss is the only thing that reads the disk.
140
+ *
141
+ * WHY REBUILT PER WORKSPACE AND NOT ONE GLOBAL INDEX. Isolation is a security property here, not a
142
+ * performance one: a hit may only ever come from the workspace the turn's capability names. A
143
+ * single shared index would need every search to filter by workspace after scoring, which is one
144
+ * forgotten filter away from a cross-tenant leak. Separate caches cannot express that mistake.
145
+ *
146
+ * WHY THERE IS NO ON-DISK INDEX. Measured, not assumed: rebuilding the index of a 16 MB workspace -
147
+ * the feature's own capacity cap - costs about 180 ms, once per process. A serialised index would be
148
+ * larger than the corpus it describes and JSON.parse of it is not reliably cheaper than the
149
+ * tokenisation, so it would trade a correctness-neutral optimisation for a second on-disk artefact
150
+ * that can drift from the documents.
151
+ */
152
+ private load;
153
+ private invalidate;
154
+ list(workspaceId: string): Promise<Array<Omit<KnowledgeDocument, "text">>>;
155
+ /**
156
+ * Whether this workspace has any document at all, which is what decides whether the turn mounts the
157
+ * extension.
158
+ *
159
+ * WHY A DIRECTORY LISTING AND NOT `list()`. This runs once per turn, before every spawn. `list()`
160
+ * would read and parse every document's full text to answer a yes/no question, so a workspace with
161
+ * a dozen real documents would pay for parsing all of them on every message - including the
162
+ * catalogue fetch the extension makes the moment it loads. `readdir` answers the same question for
163
+ * the price of one directory entry, and a name is all this needs.
164
+ */
165
+ hasDocuments(workspaceId: string): Promise<boolean>;
166
+ /**
167
+ * Adds a document, either from a workspace file or from pasted text.
168
+ *
169
+ * WHY THE FILE IS READ HERE AND NOT UPLOADED. A knowledge base is made of documents the person
170
+ * ALREADY has in their workspace - re-uploading a copy of the file would give the agent a second,
171
+ * newer version that diverges from the one the workspace serves. The path therefore goes through
172
+ * the same `resolveInsideRoot` guard the file editor uses, so symlink and `..` escapes are refused
173
+ * by construction instead of by a check someone has to remember.
174
+ */
175
+ add(workspaceId: string, body: {
176
+ path?: string;
177
+ title?: string;
178
+ text?: string;
179
+ }): Promise<Omit<KnowledgeDocument, "text">>;
180
+ remove(workspaceId: string, documentId: string): Promise<void>;
181
+ /**
182
+ * Scores this workspace's chunks on four independent lexical channels and fuses them.
183
+ *
184
+ * WHY LEXICAL AND NOT ONLY EMBEDDINGS. See docs/KNOWLEDGE_BASE.md: the vector channel is fused in
185
+ * here rather than replacing this one, because each is wrong in a way the other catches. BM25
186
+ * misses a paraphrase completely; a vector search will happily return the nearest thing at a
187
+ * confident-looking 0.58 when the answer is not in the knowledge base at all. Summing a normalised
188
+ * lexical score with a normalised cosine score keeps both signals - and keeps "no hit" reachable.
189
+ *
190
+ * WHY NORMALISE PER CHANNEL AND SUM, RATHER THAN RRF. Reciprocal rank fusion is the usual way to
191
+ * combine ranked lists, and it would be a regression here: it discards score magnitude, so a
192
+ * passage that matches decisively and one that matches barely would arrive at the same rank and
193
+ * finish within a couple of percent of each other. The gap between a clear winner and a near miss is
194
+ * exactly what BM25 was measuring. Each channel is therefore divided by its own best score for this
195
+ * query, which preserves the shape of that channel's confidence while making the weights comparable.
196
+ */
197
+ search(workspaceId: string, query: string, options?: KnowledgeSearch): Promise<KnowledgeHit[]>;
198
+ /**
199
+ * Re-orders the candidate pool with the session's own model.
200
+ *
201
+ * WHY THIS EXISTS AND WHAT IT IS WORTH. Lexical retrieval ranks on shared characters, so a passage
202
+ * that answers in different words sits below passages that merely repeat the question's wording -
203
+ * often below the fold, where the character budget has already cut it off. Reading the candidates
204
+ * is something the model can already do, so it is asked to pick.
205
+ *
206
+ * ITS CEILING, STATED PLAINLY. Reranking can only reorder what the first stage returned. A query
207
+ * with NO lexical overlap at all never reaches this function, which is why the vector channel and
208
+ * not this one is what closes the paraphrase gap - see docs/KNOWLEDGE_BASE.md.
209
+ *
210
+ * WHY IT FAILS OPEN. Every outcome that is not a clean parse returns the lexical order. A reranker
211
+ * that made things worse, hung, or answered with prose would degrade the feature invisibly, and a
212
+ * retrieval layer that is allowed to get worse by being clever is worse than one that is not.
213
+ */
214
+ private rerank;
215
+ /**
216
+ * Mints a read-only token for one turn. The agent can retrieve, never write: everything a person
217
+ * puts in a knowledge base is theirs to curate from the settings window.
218
+ */
219
+ issue(conversationId: string): string;
220
+ release(token: string): void;
221
+ private identity;
222
+ /** The agent-facing endpoint: a title catalogue and a search. Nothing that writes. */
223
+ tool(token: string, body: {
224
+ kind?: string;
225
+ query?: string;
226
+ limit?: number;
227
+ }, signal?: AbortSignal): Promise<{
228
+ documents: {
229
+ title: string;
230
+ source: string;
231
+ characters: number;
232
+ }[];
233
+ hits?: undefined;
234
+ reranked?: undefined;
235
+ } | {
236
+ documents?: undefined;
237
+ hits: KnowledgeHit[];
238
+ reranked: boolean;
239
+ }>;
240
+ /** The browser-facing endpoint, behind the ordinary access token and same-origin check. */
241
+ manage(workspaceId: string, body: {
242
+ action?: string;
243
+ path?: string;
244
+ title?: string;
245
+ text?: string;
246
+ documentId?: string;
247
+ query?: string;
248
+ limit?: number;
249
+ }): Promise<{
250
+ hits: KnowledgeHit[];
251
+ documents: Omit<KnowledgeDocument, "text">[];
252
+ semantic: import("./knowledge-vectors.js").VectorStatus | null;
253
+ }>;
254
+ /**
255
+ * What the panel shows, including whether semantic retrieval is actually working.
256
+ *
257
+ * WHY THE STATE IS WORTH SHOWING. The vector half depends on an optional package and a download from
258
+ * a host the machine may not reach, and both fail quietly by design. Without this on screen, "the
259
+ * feature is off" is indistinguishable from "the feature is broken here" - which is the difference
260
+ * between not wanting a feature and not being able to have it.
261
+ */
262
+ private overview;
263
+ }