@claude-flow/cli 3.55.0 → 3.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/helpers.manifest.json +2 -2
- package/.claude/settings.json +9 -9
- package/catalog-manifest.json +2 -2
- package/dist/src/auth/client.js +40 -24
- package/dist/src/commands/agent.js +12 -0
- package/dist/src/commands/analyze.js +18 -14
- package/dist/src/commands/daemon.js +11 -19
- package/dist/src/commands/doctor.js +65 -58
- package/dist/src/commands/memory-backup.d.ts +1 -1
- package/dist/src/commands/memory-backup.js +23 -9
- package/dist/src/commands/memory.d.ts +0 -4
- package/dist/src/commands/memory.js +10 -2
- package/dist/src/commands/plugins.js +13 -1
- package/dist/src/commands/task.js +12 -0
- package/dist/src/init/settings-generator.js +3 -3
- package/dist/src/mcp-tools/agent-execute-core.js +244 -188
- package/dist/src/mcp-tools/agent-tools.js +15 -16
- package/dist/src/mcp-tools/agentdb-tools.js +22 -3
- package/dist/src/mcp-tools/claims-tools.js +3 -11
- package/dist/src/mcp-tools/hooks-tools.js +47 -2
- package/dist/src/mcp-tools/memory-tools.js +29 -10
- package/dist/src/mcp-tools/record-store.d.ts +3 -0
- package/dist/src/mcp-tools/record-store.js +21 -0
- package/dist/src/mcp-tools/session-tools.js +47 -13
- package/dist/src/mcp-tools/task-tools.js +5 -11
- package/dist/src/mcp-tools/workflow-tools.js +78 -29
- package/dist/src/memory/append-conditions.d.ts +28 -0
- package/dist/src/memory/append-conditions.js +56 -0
- package/dist/src/memory/graph-edge-writer.d.ts +9 -0
- package/dist/src/memory/graph-edge-writer.js +90 -0
- package/dist/src/memory/memory-bridge.d.ts +4 -0
- package/dist/src/memory/memory-bridge.js +50 -9
- package/dist/src/memory/memory-initializer.d.ts +4 -0
- package/dist/src/memory/memory-initializer.js +95 -61
- package/dist/src/memory/memory-root.d.ts +2 -0
- package/dist/src/memory/memory-root.js +28 -0
- package/dist/src/memory/sona-optimizer.d.ts +2 -0
- package/dist/src/memory/sona-optimizer.js +4 -0
- package/dist/src/parser.js +7 -0
- package/dist/src/plugins/store/discovery.d.ts +6 -0
- package/dist/src/plugins/store/discovery.js +7 -3
- package/dist/src/ruvector/neural-router.js +7 -2
- package/dist/src/services/claim-service.js +39 -18
- package/dist/src/services/claims-store.d.ts +3 -0
- package/dist/src/services/claims-store.js +40 -0
- package/dist/src/services/distill-tuning.js +17 -10
- package/dist/src/services/memory-backup.d.ts +7 -0
- package/dist/src/services/memory-backup.js +29 -2
- package/dist/src/services/memory-distillation.js +2 -2
- package/dist/src/services/worker-daemon.d.ts +4 -2
- package/dist/src/services/worker-daemon.js +14 -6
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts +2 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js +48 -16
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts +2 -2
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.js +15 -4
- package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +4 -4
- package/plugins/ruflo-metaharness/CHANGELOG.md +48 -0
- package/.claude/agents/tmp.json +0 -0
|
@@ -32,6 +32,12 @@ export interface PluginDiscoveryResult {
|
|
|
32
32
|
source?: string;
|
|
33
33
|
fromCache?: boolean;
|
|
34
34
|
error?: string;
|
|
35
|
+
/**
|
|
36
|
+
* True when the signed registry could not be fetched or verified and the CLI fell back to
|
|
37
|
+
* its built-in plugin list. That list is safe to use (fixed, first-party npm names), but it
|
|
38
|
+
* is NOT the verified registry, and callers must say so rather than report a discovery.
|
|
39
|
+
*/
|
|
40
|
+
demo?: boolean;
|
|
35
41
|
}
|
|
36
42
|
/**
|
|
37
43
|
* Plugin Discovery Service
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Discovers plugin registries via IPNS and fetches from IPFS
|
|
4
4
|
* Parallel implementation to pattern store for plugins
|
|
5
5
|
*/
|
|
6
|
-
import * as crypto from 'crypto';
|
|
7
6
|
import { resolveIPNS, fetchFromIPFS, verifyEd25519Signature } from '../../transfer/ipfs/client.js';
|
|
8
7
|
/**
|
|
9
8
|
* Fetch real npm download stats for a package
|
|
@@ -115,7 +114,9 @@ export class PluginDiscoveryService {
|
|
|
115
114
|
success: true,
|
|
116
115
|
registry: cached.registry,
|
|
117
116
|
fromCache: true,
|
|
118
|
-
|
|
117
|
+
// A cached fallback is still the fallback: never relabel it as the real registry.
|
|
118
|
+
source: cached.demo ? `${registry.name} (demo)` : registry.name,
|
|
119
|
+
...(cached.demo ? { demo: true } : {}),
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
try {
|
|
@@ -223,13 +224,16 @@ export class PluginDiscoveryService {
|
|
|
223
224
|
this.cache.set(registry.ipnsName, {
|
|
224
225
|
registry: demoRegistry,
|
|
225
226
|
timestamp: Date.now(),
|
|
227
|
+
demo: true,
|
|
226
228
|
});
|
|
227
229
|
return {
|
|
228
230
|
success: true,
|
|
229
231
|
registry: demoRegistry,
|
|
230
|
-
|
|
232
|
+
// No CID: the built-in list was not fetched from IPFS. (This used to be a random
|
|
233
|
+
// `bafybeiplugin…` string, printed as "Registry CID" as if it were a real address.)
|
|
231
234
|
source: `${registry.name} (demo)`,
|
|
232
235
|
fromCache: false,
|
|
236
|
+
demo: true,
|
|
233
237
|
};
|
|
234
238
|
}
|
|
235
239
|
/**
|
|
@@ -288,8 +288,13 @@ async function resolveBackend(cfg) {
|
|
|
288
288
|
return r;
|
|
289
289
|
return {
|
|
290
290
|
route: (e) => {
|
|
291
|
-
|
|
292
|
-
|
|
291
|
+
// Calibration can move a candidate across the quality bar. Select
|
|
292
|
+
// from the calibrated predictions, not the raw router's old pick.
|
|
293
|
+
const all = r.predictAll(e).map(c => ({ ...c, predictedQuality: cal.transform(c.predictedQuality) }));
|
|
294
|
+
const clearing = all.filter(c => c.predictedQuality >= cfg.qualityBar)
|
|
295
|
+
.sort((a, b) => a.costPerMTok - b.costPerMTok);
|
|
296
|
+
const pick = clearing[0] ?? [...all].sort((a, b) => b.predictedQuality - a.predictedQuality)[0];
|
|
297
|
+
return { ...pick, metBar: pick.predictedQuality >= cfg.qualityBar };
|
|
293
298
|
},
|
|
294
299
|
predictAll: (e) => r.predictAll(e).map(c => ({ ...c, predictedQuality: cal.transform(c.predictedQuality) })),
|
|
295
300
|
};
|
|
@@ -16,6 +16,7 @@ import { EventEmitter } from 'events';
|
|
|
16
16
|
import * as fs from 'fs';
|
|
17
17
|
import * as path from 'path';
|
|
18
18
|
import { execFileSync } from 'child_process';
|
|
19
|
+
import { readClaimsStore } from './claims-store.js';
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// Default Configuration
|
|
21
22
|
// ============================================================================
|
|
@@ -57,28 +58,29 @@ export class ClaimService extends EventEmitter {
|
|
|
57
58
|
// Load existing claims
|
|
58
59
|
await this.loadClaims();
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
// Start fresh if file is corrupted
|
|
75
|
-
}
|
|
61
|
+
loadClaims() {
|
|
62
|
+
const data = readClaimsStore(path.join(this.storagePath, 'claims.json'));
|
|
63
|
+
const claims = new Map();
|
|
64
|
+
const stealable = new Map();
|
|
65
|
+
for (const claim of Object.values(data.claims)) {
|
|
66
|
+
claim.claimedAt = new Date(claim.claimedAt);
|
|
67
|
+
claim.statusChangedAt = new Date(claim.statusChangedAt);
|
|
68
|
+
if (claim.expiresAt)
|
|
69
|
+
claim.expiresAt = new Date(claim.expiresAt);
|
|
70
|
+
claims.set(claim.issueId, claim);
|
|
71
|
+
}
|
|
72
|
+
for (const [id, info] of Object.entries(data.stealable)) {
|
|
73
|
+
stealable.set(id, { ...info, stealableAt: new Date(info.stealableAt) });
|
|
76
74
|
}
|
|
75
|
+
this.claims = claims;
|
|
76
|
+
this.stealableInfo = stealable;
|
|
77
77
|
}
|
|
78
78
|
async saveClaims() {
|
|
79
79
|
const claimsFile = path.join(this.storagePath, 'claims.json');
|
|
80
80
|
const data = {
|
|
81
|
-
|
|
81
|
+
...readClaimsStore(claimsFile),
|
|
82
|
+
claims: Object.fromEntries(this.claims),
|
|
83
|
+
stealable: Object.fromEntries(this.stealableInfo),
|
|
82
84
|
savedAt: new Date().toISOString(),
|
|
83
85
|
};
|
|
84
86
|
fs.writeFileSync(claimsFile, JSON.stringify(data, null, 2));
|
|
@@ -87,6 +89,7 @@ export class ClaimService extends EventEmitter {
|
|
|
87
89
|
// Core Claiming
|
|
88
90
|
// ==========================================================================
|
|
89
91
|
async claim(issueId, claimant) {
|
|
92
|
+
this.loadClaims();
|
|
90
93
|
// Check if already claimed
|
|
91
94
|
const existing = this.claims.get(issueId);
|
|
92
95
|
if (existing && existing.status !== 'stealable') {
|
|
@@ -116,6 +119,7 @@ export class ClaimService extends EventEmitter {
|
|
|
116
119
|
return { success: true, claim };
|
|
117
120
|
}
|
|
118
121
|
async release(issueId, claimant) {
|
|
122
|
+
this.loadClaims();
|
|
119
123
|
const claim = this.claims.get(issueId);
|
|
120
124
|
if (!claim) {
|
|
121
125
|
throw new Error(`Issue ${issueId} is not claimed`);
|
|
@@ -137,6 +141,7 @@ export class ClaimService extends EventEmitter {
|
|
|
137
141
|
// Handoffs
|
|
138
142
|
// ==========================================================================
|
|
139
143
|
async requestHandoff(issueId, from, to, reason) {
|
|
144
|
+
this.loadClaims();
|
|
140
145
|
const claim = this.claims.get(issueId);
|
|
141
146
|
if (!claim) {
|
|
142
147
|
throw new Error(`Issue ${issueId} is not claimed`);
|
|
@@ -158,6 +163,7 @@ export class ClaimService extends EventEmitter {
|
|
|
158
163
|
});
|
|
159
164
|
}
|
|
160
165
|
async acceptHandoff(issueId, claimant) {
|
|
166
|
+
this.loadClaims();
|
|
161
167
|
const claim = this.claims.get(issueId);
|
|
162
168
|
if (!claim || claim.status !== 'handoff-pending') {
|
|
163
169
|
throw new Error(`No pending handoff for issue ${issueId}`);
|
|
@@ -181,6 +187,7 @@ export class ClaimService extends EventEmitter {
|
|
|
181
187
|
});
|
|
182
188
|
}
|
|
183
189
|
async rejectHandoff(issueId, claimant, reason) {
|
|
190
|
+
this.loadClaims();
|
|
184
191
|
const claim = this.claims.get(issueId);
|
|
185
192
|
if (!claim || claim.status !== 'handoff-pending') {
|
|
186
193
|
throw new Error(`No pending handoff for issue ${issueId}`);
|
|
@@ -205,6 +212,7 @@ export class ClaimService extends EventEmitter {
|
|
|
205
212
|
// Status Updates
|
|
206
213
|
// ==========================================================================
|
|
207
214
|
async updateStatus(issueId, status, note) {
|
|
215
|
+
this.loadClaims();
|
|
208
216
|
const claim = this.claims.get(issueId);
|
|
209
217
|
if (!claim) {
|
|
210
218
|
throw new Error(`Issue ${issueId} is not claimed`);
|
|
@@ -227,6 +235,7 @@ export class ClaimService extends EventEmitter {
|
|
|
227
235
|
});
|
|
228
236
|
}
|
|
229
237
|
async updateProgress(issueId, progress) {
|
|
238
|
+
this.loadClaims();
|
|
230
239
|
const claim = this.claims.get(issueId);
|
|
231
240
|
if (!claim) {
|
|
232
241
|
throw new Error(`Issue ${issueId} is not claimed`);
|
|
@@ -235,6 +244,7 @@ export class ClaimService extends EventEmitter {
|
|
|
235
244
|
await this.saveClaims();
|
|
236
245
|
}
|
|
237
246
|
async requestReview(issueId, reviewers) {
|
|
247
|
+
this.loadClaims();
|
|
238
248
|
const claim = this.claims.get(issueId);
|
|
239
249
|
if (!claim) {
|
|
240
250
|
throw new Error(`Issue ${issueId} is not claimed`);
|
|
@@ -254,6 +264,7 @@ export class ClaimService extends EventEmitter {
|
|
|
254
264
|
// Work Stealing
|
|
255
265
|
// ==========================================================================
|
|
256
266
|
async markStealable(issueId, info) {
|
|
267
|
+
this.loadClaims();
|
|
257
268
|
const claim = this.claims.get(issueId);
|
|
258
269
|
if (!claim) {
|
|
259
270
|
throw new Error(`Issue ${issueId} is not claimed`);
|
|
@@ -273,6 +284,7 @@ export class ClaimService extends EventEmitter {
|
|
|
273
284
|
});
|
|
274
285
|
}
|
|
275
286
|
async steal(issueId, stealer) {
|
|
287
|
+
this.loadClaims();
|
|
276
288
|
const claim = this.claims.get(issueId);
|
|
277
289
|
if (!claim) {
|
|
278
290
|
return { success: false, error: `Issue ${issueId} is not claimed` };
|
|
@@ -309,6 +321,7 @@ export class ClaimService extends EventEmitter {
|
|
|
309
321
|
return { success: true, claim, previousOwner, context: info };
|
|
310
322
|
}
|
|
311
323
|
async getStealable(agentType) {
|
|
324
|
+
this.loadClaims();
|
|
312
325
|
const stealable = [];
|
|
313
326
|
for (const claim of this.claims.values()) {
|
|
314
327
|
if (claim.status !== 'stealable')
|
|
@@ -323,6 +336,7 @@ export class ClaimService extends EventEmitter {
|
|
|
323
336
|
return stealable;
|
|
324
337
|
}
|
|
325
338
|
async contestSteal(issueId, originalClaimant, reason) {
|
|
339
|
+
this.loadClaims();
|
|
326
340
|
const claim = this.claims.get(issueId);
|
|
327
341
|
if (!claim) {
|
|
328
342
|
throw new Error(`Issue ${issueId} is not claimed`);
|
|
@@ -340,6 +354,7 @@ export class ClaimService extends EventEmitter {
|
|
|
340
354
|
// Load Balancing
|
|
341
355
|
// ==========================================================================
|
|
342
356
|
async getAgentLoad(agentId) {
|
|
357
|
+
this.loadClaims();
|
|
343
358
|
const claims = [];
|
|
344
359
|
let blockedCount = 0;
|
|
345
360
|
for (const claim of this.claims.values()) {
|
|
@@ -362,6 +377,7 @@ export class ClaimService extends EventEmitter {
|
|
|
362
377
|
};
|
|
363
378
|
}
|
|
364
379
|
async rebalance(swarmId) {
|
|
380
|
+
this.loadClaims();
|
|
365
381
|
const result = { moved: [], suggested: [] };
|
|
366
382
|
// Get all agent loads
|
|
367
383
|
const agentLoads = new Map();
|
|
@@ -410,6 +426,7 @@ export class ClaimService extends EventEmitter {
|
|
|
410
426
|
// Queries
|
|
411
427
|
// ==========================================================================
|
|
412
428
|
async getClaimedBy(claimant) {
|
|
429
|
+
this.loadClaims();
|
|
413
430
|
return Array.from(this.claims.values()).filter(c => this.isSameClaimant(c.claimant, claimant));
|
|
414
431
|
}
|
|
415
432
|
async getAvailableIssues(_filters) {
|
|
@@ -418,18 +435,22 @@ export class ClaimService extends EventEmitter {
|
|
|
418
435
|
return [];
|
|
419
436
|
}
|
|
420
437
|
async getIssueStatus(issueId) {
|
|
438
|
+
this.loadClaims();
|
|
421
439
|
return this.claims.get(issueId) || null;
|
|
422
440
|
}
|
|
423
441
|
async getAllClaims() {
|
|
442
|
+
this.loadClaims();
|
|
424
443
|
return Array.from(this.claims.values());
|
|
425
444
|
}
|
|
426
445
|
async getByStatus(status) {
|
|
446
|
+
this.loadClaims();
|
|
427
447
|
return Array.from(this.claims.values()).filter(c => c.status === status);
|
|
428
448
|
}
|
|
429
449
|
// ==========================================================================
|
|
430
450
|
// Auto-Management
|
|
431
451
|
// ==========================================================================
|
|
432
452
|
async expireStale(maxAgeMinutes) {
|
|
453
|
+
this.loadClaims();
|
|
433
454
|
const threshold = maxAgeMinutes ?? this.config.staleThresholdMinutes;
|
|
434
455
|
const now = Date.now();
|
|
435
456
|
const expired = [];
|
|
@@ -445,7 +466,7 @@ export class ClaimService extends EventEmitter {
|
|
|
445
466
|
progress: claim.progress,
|
|
446
467
|
context: `Stale: No activity for ${Math.round(age)} minutes`,
|
|
447
468
|
});
|
|
448
|
-
expired.push(claim);
|
|
469
|
+
expired.push(this.claims.get(claim.issueId));
|
|
449
470
|
}
|
|
450
471
|
}
|
|
451
472
|
return expired;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Shared on-disk shape for the CLI claim service and public MCP claim tools. */
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
function record(value) {
|
|
4
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
/** Read legacy CLI arrays and MCP dictionaries; never replace unreadable state. */
|
|
7
|
+
export function readClaimsStore(file) {
|
|
8
|
+
let text;
|
|
9
|
+
try {
|
|
10
|
+
text = readFileSync(file, 'utf8');
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code === 'ENOENT')
|
|
14
|
+
return { claims: {}, stealable: {}, contests: {} };
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
const data = JSON.parse(text);
|
|
18
|
+
if (!record(data) || (!Array.isArray(data.claims) && !record(data.claims))) {
|
|
19
|
+
throw new Error('Invalid claims store: expected a claims array or object');
|
|
20
|
+
}
|
|
21
|
+
const entries = Array.isArray(data.claims)
|
|
22
|
+
? data.claims.map((claim) => [record(claim) ? claim.issueId : undefined, claim])
|
|
23
|
+
: Object.entries(data.claims);
|
|
24
|
+
const claims = Object.create(null);
|
|
25
|
+
for (const [id, claim] of entries) {
|
|
26
|
+
if (typeof id !== 'string' || !id || !record(claim) || claim.issueId !== id ||
|
|
27
|
+
!record(claim.claimant) || typeof claim.status !== 'string' ||
|
|
28
|
+
typeof claim.claimedAt !== 'string' || typeof claim.statusChangedAt !== 'string' ||
|
|
29
|
+
Object.hasOwn(claims, id)) {
|
|
30
|
+
throw new Error('Invalid claims store: malformed or duplicate claim');
|
|
31
|
+
}
|
|
32
|
+
claims[id] = claim;
|
|
33
|
+
}
|
|
34
|
+
for (const key of ['stealable', 'contests']) {
|
|
35
|
+
if (data[key] !== undefined && !record(data[key]))
|
|
36
|
+
throw new Error(`Invalid claims store: ${key} must be an object`);
|
|
37
|
+
}
|
|
38
|
+
return { ...data, claims, stealable: data.stealable ?? {}, contests: data.contests ?? {} };
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=claims-store.js.map
|
|
@@ -43,6 +43,7 @@ import * as crypto from 'crypto';
|
|
|
43
43
|
import { runDistillation } from './memory-distillation.js';
|
|
44
44
|
import { distillTrajectoryContent } from '../memory/structured-distill.js';
|
|
45
45
|
import { cosineSim } from '../memory/hybrid-retrieval.js';
|
|
46
|
+
import { loadBetterSqlite3 } from '../memory/shared-sqlite.js';
|
|
46
47
|
// ── Grid defaults (requirement 1) ───────────────────────────────────────
|
|
47
48
|
export const DEFAULT_GRID_BATCH_SIZE = [100, 200, 500];
|
|
48
49
|
export const DEFAULT_GRID_DEDUP_DISTANCE = [0.05, 0.1, 0.15, 0.2, 0.3];
|
|
@@ -71,7 +72,22 @@ export async function tuneDistillation(options) {
|
|
|
71
72
|
if (!dbPath || !fs.existsSync(dbPath)) {
|
|
72
73
|
throw new Error(`tuneDistillation: source db not found at ${dbPath}`);
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
// #3883 — resolve the SAME better-sqlite3 copy AgentDB resolves (shared-sqlite.ts),
|
|
76
|
+
// not a separately-imported copy. Verified: every `new Database(...)` call in this
|
|
77
|
+
// module (readCopy below, and tmpCopy inside evaluateCandidate) opens a temp file
|
|
78
|
+
// copy made via fs.copyFileSync — `dbPath`/`sourceDbPath` themselves are NEVER
|
|
79
|
+
// opened with a DB connection anywhere in this module (see module header
|
|
80
|
+
// "Isolation" section). So the #3693 identity hazard (two better-sqlite3 copies
|
|
81
|
+
// fighting over one live file's -wal/-shm sidecars) does not apply to today's
|
|
82
|
+
// call sites here — AgentDB never holds a live handle on a temp copy. This swap
|
|
83
|
+
// is for consistency with the shared loader's contract, not an active fix.
|
|
84
|
+
let Database;
|
|
85
|
+
try {
|
|
86
|
+
Database = await loadBetterSqlite3();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
Database = null;
|
|
90
|
+
}
|
|
75
91
|
if (!Database) {
|
|
76
92
|
throw new Error('tuneDistillation: better-sqlite3 unavailable — cannot run the tuning harness');
|
|
77
93
|
}
|
|
@@ -498,13 +514,4 @@ function safeUnlink(p) {
|
|
|
498
514
|
}
|
|
499
515
|
}
|
|
500
516
|
}
|
|
501
|
-
async function loadBetterSqlite3() {
|
|
502
|
-
try {
|
|
503
|
-
const mod = 'better-sqlite3';
|
|
504
|
-
return (await import(mod)).default;
|
|
505
|
-
}
|
|
506
|
-
catch {
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
517
|
//# sourceMappingURL=distill-tuning.js.map
|
|
@@ -20,6 +20,13 @@ export interface BackupResult {
|
|
|
20
20
|
skipped?: string;
|
|
21
21
|
}
|
|
22
22
|
export declare function defaultMemoryDbPath(cwd?: string): string;
|
|
23
|
+
export interface ProjectBackupResult extends BackupResult {
|
|
24
|
+
backups: Array<BackupResult & {
|
|
25
|
+
dbPath: string;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
/** Back up the active project stores; an explicit full-file override stays single-store. */
|
|
29
|
+
export declare function backupProjectMemoryDbs(cwd: string, opts?: Omit<BackupOptions, 'dbPath'>): Promise<ProjectBackupResult>;
|
|
23
30
|
export declare function backupMemoryDb(opts?: BackupOptions): Promise<BackupResult>;
|
|
24
31
|
export interface RestoreResult {
|
|
25
32
|
restored: boolean;
|
|
@@ -15,9 +15,37 @@
|
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import { readFileMaybeEncrypted } from '../fs-secure.js';
|
|
18
|
+
import { loadBetterSqlite3 } from '../memory/shared-sqlite.js';
|
|
19
|
+
import { resolveMemoryRoot } from '../memory/memory-root.js';
|
|
18
20
|
export function defaultMemoryDbPath(cwd = process.cwd()) {
|
|
19
21
|
return path.join(cwd, '.swarm', 'memory.db');
|
|
20
22
|
}
|
|
23
|
+
/** Back up the active project stores; an explicit full-file override stays single-store. */
|
|
24
|
+
export async function backupProjectMemoryDbs(cwd, opts = {}) {
|
|
25
|
+
const override = process.env.CLAUDE_FLOW_DB_PATH;
|
|
26
|
+
let sources;
|
|
27
|
+
if (override && override.trim().length > 0) {
|
|
28
|
+
sources = [path.resolve(cwd, override)];
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const root = resolveMemoryRoot(cwd);
|
|
32
|
+
sources = [path.join(root, 'memory.db'), path.join(root, 'agentdb-memory.db')];
|
|
33
|
+
}
|
|
34
|
+
const backups = [];
|
|
35
|
+
for (const dbPath of sources) {
|
|
36
|
+
backups.push({ ...await backupMemoryDb({ ...opts, dbPath }), dbPath });
|
|
37
|
+
}
|
|
38
|
+
const successful = backups.filter(result => result.backedUp);
|
|
39
|
+
const failed = backups.find(result => !result.backedUp && result.skipped !== 'no-db');
|
|
40
|
+
return {
|
|
41
|
+
...successful[0],
|
|
42
|
+
backedUp: successful.length > 0 && !failed,
|
|
43
|
+
sizeBytes: successful.reduce((sum, result) => sum + (result.sizeBytes ?? 0), 0),
|
|
44
|
+
rotatedAway: successful.flatMap(result => result.rotatedAway ?? []),
|
|
45
|
+
skipped: failed?.skipped ?? (successful.length === 0 ? 'no-db' : undefined),
|
|
46
|
+
backups,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
21
49
|
/** ISO timestamp safe for filenames (no ':' or '.'). */
|
|
22
50
|
function fileStamp(ms) {
|
|
23
51
|
return new Date(ms).toISOString().replace(/[:.]/g, '-');
|
|
@@ -228,8 +256,7 @@ export async function restoreMemoryDbFromBackup(dbPath, opts = {}) {
|
|
|
228
256
|
// encrypted snapshots and provides the same checks on WASM-only hosts.
|
|
229
257
|
let Database = null;
|
|
230
258
|
try {
|
|
231
|
-
|
|
232
|
-
Database = (await import(mod)).default;
|
|
259
|
+
Database = await loadBetterSqlite3();
|
|
233
260
|
}
|
|
234
261
|
catch {
|
|
235
262
|
/* use the sql.js verifier */
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import * as fs from 'fs';
|
|
28
28
|
import * as path from 'path';
|
|
29
29
|
import { distillTrajectoryContent, serialiseDistilled } from '../memory/structured-distill.js';
|
|
30
|
+
import { loadBetterSqlite3 } from '../memory/shared-sqlite.js';
|
|
30
31
|
function emptyReport(dryRun, extra = {}) {
|
|
31
32
|
return {
|
|
32
33
|
processed: 0, episodes: 0, episodeEmbeddings: 0,
|
|
@@ -104,8 +105,7 @@ export async function runDistillation(options) {
|
|
|
104
105
|
return emptyReport(dryRun, { skipped: 'no-db' });
|
|
105
106
|
let Database;
|
|
106
107
|
try {
|
|
107
|
-
|
|
108
|
-
Database = (await import(mod)).default;
|
|
108
|
+
Database = await loadBetterSqlite3();
|
|
109
109
|
}
|
|
110
110
|
catch {
|
|
111
111
|
return emptyReport(dryRun, { skipped: 'better-sqlite3 unavailable' });
|
|
@@ -69,6 +69,8 @@ export interface DaemonConfig {
|
|
|
69
69
|
workers: WorkerConfig[];
|
|
70
70
|
enabledWorkers?: string[];
|
|
71
71
|
}
|
|
72
|
+
/** Parse only worker types the daemon can actually schedule. */
|
|
73
|
+
export declare function parseEnabledWorkers(value: string): WorkerType[];
|
|
72
74
|
/**
|
|
73
75
|
* Worker Daemon - Manages background workers with Node.js
|
|
74
76
|
*/
|
|
@@ -368,8 +370,8 @@ export declare class WorkerDaemon extends EventEmitter {
|
|
|
368
370
|
*/
|
|
369
371
|
private runConsolidateWorker;
|
|
370
372
|
/**
|
|
371
|
-
* Nightly memory-DB backup worker (24h interval). Takes
|
|
372
|
-
*
|
|
373
|
+
* Nightly memory-DB backup worker (24h interval). Takes WAL-safe, consistent
|
|
374
|
+
* snapshots of the active CLI and AgentDB stores with rotation (keep last N). Never throws — a
|
|
373
375
|
* worker must not crash the daemon; a skip/error is written to the metrics
|
|
374
376
|
* file. Opt-out by omitting `backup` from `-w`; offsite GCS is opt-in via
|
|
375
377
|
* RUFLO_BACKUP_GCS (a gs:// prefix), retention via RUFLO_BACKUP_KEEP.
|
|
@@ -24,7 +24,7 @@ import { HeadlessWorkerExecutor, isHeadlessWorker, } from './headless-worker-exe
|
|
|
24
24
|
// incremental (rowid cursor), non-destructive, transactional, and
|
|
25
25
|
// quick_check-gated, so it's safe to call unconditionally on every tick.
|
|
26
26
|
import { runDistillation, defaultMemoryDbPath } from './memory-distillation.js';
|
|
27
|
-
import {
|
|
27
|
+
import { backupProjectMemoryDbs } from './memory-backup.js';
|
|
28
28
|
import { resolveGitWorkspaceIdentity } from './git-workspace-identity.js';
|
|
29
29
|
import { getWorkspaceLeaseRegistry } from './workspace-lease.js';
|
|
30
30
|
import { getRepoSupervisorRegistry } from './repo-supervisor.js';
|
|
@@ -43,6 +43,14 @@ const DEFAULT_WORKERS = [
|
|
|
43
43
|
{ type: 'predict', intervalMs: 10 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Predictive preloading', enabled: false },
|
|
44
44
|
{ type: 'document', intervalMs: 60 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Auto-documentation', enabled: false },
|
|
45
45
|
];
|
|
46
|
+
/** Parse only worker types the daemon can actually schedule. */
|
|
47
|
+
export function parseEnabledWorkers(value) {
|
|
48
|
+
const names = value.split(',');
|
|
49
|
+
if (!names.length || names.some(name => !DEFAULT_WORKERS.some(worker => worker.type === name))) {
|
|
50
|
+
throw new Error(`--workers must be a comma-separated list of: ${DEFAULT_WORKERS.map(worker => worker.type).join(',')}`);
|
|
51
|
+
}
|
|
52
|
+
return [...new Set(names)];
|
|
53
|
+
}
|
|
46
54
|
// Worker timeout — must exceed the longest per-worker headless timeout (15 min for audit/refactor).
|
|
47
55
|
// Previously 5 min, which caused orphan processes when daemon timeout fired before executor timeout (#1117).
|
|
48
56
|
const DEFAULT_WORKER_TIMEOUT_MS = 16 * 60 * 1000;
|
|
@@ -192,7 +200,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
192
200
|
aiWorkersEnabled: config?.aiWorkersEnabled
|
|
193
201
|
?? fileConfig.aiWorkersEnabled
|
|
194
202
|
?? (process.env.RUFLO_DAEMON_AI_WORKERS === '1'),
|
|
195
|
-
workers: config?.workers ?? DEFAULT_WORKERS,
|
|
203
|
+
workers: (config?.workers ?? DEFAULT_WORKERS).map(worker => ({ ...worker })),
|
|
196
204
|
enabledWorkers: config?.enabledWorkers,
|
|
197
205
|
};
|
|
198
206
|
// #2935 — deferred from readDaemonConfigFromFile(): this.config (and
|
|
@@ -1627,8 +1635,8 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1627
1635
|
return result;
|
|
1628
1636
|
}
|
|
1629
1637
|
/**
|
|
1630
|
-
* Nightly memory-DB backup worker (24h interval). Takes
|
|
1631
|
-
*
|
|
1638
|
+
* Nightly memory-DB backup worker (24h interval). Takes WAL-safe, consistent
|
|
1639
|
+
* snapshots of the active CLI and AgentDB stores with rotation (keep last N). Never throws — a
|
|
1632
1640
|
* worker must not crash the daemon; a skip/error is written to the metrics
|
|
1633
1641
|
* file. Opt-out by omitting `backup` from `-w`; offsite GCS is opt-in via
|
|
1634
1642
|
* RUFLO_BACKUP_GCS (a gs:// prefix), retention via RUFLO_BACKUP_KEEP.
|
|
@@ -1641,14 +1649,14 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1641
1649
|
let result;
|
|
1642
1650
|
try {
|
|
1643
1651
|
const keepEnv = Number(process.env.RUFLO_BACKUP_KEEP);
|
|
1644
|
-
const r = await
|
|
1645
|
-
dbPath: defaultMemoryDbPath(this.projectRoot),
|
|
1652
|
+
const r = await backupProjectMemoryDbs(this.projectRoot, {
|
|
1646
1653
|
keep: Number.isFinite(keepEnv) && keepEnv > 0 ? keepEnv : 7,
|
|
1647
1654
|
gcs: process.env.RUFLO_BACKUP_GCS || undefined,
|
|
1648
1655
|
});
|
|
1649
1656
|
result = {
|
|
1650
1657
|
timestamp: new Date().toISOString(),
|
|
1651
1658
|
backedUp: r.backedUp,
|
|
1659
|
+
backups: r.backups,
|
|
1652
1660
|
path: r.path,
|
|
1653
1661
|
sizeBytes: r.sizeBytes ?? 0,
|
|
1654
1662
|
rotatedAway: r.rotatedAway?.length ?? 0,
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ephemeral-port localhost callback server for the browser-based OAuth flow
|
|
3
|
-
* (ADR-306). A minimal single-
|
|
3
|
+
* (ADR-306). A minimal single-callback HTTP server — deliberately not a full
|
|
4
4
|
* framework for one GET route that lives for one request and then shuts
|
|
5
5
|
* down. A TypeScript port of meta-proxy's `src/oauth/callback_server.rs`.
|
|
6
6
|
*
|
|
@@ -18,6 +18,7 @@ export declare class CallbackServer {
|
|
|
18
18
|
private readonly server;
|
|
19
19
|
readonly redirectUri: string;
|
|
20
20
|
readonly port: number;
|
|
21
|
+
private readonly callback;
|
|
21
22
|
private constructor();
|
|
22
23
|
/**
|
|
23
24
|
* Binds `127.0.0.1:0` (OS-assigned ephemeral port) and returns the server
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callback-server.d.ts","sourceRoot":"","sources":["../../src/oauth/callback-server.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAeD,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C;AAED,qBAAa,cAAc;
|
|
1
|
+
{"version":3,"file":"callback-server.d.ts","sourceRoot":"","sources":["../../src/oauth/callback-server.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAeD,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C;AAED,qBAAa,cAAc;IAGvB,OAAO,CAAC,QAAQ,CAAC,MAAM;aACP,WAAW,EAAE,MAAM;aACnB,IAAI,EAAE,MAAM;IAJ9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IACnD,OAAO;IAkDP;;;;;OAKG;WACU,IAAI,IAAI,OAAO,CAAC,cAAc,CAAC;IAa5C;;;;;OAKG;IACG,aAAa,CAAC,SAAS,SAAqB,GAAG,OAAO,CAAC,cAAc,CAAC;CAa7E"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ephemeral-port localhost callback server for the browser-based OAuth flow
|
|
3
|
-
* (ADR-306). A minimal single-
|
|
3
|
+
* (ADR-306). A minimal single-callback HTTP server — deliberately not a full
|
|
4
4
|
* framework for one GET route that lives for one request and then shuts
|
|
5
5
|
* down. A TypeScript port of meta-proxy's `src/oauth/callback_server.rs`.
|
|
6
6
|
*
|
|
@@ -28,10 +28,55 @@ export class CallbackServer {
|
|
|
28
28
|
server;
|
|
29
29
|
redirectUri;
|
|
30
30
|
port;
|
|
31
|
+
callback;
|
|
31
32
|
constructor(server, redirectUri, port) {
|
|
32
33
|
this.server = server;
|
|
33
34
|
this.redirectUri = redirectUri;
|
|
34
35
|
this.port = port;
|
|
36
|
+
// Install the listener before bind() returns: opening the browser can
|
|
37
|
+
// deliver a callback before the caller reaches awaitCallback().
|
|
38
|
+
this.callback = new Promise((resolve) => {
|
|
39
|
+
let received = false;
|
|
40
|
+
this.server.on('request', (req, res) => {
|
|
41
|
+
let url;
|
|
42
|
+
try {
|
|
43
|
+
url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
res.writeHead(400, { connection: 'close' });
|
|
47
|
+
res.end();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (url.pathname !== '/oauth/callback') {
|
|
51
|
+
res.writeHead(404, { connection: 'close' });
|
|
52
|
+
res.end();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (req.method !== 'GET') {
|
|
56
|
+
res.writeHead(405, { allow: 'GET', connection: 'close' });
|
|
57
|
+
res.end();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (received) {
|
|
61
|
+
res.writeHead(409, { connection: 'close' });
|
|
62
|
+
res.end();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
received = true;
|
|
66
|
+
const result = {
|
|
67
|
+
code: url.searchParams.get('code'),
|
|
68
|
+
state: url.searchParams.get('state'),
|
|
69
|
+
error: url.searchParams.get('error'),
|
|
70
|
+
};
|
|
71
|
+
res.writeHead(200, {
|
|
72
|
+
'content-type': 'text/html; charset=utf-8',
|
|
73
|
+
connection: 'close',
|
|
74
|
+
});
|
|
75
|
+
res.end(SUCCESS_PAGE);
|
|
76
|
+
this.server.close();
|
|
77
|
+
resolve(result);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
35
80
|
}
|
|
36
81
|
/**
|
|
37
82
|
* Binds `127.0.0.1:0` (OS-assigned ephemeral port) and returns the server
|
|
@@ -63,22 +108,9 @@ export class CallbackServer {
|
|
|
63
108
|
this.server.close();
|
|
64
109
|
reject(new CallbackTimeoutError());
|
|
65
110
|
}, waitForMs);
|
|
66
|
-
this.
|
|
111
|
+
this.callback.then((result) => {
|
|
67
112
|
clearTimeout(timer);
|
|
68
|
-
|
|
69
|
-
const result = {
|
|
70
|
-
code: url.searchParams.get('code'),
|
|
71
|
-
state: url.searchParams.get('state'),
|
|
72
|
-
error: url.searchParams.get('error'),
|
|
73
|
-
};
|
|
74
|
-
res.writeHead(200, {
|
|
75
|
-
'content-type': 'text/html; charset=utf-8',
|
|
76
|
-
connection: 'close',
|
|
77
|
-
});
|
|
78
|
-
res.end(SUCCESS_PAGE, () => {
|
|
79
|
-
this.server.close();
|
|
80
|
-
resolve(result);
|
|
81
|
-
});
|
|
113
|
+
resolve(result);
|
|
82
114
|
});
|
|
83
115
|
});
|
|
84
116
|
}
|