@devflow-tools/mcp-server 0.17.0 → 0.17.1
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/dist/command-registry.json +128 -24
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +90 -6
- package/dist/server.js.map +1 -1
- package/dist/tool-contracts.d.ts +2 -0
- package/dist/tool-contracts.d.ts.map +1 -1
- package/dist/tool-contracts.js +32 -6
- package/dist/tool-contracts.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +303 -36
- package/dist/tools.js.map +1 -1
- package/package.json +8 -8
package/dist/tools.js
CHANGED
|
@@ -6,6 +6,7 @@ import { normalizeTurnId, openGlobalDevFlowDatabase, } from "@devflow-tools/data
|
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { isAbsolute, normalize, relative } from "node:path";
|
|
8
8
|
import { normalizeMemoryCandidates, OBSERVATION_TYPE_SCHEMA, rejectedToolResult, TOOL_RESULT_OUTPUT_SCHEMA, } from "./tool-contracts.js";
|
|
9
|
+
import { emitSemanticControlLog } from '@devflow-tools/telemetry';
|
|
9
10
|
export const GUARDED_CORE_TOOL_NAMES = new Set([
|
|
10
11
|
"get_project_context",
|
|
11
12
|
"search_symbol",
|
|
@@ -205,7 +206,7 @@ export function getCoreTools(knowledgeScopes = []) {
|
|
|
205
206
|
},
|
|
206
207
|
{
|
|
207
208
|
name: "memory_commit_turn",
|
|
208
|
-
description: "提交当前用户轮次并返回 DevFlow canonical receipt
|
|
209
|
+
description: "提交当前用户轮次并返回 DevFlow canonical receipt。显式记忆先可靠直存,再用带逐字 evidenceSpans 的宿主 candidates 幂等精炼;普通 turn 必须提供宿主语义判断得到的 candidates。",
|
|
209
210
|
inputSchema: {
|
|
210
211
|
type: "object",
|
|
211
212
|
properties: {
|
|
@@ -213,7 +214,7 @@ export function getCoreTools(knowledgeScopes = []) {
|
|
|
213
214
|
candidates: {
|
|
214
215
|
type: "array",
|
|
215
216
|
minItems: 1,
|
|
216
|
-
description: "普通 turn
|
|
217
|
+
description: "普通 turn 必填;显式记忆恢复时可省略,已有 canonical receipt 的显式 turn 则用它执行证据绑定语义精炼。",
|
|
217
218
|
items: {
|
|
218
219
|
type: "object",
|
|
219
220
|
properties: {
|
|
@@ -224,6 +225,11 @@ export function getCoreTools(knowledgeScopes = []) {
|
|
|
224
225
|
concepts: { type: "array", items: { type: "string" } },
|
|
225
226
|
importance: { type: "number", minimum: 1, maximum: 10 },
|
|
226
227
|
confidence: { type: "number", minimum: 0, maximum: 1 },
|
|
228
|
+
disposition: {
|
|
229
|
+
type: "string",
|
|
230
|
+
enum: ["durable_memory", "turn_constraint"],
|
|
231
|
+
description: "Classify reusable project truth separately from instructions that apply only to the current turn. Defaults to durable_memory for compatibility.",
|
|
232
|
+
},
|
|
227
233
|
subject: { type: "string", minLength: 1, description: "Concrete project facet or entity the durable fact concerns" },
|
|
228
234
|
predicate: { type: "string", minLength: 1, description: "Stable relation such as use, require, prefer, version, or state" },
|
|
229
235
|
value: { type: "string", minLength: 1, description: "Exact selected value; preserve package names, flags, versions, and identifiers" },
|
|
@@ -247,8 +253,14 @@ export function getCoreTools(knowledgeScopes = []) {
|
|
|
247
253
|
items: { type: "string" },
|
|
248
254
|
description: "Session event IDs that directly support this memory. Omit only when committing the current single turn event.",
|
|
249
255
|
},
|
|
256
|
+
evidenceSpans: {
|
|
257
|
+
type: "array",
|
|
258
|
+
minItems: 1,
|
|
259
|
+
maxItems: 16,
|
|
260
|
+
items: { type: "string", minLength: 1 },
|
|
261
|
+
description: "Exact, verbatim substrings copied from the canonical user prompt that support this candidate. Required when refining an already committed explicit-memory turn.",
|
|
262
|
+
},
|
|
250
263
|
},
|
|
251
|
-
required: ["type", "title"],
|
|
252
264
|
},
|
|
253
265
|
},
|
|
254
266
|
},
|
|
@@ -411,7 +423,7 @@ function replayMemoryDecision(turn) {
|
|
|
411
423
|
replayed: true,
|
|
412
424
|
};
|
|
413
425
|
}
|
|
414
|
-
function resolveMemoryDecisionTurn(database, projectRoot, sessionId, requestedTurnId) {
|
|
426
|
+
function resolveMemoryDecisionTurn(database, projectRoot, sessionId, requestedTurnId, options = {}) {
|
|
415
427
|
if (typeof requestedTurnId === 'string' && requestedTurnId.trim()) {
|
|
416
428
|
let normalized;
|
|
417
429
|
try {
|
|
@@ -437,6 +449,7 @@ function resolveMemoryDecisionTurn(database, projectRoot, sessionId, requestedTu
|
|
|
437
449
|
};
|
|
438
450
|
}
|
|
439
451
|
return requested.status === 'pending'
|
|
452
|
+
|| (options.allowCommittedExplicitRefinement && requested.status === 'committed' && requested.source === 'explicit_intent')
|
|
440
453
|
? { turn: requested }
|
|
441
454
|
: { replay: replayMemoryDecision(requested) };
|
|
442
455
|
}
|
|
@@ -547,6 +560,92 @@ function findHostAction(actions, stepId) {
|
|
|
547
560
|
const matches = actions.filter(action => action.stepId === stepId);
|
|
548
561
|
return matches.length === 1 ? matches[0] : null;
|
|
549
562
|
}
|
|
563
|
+
function resolveBoundContextTask(input) {
|
|
564
|
+
if (!input.intentArtifactHash) {
|
|
565
|
+
return {
|
|
566
|
+
query: input.agentQuery,
|
|
567
|
+
expandedTerms: input.expandedTerms,
|
|
568
|
+
requestId: input.requestId,
|
|
569
|
+
turnId: input.turnId,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
if (!input.sessionId || !input.turnId || !input.requestId) {
|
|
573
|
+
throw new Error('TASK_IDENTITY_INCOMPLETE:intent artifact binding requires session, turn, and request');
|
|
574
|
+
}
|
|
575
|
+
const database = openGlobalDevFlowDatabase(undefined, { busyTimeoutMs: 250 });
|
|
576
|
+
try {
|
|
577
|
+
const record = database.getTaskIntentArtifact(input.intentArtifactHash);
|
|
578
|
+
if (!record)
|
|
579
|
+
throw new Error(`TASK_INTENT_NOT_FOUND:${input.intentArtifactHash}`);
|
|
580
|
+
if (record.projectRoot !== input.projectRoot || record.sessionId !== input.sessionId
|
|
581
|
+
|| record.turnId !== input.turnId || record.requestId !== input.requestId) {
|
|
582
|
+
throw new Error('TASK_IDENTITY_MISMATCH:intent artifact does not belong to this MCP request');
|
|
583
|
+
}
|
|
584
|
+
const planRecord = database.getChannelQueryPlanForIntent({
|
|
585
|
+
projectRoot: record.projectRoot,
|
|
586
|
+
sessionId: record.sessionId,
|
|
587
|
+
turnId: record.turnId,
|
|
588
|
+
sourceIntentHash: record.artifactHash,
|
|
589
|
+
});
|
|
590
|
+
if (!planRecord)
|
|
591
|
+
throw new Error(`QUERY_PLAN_NOT_FOUND:${record.artifactHash}`);
|
|
592
|
+
const identity = {
|
|
593
|
+
projectRoot: record.projectRoot,
|
|
594
|
+
projectId: record.projectId,
|
|
595
|
+
hostId: record.hostId,
|
|
596
|
+
sessionId: record.sessionId,
|
|
597
|
+
turnId: record.turnId,
|
|
598
|
+
requestId: record.requestId,
|
|
599
|
+
...(record.executionId ? { executionId: record.executionId } : {}),
|
|
600
|
+
};
|
|
601
|
+
const artifact = {
|
|
602
|
+
identity,
|
|
603
|
+
version: record.version,
|
|
604
|
+
...(record.supersedesHash ? { supersedesHash: record.supersedesHash } : {}),
|
|
605
|
+
rawPrompt: record.rawPrompt,
|
|
606
|
+
normalizedPrompt: record.normalizedPrompt,
|
|
607
|
+
...(record.command ? { command: record.command } : {}),
|
|
608
|
+
slashArgs: record.slashArgs,
|
|
609
|
+
...(record.activeSkill ? { activeSkill: record.activeSkill } : {}),
|
|
610
|
+
intent: record.intent,
|
|
611
|
+
action: record.action,
|
|
612
|
+
entities: record.entities,
|
|
613
|
+
targetAnchors: record.targetAnchors,
|
|
614
|
+
policyConstraints: record.policyConstraints,
|
|
615
|
+
classificationEvidence: record.classificationEvidence,
|
|
616
|
+
sourceEventIds: record.sourceEventIds,
|
|
617
|
+
sourceHash: record.sourceHash,
|
|
618
|
+
artifactHash: record.artifactHash,
|
|
619
|
+
createdAt: record.createdAt,
|
|
620
|
+
};
|
|
621
|
+
const queryPlan = {
|
|
622
|
+
identity,
|
|
623
|
+
sourceIntentHash: planRecord.sourceIntentHash,
|
|
624
|
+
planHash: planRecord.planHash,
|
|
625
|
+
code: planRecord.code,
|
|
626
|
+
memory: planRecord.memory,
|
|
627
|
+
knowledge: planRecord.knowledge,
|
|
628
|
+
generatedBy: planRecord.generatedBy,
|
|
629
|
+
...(planRecord.degradation ? { degradation: planRecord.degradation } : {}),
|
|
630
|
+
createdAt: planRecord.createdAt,
|
|
631
|
+
};
|
|
632
|
+
const query = stripArtifactCommand(artifact.normalizedPrompt, artifact.command);
|
|
633
|
+
const expandedTerms = [input.agentQuery, input.expandedTerms]
|
|
634
|
+
.filter((value) => typeof value === 'string' && value.trim().length > 0)
|
|
635
|
+
.join(' ')
|
|
636
|
+
.slice(0, 4_000) || undefined;
|
|
637
|
+
return { query, expandedTerms, requestId: record.requestId, turnId: record.turnId, intentArtifact: artifact, queryPlan };
|
|
638
|
+
}
|
|
639
|
+
finally {
|
|
640
|
+
database.close();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function stripArtifactCommand(prompt, command) {
|
|
644
|
+
if (!command)
|
|
645
|
+
return prompt;
|
|
646
|
+
const escaped = command.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
647
|
+
return prompt.replace(new RegExp(`^\\/${escaped}\\b`, 'iu'), '').trim() || prompt;
|
|
648
|
+
}
|
|
550
649
|
export function createToolHandlers(engines, boundProjectRoot) {
|
|
551
650
|
const hasBoundProjectRoot = typeof boundProjectRoot === 'string' && boundProjectRoot.trim().length > 0;
|
|
552
651
|
const canonicalProjectRoot = canonicalizeProjectRoot(String(boundProjectRoot ?? process.cwd()));
|
|
@@ -560,23 +659,37 @@ export function createToolHandlers(engines, boundProjectRoot) {
|
|
|
560
659
|
}
|
|
561
660
|
}
|
|
562
661
|
const handlers = {
|
|
563
|
-
get_project_context: async ({ query, mode, expandedTerms, retrievalSessionId, baselineReceipt, missingContext, _devflow_session_id, _devflow_execution_id, _devflow_request_id, _devflow_active_skill, activeSkill }) =>
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
:
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
662
|
+
get_project_context: async ({ query, mode, expandedTerms, retrievalSessionId, baselineReceipt, missingContext, _devflow_session_id, _devflow_execution_id, _devflow_request_id, _devflow_turn_id, _devflow_intent_artifact_hash, _devflow_active_skill, activeSkill }) => {
|
|
663
|
+
const boundTask = resolveBoundContextTask({
|
|
664
|
+
projectRoot: canonicalProjectRoot,
|
|
665
|
+
sessionId: typeof _devflow_session_id === 'string' ? _devflow_session_id : undefined,
|
|
666
|
+
requestId: typeof _devflow_request_id === 'string' ? _devflow_request_id : undefined,
|
|
667
|
+
turnId: typeof _devflow_turn_id === 'string' ? _devflow_turn_id : undefined,
|
|
668
|
+
intentArtifactHash: typeof _devflow_intent_artifact_hash === 'string' ? _devflow_intent_artifact_hash : undefined,
|
|
669
|
+
agentQuery: query,
|
|
670
|
+
expandedTerms: typeof expandedTerms === 'string' ? expandedTerms : undefined,
|
|
671
|
+
});
|
|
672
|
+
return getProjectContextResult(engines.context, {
|
|
673
|
+
query: boundTask.query,
|
|
674
|
+
mode: mode ?? "fast",
|
|
675
|
+
expandedTerms: boundTask.expandedTerms,
|
|
676
|
+
retrievalSessionId: typeof retrievalSessionId === 'string' ? retrievalSessionId : undefined,
|
|
677
|
+
baselineReceipt: typeof baselineReceipt === 'string' ? baselineReceipt : undefined,
|
|
678
|
+
missingContext: Array.isArray(missingContext) ? missingContext : undefined,
|
|
679
|
+
sessionId: typeof _devflow_session_id === 'string' ? _devflow_session_id : process.env.DEVFLOW_SESSION_ID ?? undefined,
|
|
680
|
+
executionId: typeof _devflow_execution_id === 'string' ? _devflow_execution_id : undefined,
|
|
681
|
+
requestId: boundTask.requestId,
|
|
682
|
+
turnId: boundTask.turnId,
|
|
683
|
+
intentArtifact: boundTask.intentArtifact,
|
|
684
|
+
queryPlan: boundTask.queryPlan,
|
|
685
|
+
activeSkill: boundTask.intentArtifact?.activeSkill ?? (typeof _devflow_active_skill === 'string'
|
|
686
|
+
? _devflow_active_skill
|
|
687
|
+
: typeof activeSkill === 'string' ? activeSkill : undefined),
|
|
688
|
+
projectRoot: boundProjectRoot,
|
|
689
|
+
host: 'claude-code',
|
|
690
|
+
command: boundTask.intentArtifact?.command ?? 'devflow:context',
|
|
691
|
+
}, getFreshnessMs());
|
|
692
|
+
},
|
|
580
693
|
search_symbol: async ({ symbol }) => {
|
|
581
694
|
const result = await engines.indexer.findSymbol(symbol);
|
|
582
695
|
return {
|
|
@@ -987,18 +1100,22 @@ export function createToolHandlers(engines, boundProjectRoot) {
|
|
|
987
1100
|
}
|
|
988
1101
|
const database = openGlobalDevFlowDatabase();
|
|
989
1102
|
try {
|
|
990
|
-
const resolution = resolveMemoryDecisionTurn(database, String(projectRoot), sessionId, turnId
|
|
1103
|
+
const resolution = resolveMemoryDecisionTurn(database, String(projectRoot), sessionId, turnId, {
|
|
1104
|
+
allowCommittedExplicitRefinement: Array.isArray(candidates) && candidates.length > 0,
|
|
1105
|
+
});
|
|
991
1106
|
if (resolution.replay)
|
|
992
1107
|
return resolution.replay;
|
|
993
1108
|
if (resolution.error || !resolution.turn)
|
|
994
1109
|
return { ...resolution.error, committed: false };
|
|
995
1110
|
const pending = resolution.turn;
|
|
1111
|
+
const explicitRefinement = pending.status === 'committed' && pending.source === 'explicit_intent';
|
|
996
1112
|
const recovery = getMemoryTurnRecoveryPayload(database, pending);
|
|
997
1113
|
const memory = engines.memory;
|
|
998
|
-
if (recovery?.operation === 'commit_explicit' && recovery.explicitContent?.trim()) {
|
|
1114
|
+
if (pending.status === 'pending' && recovery?.operation === 'commit_explicit' && recovery.explicitContent?.trim()) {
|
|
999
1115
|
await ensureMemoryTurnEvent(memory, pending, recovery);
|
|
1000
1116
|
const saved = await memory.saveExplicitMemoryIntent(recovery.explicitContent.trim(), sessionId, pending.eventId);
|
|
1001
|
-
const
|
|
1117
|
+
const recoveredMemoryIds = [...new Set([saved.id, ...(saved.relatedMemoryIds ?? [])])];
|
|
1118
|
+
const receipt = commitMemoryReceipt(database, pending, recoveredMemoryIds, 'explicit_intent', 'explicit_user_request_mcp_recovery');
|
|
1002
1119
|
if (memory.finalizeTurnDecision && receipt.receiptId) {
|
|
1003
1120
|
try {
|
|
1004
1121
|
await memory.finalizeTurnDecision({
|
|
@@ -1007,7 +1124,7 @@ export function createToolHandlers(engines, boundProjectRoot) {
|
|
|
1007
1124
|
turnId: pending.turnId,
|
|
1008
1125
|
receiptId: receipt.receiptId,
|
|
1009
1126
|
status: 'committed',
|
|
1010
|
-
memoryIds:
|
|
1127
|
+
memoryIds: recoveredMemoryIds,
|
|
1011
1128
|
reason: 'explicit_user_request_mcp_recovery',
|
|
1012
1129
|
});
|
|
1013
1130
|
}
|
|
@@ -1029,15 +1146,17 @@ export function createToolHandlers(engines, boundProjectRoot) {
|
|
|
1029
1146
|
});
|
|
1030
1147
|
}
|
|
1031
1148
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1149
|
+
for (const memoryId of recoveredMemoryIds) {
|
|
1150
|
+
database.enqueueWork({
|
|
1151
|
+
idempotencyKey: `vector:${String(projectRoot)}:${memoryId}`,
|
|
1152
|
+
kind: 'memory.vector_backfill',
|
|
1153
|
+
projectRoot: String(projectRoot),
|
|
1154
|
+
sessionId,
|
|
1155
|
+
turnId: pending.turnId,
|
|
1156
|
+
payload: { observationId: memoryId },
|
|
1157
|
+
maxAttempts: 20,
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1041
1160
|
return acceptedMemoryReceipt(receipt, {
|
|
1042
1161
|
acceptedCandidates: [],
|
|
1043
1162
|
rejectedCandidates: [],
|
|
@@ -1056,6 +1175,19 @@ export function createToolHandlers(engines, boundProjectRoot) {
|
|
|
1056
1175
|
accepted: 0, rejected: validation.rejected,
|
|
1057
1176
|
},
|
|
1058
1177
|
});
|
|
1178
|
+
if (explicitRefinement) {
|
|
1179
|
+
return {
|
|
1180
|
+
...replayMemoryDecision(pending),
|
|
1181
|
+
status: 'degraded',
|
|
1182
|
+
replayed: false,
|
|
1183
|
+
refined: false,
|
|
1184
|
+
degradation: {
|
|
1185
|
+
reason: 'invalid_host_refinement_candidates',
|
|
1186
|
+
fallback: 'deterministic_explicit_memory',
|
|
1187
|
+
},
|
|
1188
|
+
rejectedCandidates: validation.rejected,
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1059
1191
|
return {
|
|
1060
1192
|
...rejectedToolResult('INVALID_MEMORY_CANDIDATES', 'No valid durable memory candidates were supplied.', false, validation.rejected.flatMap(candidate => candidate.fieldErrors)),
|
|
1061
1193
|
committed: false,
|
|
@@ -1077,11 +1209,124 @@ export function createToolHandlers(engines, boundProjectRoot) {
|
|
|
1077
1209
|
entities: candidate.entities,
|
|
1078
1210
|
temporal: candidate.temporal,
|
|
1079
1211
|
sourceEventIds: candidate.sourceEventIds,
|
|
1212
|
+
evidenceSpans: candidate.evidenceSpans,
|
|
1213
|
+
disposition: candidate.disposition,
|
|
1080
1214
|
}));
|
|
1081
1215
|
await ensureMemoryTurnEvent(memory, pending, recovery);
|
|
1216
|
+
if (explicitRefinement) {
|
|
1217
|
+
if (!memory.refineExplicitTurnObservations) {
|
|
1218
|
+
return {
|
|
1219
|
+
...replayMemoryDecision(pending),
|
|
1220
|
+
status: 'degraded',
|
|
1221
|
+
replayed: false,
|
|
1222
|
+
degradation: {
|
|
1223
|
+
reason: 'explicit_refinement_unavailable',
|
|
1224
|
+
fallback: 'deterministic_explicit_memory',
|
|
1225
|
+
},
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
try {
|
|
1229
|
+
const refined = await memory.refineExplicitTurnObservations(pending.eventId, sessionId, parsed);
|
|
1230
|
+
const refinedMemoryIds = refined.memories.map(item => item.id);
|
|
1231
|
+
const refinementReceiptId = `memory-receipt:${createHash('sha256')
|
|
1232
|
+
.update(`${pending.turnId}\0host_refinement\0${[...refinedMemoryIds, ...refined.retiredMemoryIds].sort().join('\0')}`)
|
|
1233
|
+
.digest('hex').slice(0, 24)}`;
|
|
1234
|
+
if (memory.finalizeTurnDecision) {
|
|
1235
|
+
await memory.finalizeTurnDecision({
|
|
1236
|
+
turnEventId: pending.eventId,
|
|
1237
|
+
sessionId,
|
|
1238
|
+
turnId: pending.turnId,
|
|
1239
|
+
receiptId: refinementReceiptId,
|
|
1240
|
+
status: 'committed',
|
|
1241
|
+
memoryIds: refinedMemoryIds,
|
|
1242
|
+
reason: 'host_explicit_semantic_refinement',
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
for (const memoryId of refinedMemoryIds) {
|
|
1246
|
+
database.enqueueWork({
|
|
1247
|
+
idempotencyKey: `vector:${String(projectRoot)}:${memoryId}`,
|
|
1248
|
+
kind: 'memory.vector_backfill',
|
|
1249
|
+
projectRoot: String(projectRoot),
|
|
1250
|
+
sessionId,
|
|
1251
|
+
turnId: pending.turnId,
|
|
1252
|
+
payload: { observationId: memoryId },
|
|
1253
|
+
maxAttempts: 20,
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
database.insertEvent({
|
|
1257
|
+
kind: 'memory_semantic_refinement',
|
|
1258
|
+
timestamp: Date.now(),
|
|
1259
|
+
success: true,
|
|
1260
|
+
metadata: {
|
|
1261
|
+
projectRoot: String(projectRoot), sessionId, turnId: pending.turnId,
|
|
1262
|
+
receiptId: pending.receiptId, refinementReceiptId,
|
|
1263
|
+
accepted: refinedMemoryIds.length,
|
|
1264
|
+
retired: refined.retiredMemoryIds.length,
|
|
1265
|
+
operations: refined.operations,
|
|
1266
|
+
},
|
|
1267
|
+
});
|
|
1268
|
+
return {
|
|
1269
|
+
...replayMemoryDecision(pending),
|
|
1270
|
+
replayed: false,
|
|
1271
|
+
refined: true,
|
|
1272
|
+
refinementReceiptId,
|
|
1273
|
+
data: {
|
|
1274
|
+
acceptedCandidates: validation.accepted.map(candidate => ({
|
|
1275
|
+
index: candidate.index,
|
|
1276
|
+
type: candidate.type,
|
|
1277
|
+
disposition: candidate.disposition,
|
|
1278
|
+
normalizedFrom: candidate.normalizedFrom,
|
|
1279
|
+
})),
|
|
1280
|
+
rejectedCandidates: validation.rejected,
|
|
1281
|
+
refinedMemoryIds,
|
|
1282
|
+
retiredMemoryIds: refined.retiredMemoryIds,
|
|
1283
|
+
operations: refined.operations,
|
|
1284
|
+
fallbackReceiptPreserved: true,
|
|
1285
|
+
},
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
catch (error) {
|
|
1289
|
+
const reason = error.message;
|
|
1290
|
+
database.insertEvent({
|
|
1291
|
+
kind: 'memory_semantic_refinement',
|
|
1292
|
+
timestamp: Date.now(),
|
|
1293
|
+
success: false,
|
|
1294
|
+
metadata: {
|
|
1295
|
+
projectRoot: String(projectRoot), sessionId, turnId: pending.turnId,
|
|
1296
|
+
receiptId: pending.receiptId, reason,
|
|
1297
|
+
fallback: 'deterministic_explicit_memory',
|
|
1298
|
+
},
|
|
1299
|
+
});
|
|
1300
|
+
emitSemanticControlLog({
|
|
1301
|
+
event: 'memory.write.degraded',
|
|
1302
|
+
identity: { projectRoot: String(projectRoot), sessionId, turnId: pending.turnId },
|
|
1303
|
+
level: 'warn',
|
|
1304
|
+
timestamp: Date.now(),
|
|
1305
|
+
data: { stage: 'explicit_host_refinement', reason, fallback: 'deterministic_explicit_memory' },
|
|
1306
|
+
});
|
|
1307
|
+
return {
|
|
1308
|
+
...replayMemoryDecision(pending),
|
|
1309
|
+
status: 'degraded',
|
|
1310
|
+
replayed: false,
|
|
1311
|
+
refined: false,
|
|
1312
|
+
degradation: { reason, fallback: 'deterministic_explicit_memory' },
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
const durableParsed = parsed.filter(candidate => candidate.disposition !== 'turn_constraint');
|
|
1317
|
+
if (durableParsed.length === 0) {
|
|
1318
|
+
return {
|
|
1319
|
+
...rejectedToolResult('NO_DURABLE_MEMORY_CANDIDATES', 'All candidates were classified as turn constraints. Call memory_skip_turn for this turn.', false),
|
|
1320
|
+
committed: false,
|
|
1321
|
+
acceptedCandidates: validation.accepted.map(candidate => ({
|
|
1322
|
+
index: candidate.index,
|
|
1323
|
+
disposition: candidate.disposition,
|
|
1324
|
+
})),
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1082
1327
|
let saved;
|
|
1083
1328
|
try {
|
|
1084
|
-
saved = await memory.saveTurnObservations(pending.eventId, sessionId,
|
|
1329
|
+
saved = await memory.saveTurnObservations(pending.eventId, sessionId, durableParsed);
|
|
1085
1330
|
}
|
|
1086
1331
|
catch (error) {
|
|
1087
1332
|
const reason = error.message;
|
|
@@ -1172,6 +1417,23 @@ export function createToolHandlers(engines, boundProjectRoot) {
|
|
|
1172
1417
|
receiptId,
|
|
1173
1418
|
reason: skipReason,
|
|
1174
1419
|
});
|
|
1420
|
+
emitSemanticControlLog({
|
|
1421
|
+
event: 'memory.semantic.decided',
|
|
1422
|
+
identity: {
|
|
1423
|
+
projectRoot: String(projectRoot),
|
|
1424
|
+
sessionId,
|
|
1425
|
+
turnId: pending.turnId,
|
|
1426
|
+
actionReceipt: receipt.receiptId,
|
|
1427
|
+
},
|
|
1428
|
+
level: 'info',
|
|
1429
|
+
timestamp: Date.now(),
|
|
1430
|
+
data: {
|
|
1431
|
+
status: 'skipped',
|
|
1432
|
+
operations: [],
|
|
1433
|
+
memoryIds: [],
|
|
1434
|
+
reason: skipReason.slice(0, 500),
|
|
1435
|
+
},
|
|
1436
|
+
});
|
|
1175
1437
|
if (memory.finalizeTurnDecision && receipt.receiptId) {
|
|
1176
1438
|
try {
|
|
1177
1439
|
await memory.finalizeTurnDecision({
|
|
@@ -1254,7 +1516,12 @@ export function createToolHandlers(engines, boundProjectRoot) {
|
|
|
1254
1516
|
vectorQueueStatus = 'queued';
|
|
1255
1517
|
}
|
|
1256
1518
|
catch (error) {
|
|
1257
|
-
|
|
1519
|
+
emitSemanticControlLog({
|
|
1520
|
+
event: 'memory.write.degraded',
|
|
1521
|
+
identity: { projectRoot: canonicalProjectRoot, sessionId: typeof _devflow_session_id === 'string' ? _devflow_session_id : undefined },
|
|
1522
|
+
level: 'warn', timestamp: Date.now(),
|
|
1523
|
+
data: { stage: 'distill_vector_enqueue', batchId, reason: error.message, fallback: 'lexical_memory_ready' },
|
|
1524
|
+
});
|
|
1258
1525
|
}
|
|
1259
1526
|
finally {
|
|
1260
1527
|
database.close();
|