claude-flow 3.47.1 → 3.48.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/auto-memory-hook.mjs +6 -2
- package/.claude/helpers/learning-service.mjs +4 -2
- package/.claude/helpers/metrics-db.mjs +4 -2
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +6 -3
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
- package/node_modules/@claude-flow/security/package.json +2 -3
- package/package.json +7 -7
- package/v3/@claude-flow/cli/README.md +0 -2
- package/v3/@claude-flow/cli/bin/cli.js +4 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +15 -5
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +89 -27
- package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
- package/v3/@claude-flow/cli/dist/src/commands/session.js +6 -6
- package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +4 -2
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +0 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +139 -37
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +89 -35
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +34 -8
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +41 -31
- package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
- package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
- package/v3/@claude-flow/cli/package.json +9 -9
- package/v3/@claude-flow/guidance/package.json +5 -6
- package/v3/@claude-flow/shared/package.json +6 -3
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* AgentDB MCP Tools — Phase 6 of ADR-053
|
|
3
|
-
*
|
|
4
|
-
* Exposes AgentDB v3 controller operations as MCP tools.
|
|
5
|
-
* Provides direct access to ReasoningBank, CausalGraph, SkillLibrary,
|
|
6
|
-
* AttestationLog, and bridge health through the MCP protocol.
|
|
7
|
-
*
|
|
8
|
-
* Security: All handlers validate input types, enforce length bounds,
|
|
9
|
-
* and sanitize error messages before returning to MCP callers.
|
|
10
|
-
*
|
|
11
|
-
* @module v3/cli/mcp-tools/agentdb-tools
|
|
12
|
-
*/
|
|
1
|
+
import { feedbackPatternsSchema, validateFeedbackPatterns } from '../memory/feedback-patterns.js';
|
|
13
2
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
14
3
|
// ===== Shared validation helpers =====
|
|
15
4
|
const MAX_STRING_LENGTH = 100_000; // 100KB max for any string input
|
|
@@ -66,12 +55,6 @@ async function getBridge() {
|
|
|
66
55
|
// Lazy-cached modules used by graph-query / graph-pathfinder dispatch.
|
|
67
56
|
// Caching the resolved namespace avoids per-call dynamic-import overhead
|
|
68
57
|
// in the ADR-130 hot path (smoke harness measures elapsedMs of every call).
|
|
69
|
-
let graphBackendMod = null;
|
|
70
|
-
async function getGraphBackend() {
|
|
71
|
-
if (!graphBackendMod)
|
|
72
|
-
graphBackendMod = await import('../ruvector/graph-backend.js');
|
|
73
|
-
return graphBackendMod;
|
|
74
|
-
}
|
|
75
58
|
let graphEdgeWriterMod = null;
|
|
76
59
|
async function getGraphEdgeWriter() {
|
|
77
60
|
if (!graphEdgeWriterMod)
|
|
@@ -396,6 +379,7 @@ export const agentdbFeedback = {
|
|
|
396
379
|
type: 'object',
|
|
397
380
|
properties: {
|
|
398
381
|
taskId: { type: 'string', description: 'Task identifier' },
|
|
382
|
+
patterns: feedbackPatternsSchema,
|
|
399
383
|
success: { type: 'boolean', description: 'Whether task succeeded' },
|
|
400
384
|
quality: { type: 'number', description: 'Quality score (0-1)' },
|
|
401
385
|
agent: { type: 'string', description: 'Agent that performed the task' },
|
|
@@ -415,9 +399,11 @@ export const agentdbFeedback = {
|
|
|
415
399
|
const taskId = validateString(params.taskId, 'taskId', 500);
|
|
416
400
|
if (!taskId)
|
|
417
401
|
return { success: false, error: 'taskId is required (non-empty string, max 500 chars)' };
|
|
402
|
+
const patterns = validateFeedbackPatterns(params.patterns);
|
|
418
403
|
const bridge = await getBridge();
|
|
419
404
|
const result = await bridge.bridgeRecordFeedback({
|
|
420
405
|
taskId,
|
|
406
|
+
patterns,
|
|
421
407
|
success: params.success === true,
|
|
422
408
|
quality: validateScore(params.quality, 0.85),
|
|
423
409
|
agent: validateString(params.agent, 'agent', 200) ?? undefined,
|
|
@@ -802,21 +788,18 @@ export const agentdbHierarchicalRecall = {
|
|
|
802
788
|
// ===== agentdb_consolidate — Run memory consolidation =====
|
|
803
789
|
export const agentdbConsolidate = {
|
|
804
790
|
name: 'agentdb_consolidate',
|
|
805
|
-
description: '
|
|
791
|
+
description: 'Request memory consolidation. Use when checking whether AgentDB can consolidate retained memories; native file edits cannot perform controller consolidation. Currently returns unsupported when the installed controller is a no-op stub; no entries are promoted or compressed in that case.',
|
|
806
792
|
inputSchema: {
|
|
807
793
|
type: 'object',
|
|
808
|
-
properties: {
|
|
809
|
-
minAge: { type: 'number', description: 'Minimum age in hours since store (optional)' },
|
|
810
|
-
maxEntries: { type: 'number', description: 'Maximum entries to consolidate (optional)' },
|
|
811
|
-
},
|
|
794
|
+
properties: {},
|
|
812
795
|
},
|
|
813
796
|
handler: async (params) => {
|
|
814
797
|
try {
|
|
798
|
+
if (Object.keys(params).length > 0) {
|
|
799
|
+
return { success: false, status: 'unsupported', error: 'agentdb_consolidate does not support options' };
|
|
800
|
+
}
|
|
815
801
|
const bridge = await getBridge();
|
|
816
|
-
const result = await bridge.bridgeConsolidate({
|
|
817
|
-
minAge: typeof params.minAge === 'number' ? Math.max(0, params.minAge) : undefined,
|
|
818
|
-
maxEntries: validatePositiveInt(params.maxEntries, 1000, 10_000),
|
|
819
|
-
});
|
|
802
|
+
const result = await bridge.bridgeConsolidate({});
|
|
820
803
|
return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' };
|
|
821
804
|
}
|
|
822
805
|
catch (error) {
|
|
@@ -1060,7 +1043,7 @@ export const agentdbCausalNodeDelete = {
|
|
|
1060
1043
|
};
|
|
1061
1044
|
export const agentdbGraphQuery = {
|
|
1062
1045
|
name: 'agentdb_graph-query',
|
|
1063
|
-
description: 'Unified graph traversal across the knowledge graph (ADR-130).
|
|
1046
|
+
description: 'Unified graph traversal across the knowledge graph (ADR-130). K-hop queries read committed graph_edges relationships through SQL; a separately populated native graph is not evidence of retained-history coverage. Inspect appliedDepth/truncated because SQL is bounded at 3 hops. maxNodesVisited bounds returned rows, not traversal work or elapsed time. Semantic and PageRank modes use their own backends. Use when you need relationship traversal beyond flat memory search.',
|
|
1064
1047
|
inputSchema: {
|
|
1065
1048
|
type: 'object',
|
|
1066
1049
|
properties: {
|
|
@@ -1101,53 +1084,38 @@ export const agentdbGraphQuery = {
|
|
|
1101
1084
|
}
|
|
1102
1085
|
const budgetRaw = (params.complexityBudget ?? {});
|
|
1103
1086
|
const budget = {
|
|
1104
|
-
maxNodesVisited: budgetRaw.maxNodesVisited
|
|
1105
|
-
maxDepth: budgetRaw.maxDepth
|
|
1087
|
+
maxNodesVisited: validatePositiveInt(budgetRaw.maxNodesVisited, 10_000, 10_000),
|
|
1088
|
+
maxDepth: validatePositiveInt(budgetRaw.maxDepth, 5, 5),
|
|
1106
1089
|
maxMillis: budgetRaw.maxMillis ?? 50,
|
|
1107
1090
|
maxMemoryMB: budgetRaw.maxMemoryMB ?? 32,
|
|
1108
1091
|
};
|
|
1109
|
-
const
|
|
1092
|
+
const requestedDepth = validatePositiveInt(params.depth, 2, 5);
|
|
1093
|
+
const depth = Math.min(requestedDepth, budget.maxDepth);
|
|
1110
1094
|
const topK = validatePositiveInt(params.topK, 10, MAX_TOP_K);
|
|
1111
1095
|
const relation = validateString(params.relation, 'relation', 200) ?? undefined;
|
|
1112
1096
|
// ── k-hop mode ──────────────────────────────────────────────────────────
|
|
1113
1097
|
if (mode === 'k-hop') {
|
|
1114
|
-
// graph-node
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
// unsupported native query instead of silently widening it.
|
|
1118
|
-
try {
|
|
1119
|
-
const graphBackend = await getGraphBackend();
|
|
1120
|
-
if (await graphBackend.isGraphBackendAvailable()) {
|
|
1121
|
-
if (relation) {
|
|
1122
|
-
return { success: false, error: 'Native graph-node k-hop does not support relation filtering', unsupported: 'relation', mode, nodeId, relation, backend: 'graph-node' };
|
|
1123
|
-
}
|
|
1124
|
-
const neighbors = await graphBackend.getNeighbors(nodeId, depth);
|
|
1125
|
-
return {
|
|
1126
|
-
success: true, mode, nodeId, depth, appliedDepth: depth,
|
|
1127
|
-
results: neighbors.map(id => ({ nodeId: id })),
|
|
1128
|
-
count: neighbors.length,
|
|
1129
|
-
backend: 'graph-node',
|
|
1130
|
-
elapsedMs: Date.now() - t0,
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
catch { /* fall through to SQL */ }
|
|
1135
|
-
// SQL CTE fallback is bounded at depth 3. Report that bound so callers
|
|
1136
|
-
// cannot mistake an incomplete depth-5 cycle check for a complete one.
|
|
1098
|
+
// #3315: graph-node and retained SQL are populated independently.
|
|
1099
|
+
// Native availability does not establish historical coverage. Query
|
|
1100
|
+
// the committed relationship source, and fail if it cannot be read.
|
|
1137
1101
|
try {
|
|
1138
1102
|
const { getBridgeDb } = await getGraphEdgeWriter();
|
|
1139
1103
|
const db = await getBridgeDb();
|
|
1140
1104
|
if (db) {
|
|
1141
1105
|
const appliedDepth = Math.min(depth, 3);
|
|
1142
|
-
const cteSql = buildKHopCTE(nodeId, appliedDepth, relation, budget.maxNodesVisited);
|
|
1106
|
+
const cteSql = buildKHopCTE(nodeId, appliedDepth, relation, budget.maxNodesVisited + 1);
|
|
1143
1107
|
// graph-edge-writer returns a better-sqlite3 Database after #2431.
|
|
1144
1108
|
// `db.exec(sql, params)` (sql.js style) is a runner with no result
|
|
1145
1109
|
// on better-sqlite3 — use `prepare(sql).raw().all(...)` to get the
|
|
1146
1110
|
// same array-of-arrays shape the downstream code expects.
|
|
1147
|
-
const
|
|
1111
|
+
const allRows = db.prepare(cteSql).raw().all();
|
|
1112
|
+
const resultLimitReached = allRows.length > budget.maxNodesVisited;
|
|
1113
|
+
const rows = allRows.slice(0, budget.maxNodesVisited);
|
|
1114
|
+
const depthLimited = appliedDepth < requestedDepth;
|
|
1148
1115
|
return {
|
|
1149
|
-
success: true, mode, nodeId, depth, appliedDepth,
|
|
1150
|
-
|
|
1116
|
+
success: true, mode, nodeId, depth, requestedDepth, appliedDepth,
|
|
1117
|
+
depthLimited, resultLimitReached, truncated: depthLimited || resultLimitReached,
|
|
1118
|
+
resultLimit: budget.maxNodesVisited,
|
|
1151
1119
|
results: rows.map((r) => ({ nodeId: r[0], depth: r[1] })),
|
|
1152
1120
|
count: rows.length,
|
|
1153
1121
|
backend: 'sql-cte',
|
|
@@ -1155,11 +1123,10 @@ export const agentdbGraphQuery = {
|
|
|
1155
1123
|
};
|
|
1156
1124
|
}
|
|
1157
1125
|
}
|
|
1158
|
-
catch {
|
|
1159
|
-
|
|
1160
|
-
return { success: false, error: 'Relation-filtered k-hop query requires the SQL graph_edges backend', mode, nodeId, relation };
|
|
1126
|
+
catch (error) {
|
|
1127
|
+
return { success: false, error: `Retained SQL graph query failed: ${sanitizeError(error)}`, mode, nodeId };
|
|
1161
1128
|
}
|
|
1162
|
-
return { success: false, error: '
|
|
1129
|
+
return { success: false, error: 'Retained SQL graph_edges backend is unavailable for k-hop query', mode, nodeId };
|
|
1163
1130
|
}
|
|
1164
1131
|
// ── semantic mode ────────────────────────────────────────────────────────
|
|
1165
1132
|
if (mode === 'semantic') {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { feedbackPatternsSchema, validateFeedbackPatterns } from '../memory/feedback-patterns.js';
|
|
1
2
|
/**
|
|
2
3
|
* Hooks MCP Tools
|
|
3
4
|
* Provides intelligent hooks functionality via MCP protocol
|
|
@@ -410,8 +411,75 @@ async function getLoRAAdapter() {
|
|
|
410
411
|
}
|
|
411
412
|
return loraAdapter;
|
|
412
413
|
}
|
|
413
|
-
//
|
|
414
|
+
// Cache pending trajectories; the existing memory store also persists checkpoints.
|
|
414
415
|
const activeTrajectories = new Map();
|
|
416
|
+
const dirtyTrajectories = new Set();
|
|
417
|
+
const trajectoryOperations = new Map();
|
|
418
|
+
/** Serialize read/modify/checkpoint operations for a trajectory in this process. */
|
|
419
|
+
async function withTrajectoryOperation(id, operation) {
|
|
420
|
+
const previous = trajectoryOperations.get(id) ?? Promise.resolve();
|
|
421
|
+
let release;
|
|
422
|
+
const gate = new Promise(resolve => { release = resolve; });
|
|
423
|
+
const current = previous.then(() => gate);
|
|
424
|
+
trajectoryOperations.set(id, current);
|
|
425
|
+
await previous;
|
|
426
|
+
try {
|
|
427
|
+
return await operation();
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
release();
|
|
431
|
+
if (trajectoryOperations.get(id) === current)
|
|
432
|
+
trajectoryOperations.delete(id);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async function persistPendingTrajectory(trajectory) {
|
|
436
|
+
dirtyTrajectories.add(trajectory.id);
|
|
437
|
+
try {
|
|
438
|
+
const store = await getRealStoreFunction();
|
|
439
|
+
const result = await store?.({
|
|
440
|
+
key: `trajectory-pending-${trajectory.id}`, value: JSON.stringify(trajectory),
|
|
441
|
+
namespace: 'trajectories', tags: [trajectory.agent, 'pending', 'sona-trajectory'], upsert: true,
|
|
442
|
+
});
|
|
443
|
+
if (result?.success === true)
|
|
444
|
+
dirtyTrajectories.delete(trajectory.id);
|
|
445
|
+
return result?.success === true;
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function loadPendingTrajectory(id) {
|
|
452
|
+
try {
|
|
453
|
+
const { getEntry } = await import('../memory/memory-initializer.js');
|
|
454
|
+
// A completed record is authoritative even if pending-record cleanup failed
|
|
455
|
+
// or another process still has an old cache entry.
|
|
456
|
+
const completed = await getEntry({ key: `trajectory-${id}`, namespace: 'trajectories' });
|
|
457
|
+
if (completed.success && completed.found) {
|
|
458
|
+
activeTrajectories.delete(id);
|
|
459
|
+
dirtyTrajectories.delete(id);
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
// Failed checkpoints must not erase already-acknowledged live steps.
|
|
463
|
+
// Clean caches still reload so another process's later checkpoint is seen.
|
|
464
|
+
if (dirtyTrajectories.has(id) && activeTrajectories.has(id))
|
|
465
|
+
return activeTrajectories.get(id);
|
|
466
|
+
const pending = await getEntry({ key: `trajectory-pending-${id}`, namespace: 'trajectories' });
|
|
467
|
+
if (pending.success && pending.found) {
|
|
468
|
+
const value = JSON.parse(pending.entry?.content ?? 'null');
|
|
469
|
+
if (!value || value.id !== id || typeof value.task !== 'string' || typeof value.agent !== 'string'
|
|
470
|
+
|| typeof value.startedAt !== 'string' || !Number.isFinite(Date.parse(value.startedAt))
|
|
471
|
+
|| !Array.isArray(value.steps) || !value.steps.every((step) => step
|
|
472
|
+
&& typeof step.action === 'string' && typeof step.result === 'string'
|
|
473
|
+
&& typeof step.quality === 'number' && Number.isFinite(step.quality)
|
|
474
|
+
&& typeof step.timestamp === 'string'))
|
|
475
|
+
return undefined;
|
|
476
|
+
activeTrajectories.set(id, value);
|
|
477
|
+
return value;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch { /* store unavailable: retain same-process best-effort tracking */ }
|
|
481
|
+
return activeTrajectories.get(id);
|
|
482
|
+
}
|
|
415
483
|
const MEMORY_DIR = '.claude-flow/memory';
|
|
416
484
|
const MEMORY_FILE = 'store.json';
|
|
417
485
|
function getMemoryPath() {
|
|
@@ -897,13 +965,19 @@ export const hooksPostCommand = {
|
|
|
897
965
|
properties: {
|
|
898
966
|
command: { type: 'string', description: 'Executed command' },
|
|
899
967
|
exitCode: { type: 'number', description: 'Command exit code' },
|
|
968
|
+
success: { type: 'boolean', description: 'Explicit execution outcome; false records a failure even without a nonzero exit code' },
|
|
969
|
+
ttl: { type: 'integer', minimum: 1, maximum: 2147483647, description: 'Command history lifetime in seconds (default: 30 days)' },
|
|
900
970
|
},
|
|
901
971
|
required: ['command'],
|
|
902
972
|
},
|
|
903
973
|
handler: async (params) => {
|
|
904
974
|
const command = params.command;
|
|
905
975
|
const exitCode = params.exitCode || 0;
|
|
906
|
-
const success = exitCode === 0;
|
|
976
|
+
const success = exitCode === 0 && params.success !== false;
|
|
977
|
+
const ttl = params.ttl ?? 30 * 24 * 60 * 60;
|
|
978
|
+
if (typeof ttl !== 'number' || !Number.isSafeInteger(ttl) || ttl < 1 || ttl > 2147483647) {
|
|
979
|
+
return { success: false, recorded: false, error: 'ttl must be a positive integer number of seconds (at most 2147483647)' };
|
|
980
|
+
}
|
|
907
981
|
{
|
|
908
982
|
const v = validateText(command, 'command');
|
|
909
983
|
if (!v.valid)
|
|
@@ -916,21 +990,35 @@ export const hooksPostCommand = {
|
|
|
916
990
|
let _storedIn = 'none';
|
|
917
991
|
try {
|
|
918
992
|
const bridge = await import('../memory/memory-bridge.js');
|
|
919
|
-
await bridge.bridgeStoreEntry({
|
|
993
|
+
const stored = await bridge.bridgeStoreEntry({
|
|
920
994
|
key: `cmd-${Date.now()}`,
|
|
921
995
|
value: JSON.stringify({ command, exitCode, success }),
|
|
922
996
|
namespace: 'commands',
|
|
923
997
|
tags: [success ? 'success' : 'error'],
|
|
998
|
+
ttl,
|
|
999
|
+
generateEmbeddingFlag: false,
|
|
924
1000
|
});
|
|
1001
|
+
if (!stored?.success)
|
|
1002
|
+
throw new Error(stored?.error || 'Native bridge did not store the command');
|
|
925
1003
|
_storedIn = 'agentdb';
|
|
926
1004
|
}
|
|
927
1005
|
catch {
|
|
928
1006
|
// AgentDB not available — store in JSON
|
|
929
1007
|
try {
|
|
930
1008
|
const store = loadMemoryStore();
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
1009
|
+
const now = Date.now();
|
|
1010
|
+
for (const [entryKey, entry] of Object.entries(store.entries)) {
|
|
1011
|
+
if (entry.namespace === 'commands' && entry.expiresAt && Date.parse(entry.expiresAt) <= now) {
|
|
1012
|
+
delete store.entries[entryKey];
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
const key = `cmd-${now}`;
|
|
1016
|
+
store.entries[key] = {
|
|
1017
|
+
key, value: JSON.stringify({ command, exitCode, success }), namespace: 'commands',
|
|
1018
|
+
storedAt: new Date(now).toISOString(), lastAccessed: new Date(now).toISOString(), accessCount: 0,
|
|
1019
|
+
expiresAt: new Date(now + ttl * 1000).toISOString(),
|
|
1020
|
+
};
|
|
1021
|
+
const memDir = dirname(getMemoryPath());
|
|
934
1022
|
if (!existsSync(memDir))
|
|
935
1023
|
mkdirSync(memDir, { recursive: true });
|
|
936
1024
|
writeFileSync(getMemoryPath(), JSON.stringify(store, null, 2), 'utf-8');
|
|
@@ -1444,9 +1532,10 @@ export const hooksPostTask = {
|
|
|
1444
1532
|
type: 'object',
|
|
1445
1533
|
properties: {
|
|
1446
1534
|
taskId: { type: 'string', description: 'Task identifier' },
|
|
1535
|
+
patterns: feedbackPatternsSchema,
|
|
1447
1536
|
success: { type: 'boolean', description: 'Whether task was successful' },
|
|
1448
1537
|
agent: { type: 'string', description: 'Agent that completed the task' },
|
|
1449
|
-
quality: { type: 'number', description: 'Quality score (0-1)' },
|
|
1538
|
+
quality: { type: 'number', minimum: 0, maximum: 1, description: 'Quality score (0-1)' },
|
|
1450
1539
|
task: { type: 'string', description: 'Task description text (used for learning keyword extraction)' },
|
|
1451
1540
|
duration: { type: 'number', description: 'Observed task duration in milliseconds (used by pheromone-adaptive topology)' },
|
|
1452
1541
|
latencyBudgetMs: { type: 'number', description: 'Latency budget used to normalize duration (default 60000ms)' },
|
|
@@ -1463,8 +1552,19 @@ export const hooksPostTask = {
|
|
|
1463
1552
|
const taskId = params.taskId;
|
|
1464
1553
|
const success = params.success !== false;
|
|
1465
1554
|
const agent = params.agent;
|
|
1466
|
-
|
|
1555
|
+
if (params.quality !== undefined && (typeof params.quality !== 'number'
|
|
1556
|
+
|| !Number.isFinite(params.quality) || params.quality < 0 || params.quality > 1)) {
|
|
1557
|
+
return { success: false, error: 'quality must be a finite number between 0 and 1' };
|
|
1558
|
+
}
|
|
1559
|
+
const quality = params.quality === undefined ? (success ? 0.85 : 0.3) : params.quality;
|
|
1467
1560
|
const startTime = Date.now();
|
|
1561
|
+
let patterns;
|
|
1562
|
+
try {
|
|
1563
|
+
patterns = validateFeedbackPatterns(params.patterns);
|
|
1564
|
+
}
|
|
1565
|
+
catch (error) {
|
|
1566
|
+
return { success: false, error: error.message };
|
|
1567
|
+
}
|
|
1468
1568
|
{
|
|
1469
1569
|
const v = validateIdentifier(taskId, 'taskId');
|
|
1470
1570
|
if (!v.valid)
|
|
@@ -1502,7 +1602,7 @@ export const hooksPostTask = {
|
|
|
1502
1602
|
quality,
|
|
1503
1603
|
agent,
|
|
1504
1604
|
duration: params.duration || undefined,
|
|
1505
|
-
patterns
|
|
1605
|
+
patterns,
|
|
1506
1606
|
// ADR-147 P2: forward spawn-tree lineage so it lands in feedback + memory
|
|
1507
1607
|
parentAgentId,
|
|
1508
1608
|
depth,
|
|
@@ -2754,9 +2854,8 @@ export const hooksIntelligenceReset = {
|
|
|
2754
2854
|
// Directory read failed
|
|
2755
2855
|
}
|
|
2756
2856
|
}
|
|
2757
|
-
//
|
|
2758
|
-
cleared.trajectories =
|
|
2759
|
-
activeTrajectories.clear();
|
|
2857
|
+
// Reset learned state without abandoning tasks still recording outcomes.
|
|
2858
|
+
cleared.trajectories = 0;
|
|
2760
2859
|
return {
|
|
2761
2860
|
reset: true,
|
|
2762
2861
|
cleared,
|
|
@@ -2815,27 +2914,15 @@ export const hooksTrajectoryStart = {
|
|
|
2815
2914
|
getTrajectoryTree().openTrajectory({ sessionId, trajectoryId, task, agent });
|
|
2816
2915
|
}
|
|
2817
2916
|
catch { /* prototype path — never blocks trajectory recording */ }
|
|
2818
|
-
//
|
|
2819
|
-
const
|
|
2820
|
-
if (storeFn) {
|
|
2821
|
-
try {
|
|
2822
|
-
await storeFn({
|
|
2823
|
-
key: `trajectory-pending-${trajectoryId}`,
|
|
2824
|
-
value: JSON.stringify(trajectory),
|
|
2825
|
-
namespace: 'trajectories',
|
|
2826
|
-
tags: [agent, 'pending', 'sona-trajectory'],
|
|
2827
|
-
});
|
|
2828
|
-
}
|
|
2829
|
-
catch {
|
|
2830
|
-
// Best-effort persistence — trajectory still lives in-memory
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2917
|
+
// Checkpoint before handing the id to another process.
|
|
2918
|
+
const persisted = await persistPendingTrajectory(trajectory);
|
|
2833
2919
|
return {
|
|
2834
2920
|
trajectoryId,
|
|
2835
2921
|
task,
|
|
2836
2922
|
agent,
|
|
2837
2923
|
started: startedAt,
|
|
2838
2924
|
status: 'recording',
|
|
2925
|
+
persisted,
|
|
2839
2926
|
implementation: 'real-trajectory-tracking',
|
|
2840
2927
|
activeCount: activeTrajectories.size,
|
|
2841
2928
|
};
|
|
@@ -2850,17 +2937,21 @@ export const hooksTrajectoryStep = {
|
|
|
2850
2937
|
trajectoryId: { type: 'string', description: 'Trajectory ID' },
|
|
2851
2938
|
action: { type: 'string', description: 'Action taken' },
|
|
2852
2939
|
result: { type: 'string', description: 'Action result' },
|
|
2853
|
-
quality: { type: 'number', description: 'Quality score (0-1)' },
|
|
2940
|
+
quality: { type: 'number', minimum: 0, maximum: 1, description: 'Quality score (0-1)' },
|
|
2854
2941
|
},
|
|
2855
2942
|
required: ['trajectoryId', 'action'],
|
|
2856
2943
|
},
|
|
2857
|
-
handler: async (params) => {
|
|
2944
|
+
handler: async (params) => withTrajectoryOperation(params.trajectoryId, async () => {
|
|
2858
2945
|
const trajectoryId = params.trajectoryId;
|
|
2859
2946
|
// #14: scrub extended-thinking blocks so reasoning tokens don't contaminate
|
|
2860
2947
|
// the learning signal (DISTILL embeds this text).
|
|
2861
2948
|
const action = scrubReasoningBlocks(params.action);
|
|
2862
2949
|
const result = scrubReasoningBlocks(params.result || 'success');
|
|
2863
|
-
|
|
2950
|
+
if (params.quality !== undefined && (typeof params.quality !== 'number'
|
|
2951
|
+
|| !Number.isFinite(params.quality) || params.quality < 0 || params.quality > 1)) {
|
|
2952
|
+
return { success: false, error: 'quality must be a finite number between 0 and 1' };
|
|
2953
|
+
}
|
|
2954
|
+
const quality = params.quality === undefined ? 0.85 : params.quality;
|
|
2864
2955
|
const timestamp = new Date().toISOString();
|
|
2865
2956
|
const stepId = `step-${Date.now()}`;
|
|
2866
2957
|
{
|
|
@@ -2874,7 +2965,7 @@ export const hooksTrajectoryStep = {
|
|
|
2874
2965
|
return { success: false, error: v.error };
|
|
2875
2966
|
}
|
|
2876
2967
|
// Add step to real trajectory if it exists
|
|
2877
|
-
const trajectory =
|
|
2968
|
+
const trajectory = await loadPendingTrajectory(trajectoryId);
|
|
2878
2969
|
if (trajectory) {
|
|
2879
2970
|
trajectory.steps.push({
|
|
2880
2971
|
action,
|
|
@@ -2883,6 +2974,7 @@ export const hooksTrajectoryStep = {
|
|
|
2883
2974
|
timestamp,
|
|
2884
2975
|
});
|
|
2885
2976
|
}
|
|
2977
|
+
const persisted = trajectory ? await persistPendingTrajectory(trajectory) : false;
|
|
2886
2978
|
// MAGE-style execution-state-tree mirror (prototype, non-fatal)
|
|
2887
2979
|
try {
|
|
2888
2980
|
const { getTrajectoryTree } = await import('../ruvector/trajectory-tree.js');
|
|
@@ -2914,11 +3006,12 @@ export const hooksTrajectoryStep = {
|
|
|
2914
3006
|
result,
|
|
2915
3007
|
quality,
|
|
2916
3008
|
recorded: !!trajectory,
|
|
3009
|
+
persisted,
|
|
2917
3010
|
timestamp,
|
|
2918
3011
|
totalSteps: trajectory?.steps.length || 0,
|
|
2919
3012
|
implementation: trajectory ? 'real-step-recording' : 'trajectory-not-found',
|
|
2920
3013
|
};
|
|
2921
|
-
},
|
|
3014
|
+
}),
|
|
2922
3015
|
};
|
|
2923
3016
|
export const hooksTrajectoryEnd = {
|
|
2924
3017
|
name: 'hooks_intelligence_trajectory-end',
|
|
@@ -2932,7 +3025,7 @@ export const hooksTrajectoryEnd = {
|
|
|
2932
3025
|
},
|
|
2933
3026
|
required: ['trajectoryId'],
|
|
2934
3027
|
},
|
|
2935
|
-
handler: async (params) => {
|
|
3028
|
+
handler: async (params) => withTrajectoryOperation(params.trajectoryId, async () => {
|
|
2936
3029
|
const trajectoryId = params.trajectoryId;
|
|
2937
3030
|
{
|
|
2938
3031
|
const v = validateIdentifier(trajectoryId, 'trajectoryId');
|
|
@@ -2944,7 +3037,7 @@ export const hooksTrajectoryEnd = {
|
|
|
2944
3037
|
const endedAt = new Date().toISOString();
|
|
2945
3038
|
const startTime = Date.now();
|
|
2946
3039
|
// Get and finalize real trajectory
|
|
2947
|
-
const trajectory =
|
|
3040
|
+
const trajectory = await loadPendingTrajectory(trajectoryId);
|
|
2948
3041
|
let persistResult = { success: false };
|
|
2949
3042
|
if (trajectory) {
|
|
2950
3043
|
trajectory.success = success;
|
|
@@ -2970,8 +3063,17 @@ export const hooksTrajectoryEnd = {
|
|
|
2970
3063
|
persistResult = { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
2971
3064
|
}
|
|
2972
3065
|
}
|
|
2973
|
-
//
|
|
2974
|
-
|
|
3066
|
+
// Failed writes remain retryable. A completed row prevents stale pending
|
|
3067
|
+
// data from being replayed even if cleanup itself fails.
|
|
3068
|
+
if (persistResult.success) {
|
|
3069
|
+
activeTrajectories.delete(trajectoryId);
|
|
3070
|
+
dirtyTrajectories.delete(trajectoryId);
|
|
3071
|
+
try {
|
|
3072
|
+
const { deleteEntry } = await import('../memory/memory-initializer.js');
|
|
3073
|
+
await deleteEntry({ key: `trajectory-pending-${trajectoryId}`, namespace: 'trajectories' });
|
|
3074
|
+
}
|
|
3075
|
+
catch { /* completed record is the replay guard */ }
|
|
3076
|
+
}
|
|
2975
3077
|
}
|
|
2976
3078
|
// MAGE-style execution-state-tree mirror (prototype, non-fatal)
|
|
2977
3079
|
try {
|
|
@@ -3187,7 +3289,7 @@ export const hooksTrajectoryEnd = {
|
|
|
3187
3289
|
? `SONA learned pattern "${sonaResult.patternKey}" with ${(sonaResult.confidence * 100).toFixed(1)}% confidence`
|
|
3188
3290
|
: (persistResult.success ? 'Trajectory persisted for future learning' : (persistResult.error || 'Trajectory not found')),
|
|
3189
3291
|
};
|
|
3190
|
-
},
|
|
3292
|
+
}),
|
|
3191
3293
|
};
|
|
3192
3294
|
// Pattern store/search hooks - REAL implementation using storeEntry
|
|
3193
3295
|
export const hooksPatternStore = {
|