skyloom 1.14.6 → 1.15.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.
Files changed (157) hide show
  1. package/.github/workflows/ci.yml +2 -2
  2. package/.github/workflows/publish.yml +74 -0
  3. package/CONVERSION_PLAN.md +191 -191
  4. package/README.md +523 -220
  5. package/config/default.yaml +46 -43
  6. package/config/models.yaml +928 -155
  7. package/config/providers.yaml +109 -6
  8. package/dist/agents/snow.d.ts +2 -0
  9. package/dist/agents/snow.d.ts.map +1 -1
  10. package/dist/agents/snow.js +36 -5
  11. package/dist/agents/snow.js.map +1 -1
  12. package/dist/cli/loom_chat.d.ts.map +1 -1
  13. package/dist/cli/loom_chat.js +207 -1
  14. package/dist/cli/loom_chat.js.map +1 -1
  15. package/dist/cli/main.js +190 -40
  16. package/dist/cli/main.js.map +1 -1
  17. package/dist/cli/tui.d.ts.map +1 -1
  18. package/dist/cli/tui.js +6 -31
  19. package/dist/cli/tui.js.map +1 -1
  20. package/dist/core/agent.d.ts +6 -4
  21. package/dist/core/agent.d.ts.map +1 -1
  22. package/dist/core/agent.js +61 -20
  23. package/dist/core/agent.js.map +1 -1
  24. package/dist/core/catalog.d.ts.map +1 -1
  25. package/dist/core/catalog.js +30 -9
  26. package/dist/core/catalog.js.map +1 -1
  27. package/dist/core/commands.d.ts +110 -0
  28. package/dist/core/commands.d.ts.map +1 -0
  29. package/dist/core/commands.js +633 -0
  30. package/dist/core/commands.js.map +1 -0
  31. package/dist/core/concurrency.d.ts +38 -0
  32. package/dist/core/concurrency.d.ts.map +1 -0
  33. package/dist/core/concurrency.js +65 -0
  34. package/dist/core/concurrency.js.map +1 -0
  35. package/dist/core/factory.js +16 -16
  36. package/dist/core/file_checkpoint.d.ts +9 -0
  37. package/dist/core/file_checkpoint.d.ts.map +1 -1
  38. package/dist/core/file_checkpoint.js +33 -1
  39. package/dist/core/file_checkpoint.js.map +1 -1
  40. package/dist/core/llm.d.ts.map +1 -1
  41. package/dist/core/llm.js +66 -13
  42. package/dist/core/llm.js.map +1 -1
  43. package/dist/core/memory.js +51 -51
  44. package/dist/core/schemas.d.ts +16 -0
  45. package/dist/core/schemas.d.ts.map +1 -1
  46. package/dist/core/schemas.js +32 -0
  47. package/dist/core/schemas.js.map +1 -1
  48. package/dist/core/security.d.ts.map +1 -1
  49. package/dist/core/security.js +27 -0
  50. package/dist/core/security.js.map +1 -1
  51. package/dist/core/skymd.js +14 -14
  52. package/dist/core/trace.d.ts +105 -0
  53. package/dist/core/trace.d.ts.map +1 -0
  54. package/dist/core/trace.js +213 -0
  55. package/dist/core/trace.js.map +1 -0
  56. package/dist/tools/builtin.d.ts +2 -6
  57. package/dist/tools/builtin.d.ts.map +1 -1
  58. package/dist/tools/builtin.js +180 -125
  59. package/dist/tools/builtin.js.map +1 -1
  60. package/dist/tools/extra.d.ts +13 -0
  61. package/dist/tools/extra.d.ts.map +1 -0
  62. package/dist/tools/extra.js +827 -0
  63. package/dist/tools/extra.js.map +1 -0
  64. package/dist/tools/guards.d.ts +12 -0
  65. package/dist/tools/guards.d.ts.map +1 -0
  66. package/dist/tools/guards.js +143 -0
  67. package/dist/tools/guards.js.map +1 -0
  68. package/dist/tools/model_tool.d.ts.map +1 -1
  69. package/dist/tools/model_tool.js +24 -4
  70. package/dist/tools/model_tool.js.map +1 -1
  71. package/dist/web/markdown.d.ts +32 -0
  72. package/dist/web/markdown.d.ts.map +1 -0
  73. package/dist/web/markdown.js +202 -0
  74. package/dist/web/markdown.js.map +1 -0
  75. package/dist/web/server.d.ts +4 -0
  76. package/dist/web/server.d.ts.map +1 -1
  77. package/dist/web/server.js +14 -582
  78. package/dist/web/server.js.map +1 -1
  79. package/dist/web/ui.d.ts +31 -0
  80. package/dist/web/ui.d.ts.map +1 -0
  81. package/dist/web/ui.js +1009 -0
  82. package/dist/web/ui.js.map +1 -0
  83. package/docs/AESTHETIC_DESIGN.md +152 -152
  84. package/docs/OPTIMIZATION_PLAN.md +178 -178
  85. package/package.json +68 -68
  86. package/src/agents/snow.ts +38 -5
  87. package/src/cli/commands_md.ts +112 -112
  88. package/src/cli/input_macros.ts +83 -83
  89. package/src/cli/loom.ts +1041 -1041
  90. package/src/cli/loom_chat.ts +772 -603
  91. package/src/cli/main.ts +853 -723
  92. package/src/cli/tui.ts +264 -289
  93. package/src/core/agent/guard.ts +133 -133
  94. package/src/core/agent/task.ts +100 -100
  95. package/src/core/agent.ts +1630 -1590
  96. package/src/core/agent_helpers.ts +500 -500
  97. package/src/core/bus.ts +221 -221
  98. package/src/core/cache.ts +153 -153
  99. package/src/core/catalog.ts +199 -178
  100. package/src/core/circuit_breaker.ts +119 -119
  101. package/src/core/commands.ts +704 -0
  102. package/src/core/concurrency.ts +73 -0
  103. package/src/core/config.ts +365 -365
  104. package/src/core/constants.ts +95 -95
  105. package/src/core/factory.ts +656 -656
  106. package/src/core/file_checkpoint.ts +163 -136
  107. package/src/core/hooks.ts +126 -126
  108. package/src/core/llm.ts +972 -915
  109. package/src/core/logger.ts +143 -143
  110. package/src/core/mcp.ts +1001 -1001
  111. package/src/core/memory.ts +1201 -1201
  112. package/src/core/middleware.ts +350 -350
  113. package/src/core/model_config.ts +159 -159
  114. package/src/core/pipelines.ts +424 -424
  115. package/src/core/schemas.ts +319 -282
  116. package/src/core/security.ts +27 -0
  117. package/src/core/semantic.ts +211 -211
  118. package/src/core/skill.ts +384 -384
  119. package/src/core/skymd.ts +143 -143
  120. package/src/core/theme.ts +65 -65
  121. package/src/core/tool.ts +457 -457
  122. package/src/core/trace.ts +236 -0
  123. package/src/core/verify.ts +71 -71
  124. package/src/plugins/loader.ts +91 -91
  125. package/src/skills/loader.ts +75 -75
  126. package/src/tools/builtin.ts +571 -493
  127. package/src/tools/computer.ts +279 -279
  128. package/src/tools/extra.ts +662 -0
  129. package/src/tools/guards.ts +82 -0
  130. package/src/tools/model_tool.ts +93 -74
  131. package/src/tools/todo.ts +76 -76
  132. package/src/web/markdown.ts +193 -0
  133. package/src/web/server.ts +117 -693
  134. package/src/web/ui.ts +949 -0
  135. package/tests/agent.test.ts +211 -159
  136. package/tests/agent_helpers.test.ts +48 -48
  137. package/tests/catalog.test.ts +86 -86
  138. package/tests/checkpoint_commands.test.ts +124 -124
  139. package/tests/claude_compat.test.ts +110 -110
  140. package/tests/commands.test.ts +103 -0
  141. package/tests/concurrency.test.ts +102 -0
  142. package/tests/config.test.ts +41 -41
  143. package/tests/extra_tools.test.ts +212 -0
  144. package/tests/fence_plugin.test.ts +52 -52
  145. package/tests/guard.test.ts +75 -75
  146. package/tests/loom.test.ts +337 -337
  147. package/tests/memory.test.ts +170 -170
  148. package/tests/model_config.test.ts +109 -109
  149. package/tests/skymd.test.ts +146 -146
  150. package/tests/ssrf.test.ts +38 -38
  151. package/tests/structured_retry.test.ts +87 -0
  152. package/tests/task.test.ts +60 -60
  153. package/tests/todo_toolstats.test.ts +94 -94
  154. package/tests/trace.test.ts +128 -0
  155. package/tests/tui.test.ts +67 -67
  156. package/tests/web.test.ts +169 -0
  157. package/tsconfig.json +38 -38
