@claude-flow/cli 3.54.1 → 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/bin/cli.js +1 -1
- package/bin/mcp-server.js +1 -1
- 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/autopilot.js +2 -2
- package/dist/src/commands/daemon.js +15 -3
- package/dist/src/commands/doctor.js +65 -58
- package/dist/src/commands/hive-mind.js +7 -0
- package/dist/src/commands/mcp.js +35 -1
- 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/policy.js +10 -2
- package/dist/src/commands/task.js +12 -0
- package/dist/src/init/settings-generator.js +3 -3
- package/dist/src/mcp-server.d.ts +33 -0
- package/dist/src/mcp-server.js +96 -4
- 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/hive-mind-tools.d.ts +2 -4
- package/dist/src/mcp-tools/hive-mind-tools.js +214 -40
- 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/missions/schemas.d.ts +125 -125
- package/dist/src/mods/command-registry/schema.d.ts +199 -199
- package/dist/src/parser.d.ts +3 -0
- package/dist/src/parser.js +71 -13
- package/dist/src/plugins/store/discovery.d.ts +6 -0
- package/dist/src/plugins/store/discovery.js +7 -3
- package/dist/src/production/retry.js +1 -1
- package/dist/src/ruvector/neural-router.js +7 -2
- package/dist/src/services/bounded-worker-pool.js +3 -0
- 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/config-file-manager.d.ts +3 -0
- package/dist/src/services/config-file-manager.js +24 -7
- 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/policy-ledger-anchor.d.ts +42 -0
- package/dist/src/services/policy-ledger-anchor.js +163 -0
- package/dist/src/services/policy-runtime.d.ts +11 -5
- package/dist/src/services/policy-runtime.js +70 -15
- package/dist/src/services/worker-daemon.d.ts +5 -2
- package/dist/src/services/worker-daemon.js +26 -6
- package/dist/src/services/worker-queue.js +11 -2
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js +5 -2
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +7 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +60 -47
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts +3 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js +4 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -1
- 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/dist/policy/engine.d.ts +11 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +19 -3
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +4 -2
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +7 -7
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
- package/plugins/ruflo-metaharness/CHANGELOG.md +48 -0
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +80 -7
- package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
- package/plugins/ruflo-metaharness/scripts/test-cli-resolution-3558.mjs +141 -0
- package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +5 -0
- package/.claude/agents/tmp.json +0 -0
package/dist/src/parser.d.ts
CHANGED
|
@@ -94,6 +94,9 @@ export declare class CommandParser {
|
|
|
94
94
|
* commit message.)
|
|
95
95
|
*/
|
|
96
96
|
private getScopedBooleanFlags;
|
|
97
|
+
/** Resolve declared types from broad to narrow without borrowing string
|
|
98
|
+
* declarations from unrelated registered commands. */
|
|
99
|
+
private getFlagScope;
|
|
97
100
|
private getBooleanFlags;
|
|
98
101
|
private applyDefaults;
|
|
99
102
|
validateFlags(flags: ParsedFlags, command?: Command): string[];
|
package/dist/src/parser.js
CHANGED
|
@@ -135,14 +135,27 @@ export class CommandParser {
|
|
|
135
135
|
// with `daemon` left as a positional.
|
|
136
136
|
let resolvedCmd;
|
|
137
137
|
let resolvedSub;
|
|
138
|
+
const resolvedCommands = [];
|
|
138
139
|
let sawFirstPositional = false;
|
|
139
|
-
for (
|
|
140
|
-
|
|
140
|
+
for (let index = 0; index < args.length; index++) {
|
|
141
|
+
const arg = args[index];
|
|
142
|
+
if (arg === '--')
|
|
143
|
+
break;
|
|
144
|
+
if (arg.startsWith('-')) {
|
|
145
|
+
// Pass 2 recognizes only contiguous subcommands after the root.
|
|
146
|
+
if (sawFirstPositional)
|
|
147
|
+
break;
|
|
148
|
+
// Skip consumed values as well as flags: a global option value is not
|
|
149
|
+
// the command slot, even when it happens to name another command.
|
|
150
|
+
const scope = this.getFlagScope(resolvedCommands);
|
|
151
|
+
index = this.parseFlag(args, index, scope.aliases, scope.booleanFlags, scope.stringFlags).nextIndex - 1;
|
|
141
152
|
continue;
|
|
153
|
+
}
|
|
142
154
|
if (!sawFirstPositional) {
|
|
143
155
|
sawFirstPositional = true;
|
|
144
156
|
if (this.commands.has(arg)) {
|
|
145
157
|
resolvedCmd = this.commands.get(arg);
|
|
158
|
+
resolvedCommands.push(resolvedCmd);
|
|
146
159
|
continue;
|
|
147
160
|
}
|
|
148
161
|
// Lazy command: we know its name but not its subcommands. Stop the
|
|
@@ -153,14 +166,21 @@ export class CommandParser {
|
|
|
153
166
|
// Unknown first positional — not a command. Stop walking.
|
|
154
167
|
break;
|
|
155
168
|
}
|
|
156
|
-
|
|
157
|
-
|
|
169
|
+
// Match the same supported command depth as Pass 2, with each option
|
|
170
|
+
// declaration taking precedence over its parent scope.
|
|
171
|
+
const parent = resolvedCommands[resolvedCommands.length - 1];
|
|
172
|
+
if (resolvedCommands.length < 4 && parent?.subcommands) {
|
|
173
|
+
const child = parent.subcommands.find(sc => sc.name === arg || sc.aliases?.includes(arg));
|
|
174
|
+
if (child) {
|
|
175
|
+
resolvedCommands.push(child);
|
|
176
|
+
resolvedSub ??= child;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
158
179
|
}
|
|
180
|
+
// A positional that is not the next subcommand ends the command path.
|
|
181
|
+
break;
|
|
159
182
|
}
|
|
160
|
-
|
|
161
|
-
// Subcommand-specific aliases take priority over global ones
|
|
162
|
-
const aliases = this.buildScopedAliases(resolvedSub || resolvedCmd);
|
|
163
|
-
const booleanFlags = this.getScopedBooleanFlags(resolvedSub || resolvedCmd);
|
|
183
|
+
const { aliases, booleanFlags, stringFlags } = this.getFlagScope(resolvedCommands);
|
|
164
184
|
let i = 0;
|
|
165
185
|
let parsingFlags = true;
|
|
166
186
|
while (i < args.length) {
|
|
@@ -173,7 +193,7 @@ export class CommandParser {
|
|
|
173
193
|
}
|
|
174
194
|
// Handle flags
|
|
175
195
|
if (parsingFlags && arg.startsWith('-')) {
|
|
176
|
-
const parseResult = this.parseFlag(args, i, aliases, booleanFlags);
|
|
196
|
+
const parseResult = this.parseFlag(args, i, aliases, booleanFlags, stringFlags);
|
|
177
197
|
// Apply to result flags
|
|
178
198
|
Object.assign(result.flags, parseResult.flags);
|
|
179
199
|
i = parseResult.nextIndex;
|
|
@@ -231,7 +251,7 @@ export class CommandParser {
|
|
|
231
251
|
this.applyDefaults(result.flags, resolvedCmd, resolvedSub);
|
|
232
252
|
return result;
|
|
233
253
|
}
|
|
234
|
-
parseFlag(args, index, aliases, booleanFlags) {
|
|
254
|
+
parseFlag(args, index, aliases, booleanFlags, stringFlags) {
|
|
235
255
|
const flags = { _: [] };
|
|
236
256
|
const arg = args[index];
|
|
237
257
|
let nextIndex = index + 1;
|
|
@@ -242,7 +262,8 @@ export class CommandParser {
|
|
|
242
262
|
// --flag=value
|
|
243
263
|
const key = arg.slice(2, equalIndex);
|
|
244
264
|
const value = arg.slice(equalIndex + 1);
|
|
245
|
-
|
|
265
|
+
const normalizedKey = this.normalizeKey(key);
|
|
266
|
+
flags[normalizedKey] = stringFlags.has(normalizedKey) ? value : this.parseValue(value);
|
|
246
267
|
}
|
|
247
268
|
else if (arg.startsWith('--no-')) {
|
|
248
269
|
// --no-flag (boolean negation)
|
|
@@ -266,7 +287,7 @@ export class CommandParser {
|
|
|
266
287
|
}
|
|
267
288
|
}
|
|
268
289
|
else if (nextIndex < args.length && this.isFlagValue(args[nextIndex])) {
|
|
269
|
-
flags[normalizedKey] = this.parseValue(args[nextIndex]);
|
|
290
|
+
flags[normalizedKey] = stringFlags.has(normalizedKey) ? args[nextIndex] : this.parseValue(args[nextIndex]);
|
|
270
291
|
nextIndex++;
|
|
271
292
|
}
|
|
272
293
|
else {
|
|
@@ -293,7 +314,7 @@ export class CommandParser {
|
|
|
293
314
|
}
|
|
294
315
|
}
|
|
295
316
|
else if (nextIndex < args.length && this.isFlagValue(args[nextIndex])) {
|
|
296
|
-
flags[normalizedKey] = this.parseValue(args[nextIndex]);
|
|
317
|
+
flags[normalizedKey] = stringFlags.has(normalizedKey) ? args[nextIndex] : this.parseValue(args[nextIndex]);
|
|
297
318
|
nextIndex++;
|
|
298
319
|
}
|
|
299
320
|
else {
|
|
@@ -439,6 +460,36 @@ export class CommandParser {
|
|
|
439
460
|
}
|
|
440
461
|
return flags;
|
|
441
462
|
}
|
|
463
|
+
/** Resolve declared types from broad to narrow without borrowing string
|
|
464
|
+
* declarations from unrelated registered commands. */
|
|
465
|
+
getFlagScope(commands) {
|
|
466
|
+
const aliases = this.buildScopedAliases(commands[commands.length - 1]);
|
|
467
|
+
const booleanFlags = this.getScopedBooleanFlags(commands[commands.length - 1]);
|
|
468
|
+
const stringFlags = new Set((this.options.stringFlags ?? []).map(flag => this.normalizeKey(flag)));
|
|
469
|
+
for (const command of commands) {
|
|
470
|
+
for (const option of command.options ?? []) {
|
|
471
|
+
if (option.short)
|
|
472
|
+
aliases[option.short] = option.name;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const layers = [this.globalOptions, ...commands.map(command => command.options ?? [])];
|
|
476
|
+
for (const options of layers) {
|
|
477
|
+
for (const option of options) {
|
|
478
|
+
const key = this.normalizeKey(option.name);
|
|
479
|
+
if (option.type === 'string')
|
|
480
|
+
stringFlags.add(key);
|
|
481
|
+
else if (option.type)
|
|
482
|
+
stringFlags.delete(key);
|
|
483
|
+
if (option.type === 'boolean')
|
|
484
|
+
booleanFlags.add(key);
|
|
485
|
+
else if (option.type)
|
|
486
|
+
booleanFlags.delete(key);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
for (const flag of stringFlags)
|
|
490
|
+
booleanFlags.delete(flag);
|
|
491
|
+
return { aliases, booleanFlags, stringFlags };
|
|
492
|
+
}
|
|
442
493
|
getBooleanFlags() {
|
|
443
494
|
const flags = new Set();
|
|
444
495
|
for (const opt of this.globalOptions) {
|
|
@@ -517,6 +568,13 @@ export class CommandParser {
|
|
|
517
568
|
if (opt.required && (flags[key] === undefined || flags[key] === '')) {
|
|
518
569
|
errors.push(`Required option missing: --${opt.name}`);
|
|
519
570
|
}
|
|
571
|
+
// A value-taking option given with nothing after it (`--task`, `--key` at the end, or
|
|
572
|
+
// followed by another flag) is parsed as boolean `true`. Commands then treat `true` as
|
|
573
|
+
// the value: `hooks route --task` crashed with "task.trim is not a function" and
|
|
574
|
+
// `memory retrieve --key` looked up the key "true". Report it instead.
|
|
575
|
+
if ((opt.type === 'string' || opt.type === 'number') && flags[key] === true) {
|
|
576
|
+
errors.push(`Option --${opt.name} needs a value`);
|
|
577
|
+
}
|
|
520
578
|
// Check choices
|
|
521
579
|
if (opt.choices && flags[key] !== undefined) {
|
|
522
580
|
const value = String(flags[key]);
|
|
@@ -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
|
/**
|
|
@@ -116,7 +116,7 @@ export async function withRetry(fn, config = {}, strategy = 'exponential') {
|
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
catch (error) {
|
|
119
|
-
lastError = error;
|
|
119
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
120
120
|
// Check if we should retry
|
|
121
121
|
if (!shouldRetryError(lastError, attempt, finalConfig)) {
|
|
122
122
|
return {
|
|
@@ -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
|
};
|
|
@@ -20,6 +20,9 @@ export async function runBoundedPool(tasks, options) {
|
|
|
20
20
|
const controller = new AbortController();
|
|
21
21
|
const onAbort = () => controller.abort(options.signal?.reason ?? new Error('cancelled'));
|
|
22
22
|
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
23
|
+
// Abort events are not replayed for signals cancelled before registration.
|
|
24
|
+
if (options.signal?.aborted)
|
|
25
|
+
onAbort();
|
|
23
26
|
const timer = options.timeoutMs && options.timeoutMs > 0
|
|
24
27
|
? setTimeout(() => controller.abort(new Error('worker-pool-timeout')), options.timeoutMs)
|
|
25
28
|
: undefined;
|
|
@@ -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
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Shared JSON config file persistence with atomic writes and Zod validation
|
|
4
4
|
*/
|
|
5
5
|
export declare class ConfigFileManager {
|
|
6
|
+
private configCwd;
|
|
6
7
|
private configPath;
|
|
7
8
|
private config;
|
|
8
9
|
/** Find config file in search paths starting from cwd */
|
|
@@ -27,6 +28,8 @@ export declare class ConfigFileManager {
|
|
|
27
28
|
getConfigPath(): string | null;
|
|
28
29
|
/** Get default config */
|
|
29
30
|
getDefaults(): Record<string, unknown>;
|
|
31
|
+
/** Cached values and file paths belong to one resolved project directory. */
|
|
32
|
+
private selectProject;
|
|
30
33
|
/** Atomic write: write to .tmp then rename */
|
|
31
34
|
private writeAtomic;
|
|
32
35
|
}
|
|
@@ -54,6 +54,7 @@ const DEFAULT_CONFIG = {
|
|
|
54
54
|
},
|
|
55
55
|
};
|
|
56
56
|
export class ConfigFileManager {
|
|
57
|
+
configCwd = null;
|
|
57
58
|
configPath = null;
|
|
58
59
|
config = null;
|
|
59
60
|
/** Find config file in search paths starting from cwd */
|
|
@@ -73,6 +74,7 @@ export class ConfigFileManager {
|
|
|
73
74
|
}
|
|
74
75
|
/** Load config from file, returns null if not found */
|
|
75
76
|
load(cwd) {
|
|
77
|
+
this.selectProject(cwd);
|
|
76
78
|
this.configPath = this.findConfig(cwd);
|
|
77
79
|
if (!this.configPath) {
|
|
78
80
|
this.config = null;
|
|
@@ -101,10 +103,11 @@ export class ConfigFileManager {
|
|
|
101
103
|
}
|
|
102
104
|
/** Get the current config, loading if needed */
|
|
103
105
|
getConfig(cwd) {
|
|
106
|
+
this.selectProject(cwd);
|
|
104
107
|
if (this.config === null) {
|
|
105
108
|
this.load(cwd);
|
|
106
109
|
}
|
|
107
|
-
return this.config ??
|
|
110
|
+
return this.config ?? structuredClone(DEFAULT_CONFIG);
|
|
108
111
|
}
|
|
109
112
|
/** Get a nested config value by dot-separated key */
|
|
110
113
|
get(cwd, key) {
|
|
@@ -117,6 +120,7 @@ export class ConfigFileManager {
|
|
|
117
120
|
if (key.split('.').some(part => part === '__proto__' || part === 'constructor' || part === 'prototype')) {
|
|
118
121
|
throw new Error(`Unsafe configuration key: ${key}`);
|
|
119
122
|
}
|
|
123
|
+
this.selectProject(cwd);
|
|
120
124
|
// A targeted update must not persist unrelated defaults (notably the
|
|
121
125
|
// default memory path, which would relocate an existing memory store).
|
|
122
126
|
const config = this.config ?? this.load(cwd) ?? {};
|
|
@@ -137,17 +141,20 @@ export class ConfigFileManager {
|
|
|
137
141
|
if (fs.existsSync(targetPath) && !force) {
|
|
138
142
|
throw new Error(`Config file already exists: ${targetPath}. Use --force to overwrite.`);
|
|
139
143
|
}
|
|
140
|
-
const config = { ...DEFAULT_CONFIG, ...overrides };
|
|
144
|
+
const config = { ...structuredClone(DEFAULT_CONFIG), ...overrides };
|
|
141
145
|
this.writeAtomic(targetPath, config);
|
|
146
|
+
this.selectProject(cwd);
|
|
142
147
|
this.config = config;
|
|
143
148
|
this.configPath = targetPath;
|
|
144
149
|
return targetPath;
|
|
145
150
|
}
|
|
146
151
|
/** Reset config to defaults */
|
|
147
152
|
reset(cwd) {
|
|
148
|
-
|
|
149
|
-
this.
|
|
150
|
-
|
|
153
|
+
this.selectProject(cwd);
|
|
154
|
+
const targetPath = this.findConfig(cwd) ?? path.resolve(cwd, CONFIG_FILENAMES[0]);
|
|
155
|
+
const config = structuredClone(DEFAULT_CONFIG);
|
|
156
|
+
this.writeAtomic(targetPath, config);
|
|
157
|
+
this.config = config;
|
|
151
158
|
this.configPath = targetPath;
|
|
152
159
|
return targetPath;
|
|
153
160
|
}
|
|
@@ -174,7 +181,8 @@ export class ConfigFileManager {
|
|
|
174
181
|
if (typeof imported !== 'object' || imported === null || Array.isArray(imported)) {
|
|
175
182
|
throw new Error('Import file must contain a JSON object');
|
|
176
183
|
}
|
|
177
|
-
|
|
184
|
+
this.selectProject(cwd);
|
|
185
|
+
const targetPath = this.findConfig(cwd) ?? path.resolve(cwd, CONFIG_FILENAMES[0]);
|
|
178
186
|
this.writeAtomic(targetPath, imported);
|
|
179
187
|
this.config = imported;
|
|
180
188
|
this.configPath = targetPath;
|
|
@@ -185,7 +193,16 @@ export class ConfigFileManager {
|
|
|
185
193
|
}
|
|
186
194
|
/** Get default config */
|
|
187
195
|
getDefaults() {
|
|
188
|
-
return
|
|
196
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
197
|
+
}
|
|
198
|
+
/** Cached values and file paths belong to one resolved project directory. */
|
|
199
|
+
selectProject(cwd) {
|
|
200
|
+
const resolved = path.resolve(cwd);
|
|
201
|
+
if (this.configCwd !== resolved) {
|
|
202
|
+
this.configCwd = resolved;
|
|
203
|
+
this.config = null;
|
|
204
|
+
this.configPath = null;
|
|
205
|
+
}
|
|
189
206
|
}
|
|
190
207
|
/** Atomic write: write to .tmp then rename */
|
|
191
208
|
writeAtomic(filePath, data) {
|
|
@@ -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
|