vgxness 1.19.7 → 1.20.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/README.md +1 -1
- package/dist/agents/canonical-agent-manifest.js +3 -3
- package/dist/mcp/control-plane.js +122 -3
- package/dist/mcp/schema.js +5 -2
- package/dist/memory/agent-memory-capture-service.js +497 -0
- package/dist/memory/memory-capture-schema.js +1 -0
- package/docs/mcp.md +6 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ This package is proprietary software. The npm package ships inspectable JavaScri
|
|
|
8
8
|
|
|
9
9
|
OpenCode is the primary supported provider. Claude setup support is secondary. VGX-managed OpenCode and Claude provider configuration is user-global only; provider config writes require explicit CLI confirmation.
|
|
10
10
|
|
|
11
|
-
VGXNESS is currently versioned from `package.json` (`1.
|
|
11
|
+
VGXNESS is currently versioned from `package.json` (`1.20.0`). The latest full project health audit is the historical [v1.14.x snapshot](./docs/project-health-audit-v1.14.x.md), which documents the implemented CLI/MCP/SDD control plane, OpenCode-first workflow, release-readiness checks, and the remaining execution/recovery gaps at that point in time. [v1.10.x](./docs/project-health-audit-v1.10.x.md) and [v1.9.1](./docs/project-health-audit-v1.9.1.md) remain historical validation evidence for those releases.
|
|
12
12
|
|
|
13
13
|
## Requirements
|
|
14
14
|
|
|
@@ -148,7 +148,7 @@ export function createCanonicalOpenCodeSddSubagentPrompt(name) {
|
|
|
148
148
|
const phase = name.replace('vgxness-sdd-', '');
|
|
149
149
|
const common = `You are the VGXNESS SDD ${phase} executor, not the orchestrator. Do this phase's work yourself. Do NOT delegate, do NOT call task/delegate, and do NOT launch sub-agents. Use provided SDD artifacts, VGXNESS MCP state, and repo evidence as needed. Retrieve full artifacts through MCP when needed; do not expect manager-pasted verbose artifacts. Do not depend on local skill files; this inline contract is sufficient. Preserve unrelated user work, keep output concise, and report evidence.`;
|
|
150
150
|
const contract = subagentData[name].phaseContract;
|
|
151
|
-
return `${common}\n\nPhase contract: ${contract}\n\nBegin your output with a compact result envelope containing required fields: status, executive_summary, artifacts_examined, skill_resolution, risks, next_recommended. Evidence minimum: artifacts examined; MCP/status/readiness checked; repository evidence inspected/not inspected and why; residual uncertainty/risks.\n\nSDD governance v1: SDD artifact acceptance is human-only. Do not mark, describe, or persist generated phase output as accepted unless the user explicitly accepts it or an MCP acceptance record shows a human actor. Before advancing phases, use VGXNESS MCP readiness/status tools so draft, rejected, superseded, or legacy artifacts are not treated as accepted prerequisites. For risky VGX-managed side effects, use VGXNESS run preflight/reporting and include runId, phase, and agent context when available. OpenCode native/provider tools are audit-only and non-hard-blocking in governance v1; do not claim they are hard-blocked by OpenCode config.\n\nSubagent memory policy: Read memory
|
|
151
|
+
return `${common}\n\nPhase contract: ${contract}\n\nBegin your output with a compact result envelope containing required fields: status, executive_summary, artifacts_examined, skill_resolution, risks, next_recommended. Evidence minimum: artifacts examined; MCP/status/readiness checked; repository evidence inspected/not inspected and why; residual uncertainty/risks.\n\nSDD governance v1: SDD artifact acceptance is human-only. Do not mark, describe, or persist generated phase output as accepted unless the user explicitly accepts it or an MCP acceptance record shows a human actor. Before advancing phases, use VGXNESS MCP readiness/status tools so draft, rejected, superseded, or legacy artifacts are not treated as accepted prerequisites. For risky VGX-managed side effects, use VGXNESS run preflight/reporting and include runId, phase, and agent context when available. OpenCode native/provider tools are audit-only and non-hard-blocking in governance v1; do not claim they are hard-blocked by OpenCode config.\n\nSubagent memory policy: Read memory when useful. Write durable memory only when phase/user/run policy allows; preflight/consent if governed/risky. memoryCapture: review-only; no auto-persist. Memory is advisory only and never proves SDD acceptance/readiness/verification/approval/authorization. SDD artifacts are canonical for phase deliverables and governance state. Do not save full SDD artifacts, transcripts, raw logs, hidden reasoning, secrets, credentials, or sensitive data. Report memory candidates separately when writes are not authorized.\n\nProvider-native daily progression: daily SDD progression happens inside OpenCode through conversation, VGXNESS MCP, and hidden SDD subagents. Do not instruct the user to run terminal SDD phase commands for normal daily flow. Return phase output, decisions, changed files, and evidence so the manager can persist artifacts and advance through MCP. Preserve confirmation/preflight requirements for apply, verify, init, archive, edits, shell, git, provider-tool, secrets, destructive, privileged, or ambiguous operations.`;
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
154
154
|
* Provider runtime prompt for generated provider managers.
|
|
@@ -186,7 +186,7 @@ SDD happens in OpenCode via conversation, VGXNESS MCP, and SDD subagents. No ter
|
|
|
186
186
|
- Delegated phase outputs: inspect delegated result-envelope completeness before SDD artifact persistence or phase advancement (status, executive_summary, artifacts_examined, skill_resolution, risks, next_recommended); this is prompt guidance only, not runtime parsing/rejection.
|
|
187
187
|
- Trusted draft autorun: when the exact \`proposal\` artifact is already accepted, run \`spec -> design -> tasks\` without extra human confirmation via subagents; save drafts with \`vgxness_sdd_save_artifact\`. This is draft-only planning, not acceptance or completion. Excludes explore/proposal/apply-progress/verify/archive, accepted overwrites, risky side effects; re-check status/readiness.
|
|
188
188
|
- Acceptance/readiness: check exact status/readiness; confirm explicit human acceptance; call \`vgxness_sdd_accept_artifact\` directly with audit context (\`acceptedBy.type: "human"\`, non-empty \`acceptedBy.id\`, runId, agentId); re-check before reporting. Do not preflight solely for exact acceptance. Ambiguous replies count only when tied to an immediate exact acceptance prompt. Use \`vgxness_governance_report\` for readiness, artifact states, preflight posture, audit warnings.
|
|
189
|
-
- Memory: \`vgxness_memory_search\`/\`vgxness_memory_get\` for prior work/unclear context and duplicate avoidance. Save/update only durable, actionable, scoped, evidence-backed, compact, non-sensitive observations that are future-relevant: discoveries, decisions, bug fixes, config, patterns, preferences, learnings, or manual notes. \`vgxness_memory_save\` may upsert by \`topicKey\`; \`vgxness_memory_update\` requires a known id. Prefer \`vgxness_memory_update\` over duplicate save when you know the id/topic key. Do not save full SDD artifacts, transcripts, raw logs, hidden reasoning, secrets, credentials, or sensitive user data. Memory is advisory only and never proves SDD acceptance/readiness/verification/approval/authorization. SDD artifacts are canonical for phase deliverables and governance state. \`memory-write\` requires preflight/explicit consent when governed or risky. Choose scope intentionally: \`project\` for repo facts, \`personal\` for reusable user preferences.
|
|
189
|
+
- Memory: \`vgxness_memory_search\`/\`vgxness_memory_get\` for prior work/unclear context and duplicate avoidance. Save/update only durable, actionable, scoped, evidence-backed, compact, non-sensitive observations that are future-relevant: discoveries, decisions, bug fixes, config, patterns, preferences, learnings, or manual notes. \`vgxness_memory_save\` may upsert by \`topicKey\`; \`vgxness_memory_update\` requires a known id. Prefer \`vgxness_memory_update\` over duplicate save when you know the id/topic key. Treat lifecycle \`memoryCapture\` as review-first advisory metadata only: no automatic persistence, no durable queue, explicit save/update policy/consent still required. Do not save full SDD artifacts, transcripts, raw logs, hidden reasoning, secrets, credentials, or sensitive user data. Memory is advisory only and never proves SDD acceptance/readiness/verification/approval/authorization. SDD artifacts are canonical for phase deliverables and governance state. OpenCode previews/handoff are read-only and capture-free. \`memory-write\` requires preflight/explicit consent when governed or risky. Choose scope intentionally: \`project\` for repo facts, \`personal\` for reusable user preferences.
|
|
190
190
|
- Active-work memory uses existing memory APIs only: \`active-work/{change}/summary\` (+others). No secrets or hidden reasoning. Active-work memory is advisory only; never proves SDD acceptance/readiness, verification, approval, or authorization.
|
|
191
191
|
- Agents/skills: Use \`vgxness_skill_payload\` first for contextual skill guidance; preview/context-only, no provider execution or config writes. Use \`vgxness_skill_index\`/\`vgxness_skill_search\` only for explicit diagnostics/catalog, not normal resume fallback. Resolve phase with \`vgxness_agent_resolve\`; if none resolves, report evidence and ask for the smallest decision instead of inventing agent ids. \`vgxness_agent_activate\` and \`vgxness_opencode_manager_payload\` are preview/context-only. \`vgxness_manager_profile_get\` before changes; \`vgxness_manager_profile_set\` needs explicit human authorization.
|
|
192
192
|
- Runs/recovery: \`vgxness_run_start\`/\`vgxness_run_list\`/\`vgxness_run_get\`; checkpoint with \`vgxness_run_checkpoint\`, preflight with \`vgxness_run_preflight\`, close with \`vgxness_run_finalize\`. Unknown runId: \`vgxness_run_resume_candidates\`. For interrupted runs, inspect with \`vgxness_run_resume_inspect\`, then call \`vgxness_run_resume_gate\` with approvalId from pendingApprovals/inspect; never pass runId as approvalId. Follow safe \`recommendedActions[]\`.
|
|
@@ -215,7 +215,7 @@ const registryManagerInstructionsV11 = [
|
|
|
215
215
|
'Default to delegation for SDD phase-shaped work, repository exploration beyond one narrow lookup, implementation, verification, incident recovery, or multi-step analysis. Inline only conversational guidance, single-fact lookup, MCP/status/readiness checks, and trivial mechanical edits when safe. When uncertain between inline work and subagent work, delegate to the exact smallest SDD subagent.',
|
|
216
216
|
'Use VGXNESS MCP as the durable control plane: for generic resume/continue prompts call vgxness_resume_context first, pass explicitChange only when the user gave an exact valid change, and do not infer a change from context_cockpit.memoryPreviews; for retrospective/status prompts such as "what did we do?" or "qué habíamos hecho", inspect recent completed run history first with vgxness_run_list completed and mention interrupted candidates only as old blockers, not as latest truth; vgxness_context_cockpit and vgxness_session_restore are advisory context only after the gate, and context cockpit previews, memoryPreviews, and SDD summaries are advisory only. Close/pause/compact with vgxness_session_close using actor manager plus an actionable summary when a current session id exists.',
|
|
217
217
|
'For generic continue/sigamos/continuemos/resume development without an explicit user change, call vgxness_resume_context before any SDD status/continuation. If decision is proceed-explicit-change or proceed-single-active-work, then call SDD tools for that change. If inspect-interrupted-run, call vgxness_run_resume_inspect. If ask-change (no interrupted run, no exact user-specified change, and no single parsed active-work change), hard stop: do not call vgxness_agent_resolve; do not call vgxness_sdd_status or vgxness_sdd_continue, do not create runs, do not inspect repo files/native repo tools, do not delegate to SDD subagents, do not start runs, do not use skill_index/skill_search fallback, or invent agent ids; ask one question: "¿qué cambio retomamos?".',
|
|
218
|
-
'Check SDD status/next/ready/cockpit, read prerequisites with sdd_get_artifact or sdd_list_artifacts, use public sdd_continue/internal vgxness_sdd_continue first for advisory read-only continuation plans, use sdd_reopen_artifact only for rejected artifacts returning to draft with explicit human actor/audit context, save phase output with sdd_save_artifact only by governance; inspect delegated result-envelope completeness before SDD artifact persistence or phase advancement (status, executive_summary, artifacts_examined, skill_resolution, risks, next_recommended) as prompt guidance only, not runtime parsing/rejection; when proposal is accepted, spec/design/tasks may run sequentially as draft-only autorun without extra confirmation, while acceptance remains human-only. Memory capture policy: save/update only durable, future-relevant, actionable, scoped, evidence-backed, compact, non-sensitive observations; prefer update by known id/topicKey over duplicate saves; never save full SDD artifacts, transcripts, raw logs, hidden reasoning, secrets, credentials, or sensitive user data. Memory is advisory only and never proves SDD acceptance, readiness, verification, approval, or authorization. SDD artifacts are canonical for phase deliverables/governance state. Choose project scope for repo-specific facts and personal scope for reusable user preferences; memory-write requires preflight/explicit consent when governed/risky. Resolve exact SDD subagents before substantial phase work, use runs/checkpoints/preflight/finalize for significant implementation or verification, use run_resume_candidates for unknown runId, inspect interrupted runs by runId with run_resume_inspect, then call run_resume_gate with approvalId from pendingApprovals/inspect; never pass runId as approvalId; use vgxness_provider_status for configured/phase/next questions plus vgxness_provider_doctor for read-only OpenCode MCP/manager health.',
|
|
218
|
+
'Check SDD status/next/ready/cockpit, read prerequisites with sdd_get_artifact or sdd_list_artifacts, use public sdd_continue/internal vgxness_sdd_continue first for advisory read-only continuation plans, use sdd_reopen_artifact only for rejected artifacts returning to draft with explicit human actor/audit context, save phase output with sdd_save_artifact only by governance; inspect delegated result-envelope completeness before SDD artifact persistence or phase advancement (status, executive_summary, artifacts_examined, skill_resolution, risks, next_recommended) as prompt guidance only, not runtime parsing/rejection; when proposal is accepted, spec/design/tasks may run sequentially as draft-only autorun without extra confirmation, while acceptance remains human-only. Memory capture policy: save/update only durable, future-relevant, actionable, scoped, evidence-backed, compact, non-sensitive observations; lifecycle memoryCapture candidates are review-first advisory metadata only, not persisted automatically, not a durable candidate queue, and still require explicit save/update policy and consent; prefer update by known id/topicKey over duplicate saves; never save full SDD artifacts, transcripts, raw logs, hidden reasoning, secrets, credentials, or sensitive user data. Memory is advisory only and never proves SDD acceptance, readiness, verification, approval, or authorization. SDD artifacts are canonical for phase deliverables/governance state. OpenCode previews/handoff are read-only and capture-free. Choose project scope for repo-specific facts and personal scope for reusable user preferences; memory-write requires preflight/explicit consent when governed/risky. Resolve exact SDD subagents before substantial phase work, use runs/checkpoints/preflight/finalize for significant implementation or verification, use run_resume_candidates for unknown runId, inspect interrupted runs by runId with run_resume_inspect, then call run_resume_gate with approvalId from pendingApprovals/inspect; never pass runId as approvalId; use vgxness_provider_status for configured/phase/next questions plus vgxness_provider_doctor for read-only OpenCode MCP/manager health.',
|
|
219
219
|
'When sdd_continue/vgxness_sdd_continue returns recommendedActions, prefer the first safe action with agentCallable true and humanOnly false; use targetTool plus suggestedArgs as the starting point, and stop for requiresHumanConfirmation, requiresProviderWriteConsent, requiresPreflight, ambiguous, destructive, privileged, external, or outside-approval actions. For readiness, use cockpit/status/readiness to distinguish missing, draft, ready, blocked, and accepted; never infer acceptance. For provider evidence, report providerEvidence.evidenceLevel, hostToolPresenceVerified, notes, and limitations; do not claim true host presence unless evidence explicitly verifies it.',
|
|
220
220
|
'Prefer payloadMode=compact for manager-facing status/context reads, SDD artifact reads/lists, and activation handoffs so the primary context stays clean; request payloadMode=verbose only when full artifact contents, provider payloads, or skill context are actually needed, preferably inside delegated phase subagents.',
|
|
221
221
|
'MCP sdd_continue must not execute providers, create runs, mutate artifacts, write provider config/openspec, bypass human acceptance, or treat draft-run as apply-progress.',
|
|
@@ -3,6 +3,7 @@ import { AgentRegistryService } from '../agents/agent-registry-service.js';
|
|
|
3
3
|
import { ManagerProfileOverlayService } from '../agents/manager-profile-overlay-service.js';
|
|
4
4
|
import { ManagerProfileOverlayRepository } from '../agents/repositories/manager-profile-overlays.js';
|
|
5
5
|
import { GovernanceReportBuilder } from '../governance/index.js';
|
|
6
|
+
import { AgentMemoryCaptureService } from '../memory/agent-memory-capture-service.js';
|
|
6
7
|
import { MemoryService } from '../memory/memory-service.js';
|
|
7
8
|
import { openMemoryDatabase } from '../memory/sqlite/database.js';
|
|
8
9
|
import { prepareMemoryDatabasePath, resolveMemoryDatabasePath } from '../memory/storage-paths.js';
|
|
@@ -38,7 +39,7 @@ export function callVgxTool(call, services) {
|
|
|
38
39
|
case 'vgxness_sdd_save_artifact':
|
|
39
40
|
return auditedEnvelope(validated.tool, services.sdd.saveArtifact(validated.input), services, sddSaveAuditPayload(validated.input));
|
|
40
41
|
case 'vgxness_sdd_accept_artifact':
|
|
41
|
-
return
|
|
42
|
+
return acceptSddArtifactEnvelope(validated.input, services);
|
|
42
43
|
case 'vgxness_sdd_reopen_artifact':
|
|
43
44
|
return auditedEnvelope(validated.tool, services.sdd.reopenArtifact(validated.input), services, sddReopenAuditPayload(validated.input));
|
|
44
45
|
case 'vgxness_sdd_get_artifact':
|
|
@@ -71,7 +72,7 @@ export function callVgxTool(call, services) {
|
|
|
71
72
|
case 'vgxness_session_append_activity':
|
|
72
73
|
return toEnvelope(validated.tool, services.memory.appendActivity(validated.input, memoryContext(validated.input.sessionId)));
|
|
73
74
|
case 'vgxness_session_close':
|
|
74
|
-
return
|
|
75
|
+
return closeSessionEnvelope(validated.input, services);
|
|
75
76
|
case 'vgxness_session_restore':
|
|
76
77
|
return toEnvelope(validated.tool, services.memory.restoreSession(validated.input));
|
|
77
78
|
case 'vgxness_context_cockpit':
|
|
@@ -161,7 +162,7 @@ export function callVgxTool(call, services) {
|
|
|
161
162
|
case 'vgxness_run_checkpoint':
|
|
162
163
|
return toEnvelope(validated.tool, services.runs.appendCheckpoint(validated.input));
|
|
163
164
|
case 'vgxness_run_finalize':
|
|
164
|
-
return
|
|
165
|
+
return finalizeRunEnvelope(validated.input, services);
|
|
165
166
|
case 'vgxness_run_resume_candidates':
|
|
166
167
|
return runResumeCandidatesEnvelope(validated.input, services);
|
|
167
168
|
case 'vgxness_run_resume_inspect':
|
|
@@ -419,14 +420,132 @@ function setManagerProfileEnvelope(input, services) {
|
|
|
419
420
|
return toEnvelope('vgxness_manager_profile_set', services.managerProfiles.resolveEffectiveManager({ project: input.project, scope: input.scope, managerName: input.managerName }));
|
|
420
421
|
}
|
|
421
422
|
function saveMemoryObservationEnvelope(input, services) {
|
|
423
|
+
const policy = new AgentMemoryCaptureService().validateExplicitMemorySave(input);
|
|
424
|
+
if (!policy.ok)
|
|
425
|
+
return errorEnvelope(policy.error.code, policy.error.message, 'vgxness_memory_save');
|
|
426
|
+
if (!policy.value.allowed)
|
|
427
|
+
return memoryCapturePolicyErrorEnvelope('vgxness_memory_save', policy.value);
|
|
422
428
|
return toEnvelope('vgxness_memory_save', services.memory.saveObservation({ ...input, scope: input.scope ?? 'project' }, memoryContext()));
|
|
423
429
|
}
|
|
424
430
|
function getMemoryObservationEnvelope(input, services) {
|
|
425
431
|
return toEnvelope('vgxness_memory_get', services.memory.getObservation(input.id, memoryContext()));
|
|
426
432
|
}
|
|
427
433
|
function updateMemoryObservationEnvelope(input, services) {
|
|
434
|
+
const policy = new AgentMemoryCaptureService().validateExplicitMemoryUpdate(input);
|
|
435
|
+
if (!policy.ok)
|
|
436
|
+
return errorEnvelope(policy.error.code, policy.error.message, 'vgxness_memory_update');
|
|
437
|
+
if (!policy.value.allowed)
|
|
438
|
+
return memoryCapturePolicyErrorEnvelope('vgxness_memory_update', policy.value);
|
|
428
439
|
return toEnvelope('vgxness_memory_update', services.memory.updateObservation(input, memoryContext()));
|
|
429
440
|
}
|
|
441
|
+
function acceptSddArtifactEnvelope(input, services) {
|
|
442
|
+
const accepted = auditedEnvelope('vgxness_sdd_accept_artifact', services.sdd.acceptArtifact(input), services, sddAcceptanceAuditPayload(input));
|
|
443
|
+
if (!accepted.ok)
|
|
444
|
+
return accepted;
|
|
445
|
+
const memoryCapture = sddReferenceMemoryCapture(input, accepted.value);
|
|
446
|
+
if (memoryCapture !== undefined)
|
|
447
|
+
accepted.memoryCapture = memoryCapture;
|
|
448
|
+
return accepted;
|
|
449
|
+
}
|
|
450
|
+
function sddReferenceMemoryCapture(input, artifact) {
|
|
451
|
+
try {
|
|
452
|
+
const captureInput = {
|
|
453
|
+
source: input.phase === 'archive' ? 'sdd_archive' : 'sdd_acceptance',
|
|
454
|
+
project: artifact.project,
|
|
455
|
+
change: input.change,
|
|
456
|
+
phase: input.phase,
|
|
457
|
+
artifactId: artifact.id,
|
|
458
|
+
topicKey: artifact.topicKey,
|
|
459
|
+
artifactState: 'accepted',
|
|
460
|
+
};
|
|
461
|
+
if (input.acceptedBy?.type !== undefined && input.acceptedBy.id !== undefined) {
|
|
462
|
+
captureInput.acceptedBy = {
|
|
463
|
+
type: input.acceptedBy.type,
|
|
464
|
+
id: input.acceptedBy.id,
|
|
465
|
+
...(input.acceptedBy.displayName === undefined ? {} : { displayName: input.acceptedBy.displayName }),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (input.acceptedAt !== undefined)
|
|
469
|
+
captureInput.acceptedAt = input.acceptedAt;
|
|
470
|
+
if (input.runId !== undefined)
|
|
471
|
+
captureInput.runId = input.runId;
|
|
472
|
+
if (input.agentId !== undefined)
|
|
473
|
+
captureInput.agentId = input.agentId;
|
|
474
|
+
const result = new AgentMemoryCaptureService().captureFromSddReferenceEvent(captureInput);
|
|
475
|
+
return result.ok ? result.value : undefined;
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function closeSessionEnvelope(input, services) {
|
|
482
|
+
const closed = services.memory.closeSession(input, memoryContext(input.sessionId));
|
|
483
|
+
if (!closed.ok)
|
|
484
|
+
return errorEnvelope(closed.error.code, closed.error.message, 'vgxness_session_close');
|
|
485
|
+
const memoryCapture = sessionCloseMemoryCapture(input, closed.value);
|
|
486
|
+
const envelope = { ok: true, tool: 'vgxness_session_close', value: closed.value };
|
|
487
|
+
if (memoryCapture !== undefined)
|
|
488
|
+
envelope.memoryCapture = memoryCapture;
|
|
489
|
+
return envelope;
|
|
490
|
+
}
|
|
491
|
+
function sessionCloseMemoryCapture(input, session) {
|
|
492
|
+
const result = new AgentMemoryCaptureService().captureFromSessionClose({
|
|
493
|
+
project: session.project,
|
|
494
|
+
sessionId: session.id,
|
|
495
|
+
...(session.directory === undefined ? {} : { directory: session.directory }),
|
|
496
|
+
summary: input.summary,
|
|
497
|
+
});
|
|
498
|
+
return result.ok ? result.value : undefined;
|
|
499
|
+
}
|
|
500
|
+
function finalizeRunEnvelope(input, services) {
|
|
501
|
+
const finalized = services.runs.updateFinalStatus(input);
|
|
502
|
+
if (!finalized.ok)
|
|
503
|
+
return errorEnvelope(finalized.error.code, finalized.error.message, 'vgxness_run_finalize');
|
|
504
|
+
const memoryCapture = runFinalizeMemoryCapture(input, finalized.value);
|
|
505
|
+
const envelope = { ok: true, tool: 'vgxness_run_finalize', value: finalized.value };
|
|
506
|
+
if (memoryCapture !== undefined)
|
|
507
|
+
envelope.memoryCapture = memoryCapture;
|
|
508
|
+
return envelope;
|
|
509
|
+
}
|
|
510
|
+
function runFinalizeMemoryCapture(input, run) {
|
|
511
|
+
try {
|
|
512
|
+
const boundedEvidence = [{ kind: 'run-finalization', ref: run.id, note: 'Derived from bounded run finalization response fields only.' }];
|
|
513
|
+
if (run.latestCheckpointId !== undefined) {
|
|
514
|
+
boundedEvidence.push({ kind: 'run-checkpoint', ref: run.latestCheckpointId, note: 'Checkpoint id reference only; checkpoint state omitted.' });
|
|
515
|
+
}
|
|
516
|
+
const captureInput = {
|
|
517
|
+
project: run.project,
|
|
518
|
+
runId: run.id,
|
|
519
|
+
workflow: run.workflow,
|
|
520
|
+
phase: run.phase,
|
|
521
|
+
status: run.status,
|
|
522
|
+
outcome: run.outcome ?? input.outcome,
|
|
523
|
+
boundedEvidence,
|
|
524
|
+
};
|
|
525
|
+
const outcomeReason = run.outcomeReason ?? input.outcomeReason;
|
|
526
|
+
if (outcomeReason !== undefined)
|
|
527
|
+
captureInput.outcomeReason = outcomeReason;
|
|
528
|
+
const result = new AgentMemoryCaptureService().captureFromRunFinalize(captureInput);
|
|
529
|
+
return result.ok ? result.value : undefined;
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function memoryCapturePolicyErrorEnvelope(tool, decision) {
|
|
536
|
+
return errorEnvelope('validation_failed', `Memory capture policy rejected explicit durable memory write. Reason codes: ${decision.reasonCodes.join(', ')}`, tool, {
|
|
537
|
+
policy: {
|
|
538
|
+
service: 'AgentMemoryCaptureService',
|
|
539
|
+
version: decision.version,
|
|
540
|
+
allowed: decision.allowed,
|
|
541
|
+
severity: decision.severity,
|
|
542
|
+
persisted: decision.persisted,
|
|
543
|
+
reasonCodes: decision.reasonCodes,
|
|
544
|
+
warnings: decision.warnings,
|
|
545
|
+
redactions: decision.redactions,
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
}
|
|
430
549
|
function memoryContext(sessionId) {
|
|
431
550
|
const context = { actor: 'mcp-control-plane' };
|
|
432
551
|
if (sessionId !== undefined)
|
package/dist/mcp/schema.js
CHANGED
|
@@ -974,8 +974,11 @@ export const VGX_MCP_TOOL_OUTPUT_SCHEMAS = {
|
|
|
974
974
|
export function successEnvelope(tool, value) {
|
|
975
975
|
return { ok: true, tool, value };
|
|
976
976
|
}
|
|
977
|
-
export function errorEnvelope(code, message, tool) {
|
|
978
|
-
const
|
|
977
|
+
export function errorEnvelope(code, message, tool, details) {
|
|
978
|
+
const error = { code, message };
|
|
979
|
+
if (details !== undefined)
|
|
980
|
+
error.details = details;
|
|
981
|
+
const result = { ok: false, error };
|
|
979
982
|
if (tool !== undefined)
|
|
980
983
|
result.tool = tool;
|
|
981
984
|
return result;
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const MEMORY_TYPES = [
|
|
3
|
+
'architecture',
|
|
4
|
+
'decision',
|
|
5
|
+
'bugfix',
|
|
6
|
+
'pattern',
|
|
7
|
+
'config',
|
|
8
|
+
'discovery',
|
|
9
|
+
'learning',
|
|
10
|
+
'preference',
|
|
11
|
+
'manual',
|
|
12
|
+
];
|
|
13
|
+
const DEFAULT_LIMITS = {
|
|
14
|
+
maxCandidates: 3,
|
|
15
|
+
maxCandidateTitleChars: 120,
|
|
16
|
+
maxCandidateContentChars: 1500,
|
|
17
|
+
maxExplicitContentChars: 4000,
|
|
18
|
+
maxSourceContentChars: 12000,
|
|
19
|
+
maxEvidenceRefs: 10,
|
|
20
|
+
maxReasonCodes: 8,
|
|
21
|
+
};
|
|
22
|
+
export class AgentMemoryCaptureService {
|
|
23
|
+
now;
|
|
24
|
+
limits;
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
27
|
+
this.limits = {
|
|
28
|
+
...DEFAULT_LIMITS,
|
|
29
|
+
...definedLimits(options),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
validateExplicitMemorySave(input) {
|
|
33
|
+
const reasonCodes = [];
|
|
34
|
+
const warnings = [warning('memory_is_advisory_only')];
|
|
35
|
+
const project = clean(input.project);
|
|
36
|
+
const title = clean(input.title);
|
|
37
|
+
const content = clean(input.content);
|
|
38
|
+
const scope = input.scope ?? 'project';
|
|
39
|
+
const memoryType = normalizeMemoryType(input.type);
|
|
40
|
+
if (project === undefined)
|
|
41
|
+
reasonCodes.push('missing_required_field');
|
|
42
|
+
if (title === undefined)
|
|
43
|
+
reasonCodes.push('missing_required_field');
|
|
44
|
+
if (content === undefined)
|
|
45
|
+
reasonCodes.push('missing_required_field');
|
|
46
|
+
if (memoryType === undefined)
|
|
47
|
+
reasonCodes.push('invalid_memory_type');
|
|
48
|
+
reasonCodes.push(...this.policyReasonsForText(content ?? '', this.limits.maxExplicitContentChars));
|
|
49
|
+
reasonCodes.push(...scopeReasonCodes(scope));
|
|
50
|
+
if (input.topicKey !== undefined && clean(input.topicKey) === undefined)
|
|
51
|
+
reasonCodes.push('ambiguous_topic_key');
|
|
52
|
+
const rejectCodes = rejectCodesFrom(reasonCodes);
|
|
53
|
+
if (rejectCodes.length > 0)
|
|
54
|
+
return okDecision(false, rejectCodes, warnings);
|
|
55
|
+
const normalizedPayload = { scope };
|
|
56
|
+
assignIfDefined(normalizedPayload, 'project', project);
|
|
57
|
+
assignIfDefined(normalizedPayload, 'type', memoryType);
|
|
58
|
+
assignIfDefined(normalizedPayload, 'title', trimTo(title, this.limits.maxCandidateTitleChars));
|
|
59
|
+
assignIfDefined(normalizedPayload, 'content', content);
|
|
60
|
+
const topicKey = clean(input.topicKey);
|
|
61
|
+
if (topicKey !== undefined)
|
|
62
|
+
normalizedPayload.topicKey = topicKey;
|
|
63
|
+
return okDecision(true, uniqueReasonCodes(['durable_future_relevant', 'compact_summary', ...reasonCodes, 'memory_is_advisory_only']), warnings, normalizedPayload);
|
|
64
|
+
}
|
|
65
|
+
validateExplicitMemoryUpdate(input) {
|
|
66
|
+
const reasonCodes = [];
|
|
67
|
+
const warnings = [warning('memory_is_advisory_only')];
|
|
68
|
+
const id = clean(input.id);
|
|
69
|
+
if (id === undefined)
|
|
70
|
+
reasonCodes.push('missing_required_field');
|
|
71
|
+
const memoryType = input.type === undefined ? undefined : normalizeMemoryType(input.type);
|
|
72
|
+
if (input.type !== undefined && memoryType === undefined)
|
|
73
|
+
reasonCodes.push('invalid_memory_type');
|
|
74
|
+
if (input.content !== undefined)
|
|
75
|
+
reasonCodes.push(...this.policyReasonsForText(input.content, this.limits.maxExplicitContentChars));
|
|
76
|
+
if (input.topicKey !== undefined && clean(input.topicKey) === undefined)
|
|
77
|
+
reasonCodes.push('ambiguous_topic_key');
|
|
78
|
+
if (input.scope !== undefined)
|
|
79
|
+
reasonCodes.push(...scopeReasonCodes(input.scope));
|
|
80
|
+
const hasField = input.project !== undefined || input.scope !== undefined || input.type !== undefined || input.title !== undefined || input.content !== undefined || input.topicKey !== undefined;
|
|
81
|
+
if (!hasField)
|
|
82
|
+
reasonCodes.push('missing_required_field');
|
|
83
|
+
const rejectCodes = rejectCodesFrom(reasonCodes);
|
|
84
|
+
if (rejectCodes.length > 0)
|
|
85
|
+
return okDecision(false, rejectCodes, warnings);
|
|
86
|
+
const normalizedPayload = {};
|
|
87
|
+
assignIfDefined(normalizedPayload, 'project', clean(input.project));
|
|
88
|
+
assignIfDefined(normalizedPayload, 'scope', input.scope);
|
|
89
|
+
assignIfDefined(normalizedPayload, 'type', memoryType);
|
|
90
|
+
assignIfDefined(normalizedPayload, 'title', trimTo(clean(input.title), this.limits.maxCandidateTitleChars));
|
|
91
|
+
assignIfDefined(normalizedPayload, 'content', clean(input.content));
|
|
92
|
+
assignIfDefined(normalizedPayload, 'topicKey', clean(input.topicKey));
|
|
93
|
+
return okDecision(true, uniqueReasonCodes(['durable_future_relevant', ...reasonCodes, 'memory_is_advisory_only']), warnings, normalizedPayload);
|
|
94
|
+
}
|
|
95
|
+
captureFromSessionClose(input) {
|
|
96
|
+
const sourceRef = makeSourceRef({ kind: 'session', project: input.project }, { id: input.sessionId, sessionId: input.sessionId });
|
|
97
|
+
const base = this.baseResult('session_close');
|
|
98
|
+
const summary = clean(input.summary);
|
|
99
|
+
if (summary === undefined) {
|
|
100
|
+
return { ok: true, value: withRejection(base, sourceRef, ['source_content_empty'], 'Session close summary was empty.') };
|
|
101
|
+
}
|
|
102
|
+
const sourceProblems = this.policyReasonsForText(summary, this.limits.maxSourceContentChars);
|
|
103
|
+
if (rejectCodesFrom(sourceProblems).length > 0) {
|
|
104
|
+
return { ok: true, value: withRejection(base, sourceRef, rejectCodesFrom(sourceProblems), 'Session close summary contained prohibited or unbounded content.') };
|
|
105
|
+
}
|
|
106
|
+
const sections = parseMarkdownSections(summary);
|
|
107
|
+
const candidates = [];
|
|
108
|
+
const preferenceSection = findSection(sections, 'Instructions');
|
|
109
|
+
if (preferenceSection !== undefined) {
|
|
110
|
+
candidates.push(this.candidate({
|
|
111
|
+
source: 'session_close',
|
|
112
|
+
sourceRef,
|
|
113
|
+
project: input.project,
|
|
114
|
+
type: 'preference',
|
|
115
|
+
title: 'Captured session instruction preference',
|
|
116
|
+
content: memoryContent('What', firstBulletOrLine(preferenceSection.body), 'Why', 'Session close summary recorded an instruction that may affect future work.', 'Where', input.directory ?? input.project),
|
|
117
|
+
topicKey: 'preference/session-instructions',
|
|
118
|
+
evidenceRefs: [{ kind: 'session-summary', ref: input.sessionId ?? input.project, note: 'Derived from close summary only.' }],
|
|
119
|
+
rationale: 'Session summary includes reusable instructions.',
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
const discoverySection = findSection(sections, 'Discoveries');
|
|
123
|
+
if (discoverySection !== undefined) {
|
|
124
|
+
candidates.push(this.candidate({
|
|
125
|
+
source: 'session_close',
|
|
126
|
+
sourceRef,
|
|
127
|
+
project: input.project,
|
|
128
|
+
type: 'discovery',
|
|
129
|
+
title: 'Captured session discovery',
|
|
130
|
+
content: memoryContent('What', firstBulletOrLine(discoverySection.body), 'Why', 'Session close summary marked this as a durable discovery.', 'Where', input.directory ?? input.project),
|
|
131
|
+
topicKey: 'discovery/session-close',
|
|
132
|
+
evidenceRefs: [{ kind: 'session-summary', ref: input.sessionId ?? input.project, note: 'Derived from close summary only.' }],
|
|
133
|
+
rationale: 'Session summary includes a discovery section.',
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
const accomplishedSection = findSection(sections, 'Accomplished');
|
|
137
|
+
if (accomplishedSection !== undefined) {
|
|
138
|
+
candidates.push(this.candidate({
|
|
139
|
+
source: 'session_close',
|
|
140
|
+
sourceRef,
|
|
141
|
+
project: input.project,
|
|
142
|
+
type: inferTypeFromText(accomplishedSection.body),
|
|
143
|
+
title: 'Captured completed session outcome',
|
|
144
|
+
content: memoryContent('What', firstBulletOrLine(accomplishedSection.body), 'Why', 'Session close summary marked this work as accomplished.', 'Where', input.directory ?? input.project),
|
|
145
|
+
topicKey: 'manual/session-outcome',
|
|
146
|
+
evidenceRefs: [{ kind: 'session-summary', ref: input.sessionId ?? input.project, note: 'Derived from close summary only.' }],
|
|
147
|
+
rationale: 'Session summary includes completed durable work.',
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
base.candidates = candidates.slice(0, this.limits.maxCandidates);
|
|
151
|
+
if (base.candidates.length === 0)
|
|
152
|
+
base.rejections.push(rejection(sourceRef, ['no_candidate_worthy_content'], 'No durable candidate-worthy summary section was found.'));
|
|
153
|
+
return { ok: true, value: base };
|
|
154
|
+
}
|
|
155
|
+
captureFromRunFinalize(input) {
|
|
156
|
+
const sourceRef = makeSourceRef({ kind: 'run', id: input.runId, project: input.project, runId: input.runId });
|
|
157
|
+
const base = this.baseResult('run_finalize');
|
|
158
|
+
const reason = clean(input.outcomeReason);
|
|
159
|
+
if (reason === undefined) {
|
|
160
|
+
return { ok: true, value: withRejection(base, sourceRef, ['source_content_empty'], 'Run outcome reason was empty.') };
|
|
161
|
+
}
|
|
162
|
+
if (input.outcome === 'cancelled') {
|
|
163
|
+
return { ok: true, value: withRejection(base, sourceRef, ['cancelled_run_without_durable_learning'], 'Cancelled run did not produce a durable candidate by default.') };
|
|
164
|
+
}
|
|
165
|
+
const sourceProblems = this.policyReasonsForText(reason, this.limits.maxSourceContentChars);
|
|
166
|
+
if (rejectCodesFrom(sourceProblems).length > 0) {
|
|
167
|
+
return { ok: true, value: withRejection(base, sourceRef, rejectCodesFrom(sourceProblems), 'Run finalization evidence contained prohibited content.') };
|
|
168
|
+
}
|
|
169
|
+
const evidenceRefs = boundedEvidence(input.boundedEvidence, this.limits.maxEvidenceRefs);
|
|
170
|
+
const content = memoryContent('What', `Run ${input.runId} finalized with status=${input.status} outcome=${input.outcome}.`, 'Why', reason, 'Where', [input.workflow, input.phase].filter((value) => value !== undefined && value.length > 0).join('/') || input.project);
|
|
171
|
+
base.candidates = [
|
|
172
|
+
this.candidate({
|
|
173
|
+
source: 'run_finalize',
|
|
174
|
+
sourceRef,
|
|
175
|
+
project: input.project,
|
|
176
|
+
type: input.outcome === 'success' ? 'discovery' : 'manual',
|
|
177
|
+
title: `Captured run ${input.outcome} outcome`,
|
|
178
|
+
content,
|
|
179
|
+
topicKey: `run/${input.runId}/finalize`,
|
|
180
|
+
evidenceRefs: evidenceRefs.length > 0 ? evidenceRefs : [{ kind: 'run-finalization', ref: input.runId }],
|
|
181
|
+
rationale: 'Run finalization has bounded outcome evidence only.',
|
|
182
|
+
}),
|
|
183
|
+
];
|
|
184
|
+
return { ok: true, value: base };
|
|
185
|
+
}
|
|
186
|
+
captureFromSddReferenceEvent(input) {
|
|
187
|
+
const sourceRef = makeSourceRef({ kind: 'sdd-artifact', project: input.project, change: input.change, phase: input.phase, topicKey: input.topicKey }, { id: input.artifactId, runId: input.runId });
|
|
188
|
+
const base = this.baseResult(input.source);
|
|
189
|
+
base.warnings.push(warning('sdd_acceptance_requires_human_record'), warning('canonical_state_required_for_governance'));
|
|
190
|
+
const actor = input.acceptedBy?.type === 'human' ? `human:${input.acceptedBy.id}` : 'not-provided';
|
|
191
|
+
const content = memoryContent('What', `${input.source === 'sdd_archive' ? 'SDD archive reference' : 'SDD acceptance reference'} for change=${input.change} phase=${input.phase} topicKey=${input.topicKey}.`, 'Why', 'Reference-only lifecycle candidate; canonical SDD artifact state remains the source of truth.', 'Where', `project=${input.project}; artifactId=${input.artifactId ?? 'unknown'}; state=${input.artifactState ?? 'unknown'}; acceptedBy=${actor}`);
|
|
192
|
+
base.candidates = [
|
|
193
|
+
this.candidate({
|
|
194
|
+
source: input.source,
|
|
195
|
+
sourceRef,
|
|
196
|
+
project: input.project,
|
|
197
|
+
type: input.source === 'sdd_archive' ? 'learning' : 'decision',
|
|
198
|
+
title: input.source === 'sdd_archive' ? 'Captured SDD archive reference' : 'Captured SDD acceptance reference',
|
|
199
|
+
content,
|
|
200
|
+
topicKey: `sdd-reference/${input.change}/${input.phase}`,
|
|
201
|
+
evidenceRefs: [
|
|
202
|
+
{ kind: 'sdd-topic-key', ref: input.topicKey, note: 'Reference only; artifact body omitted.' },
|
|
203
|
+
{ kind: 'sdd-event', ref: input.acceptedAt ?? this.now(), note: input.source },
|
|
204
|
+
],
|
|
205
|
+
rationale: 'SDD lifecycle memory candidates must reference canonical artifacts only.',
|
|
206
|
+
}),
|
|
207
|
+
];
|
|
208
|
+
return { ok: true, value: base };
|
|
209
|
+
}
|
|
210
|
+
baseResult(source) {
|
|
211
|
+
return {
|
|
212
|
+
version: 1,
|
|
213
|
+
source,
|
|
214
|
+
mode: 'ephemeral-review-first',
|
|
215
|
+
persisted: false,
|
|
216
|
+
generatedAt: this.now(),
|
|
217
|
+
policy: {
|
|
218
|
+
service: 'AgentMemoryCaptureService',
|
|
219
|
+
version: 1,
|
|
220
|
+
maxCandidates: this.limits.maxCandidates,
|
|
221
|
+
maxCandidateContentChars: this.limits.maxCandidateContentChars,
|
|
222
|
+
maxEvidenceRefs: this.limits.maxEvidenceRefs,
|
|
223
|
+
},
|
|
224
|
+
safety: {
|
|
225
|
+
advisoryOnly: true,
|
|
226
|
+
provesGovernance: false,
|
|
227
|
+
automaticPersistence: false,
|
|
228
|
+
persistedCandidateQueue: false,
|
|
229
|
+
},
|
|
230
|
+
candidates: [],
|
|
231
|
+
rejections: [],
|
|
232
|
+
warnings: [warning('lifecycle_candidate_not_persisted'), warning('candidate_requires_review'), warning('memory_is_advisory_only')],
|
|
233
|
+
nextActions: defaultNextActions(),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
candidate(input) {
|
|
237
|
+
const content = trimTo(input.content, this.limits.maxCandidateContentChars) ?? '';
|
|
238
|
+
const title = trimTo(input.title, this.limits.maxCandidateTitleChars) ?? input.title;
|
|
239
|
+
const suggestedPayload = {
|
|
240
|
+
project: input.project,
|
|
241
|
+
scope: 'project',
|
|
242
|
+
type: input.type,
|
|
243
|
+
title,
|
|
244
|
+
content,
|
|
245
|
+
topicKey: input.topicKey,
|
|
246
|
+
};
|
|
247
|
+
return {
|
|
248
|
+
id: stableId(['memory-candidate', input.source, input.project, input.topicKey, content].join('|')),
|
|
249
|
+
status: 'proposed',
|
|
250
|
+
sourceItemRef: input.sourceRef,
|
|
251
|
+
suggestedOperation: 'save',
|
|
252
|
+
suggestedPayload,
|
|
253
|
+
rationale: input.rationale,
|
|
254
|
+
reasonCodes: uniqueReasonCodes(['review_first_candidate', 'safe_reference_only', 'compact_summary', 'evidence_backed']).slice(0, this.limits.maxReasonCodes),
|
|
255
|
+
evidenceRefs: boundedEvidence(input.evidenceRefs, this.limits.maxEvidenceRefs),
|
|
256
|
+
reviewRequired: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
policyReasonsForText(text, maxChars) {
|
|
260
|
+
const lower = text.toLowerCase();
|
|
261
|
+
const reasons = [];
|
|
262
|
+
if (text.length > maxChars)
|
|
263
|
+
reasons.push('unbounded_content');
|
|
264
|
+
if (/full transcript|conversation transcript|^user:|\nuser:|\nassistant:/i.test(text))
|
|
265
|
+
reasons.push('contains_transcript');
|
|
266
|
+
if (/stdout:|stderr:|stack trace|raw log|traceback|exception:/i.test(text))
|
|
267
|
+
reasons.push('contains_raw_logs');
|
|
268
|
+
if (/chain-of-thought|hidden reasoning|private reasoning|internal reasoning|deliberation/i.test(text))
|
|
269
|
+
reasons.push('contains_hidden_reasoning');
|
|
270
|
+
if (/api[_-]?key|secret|password|token\s*=|bearer\s+[a-z0-9._-]+|private key|sk_(?:test|live)_/i.test(text))
|
|
271
|
+
reasons.push('contains_secret_or_credential');
|
|
272
|
+
if (/ssn|social security|credit card|passport number|medical record/i.test(text))
|
|
273
|
+
reasons.push('contains_sensitive_user_data');
|
|
274
|
+
if (/^#\s*sdd\s+(explore|proposal|spec|design|tasks|archive)|#\s*sdd .*draft|##\s+1\.\s+overview/i.test(text))
|
|
275
|
+
reasons.push('contains_full_sdd_artifact');
|
|
276
|
+
if (lower.includes('approval is granted') || lower.includes('authorized by memory'))
|
|
277
|
+
reasons.push('unsafe_authorization_claim');
|
|
278
|
+
if (lower.includes('verification is proven by memory'))
|
|
279
|
+
reasons.push('unsafe_verification_claim');
|
|
280
|
+
if (lower.includes('sdd acceptance is proven by memory'))
|
|
281
|
+
reasons.push('unsafe_sdd_acceptance_claim');
|
|
282
|
+
return uniqueReasonCodes(reasons);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function definedLimits(options) {
|
|
286
|
+
const limits = {};
|
|
287
|
+
assignIfDefined(limits, 'maxCandidates', options.maxCandidates);
|
|
288
|
+
assignIfDefined(limits, 'maxCandidateTitleChars', options.maxCandidateTitleChars);
|
|
289
|
+
assignIfDefined(limits, 'maxCandidateContentChars', options.maxCandidateContentChars);
|
|
290
|
+
assignIfDefined(limits, 'maxExplicitContentChars', options.maxExplicitContentChars);
|
|
291
|
+
assignIfDefined(limits, 'maxSourceContentChars', options.maxSourceContentChars);
|
|
292
|
+
assignIfDefined(limits, 'maxEvidenceRefs', options.maxEvidenceRefs);
|
|
293
|
+
return limits;
|
|
294
|
+
}
|
|
295
|
+
function okDecision(allowed, reasonCodes, warnings, normalizedPayload) {
|
|
296
|
+
const decision = {
|
|
297
|
+
version: 1,
|
|
298
|
+
allowed,
|
|
299
|
+
severity: allowed ? (warnings.length > 0 ? 'warn' : 'allow') : 'reject',
|
|
300
|
+
persisted: false,
|
|
301
|
+
reasonCodes: uniqueReasonCodes(reasonCodes),
|
|
302
|
+
warnings,
|
|
303
|
+
redactions: [],
|
|
304
|
+
};
|
|
305
|
+
if (normalizedPayload !== undefined)
|
|
306
|
+
decision.normalizedPayload = normalizedPayload;
|
|
307
|
+
return { ok: true, value: decision };
|
|
308
|
+
}
|
|
309
|
+
function rejectCodesFrom(reasonCodes) {
|
|
310
|
+
const rejectPrefixes = new Set([
|
|
311
|
+
'contains_secret_or_credential',
|
|
312
|
+
'contains_sensitive_user_data',
|
|
313
|
+
'contains_hidden_reasoning',
|
|
314
|
+
'contains_raw_logs',
|
|
315
|
+
'contains_transcript',
|
|
316
|
+
'contains_full_sdd_artifact',
|
|
317
|
+
'unbounded_content',
|
|
318
|
+
'ambiguous_scope',
|
|
319
|
+
'invalid_memory_type',
|
|
320
|
+
'unsafe_authorization_claim',
|
|
321
|
+
'unsafe_approval_claim',
|
|
322
|
+
'unsafe_verification_claim',
|
|
323
|
+
'unsafe_sdd_acceptance_claim',
|
|
324
|
+
'missing_required_field',
|
|
325
|
+
'policy_input_invalid',
|
|
326
|
+
]);
|
|
327
|
+
return uniqueReasonCodes(reasonCodes.filter((code) => rejectPrefixes.has(code)));
|
|
328
|
+
}
|
|
329
|
+
function normalizeMemoryType(type) {
|
|
330
|
+
if (type === undefined)
|
|
331
|
+
return undefined;
|
|
332
|
+
return MEMORY_TYPES.includes(type) ? type : undefined;
|
|
333
|
+
}
|
|
334
|
+
function scopeReasonCodes(scope) {
|
|
335
|
+
if (scope === 'project')
|
|
336
|
+
return ['scope_project_appropriate'];
|
|
337
|
+
if (scope === 'personal')
|
|
338
|
+
return ['scope_personal_appropriate'];
|
|
339
|
+
return ['ambiguous_scope'];
|
|
340
|
+
}
|
|
341
|
+
function clean(value) {
|
|
342
|
+
if (value === undefined)
|
|
343
|
+
return undefined;
|
|
344
|
+
const trimmed = value.trim();
|
|
345
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
346
|
+
}
|
|
347
|
+
function trimTo(value, maxChars) {
|
|
348
|
+
if (value === undefined)
|
|
349
|
+
return undefined;
|
|
350
|
+
if (value.length <= maxChars)
|
|
351
|
+
return value;
|
|
352
|
+
return `${value.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
|
|
353
|
+
}
|
|
354
|
+
function uniqueReasonCodes(codes) {
|
|
355
|
+
return Array.from(new Set(codes));
|
|
356
|
+
}
|
|
357
|
+
function warning(reasonCode) {
|
|
358
|
+
const messages = {
|
|
359
|
+
durable_future_relevant: 'Content appears durable and future-relevant.',
|
|
360
|
+
actionable_project_fact: 'Content appears actionable for future project work.',
|
|
361
|
+
actionable_user_preference: 'Content appears to describe a reusable preference.',
|
|
362
|
+
compact_summary: 'Content is represented as a compact summary.',
|
|
363
|
+
evidence_backed: 'Candidate includes bounded evidence references.',
|
|
364
|
+
scope_project_appropriate: 'Project scope is appropriate.',
|
|
365
|
+
scope_personal_appropriate: 'Personal scope is appropriate.',
|
|
366
|
+
topic_key_stable: 'Topic key appears stable.',
|
|
367
|
+
safe_reference_only: 'Candidate uses safe references only.',
|
|
368
|
+
review_first_candidate: 'Candidate requires review before any explicit memory write.',
|
|
369
|
+
memory_is_advisory_only: 'Memory is advisory and never proves governance, acceptance, authorization, or verification.',
|
|
370
|
+
canonical_state_required_for_governance: 'Canonical SDD/run/session state must be inspected for governance facts.',
|
|
371
|
+
verification_requires_run_or_test_evidence: 'Verification requires run or test evidence, not memory alone.',
|
|
372
|
+
sdd_acceptance_requires_human_record: 'SDD acceptance is canonical only when backed by a human acceptance record.',
|
|
373
|
+
low_evidence_candidate: 'Candidate has low evidence.',
|
|
374
|
+
ambiguous_topic_key: 'Topic key is missing or ambiguous.',
|
|
375
|
+
candidate_requires_review: 'Lifecycle candidates are suggestions that require review.',
|
|
376
|
+
lifecycle_candidate_not_persisted: 'Lifecycle capture did not persist memory.',
|
|
377
|
+
content_truncated_to_policy_limit: 'Content was truncated to policy limits.',
|
|
378
|
+
contains_secret_or_credential: 'Content appears to contain a secret or credential.',
|
|
379
|
+
contains_sensitive_user_data: 'Content appears to contain sensitive user data.',
|
|
380
|
+
contains_hidden_reasoning: 'Content appears to contain hidden reasoning.',
|
|
381
|
+
contains_raw_logs: 'Content appears to contain raw logs.',
|
|
382
|
+
contains_transcript: 'Content appears to contain a transcript.',
|
|
383
|
+
contains_full_sdd_artifact: 'Content appears to contain a full SDD artifact.',
|
|
384
|
+
unbounded_content: 'Content exceeds policy size limits.',
|
|
385
|
+
not_future_relevant: 'Content does not appear future-relevant.',
|
|
386
|
+
not_actionable: 'Content does not appear actionable.',
|
|
387
|
+
ambiguous_scope: 'Scope is ambiguous.',
|
|
388
|
+
invalid_memory_type: 'Memory type is invalid.',
|
|
389
|
+
unsafe_authorization_claim: 'Content makes an unsafe authorization claim.',
|
|
390
|
+
unsafe_approval_claim: 'Content makes an unsafe approval claim.',
|
|
391
|
+
unsafe_verification_claim: 'Content makes an unsafe verification claim.',
|
|
392
|
+
unsafe_sdd_acceptance_claim: 'Content makes an unsafe SDD acceptance claim.',
|
|
393
|
+
missing_required_field: 'A required field is missing.',
|
|
394
|
+
policy_input_invalid: 'Policy input is invalid.',
|
|
395
|
+
no_candidate_worthy_content: 'No candidate-worthy content was found.',
|
|
396
|
+
cancelled_run_without_durable_learning: 'Cancelled run did not include durable learning.',
|
|
397
|
+
source_content_empty: 'Source content was empty.',
|
|
398
|
+
source_content_too_ambiguous: 'Source content was too ambiguous.',
|
|
399
|
+
redaction_removed_required_context: 'Redaction removed required context.',
|
|
400
|
+
unsupported_lifecycle_source: 'Lifecycle source is unsupported.',
|
|
401
|
+
};
|
|
402
|
+
return { reasonCode, message: messages[reasonCode] };
|
|
403
|
+
}
|
|
404
|
+
function defaultNextActions() {
|
|
405
|
+
return [
|
|
406
|
+
{ kind: 'review_candidate', description: 'Review candidates before taking any durable memory action.' },
|
|
407
|
+
{ kind: 'explicit_memory_save', description: 'Persist only selected safe candidates through an explicit memory save.', tool: 'vgxness_memory_save' },
|
|
408
|
+
{ kind: 'discard_candidate', description: 'Discard candidates that are noisy, unsafe, or not durable.' },
|
|
409
|
+
{ kind: 'inspect_canonical_source', description: 'Inspect canonical run, session, or SDD state for governance facts.' },
|
|
410
|
+
];
|
|
411
|
+
}
|
|
412
|
+
function makeSourceRef(input, optional = {}) {
|
|
413
|
+
const ref = { kind: input.kind };
|
|
414
|
+
assignIfDefined(ref, 'id', input.id);
|
|
415
|
+
assignIfDefined(ref, 'id', optional.id);
|
|
416
|
+
assignIfDefined(ref, 'project', input.project);
|
|
417
|
+
assignIfDefined(ref, 'change', input.change);
|
|
418
|
+
assignIfDefined(ref, 'phase', input.phase);
|
|
419
|
+
assignIfDefined(ref, 'topicKey', input.topicKey);
|
|
420
|
+
assignIfDefined(ref, 'runId', input.runId);
|
|
421
|
+
assignIfDefined(ref, 'runId', optional.runId);
|
|
422
|
+
assignIfDefined(ref, 'sessionId', input.sessionId);
|
|
423
|
+
assignIfDefined(ref, 'sessionId', optional.sessionId);
|
|
424
|
+
return ref;
|
|
425
|
+
}
|
|
426
|
+
function withRejection(result, ref, reasonCodes, message) {
|
|
427
|
+
result.rejections.push(rejection(ref, reasonCodes, message));
|
|
428
|
+
return result;
|
|
429
|
+
}
|
|
430
|
+
function rejection(ref, reasonCodes, message) {
|
|
431
|
+
return { sourceItemRef: ref, reasonCodes: uniqueReasonCodes(reasonCodes), message };
|
|
432
|
+
}
|
|
433
|
+
function boundedEvidence(refs, maxRefs) {
|
|
434
|
+
if (refs === undefined)
|
|
435
|
+
return [];
|
|
436
|
+
return refs.slice(0, maxRefs).map((ref) => {
|
|
437
|
+
const copy = { kind: ref.kind, ref: ref.ref };
|
|
438
|
+
assignIfDefined(copy, 'note', ref.note);
|
|
439
|
+
return copy;
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function parseMarkdownSections(summary) {
|
|
443
|
+
const sections = [];
|
|
444
|
+
let currentHeading = 'Summary';
|
|
445
|
+
let currentLines = [];
|
|
446
|
+
for (const line of summary.split(/\r?\n/)) {
|
|
447
|
+
const heading = /^#{1,3}\s+(.+)$/.exec(line.trim());
|
|
448
|
+
if (heading?.[1] !== undefined) {
|
|
449
|
+
pushSection(sections, currentHeading, currentLines);
|
|
450
|
+
currentHeading = heading[1].trim();
|
|
451
|
+
currentLines = [];
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
currentLines.push(line);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
pushSection(sections, currentHeading, currentLines);
|
|
458
|
+
return sections;
|
|
459
|
+
}
|
|
460
|
+
function pushSection(sections, heading, lines) {
|
|
461
|
+
const body = lines.join('\n').trim();
|
|
462
|
+
if (body.length > 0)
|
|
463
|
+
sections.push({ heading, body });
|
|
464
|
+
}
|
|
465
|
+
function findSection(sections, heading) {
|
|
466
|
+
const target = heading.toLowerCase();
|
|
467
|
+
return sections.find((section) => section.heading.toLowerCase() === target);
|
|
468
|
+
}
|
|
469
|
+
function firstBulletOrLine(text) {
|
|
470
|
+
const lines = text
|
|
471
|
+
.split(/\r?\n/)
|
|
472
|
+
.map((line) => line.replace(/^[-*]\s+/, '').trim())
|
|
473
|
+
.filter((line) => line.length > 0);
|
|
474
|
+
return lines[0] ?? text.trim();
|
|
475
|
+
}
|
|
476
|
+
function memoryContent(labelA, valueA, labelB, valueB, labelC, valueC) {
|
|
477
|
+
return `${labelA}: ${valueA}\n${labelB}: ${valueB}\n${labelC}: ${valueC}`;
|
|
478
|
+
}
|
|
479
|
+
function inferTypeFromText(text) {
|
|
480
|
+
const lower = text.toLowerCase();
|
|
481
|
+
if (lower.includes('fix'))
|
|
482
|
+
return 'bugfix';
|
|
483
|
+
if (lower.includes('decision') || lower.includes('chose'))
|
|
484
|
+
return 'decision';
|
|
485
|
+
if (lower.includes('config'))
|
|
486
|
+
return 'config';
|
|
487
|
+
if (lower.includes('pattern'))
|
|
488
|
+
return 'pattern';
|
|
489
|
+
return 'manual';
|
|
490
|
+
}
|
|
491
|
+
function stableId(value) {
|
|
492
|
+
return `memcap_${createHash('sha256').update(value).digest('hex').slice(0, 16)}`;
|
|
493
|
+
}
|
|
494
|
+
function assignIfDefined(target, key, value) {
|
|
495
|
+
if (value !== undefined)
|
|
496
|
+
target[key] = value;
|
|
497
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/docs/mcp.md
CHANGED
|
@@ -49,13 +49,15 @@ Use memory for durable, future-relevant facts that are reusable beyond the curre
|
|
|
49
49
|
|
|
50
50
|
Memory is advisory only: it never proves SDD acceptance, readiness, verification, approval, or authorization. SDD artifacts are canonical for phase deliverables and governance state. Active-work memory remains advisory and should point to current context without replacing artifacts or runs. `memory-write` is a durable governed action; use preflight or explicit consent whenever policy marks it governed/risky.
|
|
51
51
|
|
|
52
|
+
Lifecycle responses may include `memoryCapture` metadata for review-first memory candidates, such as session close summaries, run finalization outcomes, and SDD acceptance/archive references. These candidates are advisory only and are never persisted automatically: there is no durable candidate queue, and callers must make a separate explicit `vgxness_memory_save` or `vgxness_memory_update` decision under the same durable memory policy. `memoryCapture` must not include full SDD artifacts, transcripts, raw logs, hidden reasoning, secrets, credentials, sensitive data, or other protected content, and it is not proof of SDD acceptance, readiness, verification, approval, or authorization.
|
|
53
|
+
|
|
52
54
|
## Sessions (5)
|
|
53
55
|
|
|
54
56
|
| Tool | Purpose | Required inputs | Notes |
|
|
55
57
|
|---|---|---|---|
|
|
56
58
|
| `vgxness_session_start` | Begin a session record. | `project`; optional `id`, `directory` | If `id` is omitted, the runtime generates one. |
|
|
57
59
|
| `vgxness_session_append_activity` | Append a structured event to a session. | `sessionId`, `actor`, `kind` (`prompt`/`tool_call`/`artifact`/`summary`/`error`), `payloadJson` | `payloadJson` is a stringified JSON value, validated before append. |
|
|
58
|
-
| `vgxness_session_close` | Close a session with a summary. | `sessionId`, `actor`, `summary` (non-empty, trimmed) | |
|
|
60
|
+
| `vgxness_session_close` | Close a session with a summary. | `sessionId`, `actor`, `summary` (non-empty, trimmed) | May return advisory `memoryCapture` candidates derived from the close summary only; candidates are not persisted automatically. |
|
|
59
61
|
| `vgxness_session_restore` | Read the latest restorable session for a project/directory. | `project`; optional `directory` | One signal — not authoritative. Prefer `vgxness_context_cockpit`. |
|
|
60
62
|
| `vgxness_context_cockpit` | Read-only cockpit: recent sessions, bounded memory previews, last restorable session. | `project`; optional `directory`, `limit` | Does not trace, mutate state, run preflight, or write provider config. |
|
|
61
63
|
|
|
@@ -79,7 +81,7 @@ Payload tools that accept `agentId` resolve it as a canonical registry id first.
|
|
|
79
81
|
| Tool | Purpose | Required inputs | Notes |
|
|
80
82
|
|---|---|---|---|
|
|
81
83
|
| `vgxness_opencode_manager_payload` | Build the OpenCode manager payload envelope. | `OpenCodeManagerPayloadInput` shape (project, scope, agent, workflow/phase, workspaceRoot, maxSourceBytes, payloadMode) | `installable: false`, `readOnly: true`. |
|
|
82
|
-
| `vgxness_opencode_handoff_preview` | Compose a full handoff preview: provider artifacts + skill diagnostics + SDD status + provider status + safety flags. | `project`; optional `scope`, `agentId`, `agentName`, `workspaceRoot`, `maxSourceBytes`, `change`, `phase` | Read-only preview. Does not execute OpenCode, install hooks,
|
|
84
|
+
| `vgxness_opencode_handoff_preview` | Compose a full handoff preview: provider artifacts + skill diagnostics + SDD status + provider status + safety flags. | `project`; optional `scope`, `agentId`, `agentName`, `workspaceRoot`, `maxSourceBytes`, `change`, `phase` | Read-only preview. Does not execute OpenCode, install hooks, write provider config, or capture memory candidates. |
|
|
83
85
|
|
|
84
86
|
## Runs (9)
|
|
85
87
|
|
|
@@ -89,7 +91,7 @@ Payload tools that accept `agentId` resolve it as a canonical registry id first.
|
|
|
89
91
|
| `vgxness_run_get` | Read a run with `events`, `checkpoints`, `approvals`, `operationAttempts`. | `id` | |
|
|
90
92
|
| `vgxness_run_start` | Create a run record. | `CreateRunInput` shape | |
|
|
91
93
|
| `vgxness_run_checkpoint` | Append a resumable JSON state. | `AppendRunCheckpointInput` shape | |
|
|
92
|
-
| `vgxness_run_finalize` | Finalize a run with `outcome` and `outcomeReason`. | `FinalizeRunInput` shape | `outcome` must match a terminal `status`. Re-finalization is rejected. |
|
|
94
|
+
| `vgxness_run_finalize` | Finalize a run with `outcome` and `outcomeReason`. | `FinalizeRunInput` shape | `outcome` must match a terminal `status`. Re-finalization is rejected. May return advisory `memoryCapture` candidates from bounded finalization fields; candidates are not persisted automatically. |
|
|
93
95
|
| `vgxness_run_preflight` | Run policy evaluation and return the decision, workspace strategy recommendation, and planned execution isolation strategy. | `PreflightRunOperationInput` shape | May create a pending approval when the decision is `ask`. Does not invoke an executor, write provider config, create branches, create worktrees, create PRs, or prove isolation exists. |
|
|
94
96
|
| `vgxness_run_resume_candidates` | List recent interrupted runs for a project when the run id is unknown. | `project`; optional `limit` (1-100, default 5) | Returns failed, blocked, and needs-human candidates with `inspectInput` for `run_resume_inspect`. Does not retry, resume, mutate runs, create sandboxes/worktrees, invoke providers, or write artifacts/config. |
|
|
95
97
|
| `vgxness_run_resume_inspect` | Plan-only resume advisory. | `runId` | Returns `RunResumeOrchestrationPlan` with blockers and `nextAction` (`resolve-approval`/`inspect-run`/`retry-check`/`manual-recovery`/`no-action`). |
|
|
@@ -168,6 +170,7 @@ MCP tools do not:
|
|
|
168
170
|
- Write project-local provider config (`.opencode/`, `.mcp.json`, `.claude/`, project-root `CLAUDE.md`). VGX-managed OpenCode and Claude writes are user-global only and must stay behind explicit install/apply confirmation outside read-only status/preview tools.
|
|
169
171
|
- Execute providers (`opencode`, `claude`, etc.).
|
|
170
172
|
- Install global memory.
|
|
173
|
+
- Persist `memoryCapture` lifecycle candidates automatically or use them as proof of SDD acceptance, readiness, verification, approval, or authorization.
|
|
171
174
|
- Create `openspec/`.
|
|
172
175
|
- Bypass permission policy.
|
|
173
176
|
- Mutate other tools' state outside the VGXNESS SQLite store.
|