@@ -1,211 +1,211 @@
1
- /**
2
- * Lightweight semantic retrieval — zero external dependencies.
3
- *
4
- * Why: The existing `recall_for_injection` uses SQL LIKE on tokens, which
5
- * misses semantically related facts that share few literal characters (e.g.
6
- * query "deploy" vs. stored fact "release_command"). This module provides
7
- * a character n-gram Jaccard similarity scorer that catches those cross-
8
- * lingual and synonym relationships without adding PyTorch / sentence-transformers.
9
- *
10
- * Design:
11
- * - Character n-grams (size 2-4) naturally handle CJK, mixed-language, and
12
- * code identifiers better than word-level tokenization.
13
- * - Jaccard similarity on n-gram sets is fast (< 10 µs per pair) and
14
- * well-correlated with human relevance judgments for short text.
15
- * - Zero dependencies beyond stdlib.
16
- */
17
-
18
- /**
19
- * Represents a candidate item for ranking
20
- */
21
- export interface Candidate {
22
- key?: string;
23
- value?: string | Record<string, unknown>;
24
- [key: string]: unknown;
25
- }
26
-
27
- /**
28
- * Character n-gram semantic similarity scorer.
29
- *
30
- * Usage:
31
- * ```
32
- * const scorer = new SemanticScorer();
33
- * const score = scorer.similarity("deploy to prod", "release_command");
34
- * // score ≈ 0.15 (low but non-zero — catches partial overlap)
35
- *
36
- * const ranked = scorer.rank(
37
- * "search query",
38
- * [{value: "candidate A"}, {value: "candidate B"}]
39
- * );
40
- * // ranked → [[0.85, {value: "candidate A"}], [0.30, {value: "candidate B"}]]
41
- * ```
42
- */
43
- export class SemanticScorer {
44
- private nRange: [number, number];
45
- private cache: Map<string, Set<string>>;
46
- private maxCacheSize = 512;
47
-
48
- /**
49
- * Initialize scorer with n-gram range.
50
- * @param nRange - Tuple of [minSize, maxSize] for n-grams (default [2, 4])
51
- */
52
- constructor(nRange: [number, number] = [2, 4]) {
53
- this.nRange = nRange;
54
- this.cache = new Map();
55
- }
56
-
57
- /**
58
- * Generate character n-gram fingerprint (cached).
59
- * @param text - Input text to fingerprint
60
- * @returns Set of n-grams for the text
61
- */
62
- private fingerprint(text: string): Set<string> {
63
- // Return cached result if available
64
- if (this.cache.has(text)) {
65
- return this.cache.get(text)!;
66
- }
67
-
68
- const lowered = text.toLowerCase();
69
- const ngrams = new Set<string>();
70
-
71
- // Generate n-grams for each size in range
72
- for (let n = this.nRange[0]; n <= this.nRange[1]; n++) {
73
- if (lowered.length < n) {
74
- continue;
75
- }
76
- for (let i = 0; i <= lowered.length - n; i++) {
77
- ngrams.add(lowered.slice(i, i + n));
78
- }
79
- }
80
-
81
- // Cache result if we haven't hit size limit
82
- if (this.cache.size < this.maxCacheSize) {
83
- this.cache.set(text, ngrams);
84
- }
85
-
86
- return ngrams;
87
- }
88
-
89
- /**
90
- * Calculate Jaccard similarity between two texts using character n-grams.
91
- * @param a - First text
92
- * @param b - Second text
93
- * @returns Similarity score between 0 and 1
94
- */
95
- similarity(a: string, b: string): number {
96
- if (!a || !b) {
97
- return 0.0;
98
- }
99
-
100
- const fpA = this.fingerprint(a);
101
- const fpB = this.fingerprint(b);
102
-
103
- if (fpA.size === 0 || fpB.size === 0) {
104
- return 0.0;
105
- }
106
-
107
- // Calculate intersection
108
- let intersection = 0;
109
- for (const ngram of fpA) {
110
- if (fpB.has(ngram)) {
111
- intersection++;
112
- }
113
- }
114
-
115
- // Calculate union
116
- const union = fpA.size + fpB.size - intersection;
117
-
118
- return union > 0 ? intersection / union : 0.0;
119
- }
120
-
121
- /**
122
- * Rank candidates by semantic similarity to the query.
123
- *
124
- * Each candidate is a dict. The scorer reads `candidate[keyField]`
125
- * for text to compare. Also scores the "key" field if present.
126
- * Returns `[score, candidate]` tuples sorted descending, filtered by `minScore`.
127
- *
128
- * @param query - Query string to match against
129
- * @param candidates - Array of candidate objects
130
- * @param keyField - Field name to extract text from (default "value")
131
- * @param topK - Maximum number of results to return (default 3)
132
- * @param minScore - Minimum similarity threshold (default 0.02)
133
- * @returns Array of [score, candidate] tuples, sorted by score descending
134
- */
135
- rank(
136
- query: string,
137
- candidates: Candidate[],
138
- keyField: string = "value",
139
- topK: number = 3,
140
- minScore: number = 0.02
141
- ): Array<[number, Candidate]> {
142
- const scored: Array<[number, Candidate]> = [];
143
-
144
- for (const candidate of candidates) {
145
- let text = candidate[keyField];
146
-
147
- // Skip if field is missing or empty
148
- if (!text) {
149
- continue;
150
- }
151
-
152
- // Convert objects to string representation
153
- if (typeof text === "object") {
154
- text = JSON.stringify(text);
155
- } else {
156
- text = String(text);
157
- }
158
-
159
- // Calculate similarity with the candidate's value field
160
- let score = this.similarity(query, text as string);
161
-
162
- // Also score the "key" field if present — often more discriminative
163
- const keyText = candidate.key;
164
- if (keyText) {
165
- const keyScore = this.similarity(query, String(keyText));
166
- score = Math.max(score, keyScore);
167
- }
168
-
169
- // Add to results if above threshold
170
- if (score >= minScore) {
171
- scored.push([score, candidate]);
172
- }
173
- }
174
-
175
- // Sort descending by score
176
- scored.sort((a, b) => b[0] - a[0]);
177
-
178
- // Return top K results
179
- return scored.slice(0, topK);
180
- }
181
-
182
- /**
183
- * Clear the fingerprint cache.
184
- */
185
- clearCache(): void {
186
- this.cache.clear();
187
- }
188
-
189
- /**
190
- * Get current cache size.
191
- */
192
- getCacheSize(): number {
193
- return this.cache.size;
194
- }
195
- }
196
-
197
- /**
198
- * Module-level singleton instance (lazy initialization).
199
- */
200
- let scorer: SemanticScorer | null = null;
201
-
202
- /**
203
- * Get or create the module-level semantic scorer singleton.
204
- * @returns SemanticScorer instance
205
- */
206
- export function getScorer(): SemanticScorer {
207
- if (scorer === null) {
208
- scorer = new SemanticScorer();
209
- }
210
- return scorer;
211
- }
1
+ /**
2
+ * Lightweight semantic retrieval — zero external dependencies.
3
+ *
4
+ * Why: The existing `recall_for_injection` uses SQL LIKE on tokens, which
5
+ * misses semantically related facts that share few literal characters (e.g.
6
+ * query "deploy" vs. stored fact "release_command"). This module provides
7
+ * a character n-gram Jaccard similarity scorer that catches those cross-
8
+ * lingual and synonym relationships without adding PyTorch / sentence-transformers.
9
+ *
10
+ * Design:
11
+ * - Character n-grams (size 2-4) naturally handle CJK, mixed-language, and
12
+ * code identifiers better than word-level tokenization.
13
+ * - Jaccard similarity on n-gram sets is fast (< 10 µs per pair) and
14
+ * well-correlated with human relevance judgments for short text.
15
+ * - Zero dependencies beyond stdlib.
16
+ */
17
+
18
+ /**
19
+ * Represents a candidate item for ranking
20
+ */
21
+ export interface Candidate {
22
+ key?: string;
23
+ value?: string | Record<string, unknown>;
24
+ [key: string]: unknown;
25
+ }
26
+
27
+ /**
28
+ * Character n-gram semantic similarity scorer.
29
+ *
30
+ * Usage:
31
+ * ```
32
+ * const scorer = new SemanticScorer();
33
+ * const score = scorer.similarity("deploy to prod", "release_command");
34
+ * // score ≈ 0.15 (low but non-zero — catches partial overlap)
35
+ *
36
+ * const ranked = scorer.rank(
37
+ * "search query",
38
+ * [{value: "candidate A"}, {value: "candidate B"}]
39
+ * );
40
+ * // ranked → [[0.85, {value: "candidate A"}], [0.30, {value: "candidate B"}]]
41
+ * ```
42
+ */
43
+ export class SemanticScorer {
44
+ private nRange: [number, number];
45
+ private cache: Map<string, Set<string>>;
46
+ private maxCacheSize = 512;
47
+
48
+ /**
49
+ * Initialize scorer with n-gram range.
50
+ * @param nRange - Tuple of [minSize, maxSize] for n-grams (default [2, 4])
51
+ */
52
+ constructor(nRange: [number, number] = [2, 4]) {
53
+ this.nRange = nRange;
54
+ this.cache = new Map();
55
+ }
56
+
57
+ /**
58
+ * Generate character n-gram fingerprint (cached).
59
+ * @param text - Input text to fingerprint
60
+ * @returns Set of n-grams for the text
61
+ */
62
+ private fingerprint(text: string): Set<string> {
63
+ // Return cached result if available
64
+ if (this.cache.has(text)) {
65
+ return this.cache.get(text)!;
66
+ }
67
+
68
+ const lowered = text.toLowerCase();
69
+ const ngrams = new Set<string>();
70
+
71
+ // Generate n-grams for each size in range
72
+ for (let n = this.nRange[0]; n <= this.nRange[1]; n++) {
73
+ if (lowered.length < n) {
74
+ continue;
75
+ }
76
+ for (let i = 0; i <= lowered.length - n; i++) {
77
+ ngrams.add(lowered.slice(i, i + n));
78
+ }
79
+ }
80
+
81
+ // Cache result if we haven't hit size limit
82
+ if (this.cache.size < this.maxCacheSize) {
83
+ this.cache.set(text, ngrams);
84
+ }
85
+
86
+ return ngrams;
87
+ }
88
+
89
+ /**
90
+ * Calculate Jaccard similarity between two texts using character n-grams.
91
+ * @param a - First text
92
+ * @param b - Second text
93
+ * @returns Similarity score between 0 and 1
94
+ */
95
+ similarity(a: string, b: string): number {
96
+ if (!a || !b) {
97
+ return 0.0;
98
+ }
99
+
100
+ const fpA = this.fingerprint(a);
101
+ const fpB = this.fingerprint(b);
102
+
103
+ if (fpA.size === 0 || fpB.size === 0) {
104
+ return 0.0;
105
+ }
106
+
107
+ // Calculate intersection
108
+ let intersection = 0;
109
+ for (const ngram of fpA) {
110
+ if (fpB.has(ngram)) {
111
+ intersection++;
112
+ }
113
+ }
114
+
115
+ // Calculate union
116
+ const union = fpA.size + fpB.size - intersection;
117
+
118
+ return union > 0 ? intersection / union : 0.0;
119
+ }
120
+
121
+ /**
122
+ * Rank candidates by semantic similarity to the query.
123
+ *
124
+ * Each candidate is a dict. The scorer reads `candidate[keyField]`
125
+ * for text to compare. Also scores the "key" field if present.
126
+ * Returns `[score, candidate]` tuples sorted descending, filtered by `minScore`.
127
+ *
128
+ * @param query - Query string to match against
129
+ * @param candidates - Array of candidate objects
130
+ * @param keyField - Field name to extract text from (default "value")
131
+ * @param topK - Maximum number of results to return (default 3)
132
+ * @param minScore - Minimum similarity threshold (default 0.02)
133
+ * @returns Array of [score, candidate] tuples, sorted by score descending
134
+ */
135
+ rank(
136
+ query: string,
137
+ candidates: Candidate[],
138
+ keyField: string = "value",
139
+ topK: number = 3,
140
+ minScore: number = 0.02
141
+ ): Array<[number, Candidate]> {
142
+ const scored: Array<[number, Candidate]> = [];
143
+
144
+ for (const candidate of candidates) {
145
+ let text = candidate[keyField];
146
+
147
+ // Skip if field is missing or empty
148
+ if (!text) {
149
+ continue;
150
+ }
151
+
152
+ // Convert objects to string representation
153
+ if (typeof text === "object") {
154
+ text = JSON.stringify(text);
155
+ } else {
156
+ text = String(text);
157
+ }
158
+
159
+ // Calculate similarity with the candidate's value field
160
+ let score = this.similarity(query, text as string);
161
+
162
+ // Also score the "key" field if present — often more discriminative
163
+ const keyText = candidate.key;
164
+ if (keyText) {
165
+ const keyScore = this.similarity(query, String(keyText));
166
+ score = Math.max(score, keyScore);
167
+ }
168
+
169
+ // Add to results if above threshold
170
+ if (score >= minScore) {
171
+ scored.push([score, candidate]);
172
+ }
173
+ }
174
+
175
+ // Sort descending by score
176
+ scored.sort((a, b) => b[0] - a[0]);
177
+
178
+ // Return top K results
179
+ return scored.slice(0, topK);
180
+ }
181
+
182
+ /**
183
+ * Clear the fingerprint cache.
184
+ */
185
+ clearCache(): void {
186
+ this.cache.clear();
187
+ }
188
+
189
+ /**
190
+ * Get current cache size.
191
+ */
192
+ getCacheSize(): number {
193
+ return this.cache.size;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Module-level singleton instance (lazy initialization).
199
+ */
200
+ let scorer: SemanticScorer | null = null;
201
+
202
+ /**
203
+ * Get or create the module-level semantic scorer singleton.
204
+ * @returns SemanticScorer instance
205
+ */
206
+ export function getScorer(): SemanticScorer {
207
+ if (scorer === null) {
208
+ scorer = new SemanticScorer();
209
+ }
210
+ return scorer;
211
+ }