@monoes/monomindcli 2.9.3 → 2.9.4
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/.claude/helpers/intelligence.cjs +27 -6
- package/.claude/skills/agentic-jujutsu/SKILL.md +17 -15
- package/.claude/skills/hive-mind-advanced/SKILL.md +212 -559
- package/.claude/skills/hooks-automation/SKILL.md +1 -1
- package/.claude/skills/mastermind-createorg/SKILL.md +2 -1
- package/.claude/skills/pair-programming/SKILL.md +1 -1
- package/.claude/skills/performance-analysis/SKILL.md +228 -484
- package/.claude/skills/swarm-advanced/SKILL.md +2 -2
- package/.claude/skills/swarm-orchestration/SKILL.md +220 -150
- package/.claude/skills/verification-quality/SKILL.md +247 -571
- package/README.md +6 -2
- package/dist/src/autopilot-state.d.ts +16 -4
- package/dist/src/autopilot-state.d.ts.map +1 -1
- package/dist/src/autopilot-state.js +42 -19
- package/dist/src/autopilot-state.js.map +1 -1
- package/dist/src/commands/agent-lifecycle.js +3 -3
- package/dist/src/commands/agent-lifecycle.js.map +1 -1
- package/dist/src/commands/autopilot.d.ts.map +1 -1
- package/dist/src/commands/autopilot.js +113 -26
- package/dist/src/commands/autopilot.js.map +1 -1
- package/dist/src/commands/doc.d.ts.map +1 -1
- package/dist/src/commands/doc.js +14 -3
- package/dist/src/commands/doc.js.map +1 -1
- package/dist/src/commands/doctor-env-checks.d.ts +1 -1
- package/dist/src/commands/doctor-env-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +30 -1
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/hooks-routing-commands.d.ts.map +1 -1
- package/dist/src/commands/hooks-routing-commands.js +12 -26
- package/dist/src/commands/hooks-routing-commands.js.map +1 -1
- package/dist/src/commands/hooks-workers.js +8 -8
- package/dist/src/commands/hooks-workers.js.map +1 -1
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +96 -54
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +80 -7
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +78 -2
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/memory-list.d.ts.map +1 -1
- package/dist/src/commands/memory-list.js +79 -36
- package/dist/src/commands/memory-list.js.map +1 -1
- package/dist/src/commands/org.d.ts.map +1 -1
- package/dist/src/commands/org.js +114 -1
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/commands/performance.js +1 -1
- package/dist/src/commands/performance.js.map +1 -1
- package/dist/src/commands/swarm.d.ts.map +1 -1
- package/dist/src/commands/swarm.js +144 -98
- package/dist/src/commands/swarm.js.map +1 -1
- package/dist/src/init/claudemd-generator.js +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +13 -3
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/kimi-generator.d.ts +3 -2
- package/dist/src/init/kimi-generator.d.ts.map +1 -1
- package/dist/src/init/kimi-generator.js +45 -13
- package/dist/src/init/kimi-generator.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +48 -46
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/init/types.d.ts +1 -1
- package/dist/src/init/types.d.ts.map +1 -1
- package/dist/src/init/types.js +5 -1
- package/dist/src/init/types.js.map +1 -1
- package/dist/src/mcp-server.d.ts +6 -0
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +47 -22
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +13 -11
- package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-intelligence.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-intelligence.js +97 -40
- package/dist/src/mcp-tools/hooks-intelligence.js.map +1 -1
- package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-routing.js +406 -205
- package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
- package/dist/src/mcp-tools/monograph/impact-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph/impact-tools.js +109 -44
- package/dist/src/mcp-tools/monograph/impact-tools.js.map +1 -1
- package/dist/src/mcp-tools/monograph/query-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph/query-tools.js +48 -18
- package/dist/src/mcp-tools/monograph/query-tools.js.map +1 -1
- package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/system-tools.js +100 -38
- package/dist/src/mcp-tools/system-tools.js.map +1 -1
- package/dist/src/memory/embedding-operations.d.ts +4 -0
- package/dist/src/memory/embedding-operations.d.ts.map +1 -1
- package/dist/src/memory/embedding-operations.js +125 -32
- package/dist/src/memory/embedding-operations.js.map +1 -1
- package/dist/src/memory/hnsw-operations.d.ts +1 -1
- package/dist/src/memory/hnsw-operations.js +1 -1
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +172 -69
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-read.d.ts +1 -1
- package/dist/src/memory/memory-read.js +2 -2
- package/dist/src/memory/memory-read.js.map +1 -1
- package/dist/src/orgrt/checkpoint-ops.d.ts +11 -1
- package/dist/src/orgrt/checkpoint-ops.d.ts.map +1 -1
- package/dist/src/orgrt/checkpoint-ops.js +15 -1
- package/dist/src/orgrt/checkpoint-ops.js.map +1 -1
- package/dist/src/orgrt/checkpoint.js +1 -1
- package/dist/src/orgrt/checkpoint.js.map +1 -1
- package/dist/src/orgrt/daemon.d.ts +11 -3
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +317 -93
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/decisions.d.ts +13 -0
- package/dist/src/orgrt/decisions.d.ts.map +1 -1
- package/dist/src/orgrt/decisions.js +95 -0
- package/dist/src/orgrt/decisions.js.map +1 -1
- package/dist/src/orgrt/remote.d.ts.map +1 -1
- package/dist/src/orgrt/remote.js +12 -6
- package/dist/src/orgrt/remote.js.map +1 -1
- package/dist/src/orgrt/server.d.ts.map +1 -1
- package/dist/src/orgrt/server.js +71 -14
- package/dist/src/orgrt/server.js.map +1 -1
- package/dist/src/orgrt/session.d.ts +27 -1
- package/dist/src/orgrt/session.d.ts.map +1 -1
- package/dist/src/orgrt/session.js +42 -3
- package/dist/src/orgrt/session.js.map +1 -1
- package/dist/src/orgrt/task-dag.d.ts +11 -1
- package/dist/src/orgrt/task-dag.d.ts.map +1 -1
- package/dist/src/orgrt/task-dag.js +93 -2
- package/dist/src/orgrt/task-dag.js.map +1 -1
- package/dist/src/orgrt/templates.d.ts.map +1 -1
- package/dist/src/orgrt/templates.js +24 -0
- package/dist/src/orgrt/templates.js.map +1 -1
- package/dist/src/orgrt/types.d.ts +70 -1
- package/dist/src/orgrt/types.d.ts.map +1 -1
- package/dist/src/orgrt/types.js +19 -0
- package/dist/src/orgrt/types.js.map +1 -1
- package/dist/src/orgrt/vercel-providers.d.ts.map +1 -1
- package/dist/src/orgrt/vercel-providers.js +9 -4
- package/dist/src/orgrt/vercel-providers.js.map +1 -1
- package/dist/src/orgrt/vercel-runner.d.ts +0 -23
- package/dist/src/orgrt/vercel-runner.d.ts.map +1 -1
- package/dist/src/orgrt/vercel-runner.js +26 -1
- package/dist/src/orgrt/vercel-runner.js.map +1 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/ui/server.mjs +2647 -838
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/.claude/commands/mastermind/approvev1.md +0 -94
- package/.claude/commands/mastermind/architect.md +0 -52
- package/.claude/commands/mastermind/autodev.md +0 -28
- package/.claude/commands/mastermind/build.md +0 -23
- package/.claude/commands/mastermind/finish.md +0 -17
- package/.claude/commands/mastermind/runorgv1.md +0 -159
- package/.claude/commands/mastermind/taskdev.md +0 -23
- package/.claude/commands/mastermind/tdd.md +0 -19
- package/.claude/commands/mastermind/verify.md +0 -19
- package/.claude/skills/mastermind-approvev1/SKILL.md +0 -191
- package/.claude/skills/mastermind-architect/SKILL.md +0 -862
- package/.claude/skills/mastermind-autodev/SKILL.md +0 -360
- package/.claude/skills/mastermind-build/SKILL.md +0 -169
- package/.claude/skills/mastermind-companies/SKILL.md +0 -256
- package/.claude/skills/mastermind-content/SKILL.md +0 -197
- package/.claude/skills/mastermind-costs/SKILL.md +0 -151
- package/.claude/skills/mastermind-finance/SKILL.md +0 -166
- package/.claude/skills/mastermind-finish/SKILL.md +0 -251
- package/.claude/skills/mastermind-heartbeatv1/SKILL.md +0 -167
- package/.claude/skills/mastermind-instance-settings/SKILL.md +0 -315
- package/.claude/skills/mastermind-marketing/SKILL.md +0 -228
- package/.claude/skills/mastermind-marketing/references/copywriting-frameworks.md +0 -181
- package/.claude/skills/mastermind-marketing/references/persuasion-psychology.md +0 -158
- package/.claude/skills/mastermind-ops/SKILL.md +0 -168
- package/.claude/skills/mastermind-org-chart/SKILL.md +0 -209
- package/.claude/skills/mastermind-project-detail/SKILL.md +0 -249
- package/.claude/skills/mastermind-project-workspace/SKILL.md +0 -244
- package/.claude/skills/mastermind-projects/SKILL.md +0 -167
- package/.claude/skills/mastermind-runorgv1/SKILL.md +0 -731
- package/.claude/skills/mastermind-sales/SKILL.md +0 -170
- package/.claude/skills/mastermind-taskdev/SKILL.md +0 -377
- package/.claude/skills/mastermind-taskdev/code-quality-reviewer-prompt.md +0 -60
- package/.claude/skills/mastermind-taskdev/final-reviewer-prompt.md +0 -144
- package/.claude/skills/mastermind-taskdev/implementer-prompt.md +0 -114
- package/.claude/skills/mastermind-taskdev/spec-reviewer-prompt.md +0 -80
- package/.claude/skills/mastermind-tdd/SKILL.md +0 -424
- package/.claude/skills/mastermind-verify/SKILL.md +0 -196
- package/.claude/skills/mastermind-wiki/SKILL.md +0 -314
- package/.claude/skills/monolean-review/SKILL.md +0 -57
|
@@ -39,7 +39,8 @@ export function safeParseEmbedding(raw) {
|
|
|
39
39
|
try {
|
|
40
40
|
parsed = JSON.parse(raw);
|
|
41
41
|
}
|
|
42
|
-
catch {
|
|
42
|
+
catch (e) {
|
|
43
|
+
logBridgeError('safeParseEmbedding', e);
|
|
43
44
|
return null;
|
|
44
45
|
}
|
|
45
46
|
if (!Array.isArray(parsed))
|
|
@@ -65,7 +66,8 @@ const MAX_TAG_LEN = 64;
|
|
|
65
66
|
const BRIDGE_RESULT_CONTENT_CAP = 500;
|
|
66
67
|
function capResultContent(content) {
|
|
67
68
|
return content.length > BRIDGE_RESULT_CONTENT_CAP
|
|
68
|
-
? content.slice(0, BRIDGE_RESULT_CONTENT_CAP) + '…'
|
|
69
|
+
? content.slice(0, BRIDGE_RESULT_CONTENT_CAP) + '…'
|
|
70
|
+
: content;
|
|
69
71
|
}
|
|
70
72
|
// ===== DB path resolution =====
|
|
71
73
|
// LanceDB commits via atomic rename, which exFAT/SMB project volumes don't support
|
|
@@ -88,7 +90,9 @@ function walkToProjectRoot(start) {
|
|
|
88
90
|
if (fs.existsSync(path.join(dir, '.monomind')) || fs.existsSync(path.join(dir, '.git')))
|
|
89
91
|
return dir;
|
|
90
92
|
}
|
|
91
|
-
catch {
|
|
93
|
+
catch (e) {
|
|
94
|
+
logBridgeError('walkToProjectRoot', e); /* unreadable dir — keep walking */
|
|
95
|
+
}
|
|
92
96
|
const parent = path.dirname(dir);
|
|
93
97
|
if (parent === dir)
|
|
94
98
|
break;
|
|
@@ -136,7 +140,10 @@ export function getProjectRoot(from = process.env.MONOMIND_CWD || process.cwd())
|
|
|
136
140
|
function projectDataDir() {
|
|
137
141
|
const resolved = path.resolve(getProjectRoot());
|
|
138
142
|
const hash = crypto.createHash('sha256').update(resolved).digest('hex').slice(0, 16);
|
|
139
|
-
const readable = path
|
|
143
|
+
const readable = path
|
|
144
|
+
.basename(resolved)
|
|
145
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
146
|
+
.slice(0, 40) || 'project';
|
|
140
147
|
return path.join(os.homedir(), '.monomind', 'projects', `${readable}-${hash}`);
|
|
141
148
|
}
|
|
142
149
|
/** Resolve symlinks so the traversal check below can't be bypassed by a link
|
|
@@ -145,7 +152,8 @@ function realOrResolved(p) {
|
|
|
145
152
|
try {
|
|
146
153
|
return fs.realpathSync(p);
|
|
147
154
|
}
|
|
148
|
-
catch {
|
|
155
|
+
catch (e) {
|
|
156
|
+
logBridgeError('realOrResolved', e);
|
|
149
157
|
return p;
|
|
150
158
|
}
|
|
151
159
|
}
|
|
@@ -155,7 +163,7 @@ function realOrResolved(p) {
|
|
|
155
163
|
* users who keep their brain on a synced/external location. Resolved lazily
|
|
156
164
|
* so the override works regardless of import order. */
|
|
157
165
|
export function getGlobalBrainDir() {
|
|
158
|
-
return process.env.MONOMIND_GLOBAL_BRAIN_DIR || path.join(os.homedir(), '.monomind', 'global-brain');
|
|
166
|
+
return (process.env.MONOMIND_GLOBAL_BRAIN_DIR || path.join(os.homedir(), '.monomind', 'global-brain'));
|
|
159
167
|
}
|
|
160
168
|
/** Sentinel callers pass as dbPath to address the global brain. */
|
|
161
169
|
export const GLOBAL_BRAIN = '@global';
|
|
@@ -198,10 +206,15 @@ function getAutomemConfig() {
|
|
|
198
206
|
return defaults;
|
|
199
207
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
200
208
|
return {
|
|
201
|
-
dedupThreshold: typeof config?.scaffold?.dedupThreshold === 'number'
|
|
202
|
-
|
|
209
|
+
dedupThreshold: typeof config?.scaffold?.dedupThreshold === 'number'
|
|
210
|
+
? config.scaffold.dedupThreshold
|
|
211
|
+
: defaults.dedupThreshold,
|
|
212
|
+
staleDays: typeof config?.scaffold?.staleDays === 'number'
|
|
213
|
+
? config.scaffold.staleDays
|
|
214
|
+
: defaults.staleDays,
|
|
203
215
|
feedbackInfluence: typeof config?.scaffold?.feedbackInfluence === 'number'
|
|
204
|
-
? Math.max(0, Math.min(1, config.scaffold.feedbackInfluence))
|
|
216
|
+
? Math.max(0, Math.min(1, config.scaffold.feedbackInfluence))
|
|
217
|
+
: defaults.feedbackInfluence,
|
|
205
218
|
};
|
|
206
219
|
}
|
|
207
220
|
catch (e) {
|
|
@@ -223,9 +236,11 @@ const FREQUENCY_NORM_CAP = 10;
|
|
|
223
236
|
function entryWeights(metadata) {
|
|
224
237
|
const md = (metadata ?? {});
|
|
225
238
|
const fw = typeof md.feedback_weight === 'number' && Number.isFinite(md.feedback_weight)
|
|
226
|
-
? Math.max(0, Math.min(1, md.feedback_weight))
|
|
239
|
+
? Math.max(0, Math.min(1, md.feedback_weight))
|
|
240
|
+
: DEFAULT_FEEDBACK_WEIGHT;
|
|
227
241
|
const freq = typeof md.frequency_weight === 'number' && Number.isFinite(md.frequency_weight)
|
|
228
|
-
? Math.max(0, md.frequency_weight)
|
|
242
|
+
? Math.max(0, md.frequency_weight)
|
|
243
|
+
: 0;
|
|
229
244
|
return { feedback: fw, frequency: freq };
|
|
230
245
|
}
|
|
231
246
|
/** Blend learned usefulness into a GENUINE embedding-similarity score.
|
|
@@ -289,7 +304,11 @@ export async function loadReranker() {
|
|
|
289
304
|
const tokenizer = await hf.AutoTokenizer.from_pretrained(modelId, opts);
|
|
290
305
|
const model = await hf.AutoModelForSequenceClassification.from_pretrained(modelId, opts);
|
|
291
306
|
_reranker = async (query, passage) => {
|
|
292
|
-
const inputs = await tokenizer(query, {
|
|
307
|
+
const inputs = await tokenizer(query, {
|
|
308
|
+
text_pair: passage,
|
|
309
|
+
padding: true,
|
|
310
|
+
truncation: true,
|
|
311
|
+
});
|
|
293
312
|
const output = await model(inputs);
|
|
294
313
|
const logits = output.logits.data;
|
|
295
314
|
// num_labels=1 → [1,1] regression score, apply sigmoid
|
|
@@ -335,7 +354,9 @@ async function flushBackend(backend) {
|
|
|
335
354
|
try {
|
|
336
355
|
await backend?.persist?.();
|
|
337
356
|
}
|
|
338
|
-
catch {
|
|
357
|
+
catch (e) {
|
|
358
|
+
logBridgeError('flushBackend', e); /* best effort */
|
|
359
|
+
}
|
|
339
360
|
}
|
|
340
361
|
/** Loads the local embedding model.
|
|
341
362
|
*
|
|
@@ -360,7 +381,11 @@ async function loadEmbedder() {
|
|
|
360
381
|
// silently killed embeddings (every search degraded to keyword matching)
|
|
361
382
|
// dtype pinned explicitly: transformers.js logs a "dtype not specified"
|
|
362
383
|
// warning to the console on every load otherwise (leaks into CLI output).
|
|
363
|
-
const extractor = await hf.pipeline('feature-extraction', BRIDGE_EMBEDDING_MODEL, {
|
|
384
|
+
const extractor = await hf.pipeline('feature-extraction', BRIDGE_EMBEDDING_MODEL, {
|
|
385
|
+
revision: 'main',
|
|
386
|
+
dtype: 'q8',
|
|
387
|
+
local_files_only: true,
|
|
388
|
+
});
|
|
364
389
|
_embedder = async (text) => {
|
|
365
390
|
const output = await extractor(text, { pooling: 'cls', normalize: true });
|
|
366
391
|
return new Float32Array(output.data);
|
|
@@ -390,7 +415,9 @@ async function getBackend(dbPath) {
|
|
|
390
415
|
try {
|
|
391
416
|
await evicted?.instance?.shutdown?.();
|
|
392
417
|
}
|
|
393
|
-
catch {
|
|
418
|
+
catch (e) {
|
|
419
|
+
logBridgeError('getBackend.evictedShutdown', e); /* best effort */
|
|
420
|
+
}
|
|
394
421
|
backendSlots.delete(oldest);
|
|
395
422
|
}
|
|
396
423
|
slot = { promise: null, instance: null, available: null, attempts: 0 };
|
|
@@ -426,9 +453,12 @@ async function getBackend(dbPath) {
|
|
|
426
453
|
// would make `cleanup --data` prune the WHOLE project's brain as
|
|
427
454
|
// orphaned.
|
|
428
455
|
const originFile = path.join(projectDataDir(), 'origin.json');
|
|
429
|
-
fs.writeFileSync(originFile, JSON.stringify({ path: getProjectRoot(), updatedAt: new Date().toISOString() }) +
|
|
456
|
+
fs.writeFileSync(originFile, JSON.stringify({ path: getProjectRoot(), updatedAt: new Date().toISOString() }) +
|
|
457
|
+
'\n', 'utf-8');
|
|
458
|
+
}
|
|
459
|
+
catch (e) {
|
|
460
|
+
logBridgeError('getBackend.originWrite', e); /* non-fatal */
|
|
430
461
|
}
|
|
431
|
-
catch { /* non-fatal */ }
|
|
432
462
|
}
|
|
433
463
|
const cfg = {
|
|
434
464
|
databasePath: path.join(dir, 'memory.db'),
|
|
@@ -489,7 +519,8 @@ export async function bridgeStoreEntry(options) {
|
|
|
489
519
|
return null;
|
|
490
520
|
try {
|
|
491
521
|
const key = typeof options.key === 'string' && options.key.length > BRIDGE_MAX_KEY_LEN
|
|
492
|
-
? options.key.slice(0, BRIDGE_MAX_KEY_LEN)
|
|
522
|
+
? options.key.slice(0, BRIDGE_MAX_KEY_LEN)
|
|
523
|
+
: options.key;
|
|
493
524
|
if (typeof options.value === 'string' && options.value.length > BRIDGE_MAX_VALUE_LEN) {
|
|
494
525
|
return {
|
|
495
526
|
success: false,
|
|
@@ -500,9 +531,13 @@ export async function bridgeStoreEntry(options) {
|
|
|
500
531
|
const value = options.value;
|
|
501
532
|
const namespace = options.namespace ?? 'default';
|
|
502
533
|
const tags = Array.isArray(options.tags)
|
|
503
|
-
// src: tags carry the ingest source path for excerpt provenance — paths
|
|
504
|
-
|
|
505
|
-
|
|
534
|
+
? // src: tags carry the ingest source path for excerpt provenance — paths
|
|
535
|
+
// routinely exceed the general 64-char tag cap, so they get 512.
|
|
536
|
+
options.tags
|
|
537
|
+
.filter((t) => typeof t === 'string' &&
|
|
538
|
+
t.length > 0 &&
|
|
539
|
+
t.length <= (t.startsWith('src:') ? 512 : MAX_TAG_LEN))
|
|
540
|
+
.slice(0, MAX_TAGS)
|
|
506
541
|
: [];
|
|
507
542
|
const now = Date.now();
|
|
508
543
|
const id = generateId('entry');
|
|
@@ -540,7 +575,9 @@ export async function bridgeStoreEntry(options) {
|
|
|
540
575
|
try {
|
|
541
576
|
upsertVictim = await backend.getByKey(namespace, key);
|
|
542
577
|
}
|
|
543
|
-
catch {
|
|
578
|
+
catch (e) {
|
|
579
|
+
logBridgeError('bridgeStoreEntry.upsertLookup', e); /* treat as no existing entry */
|
|
580
|
+
}
|
|
544
581
|
}
|
|
545
582
|
// Dedup gate: skip if a near-duplicate already exists IN THIS NAMESPACE —
|
|
546
583
|
// an unscoped search let a similar entry in some other namespace swallow
|
|
@@ -549,17 +586,22 @@ export async function bridgeStoreEntry(options) {
|
|
|
549
586
|
if (embedding && !options.upsert) {
|
|
550
587
|
try {
|
|
551
588
|
const similar = await backend.search(embedding, {
|
|
552
|
-
k: 1,
|
|
589
|
+
k: 1,
|
|
590
|
+
threshold: automemCfg.dedupThreshold,
|
|
553
591
|
filters: { type: 'exact', namespace },
|
|
554
592
|
});
|
|
555
593
|
if (similar.length > 0 && similar[0].score >= automemCfg.dedupThreshold) {
|
|
556
594
|
// Re-storing near-identical content is a usage signal: the fact keeps
|
|
557
595
|
// being worth remembering. Reinforce the surviving entry.
|
|
558
|
-
await recordUsageOnBackend(backend, [similar[0].entry.id]).catch(() => {
|
|
596
|
+
await recordUsageOnBackend(backend, [similar[0].entry.id]).catch(() => {
|
|
597
|
+
/* best effort */
|
|
598
|
+
});
|
|
559
599
|
return { success: true, id: similar[0].entry.id, duplicate: true };
|
|
560
600
|
}
|
|
561
601
|
}
|
|
562
|
-
catch {
|
|
602
|
+
catch (e) {
|
|
603
|
+
logBridgeError('bridgeStoreEntry.dedupSearch', e); /* non-fatal — store anyway */
|
|
604
|
+
}
|
|
563
605
|
}
|
|
564
606
|
await backend.store(entry);
|
|
565
607
|
if (upsertVictim && upsertVictim.id !== id) {
|
|
@@ -575,7 +617,9 @@ export async function bridgeStoreEntry(options) {
|
|
|
575
617
|
return { success: true, id, embedding: embeddingInfo };
|
|
576
618
|
}
|
|
577
619
|
catch (err) {
|
|
578
|
-
|
|
620
|
+
logBridgeError('bridgeStoreEntry', err);
|
|
621
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
622
|
+
return { success: false, id: '', error: message };
|
|
579
623
|
}
|
|
580
624
|
}
|
|
581
625
|
export async function bridgeSearchEntries(options) {
|
|
@@ -601,29 +645,35 @@ export async function bridgeSearchEntries(options) {
|
|
|
601
645
|
if (knowledgeFilterActive) {
|
|
602
646
|
try {
|
|
603
647
|
const dp = await import('../knowledge/document-pipeline.js');
|
|
604
|
-
const rootDir = options.dbPath === GLOBAL_BRAIN
|
|
605
|
-
? getGlobalBrainDir() : getProjectRoot();
|
|
648
|
+
const rootDir = options.dbPath === GLOBAL_BRAIN ? getGlobalBrainDir() : getProjectRoot();
|
|
606
649
|
_knowledgeLive = dp.liveContentHashes(rootDir);
|
|
607
650
|
_knowledgeHasMeta = dp.hasKnowledgeMetadata(rootDir);
|
|
608
651
|
_isSupersededKey = dp.isSupersededKey;
|
|
609
652
|
}
|
|
610
|
-
catch {
|
|
653
|
+
catch (e) {
|
|
654
|
+
logBridgeError('bridgeSearchEntries.knowledgeFilter', e); /* non-fatal: skip filtering when pipeline is unavailable */
|
|
655
|
+
}
|
|
611
656
|
}
|
|
612
657
|
// Over-retrieve when the reranker is available: fetch more candidates so the
|
|
613
658
|
// cross-encoder can reshuffle them. The reranker trims back to `limit`.
|
|
614
659
|
// For knowledge namespaces, also over-fetch to compensate for superseded
|
|
615
660
|
// document versions that will be filtered out below.
|
|
616
661
|
const rerankerActive = !options.skipRerank && _reranker !== null && process.env.MONOMIND_RERANKER !== '0';
|
|
617
|
-
const knowledgeLimit =
|
|
618
|
-
? Math.min(Math.max(limit * 20, limit), 300)
|
|
619
|
-
|
|
662
|
+
const knowledgeLimit = _knowledgeLive && _knowledgeLive.size > 0
|
|
663
|
+
? Math.min(Math.max(limit * 20, limit), 300)
|
|
664
|
+
: limit;
|
|
665
|
+
const retrieveK = rerankerActive
|
|
666
|
+
? Math.min(knowledgeLimit * 3, Math.max(20, knowledgeLimit))
|
|
667
|
+
: knowledgeLimit;
|
|
620
668
|
let results = [];
|
|
621
669
|
let searchMethod = 'keyword';
|
|
622
670
|
// Reported to callers so "(semantic)" can never be printed over keyword hits.
|
|
623
671
|
// The two reasons for skipping the vector path are distinct and must not be
|
|
624
672
|
// conflated: a healthy model given an empty query is not a missing model.
|
|
625
|
-
let fallbackReason = !_embedder
|
|
626
|
-
|
|
673
|
+
let fallbackReason = !_embedder
|
|
674
|
+
? 'no-embedding-model'
|
|
675
|
+
: queryStr.length === 0
|
|
676
|
+
? 'empty-query'
|
|
627
677
|
: undefined;
|
|
628
678
|
let semanticAttempted = false;
|
|
629
679
|
if (_embedder && queryStr.length > 0) {
|
|
@@ -636,7 +686,8 @@ export async function bridgeSearchEntries(options) {
|
|
|
636
686
|
filters: namespace ? { type: 'exact', namespace } : undefined,
|
|
637
687
|
});
|
|
638
688
|
const { feedbackInfluence } = getAutomemConfig();
|
|
639
|
-
results = searchResults
|
|
689
|
+
results = searchResults
|
|
690
|
+
.map((r) => {
|
|
640
691
|
const weights = entryWeights(r.entry.metadata);
|
|
641
692
|
// Blend only here (semantic path): r.score is a genuine cosine similarity.
|
|
642
693
|
const blended = blendScore(r.score, weights, feedbackInfluence);
|
|
@@ -650,7 +701,8 @@ export async function bridgeSearchEntries(options) {
|
|
|
650
701
|
tags: r.entry.tags ?? [],
|
|
651
702
|
_createdAt: r.entry.createdAt || 0,
|
|
652
703
|
};
|
|
653
|
-
})
|
|
704
|
+
})
|
|
705
|
+
.sort((a, b) => b.score - a.score);
|
|
654
706
|
searchMethod = 'semantic';
|
|
655
707
|
fallbackReason = undefined;
|
|
656
708
|
}
|
|
@@ -672,7 +724,10 @@ export async function bridgeSearchEntries(options) {
|
|
|
672
724
|
// loaded up to 50k rows and scanned them in JS. The JS fallback is
|
|
673
725
|
// kept for sql.js WASM builds that lack the FTS5 extension.
|
|
674
726
|
{
|
|
675
|
-
const tokens = queryStr
|
|
727
|
+
const tokens = queryStr
|
|
728
|
+
.toLowerCase()
|
|
729
|
+
.split(/[^a-z0-9]+/)
|
|
730
|
+
.filter((t) => t.length > 1);
|
|
676
731
|
let keywordHits = [];
|
|
677
732
|
if (tokens.length) {
|
|
678
733
|
// ── FTS5 fast path ──────────────────────────────────────────
|
|
@@ -723,7 +778,10 @@ export async function bridgeSearchEntries(options) {
|
|
|
723
778
|
// sharing that key string resolved to whichever entry happened
|
|
724
779
|
// to be inserted last into the lookup map, silently returning
|
|
725
780
|
// the wrong entry's id/content/namespace.
|
|
726
|
-
const chunks = entries.map((e, i) => ({
|
|
781
|
+
const chunks = entries.map((e, i) => ({
|
|
782
|
+
key: String(i),
|
|
783
|
+
text: `${e.key || ''} ${e.content || ''}`,
|
|
784
|
+
}));
|
|
727
785
|
const idx = Bm25Index.build(chunks, () => false); // no superseded concept at this generic KV level — filtered later by callers that care
|
|
728
786
|
const hits = idx.search(queryStr, limit);
|
|
729
787
|
// #126-review: Math.max(..., 1) as a divide-by-zero guard also
|
|
@@ -732,7 +790,7 @@ export async function bridgeSearchEntries(options) {
|
|
|
732
790
|
// regime this capped fallback runs in), so the top hit stopped
|
|
733
791
|
// normalizing to 1.0 as the comment below claims. Only fall
|
|
734
792
|
// back to 1 when there is no positive score to divide by.
|
|
735
|
-
const rawMax = hits.length ? Math.max(...hits.map(h => h.score)) : 0;
|
|
793
|
+
const rawMax = hits.length ? Math.max(...hits.map((h) => h.score)) : 0;
|
|
736
794
|
const maxScore = rawMax > 0 ? rawMax : 1;
|
|
737
795
|
keywordHits = hits.map((h) => {
|
|
738
796
|
const e = entries[Number(h.key)];
|
|
@@ -765,7 +823,7 @@ export async function bridgeSearchEntries(options) {
|
|
|
765
823
|
keywordHits = entries
|
|
766
824
|
.map((e) => {
|
|
767
825
|
const haystack = `${e.key || ''} ${e.content || ''}`.toLowerCase();
|
|
768
|
-
const hits = tokens.filter(t => haystack.includes(t)).length;
|
|
826
|
+
const hits = tokens.filter((t) => haystack.includes(t)).length;
|
|
769
827
|
return { e, score: hits / tokens.length };
|
|
770
828
|
})
|
|
771
829
|
.filter((x) => x.score > 0)
|
|
@@ -817,13 +875,16 @@ export async function bridgeSearchEntries(options) {
|
|
|
817
875
|
// stale cutoff.
|
|
818
876
|
// org:* (cross-run org memory) and rules are durable learned state like
|
|
819
877
|
// documents — the stale cliff silently erased org recall after a week.
|
|
820
|
-
const durableNs = (ns) => ns.startsWith('knowledge:') ||
|
|
878
|
+
const durableNs = (ns) => ns.startsWith('knowledge:') ||
|
|
879
|
+
ns.startsWith('org:') ||
|
|
880
|
+
ns.startsWith('agent:') ||
|
|
881
|
+
ns.startsWith('kg:') ||
|
|
882
|
+
ns === 'rules';
|
|
821
883
|
const isKnowledgeNs = namespace ? durableNs(namespace) : false;
|
|
822
884
|
if (!isKnowledgeNs) {
|
|
823
885
|
const { staleDays } = getAutomemConfig();
|
|
824
886
|
const staleCutoff = Date.now() - staleDays * 86400000;
|
|
825
|
-
results = results.filter((r) => durableNs(String(r.namespace ?? ''))
|
|
826
|
-
|| !r._createdAt || r._createdAt > staleCutoff);
|
|
887
|
+
results = results.filter((r) => durableNs(String(r.namespace ?? '')) || !r._createdAt || r._createdAt > staleCutoff);
|
|
827
888
|
}
|
|
828
889
|
results.forEach((r) => delete r._createdAt);
|
|
829
890
|
// ── Knowledge superseded filtering (issue #106) ─────────────────
|
|
@@ -846,7 +907,9 @@ export async function bridgeSearchEntries(options) {
|
|
|
846
907
|
if (!_reranker && !_rerankerPromise) {
|
|
847
908
|
// First qualifying search — kick off the lazy load. This search
|
|
848
909
|
// proceeds without reranking; the NEXT search will use it.
|
|
849
|
-
loadReranker().catch(() => {
|
|
910
|
+
loadReranker().catch(() => {
|
|
911
|
+
/* swallowed — retry next time */
|
|
912
|
+
});
|
|
850
913
|
}
|
|
851
914
|
if (_reranker) {
|
|
852
915
|
const rr = await rerankResults(queryStr, results, limit);
|
|
@@ -1008,7 +1071,8 @@ export async function bridgeGetBackendStats(dbPath) {
|
|
|
1008
1071
|
memoryUsage: stats?.memoryUsage ?? 0,
|
|
1009
1072
|
};
|
|
1010
1073
|
}
|
|
1011
|
-
catch {
|
|
1074
|
+
catch (e) {
|
|
1075
|
+
logBridgeError('bridgeGetBackendStats', e);
|
|
1012
1076
|
return null;
|
|
1013
1077
|
}
|
|
1014
1078
|
}
|
|
@@ -1021,7 +1085,8 @@ export async function bridgeGetHNSWStatus(dbPath) {
|
|
|
1021
1085
|
const stats = await backend.getStats();
|
|
1022
1086
|
return { built: true, size: stats?.totalEntries ?? 0, dimensions: BRIDGE_EMBEDDING_DIMS };
|
|
1023
1087
|
}
|
|
1024
|
-
catch {
|
|
1088
|
+
catch (e) {
|
|
1089
|
+
logBridgeError('bridgeGetHNSWStatus', e);
|
|
1025
1090
|
return { built: false, size: 0, dimensions: BRIDGE_EMBEDDING_DIMS };
|
|
1026
1091
|
}
|
|
1027
1092
|
}
|
|
@@ -1038,7 +1103,12 @@ export async function bridgeSearchHNSW(options) {
|
|
|
1038
1103
|
return null;
|
|
1039
1104
|
return {
|
|
1040
1105
|
success: result.success,
|
|
1041
|
-
results: result.results.map(r => ({
|
|
1106
|
+
results: result.results.map((r) => ({
|
|
1107
|
+
id: r.id,
|
|
1108
|
+
key: r.key,
|
|
1109
|
+
score: r.score,
|
|
1110
|
+
namespace: r.namespace,
|
|
1111
|
+
})),
|
|
1042
1112
|
searchTime: result.searchTime,
|
|
1043
1113
|
};
|
|
1044
1114
|
}
|
|
@@ -1051,7 +1121,8 @@ export async function bridgeAddToHNSW(options) {
|
|
|
1051
1121
|
const stats = await backend.getStats();
|
|
1052
1122
|
return { success: true, indexSize: stats?.totalEntries ?? 0 };
|
|
1053
1123
|
}
|
|
1054
|
-
catch {
|
|
1124
|
+
catch (e) {
|
|
1125
|
+
logBridgeError('bridgeAddToHNSW', e);
|
|
1055
1126
|
return { success: true };
|
|
1056
1127
|
}
|
|
1057
1128
|
}
|
|
@@ -1083,7 +1154,9 @@ export async function shutdownBridge() {
|
|
|
1083
1154
|
try {
|
|
1084
1155
|
await slot.instance.shutdown();
|
|
1085
1156
|
}
|
|
1086
|
-
catch {
|
|
1157
|
+
catch (e) {
|
|
1158
|
+
logBridgeError('bridgeShutdown.slotShutdown', e); /* ignore */
|
|
1159
|
+
}
|
|
1087
1160
|
}
|
|
1088
1161
|
}
|
|
1089
1162
|
backendSlots.clear();
|
|
@@ -1119,12 +1192,14 @@ export async function bridgeSearchPatterns(options) {
|
|
|
1119
1192
|
return null;
|
|
1120
1193
|
return {
|
|
1121
1194
|
success: result.success,
|
|
1122
|
-
patterns: result.results.map(r => {
|
|
1195
|
+
patterns: result.results.map((r) => {
|
|
1123
1196
|
let parsed = {};
|
|
1124
1197
|
try {
|
|
1125
1198
|
parsed = JSON.parse(r.content);
|
|
1126
1199
|
}
|
|
1127
|
-
catch {
|
|
1200
|
+
catch (e) {
|
|
1201
|
+
logBridgeError('bridgeSearchPatterns.parseContent', e); /* use raw */
|
|
1202
|
+
}
|
|
1128
1203
|
return {
|
|
1129
1204
|
id: r.id,
|
|
1130
1205
|
pattern: parsed.pattern ?? r.content,
|
|
@@ -1152,7 +1227,9 @@ async function recordUsageOnBackend(backend, entryIds) {
|
|
|
1152
1227
|
});
|
|
1153
1228
|
updated++;
|
|
1154
1229
|
}
|
|
1155
|
-
catch {
|
|
1230
|
+
catch (e) {
|
|
1231
|
+
logBridgeError('recordUsageOnBackend.entryUpdate', e); /* skip unreadable entries */
|
|
1232
|
+
}
|
|
1156
1233
|
}
|
|
1157
1234
|
return updated;
|
|
1158
1235
|
}
|
|
@@ -1168,7 +1245,8 @@ export async function bridgeRecordUsage(options) {
|
|
|
1168
1245
|
await flushBackend(backend);
|
|
1169
1246
|
return { success: true, updated };
|
|
1170
1247
|
}
|
|
1171
|
-
catch {
|
|
1248
|
+
catch (e) {
|
|
1249
|
+
logBridgeError('bridgeRecordUsage', e);
|
|
1172
1250
|
return { success: false, updated: 0 };
|
|
1173
1251
|
}
|
|
1174
1252
|
}
|
|
@@ -1182,7 +1260,9 @@ export async function bridgeApplyFeedback(options) {
|
|
|
1182
1260
|
return null;
|
|
1183
1261
|
try {
|
|
1184
1262
|
const score = Math.max(0, Math.min(1, options.score));
|
|
1185
|
-
const alpha = typeof options.alpha === 'number'
|
|
1263
|
+
const alpha = typeof options.alpha === 'number'
|
|
1264
|
+
? Math.max(0, Math.min(1, options.alpha))
|
|
1265
|
+
: FEEDBACK_EWMA_ALPHA;
|
|
1186
1266
|
const ledgerEntryKey = options.ledgerKey ? `applied_${options.ledgerKey.slice(0, 500)}` : null;
|
|
1187
1267
|
if (ledgerEntryKey) {
|
|
1188
1268
|
const existing = await backend.getByKey('feedback', ledgerEntryKey).catch(() => null);
|
|
@@ -1202,12 +1282,19 @@ export async function bridgeApplyFeedback(options) {
|
|
|
1202
1282
|
await backend.update(id, { metadata: { feedback_weight: next } });
|
|
1203
1283
|
applied++;
|
|
1204
1284
|
}
|
|
1205
|
-
catch {
|
|
1285
|
+
catch (e) {
|
|
1286
|
+
logBridgeError('bridgeApplyFeedback.entryUpdate', e); /* skip unreadable entries */
|
|
1287
|
+
}
|
|
1206
1288
|
}
|
|
1207
1289
|
if (ledgerEntryKey) {
|
|
1208
1290
|
await bridgeStoreEntry({
|
|
1209
1291
|
key: ledgerEntryKey,
|
|
1210
|
-
value: JSON.stringify({
|
|
1292
|
+
value: JSON.stringify({
|
|
1293
|
+
score,
|
|
1294
|
+
entryIds: options.entryIds.slice(0, 100),
|
|
1295
|
+
appliedAt: Date.now(),
|
|
1296
|
+
applied,
|
|
1297
|
+
}),
|
|
1211
1298
|
namespace: 'feedback',
|
|
1212
1299
|
generateEmbeddingFlag: false,
|
|
1213
1300
|
dbPath: options.dbPath,
|
|
@@ -1219,7 +1306,9 @@ export async function bridgeApplyFeedback(options) {
|
|
|
1219
1306
|
return { success: true, applied };
|
|
1220
1307
|
}
|
|
1221
1308
|
catch (err) {
|
|
1222
|
-
|
|
1309
|
+
logBridgeError('bridgeApplyFeedback', err);
|
|
1310
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1311
|
+
return { success: false, applied: 0, error: message };
|
|
1223
1312
|
}
|
|
1224
1313
|
}
|
|
1225
1314
|
// ===== Feedback =====
|
|
@@ -1304,7 +1393,8 @@ export async function bridgeSessionEnd(options) {
|
|
|
1304
1393
|
}
|
|
1305
1394
|
return { success: true };
|
|
1306
1395
|
}
|
|
1307
|
-
catch {
|
|
1396
|
+
catch (e) {
|
|
1397
|
+
logBridgeError('bridgeSessionEnd', e);
|
|
1308
1398
|
return { success: false };
|
|
1309
1399
|
}
|
|
1310
1400
|
}
|
|
@@ -1320,12 +1410,14 @@ export async function bridgeRouteTask(options) {
|
|
|
1320
1410
|
return null;
|
|
1321
1411
|
return {
|
|
1322
1412
|
success: result.success,
|
|
1323
|
-
routes: result.results.map(r => {
|
|
1413
|
+
routes: result.results.map((r) => {
|
|
1324
1414
|
let parsed = {};
|
|
1325
1415
|
try {
|
|
1326
1416
|
parsed = JSON.parse(r.content);
|
|
1327
1417
|
}
|
|
1328
|
-
catch {
|
|
1418
|
+
catch (e) {
|
|
1419
|
+
logBridgeError('bridgeRouteTask.parseContent', e); /* use raw */
|
|
1420
|
+
}
|
|
1329
1421
|
return {
|
|
1330
1422
|
agentType: parsed.taskType ?? 'coder',
|
|
1331
1423
|
confidence: r.score,
|
|
@@ -1345,13 +1437,16 @@ export async function bridgeHealthCheck(dbPath) {
|
|
|
1345
1437
|
return {
|
|
1346
1438
|
healthy: health?.healthy ?? true,
|
|
1347
1439
|
backend: 'sqlite',
|
|
1348
|
-
stats: stats
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1440
|
+
stats: stats
|
|
1441
|
+
? {
|
|
1442
|
+
totalEntries: stats.totalEntries ?? 0,
|
|
1443
|
+
namespaces: Object.keys(stats.entriesByNamespace ?? {}),
|
|
1444
|
+
}
|
|
1445
|
+
: undefined,
|
|
1352
1446
|
};
|
|
1353
1447
|
}
|
|
1354
|
-
catch {
|
|
1448
|
+
catch (e) {
|
|
1449
|
+
logBridgeError('bridgeHealthCheck', e);
|
|
1355
1450
|
return { healthy: false, backend: 'sqlite' };
|
|
1356
1451
|
}
|
|
1357
1452
|
}
|
|
@@ -1404,7 +1499,9 @@ export async function bridgeConsolidate(params) {
|
|
|
1404
1499
|
continue;
|
|
1405
1500
|
}
|
|
1406
1501
|
if ((e.accessCount ?? 0) === 0) {
|
|
1407
|
-
await backend.delete(e.id).catch(() => {
|
|
1502
|
+
await backend.delete(e.id).catch(() => {
|
|
1503
|
+
/* non-fatal */
|
|
1504
|
+
});
|
|
1408
1505
|
deleted++;
|
|
1409
1506
|
}
|
|
1410
1507
|
}
|
|
@@ -1412,7 +1509,8 @@ export async function bridgeConsolidate(params) {
|
|
|
1412
1509
|
await flushBackend(backend);
|
|
1413
1510
|
return { success: true, consolidated: deleted, preserved: kept };
|
|
1414
1511
|
}
|
|
1415
|
-
catch {
|
|
1512
|
+
catch (e) {
|
|
1513
|
+
logBridgeError('bridgeConsolidate', e);
|
|
1416
1514
|
return { success: false, consolidated: 0 };
|
|
1417
1515
|
}
|
|
1418
1516
|
}
|
|
@@ -1425,7 +1523,11 @@ export async function bridgeBatchOperation(params) {
|
|
|
1425
1523
|
let processed = 0;
|
|
1426
1524
|
if (params.operation === 'store') {
|
|
1427
1525
|
for (const e of params.entries) {
|
|
1428
|
-
const result = await bridgeStoreEntry({
|
|
1526
|
+
const result = await bridgeStoreEntry({
|
|
1527
|
+
key: e.key,
|
|
1528
|
+
value: e.value,
|
|
1529
|
+
namespace: e.namespace,
|
|
1530
|
+
});
|
|
1429
1531
|
if (result?.success)
|
|
1430
1532
|
processed++;
|
|
1431
1533
|
}
|
|
@@ -1439,7 +1541,8 @@ export async function bridgeBatchOperation(params) {
|
|
|
1439
1541
|
}
|
|
1440
1542
|
return { success: true, processed };
|
|
1441
1543
|
}
|
|
1442
|
-
catch {
|
|
1544
|
+
catch (e) {
|
|
1545
|
+
logBridgeError('bridgeBatchOperation', e);
|
|
1443
1546
|
return { success: false, processed: 0 };
|
|
1444
1547
|
}
|
|
1445
1548
|
}
|