recallnest 1.4.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/.claude-plugin/CLAUDE.md +29 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/.env.example +3 -0
- package/.github/workflows/ci.yml +28 -0
- package/.mcp.json +9 -0
- package/CHANGELOG.md +51 -0
- package/CLAUDE.md +42 -0
- package/DEMO.md +94 -0
- package/EVAL.md +89 -0
- package/FAILURES.md +75 -0
- package/LICENSE +21 -0
- package/OPERATIONS.md +59 -0
- package/README.md +427 -0
- package/README_CN.md +425 -0
- package/ROADMAP.md +198 -0
- package/assets/recallnest-workbench.png +0 -0
- package/assets/ui/app.js +807 -0
- package/assets/ui/index.html +154 -0
- package/assets/ui/styles.css +605 -0
- package/assets/wechat-qr.jpg +0 -0
- package/config.json.example +38 -0
- package/daily-recall.sh +87 -0
- package/daily-reflections/2026-03-05.md +15 -0
- package/daily-reflections/2026-03-06.md +15 -0
- package/daily-reflections/2026-03-07.md +7 -0
- package/daily-reflections/2026-03-08.md +34 -0
- package/daily-reflections/2026-03-09.md +35 -0
- package/daily-reflections/2026-03-10.md +36 -0
- package/daily-reflections/2026-03-11.md +35 -0
- package/daily-reflections/2026-03-12.md +36 -0
- package/daily-reflections/2026-03-13.md +35 -0
- package/daily-reflections/2026-03-14.md +35 -0
- package/daily-reflections/2026-03-15.md +35 -0
- package/daily-reflections/2026-03-16.md +41 -0
- package/daily-reflections/2026-03-17.md +10 -0
- package/daily-reflections/2026-03-18.md +10 -0
- package/daily-reflections/2026-03-19.md +10 -0
- package/daily-reflections/2026-03-20.md +10 -0
- package/daily-reflections/2026-03-21.md +10 -0
- package/daily-reflections/2026-03-22.md +10 -0
- package/daily-reflections/2026-03-23.md +10 -0
- package/daily-reflections/2026-03-24.md +10 -0
- package/daily-reflections/2026-03-25.md +32 -0
- package/daily-reflections/2026-03-26.md +37 -0
- package/daily-reflections/2026-03-27.md +35 -0
- package/daily-reflections/2026-03-28.md +35 -0
- package/daily-reflections/2026-03-29.md +35 -0
- package/daily-reflections/2026-03-30.md +36 -0
- package/daily-reflections/2026-03-31.md +35 -0
- package/daily-reflections/2026-04-01.md +35 -0
- package/docs/api-reference.md +1291 -0
- package/docs/architecture.md +168 -0
- package/docs/memory-boundary-contract.md +226 -0
- package/docs/memory-categories.md +119 -0
- package/docs/pipeline-audit-ultramemory.md +177 -0
- package/docs/plans/2026-03-08-query-expansion.md +270 -0
- package/docs/plans/2026-03-16-continuity-layer.md +196 -0
- package/docs/plans/mcp-tiering-plan.md +126 -0
- package/docs/research/recall-quality-audit-2026-03-22.md +153 -0
- package/docs/self-evolution.md +198 -0
- package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +782 -0
- package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +77 -0
- package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +95 -0
- package/eval/cases.json +85 -0
- package/eval/continuity/case-seeds.json +90 -0
- package/eval/continuity/cases.json +302 -0
- package/eval/continuity/memory-seeds.json +26 -0
- package/eval/continuity/pattern-seeds.json +100 -0
- package/eval/continuity/results-2026-03-16.md +117 -0
- package/eval/continuity/results-2026-03-19.md +453 -0
- package/eval/continuity/runbook.md +110 -0
- package/eval/continuity/seed-cases.md +54 -0
- package/eval/reports/2026-03-06-abstract-query-baseline.md +96 -0
- package/eval/reports/2026-03-06-aws-ssh-baseline.md +82 -0
- package/eval/reports/2026-03-06-baseline.md +82 -0
- package/eval/reports/query-expansion.md +138 -0
- package/integrations/README.md +57 -0
- package/integrations/claude-code/README.md +133 -0
- package/integrations/claude-code/claude-md-snippet.md +31 -0
- package/integrations/claude-code/setup.sh +66 -0
- package/integrations/claude-code/smoke-continuity.sh +452 -0
- package/integrations/codex/README.md +49 -0
- package/integrations/codex/agents-md-snippet.md +31 -0
- package/integrations/codex/setup.sh +58 -0
- package/integrations/examples/claude-agent-sdk/memory-agent.ts +182 -0
- package/integrations/examples/langchain/memory-chain.py +111 -0
- package/integrations/examples/openai-agents-sdk/memory-agent.py +109 -0
- package/integrations/gemini-cli/README.md +57 -0
- package/integrations/gemini-cli/gemini-md-snippet.md +31 -0
- package/integrations/gemini-cli/setup.sh +64 -0
- package/integrations/shared/install-managed-block.sh +51 -0
- package/lm +19 -0
- package/package.json +50 -0
- package/plan-v1.2-distributable.md +105 -0
- package/scripts/archive-distilled-junk.ts +116 -0
- package/scripts/backfill-l0l1l2.ts +98 -0
- package/scripts/blind-scan.ts +588 -0
- package/scripts/cleanup-step1-exact-dups.ts +117 -0
- package/scripts/cleanup-step2-template-dups.ts +113 -0
- package/scripts/cleanup-step3-stale-content.ts +149 -0
- package/scripts/cleanup-step4-orphan-scopes.ts +131 -0
- package/scripts/delete-distilled-junk.ts +88 -0
- package/scripts/distill-facts.ts +305 -0
- package/scripts/health-check.ts +343 -0
- package/scripts/incremental-ingest.sh +71 -0
- package/scripts/mlp-health-check.ts +382 -0
- package/scripts/pull-claude-desktop.ts +515 -0
- package/scripts/re-ingest-cc.sh +66 -0
- package/scripts/rebalance-memory-health.ts +181 -0
- package/scripts/run-desktop-ingest.sh +11 -0
- package/scripts/start-server.sh +12 -0
- package/scripts/viz-dashboard.html +423 -0
- package/scripts/viz-export.ts +703 -0
- package/src/__tests__/access-tracker-novelty.test.ts +228 -0
- package/src/__tests__/auto-consolidation.test.ts +168 -0
- package/src/__tests__/auto-recall.test.ts +131 -0
- package/src/__tests__/batch-dedup.test.ts +65 -0
- package/src/__tests__/capture-engine.test.ts +1142 -0
- package/src/__tests__/capture-heuristic.test.ts +171 -0
- package/src/__tests__/cascade-forget.test.ts +210 -0
- package/src/__tests__/category-min-scores.test.ts +33 -0
- package/src/__tests__/confidence-tracker.test.ts +196 -0
- package/src/__tests__/conflict-advisor.test.ts +206 -0
- package/src/__tests__/conflict-escalation.test.ts +149 -0
- package/src/__tests__/conflict-lifecycle.test.ts +83 -0
- package/src/__tests__/conflict-output.test.ts +85 -0
- package/src/__tests__/conflict-store.test.ts +374 -0
- package/src/__tests__/consolidation-engine.test.ts +181 -0
- package/src/__tests__/context-composer-pins.test.ts +64 -0
- package/src/__tests__/context-composer-task-selection.test.ts +24 -0
- package/src/__tests__/context-renderer.test.ts +91 -0
- package/src/__tests__/continuity-eval.test.ts +275 -0
- package/src/__tests__/continuity-schema.test.ts +97 -0
- package/src/__tests__/core-recall-eval.test.ts +235 -0
- package/src/__tests__/core-summary.test.ts +109 -0
- package/src/__tests__/dedup-actions.test.ts +58 -0
- package/src/__tests__/deterministic-id.test.ts +39 -0
- package/src/__tests__/doctor.test.ts +303 -0
- package/src/__tests__/drill-down.test.ts +108 -0
- package/src/__tests__/embedder.test.ts +122 -0
- package/src/__tests__/entity-resolver.test.ts +58 -0
- package/src/__tests__/event-segmenter.test.ts +153 -0
- package/src/__tests__/hotness-blend.test.ts +76 -0
- package/src/__tests__/ingest.test.ts +360 -0
- package/src/__tests__/kg-extractor.test.ts +184 -0
- package/src/__tests__/kg-store.test.ts +198 -0
- package/src/__tests__/llm-consolidation.test.ts +226 -0
- package/src/__tests__/memory-boundaries.test.ts +237 -0
- package/src/__tests__/memory-health-rebalance.test.ts +144 -0
- package/src/__tests__/memory-output.test.ts +215 -0
- package/src/__tests__/multi-vector.test.ts +242 -0
- package/src/__tests__/ppr-traversal.test.ts +256 -0
- package/src/__tests__/preference-guard-smoke.test.ts +211 -0
- package/src/__tests__/preference-matcher.test.ts +230 -0
- package/src/__tests__/preference-slots.test.ts +106 -0
- package/src/__tests__/prospective-memory.test.ts +283 -0
- package/src/__tests__/query-entity-detector.test.ts +171 -0
- package/src/__tests__/query-expander.test.ts +34 -0
- package/src/__tests__/recall-mode.test.ts +64 -0
- package/src/__tests__/result-synthesizer.test.ts +198 -0
- package/src/__tests__/resume-context.test.ts +4743 -0
- package/src/__tests__/retrieval-trace.test.ts +213 -0
- package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
- package/src/__tests__/retroactive-boost.test.ts +223 -0
- package/src/__tests__/scope-inventory.test.ts +200 -0
- package/src/__tests__/scope-policy.test.ts +69 -0
- package/src/__tests__/session-checkpoint.test.ts +259 -0
- package/src/__tests__/session-gc.test.ts +215 -0
- package/src/__tests__/store-scope-audit.test.ts +81 -0
- package/src/__tests__/temporal-parser.test.ts +241 -0
- package/src/__tests__/term-registry.test.ts +121 -0
- package/src/__tests__/tool-output-compressor.test.ts +133 -0
- package/src/__tests__/version-manager.test.ts +213 -0
- package/src/__tests__/workflow-observation.test.ts +400 -0
- package/src/access-tracker.ts +316 -0
- package/src/adaptive-retrieval.ts +64 -0
- package/src/api-server.ts +667 -0
- package/src/asset-sync.ts +79 -0
- package/src/auto-consolidation.ts +133 -0
- package/src/auto-gc.ts +124 -0
- package/src/auto-recall.ts +95 -0
- package/src/capture-engine.ts +931 -0
- package/src/capture-heuristic.ts +177 -0
- package/src/cascade-forget.ts +135 -0
- package/src/chunker.ts +253 -0
- package/src/cli.ts +1917 -0
- package/src/confidence-tracker.ts +166 -0
- package/src/conflict-advisor.ts +433 -0
- package/src/conflict-engine.ts +181 -0
- package/src/conflict-escalation.ts +138 -0
- package/src/conflict-lifecycle.ts +67 -0
- package/src/conflict-output.ts +218 -0
- package/src/conflict-schema.ts +130 -0
- package/src/conflict-store.ts +127 -0
- package/src/consolidation-engine.ts +256 -0
- package/src/context-composer-pins.ts +183 -0
- package/src/context-composer-queries.ts +121 -0
- package/src/context-composer-scope.ts +56 -0
- package/src/context-composer-stable-selection.ts +200 -0
- package/src/context-composer-stable.ts +217 -0
- package/src/context-composer-task-fallbacks.ts +36 -0
- package/src/context-composer-task-ranking.ts +197 -0
- package/src/context-composer-task-results.ts +137 -0
- package/src/context-composer-task-selection.ts +342 -0
- package/src/context-composer-text.ts +57 -0
- package/src/context-composer.ts +300 -0
- package/src/context-renderer.ts +179 -0
- package/src/decay-engine.ts +189 -0
- package/src/doctor.ts +543 -0
- package/src/embedder.ts +557 -0
- package/src/entity-resolver.ts +131 -0
- package/src/eval.ts +632 -0
- package/src/event-segmenter.ts +216 -0
- package/src/ingest.ts +1822 -0
- package/src/kg-extractor.ts +217 -0
- package/src/kg-store.ts +353 -0
- package/src/legacy-scope-review.ts +53 -0
- package/src/llm-client.ts +629 -0
- package/src/llm-consolidation.ts +168 -0
- package/src/mcp-server.ts +1337 -0
- package/src/memory-assets.ts +475 -0
- package/src/memory-boundaries.ts +354 -0
- package/src/memory-health-rebalance.ts +220 -0
- package/src/memory-output.ts +474 -0
- package/src/memory-schema.ts +184 -0
- package/src/multi-vector.ts +234 -0
- package/src/noise-filter.ts +155 -0
- package/src/ppr-traversal.ts +279 -0
- package/src/preference-matcher.ts +174 -0
- package/src/preference-slots.ts +259 -0
- package/src/prospective-memory.ts +198 -0
- package/src/query-entity-detector.ts +207 -0
- package/src/query-expander.ts +98 -0
- package/src/result-synthesizer.ts +114 -0
- package/src/retrieval-profiles.ts +101 -0
- package/src/retrieval-trace.ts +94 -0
- package/src/retriever.ts +1338 -0
- package/src/retroactive-boost.ts +154 -0
- package/src/rif.ts +73 -0
- package/src/runtime-config.ts +172 -0
- package/src/schema-utils.ts +56 -0
- package/src/scope-inventory.ts +310 -0
- package/src/scope-policy.ts +142 -0
- package/src/session-engine.ts +168 -0
- package/src/session-output.ts +96 -0
- package/src/session-schema.ts +60 -0
- package/src/session-store.ts +293 -0
- package/src/stderr-log.ts +16 -0
- package/src/store.ts +903 -0
- package/src/temporal-parser.ts +318 -0
- package/src/term-registry.ts +791 -0
- package/src/tool-output-compressor.ts +115 -0
- package/src/tracker.ts +57 -0
- package/src/ui-server.ts +430 -0
- package/src/version-manager.ts +172 -0
- package/src/workflow-observation-engine.ts +316 -0
- package/src/workflow-observation-managed.ts +83 -0
- package/src/workflow-observation-output.ts +86 -0
- package/src/workflow-observation-schema.ts +87 -0
- package/src/workflow-observation-scope-review.ts +409 -0
- package/src/workflow-observation-store.ts +69 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* RecallNest HTTP API Server
|
|
4
|
+
*
|
|
5
|
+
* Universal REST API for any agent framework to access RecallNest memory.
|
|
6
|
+
* Port 4318 by default (configurable via RECALLNEST_API_PORT).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createComponentResolver, loadConfig, loadDotEnv } from "./runtime-config.js";
|
|
10
|
+
import { DurableMemoryCategorySchema } from "./memory-schema.js";
|
|
11
|
+
import { persistCaseMemory, persistMemory, persistMemoryBatch, persistWorkflowPattern, promoteMemory } from "./capture-engine.js";
|
|
12
|
+
import { buildSessionCheckpointResult } from "./session-engine.js";
|
|
13
|
+
import { SessionCheckpointStore } from "./session-store.js";
|
|
14
|
+
import { composeResumeContext } from "./context-composer.js";
|
|
15
|
+
import { extractMemoryProvenance } from "./memory-boundaries.js";
|
|
16
|
+
import { ConflictStatusSchema } from "./conflict-schema.js";
|
|
17
|
+
import { resolveConflictCandidate } from "./conflict-engine.js";
|
|
18
|
+
import { escalateConflicts } from "./conflict-escalation.js";
|
|
19
|
+
import { ConflictCandidateStore } from "./conflict-store.js";
|
|
20
|
+
import { parseConflictAttention, summarizeConflictLifecycle } from "./conflict-lifecycle.js";
|
|
21
|
+
import { buildConflictAuditSummary, clusterConflicts, summarizeConflictAdvice } from "./conflict-advisor.js";
|
|
22
|
+
import { buildWorkflowEvidence, buildWorkflowObservationRecord, inspectWorkflowDashboard, inspectWorkflowHealth } from "./workflow-observation-engine.js";
|
|
23
|
+
import { WorkflowObservationStore } from "./workflow-observation-store.js";
|
|
24
|
+
import { buildManagedCheckpointObservation, buildManagedResumeObservation } from "./workflow-observation-managed.js";
|
|
25
|
+
import { runAutoRecall } from "./auto-recall.js";
|
|
26
|
+
import { buildRetrievalContext, resolveScopeSelection } from "./scope-policy.js";
|
|
27
|
+
|
|
28
|
+
const config = (loadDotEnv(), loadConfig());
|
|
29
|
+
const getComponents = createComponentResolver(config);
|
|
30
|
+
const checkpointStore = new SessionCheckpointStore();
|
|
31
|
+
const conflictStore = new ConflictCandidateStore();
|
|
32
|
+
const workflowObservationStore = new WorkflowObservationStore();
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Helpers
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
function clampInt(value: unknown, fallback: number, min: number, max: number): number {
|
|
39
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
40
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
41
|
+
return Math.min(max, Math.max(min, parsed));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function clampFloat(value: unknown, fallback: number, min: number, max: number): number {
|
|
45
|
+
const parsed = Number.parseFloat(String(value ?? ""));
|
|
46
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
47
|
+
return Math.min(max, Math.max(min, parsed));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function readJson(request: Request): Promise<Record<string, any>> {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = await request.json();
|
|
53
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
return parsed as Record<string, any>;
|
|
57
|
+
} catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function jsonResponse(data: unknown, status = 200): Response {
|
|
63
|
+
return Response.json(data, { status });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function errorResponse(status: number, error: string): Response {
|
|
67
|
+
return Response.json({ error }, { status });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseMetadata(raw?: string): Record<string, unknown> {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(raw || "{}") as Record<string, unknown>;
|
|
73
|
+
} catch {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseDurableMemoryCategory(value: unknown) {
|
|
79
|
+
if (typeof value !== "string") return undefined;
|
|
80
|
+
const parsed = DurableMemoryCategorySchema.safeParse(value);
|
|
81
|
+
return parsed.success ? parsed.data : undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseConflictStatus(value: unknown) {
|
|
85
|
+
if (typeof value !== "string") return undefined;
|
|
86
|
+
const parsed = ConflictStatusSchema.safeParse(value);
|
|
87
|
+
return parsed.success ? parsed.data : undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildProvenance(scope: string, metadata?: string) {
|
|
91
|
+
const provenance = extractMemoryProvenance({ scope, metadata });
|
|
92
|
+
return {
|
|
93
|
+
boundary: provenance.boundary,
|
|
94
|
+
canonicalKey: provenance.canonicalKey,
|
|
95
|
+
promotedFrom: provenance.promotedFrom,
|
|
96
|
+
provenanceHistory: provenance.provenanceHistory,
|
|
97
|
+
provenanceHistoryCount: provenance.provenanceHistoryCount,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function serializeRetrievalResult(result: Awaited<ReturnType<ReturnType<typeof getComponents>["retriever"]["retrieve"]>>[number]) {
|
|
102
|
+
const metadata = parseMetadata(result.entry.metadata);
|
|
103
|
+
const provenance = buildProvenance(result.entry.scope, result.entry.metadata);
|
|
104
|
+
return {
|
|
105
|
+
id: result.entry.id,
|
|
106
|
+
text: result.entry.text,
|
|
107
|
+
category: result.entry.category,
|
|
108
|
+
tier: String(metadata.tier || "peripheral"),
|
|
109
|
+
source: String(metadata.source || result.entry.scope || "?"),
|
|
110
|
+
scope: result.entry.scope,
|
|
111
|
+
score: Math.round(result.score * 1000) / 1000,
|
|
112
|
+
importance: result.entry.importance,
|
|
113
|
+
timestamp: result.entry.timestamp,
|
|
114
|
+
date: new Date(result.entry.timestamp).toISOString().split("T")[0],
|
|
115
|
+
metadata,
|
|
116
|
+
boundary: provenance.boundary,
|
|
117
|
+
canonicalKey: provenance.canonicalKey,
|
|
118
|
+
promotedFrom: provenance.promotedFrom,
|
|
119
|
+
provenanceHistory: provenance.provenanceHistory,
|
|
120
|
+
provenanceHistoryCount: provenance.provenanceHistoryCount,
|
|
121
|
+
sources: result.sources,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function saveManagedObservation(observation: Parameters<typeof buildWorkflowObservationRecord>[0]): Promise<void> {
|
|
126
|
+
try {
|
|
127
|
+
const record = buildWorkflowObservationRecord(observation);
|
|
128
|
+
await workflowObservationStore.save(record);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("[API] Failed to persist managed workflow observation:", error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// Route handlers
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
/** POST /v1/recall — search memories (simple mode) */
|
|
139
|
+
async function handleRecall(request: Request): Promise<Response> {
|
|
140
|
+
const body = await readJson(request);
|
|
141
|
+
const query = body.query;
|
|
142
|
+
if (!query || typeof query !== "string") {
|
|
143
|
+
return errorResponse(400, "query is required");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const limit = clampInt(body.limit, 5, 1, 20);
|
|
147
|
+
const minScore = clampFloat(body.minScore, 0, 0, 1);
|
|
148
|
+
const category = parseDurableMemoryCategory(body.category);
|
|
149
|
+
const profileName = typeof body.profile === "string" ? body.profile : undefined;
|
|
150
|
+
|
|
151
|
+
const { retriever, profile, store } = getComponents(profileName);
|
|
152
|
+
const results = await retriever.retrieve(buildRetrievalContext({
|
|
153
|
+
query,
|
|
154
|
+
limit,
|
|
155
|
+
category,
|
|
156
|
+
scope: typeof body.scope === "string" ? body.scope : undefined,
|
|
157
|
+
sessionId: typeof body.sessionId === "string" ? body.sessionId : undefined,
|
|
158
|
+
allScopes: body.allScopes === true,
|
|
159
|
+
}, {
|
|
160
|
+
operation: "api:/v1/recall",
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
const filtered = minScore > 0 ? results.filter((r) => r.score >= minScore) : results;
|
|
164
|
+
|
|
165
|
+
const stats = await store.stats();
|
|
166
|
+
|
|
167
|
+
return jsonResponse({
|
|
168
|
+
results: filtered.map((r) => serializeRetrievalResult(r)),
|
|
169
|
+
query,
|
|
170
|
+
profile: profile.name,
|
|
171
|
+
totalMemories: stats.totalCount,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** POST /v1/auto-recall — compose resume context and scoped focused recall in one call */
|
|
176
|
+
async function handleAutoRecall(request: Request): Promise<Response> {
|
|
177
|
+
const body = await readJson(request);
|
|
178
|
+
const message = typeof body.message === "string"
|
|
179
|
+
? body.message
|
|
180
|
+
: typeof body.query === "string"
|
|
181
|
+
? body.query
|
|
182
|
+
: undefined;
|
|
183
|
+
if (!message) {
|
|
184
|
+
return errorResponse(400, "message is required");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const profileName = typeof body.profile === "string" ? body.profile : undefined;
|
|
189
|
+
const category = parseDurableMemoryCategory(body.category);
|
|
190
|
+
const { retriever, profile } = getComponents(profileName);
|
|
191
|
+
const autoRecall = await runAutoRecall({
|
|
192
|
+
retriever,
|
|
193
|
+
checkpointStore,
|
|
194
|
+
}, {
|
|
195
|
+
message,
|
|
196
|
+
task: typeof body.task === "string" ? body.task : undefined,
|
|
197
|
+
scope: typeof body.scope === "string" ? body.scope : undefined,
|
|
198
|
+
sessionId: typeof body.sessionId === "string" ? body.sessionId : undefined,
|
|
199
|
+
allScopes: body.allScopes === true,
|
|
200
|
+
limit: clampInt(body.limit, 5, 1, 20),
|
|
201
|
+
limitPerSection: clampInt(body.limitPerSection, 3, 1, 6),
|
|
202
|
+
includeLatestCheckpoint: typeof body.includeLatestCheckpoint === "boolean"
|
|
203
|
+
? body.includeLatestCheckpoint
|
|
204
|
+
: true,
|
|
205
|
+
category,
|
|
206
|
+
profile: profile.name,
|
|
207
|
+
operation: "api:/v1/auto-recall",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await saveManagedObservation(buildManagedResumeObservation({
|
|
211
|
+
task: typeof body.task === "string" ? body.task : message,
|
|
212
|
+
scope: typeof body.scope === "string" ? body.scope : undefined,
|
|
213
|
+
sessionId: typeof body.sessionId === "string" ? body.sessionId : undefined,
|
|
214
|
+
}, autoRecall.resume));
|
|
215
|
+
|
|
216
|
+
return jsonResponse({
|
|
217
|
+
mode: autoRecall.mode,
|
|
218
|
+
query: message,
|
|
219
|
+
profile: profile.name,
|
|
220
|
+
resolvedScope: autoRecall.resolvedScope,
|
|
221
|
+
searchSkippedReason: autoRecall.searchSkippedReason,
|
|
222
|
+
resume: autoRecall.resume,
|
|
223
|
+
results: autoRecall.results.map((result) => serializeRetrievalResult(result)),
|
|
224
|
+
count: autoRecall.results.length,
|
|
225
|
+
});
|
|
226
|
+
} catch (err) {
|
|
227
|
+
const messageText = err instanceof Error ? err.message : String(err);
|
|
228
|
+
return errorResponse(400, messageText);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** POST /v1/store — store a new memory */
|
|
233
|
+
async function handleStore(request: Request): Promise<Response> {
|
|
234
|
+
const body = await readJson(request);
|
|
235
|
+
try {
|
|
236
|
+
const { store, embedder } = getComponents();
|
|
237
|
+
const stored = await persistMemory({ store, embedder, conflictStore }, body);
|
|
238
|
+
|
|
239
|
+
return jsonResponse({
|
|
240
|
+
id: stored.id,
|
|
241
|
+
stored: stored.disposition !== "conflict",
|
|
242
|
+
disposition: stored.disposition,
|
|
243
|
+
storedAt: stored.storedAt,
|
|
244
|
+
category: stored.category,
|
|
245
|
+
scope: stored.resolvedScope,
|
|
246
|
+
canonicalKey: stored.canonicalKey,
|
|
247
|
+
conflictId: stored.conflictId,
|
|
248
|
+
}, 201);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
251
|
+
return errorResponse(400, message);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** POST /v1/capture — store multiple structured memories in one request */
|
|
256
|
+
async function handleCapture(request: Request): Promise<Response> {
|
|
257
|
+
const body = await readJson(request);
|
|
258
|
+
try {
|
|
259
|
+
const { store, embedder } = getComponents();
|
|
260
|
+
const stored = await persistMemoryBatch({ store, embedder, conflictStore }, body);
|
|
261
|
+
return jsonResponse({
|
|
262
|
+
stored: stored.length,
|
|
263
|
+
memories: stored.map((memory) => ({
|
|
264
|
+
id: memory.id,
|
|
265
|
+
text: memory.text,
|
|
266
|
+
category: memory.category,
|
|
267
|
+
scope: memory.resolvedScope,
|
|
268
|
+
source: memory.source,
|
|
269
|
+
storedAt: memory.storedAt,
|
|
270
|
+
disposition: memory.disposition,
|
|
271
|
+
canonicalKey: memory.canonicalKey,
|
|
272
|
+
conflictId: memory.conflictId,
|
|
273
|
+
})),
|
|
274
|
+
}, 201);
|
|
275
|
+
} catch (err) {
|
|
276
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
277
|
+
return errorResponse(400, message);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** POST /v1/pattern — store a structured workflow pattern as durable memory */
|
|
282
|
+
async function handlePattern(request: Request): Promise<Response> {
|
|
283
|
+
const body = await readJson(request);
|
|
284
|
+
try {
|
|
285
|
+
const { store, embedder } = getComponents();
|
|
286
|
+
const stored = await persistWorkflowPattern({ store, embedder, conflictStore }, body);
|
|
287
|
+
return jsonResponse({
|
|
288
|
+
id: stored.id,
|
|
289
|
+
stored: stored.disposition !== "conflict",
|
|
290
|
+
disposition: stored.disposition,
|
|
291
|
+
category: stored.category,
|
|
292
|
+
title: stored.title,
|
|
293
|
+
scope: stored.resolvedScope,
|
|
294
|
+
tags: stored.tags,
|
|
295
|
+
storedAt: stored.storedAt,
|
|
296
|
+
canonicalKey: stored.canonicalKey,
|
|
297
|
+
conflictId: stored.conflictId,
|
|
298
|
+
}, 201);
|
|
299
|
+
} catch (err) {
|
|
300
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
301
|
+
return errorResponse(400, message);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** POST /v1/case — store a structured case as durable memory */
|
|
306
|
+
async function handleCase(request: Request): Promise<Response> {
|
|
307
|
+
const body = await readJson(request);
|
|
308
|
+
try {
|
|
309
|
+
const { store, embedder } = getComponents();
|
|
310
|
+
const stored = await persistCaseMemory({ store, embedder, conflictStore }, body);
|
|
311
|
+
return jsonResponse({
|
|
312
|
+
id: stored.id,
|
|
313
|
+
stored: stored.disposition !== "conflict",
|
|
314
|
+
disposition: stored.disposition,
|
|
315
|
+
category: stored.category,
|
|
316
|
+
title: stored.title,
|
|
317
|
+
scope: stored.resolvedScope,
|
|
318
|
+
tags: stored.tags,
|
|
319
|
+
storedAt: stored.storedAt,
|
|
320
|
+
canonicalKey: stored.canonicalKey,
|
|
321
|
+
conflictId: stored.conflictId,
|
|
322
|
+
}, 201);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
325
|
+
return errorResponse(400, message);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** POST /v1/promote — promote an evidence memory into durable memory */
|
|
330
|
+
async function handlePromote(request: Request): Promise<Response> {
|
|
331
|
+
const body = await readJson(request);
|
|
332
|
+
try {
|
|
333
|
+
const { store, embedder } = getComponents();
|
|
334
|
+
const stored = await promoteMemory({ store, embedder, conflictStore }, body);
|
|
335
|
+
return jsonResponse({
|
|
336
|
+
id: stored.id,
|
|
337
|
+
stored: stored.disposition !== "conflict",
|
|
338
|
+
disposition: stored.disposition,
|
|
339
|
+
category: stored.category,
|
|
340
|
+
scope: stored.resolvedScope,
|
|
341
|
+
sourceMemoryId: stored.sourceMemoryId,
|
|
342
|
+
sourceCategory: stored.sourceCategory,
|
|
343
|
+
storedAt: stored.storedAt,
|
|
344
|
+
canonicalKey: stored.canonicalKey,
|
|
345
|
+
conflictId: stored.conflictId,
|
|
346
|
+
}, 201);
|
|
347
|
+
} catch (err) {
|
|
348
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
349
|
+
return errorResponse(400, message);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** GET /v1/conflicts — list recent conflicts or inspect a single conflict */
|
|
354
|
+
async function handleConflicts(request: Request): Promise<Response> {
|
|
355
|
+
const url = new URL(request.url);
|
|
356
|
+
const conflictId = url.searchParams.get("conflictId") || undefined;
|
|
357
|
+
const groupBy = url.searchParams.get("groupBy") === "cluster" ? "cluster" : "record";
|
|
358
|
+
if (conflictId) {
|
|
359
|
+
const conflict = await conflictStore.getById(conflictId);
|
|
360
|
+
return jsonResponse({
|
|
361
|
+
conflict: conflict
|
|
362
|
+
? {
|
|
363
|
+
...conflict,
|
|
364
|
+
lifecycle: summarizeConflictLifecycle(conflict),
|
|
365
|
+
advice: summarizeConflictAdvice(conflict),
|
|
366
|
+
}
|
|
367
|
+
: null,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const status = parseConflictStatus(url.searchParams.get("status"));
|
|
372
|
+
const attention = parseConflictAttention(url.searchParams.get("attention"));
|
|
373
|
+
const canonicalKey = url.searchParams.get("canonicalKey") || undefined;
|
|
374
|
+
const limit = clampInt(url.searchParams.get("limit"), 20, 1, 50);
|
|
375
|
+
const conflicts = (await conflictStore.listRecent({ status, canonicalKey, limit: Math.max(limit * 2, limit) }))
|
|
376
|
+
.map((conflict) => ({
|
|
377
|
+
...conflict,
|
|
378
|
+
lifecycle: summarizeConflictLifecycle(conflict),
|
|
379
|
+
advice: summarizeConflictAdvice(conflict),
|
|
380
|
+
}))
|
|
381
|
+
.filter((conflict) => !attention || conflict.lifecycle.attention === attention)
|
|
382
|
+
.slice(0, limit);
|
|
383
|
+
if (groupBy === "cluster") {
|
|
384
|
+
const clusters = clusterConflicts(conflicts).slice(0, limit);
|
|
385
|
+
return jsonResponse({
|
|
386
|
+
groupBy,
|
|
387
|
+
clusters,
|
|
388
|
+
count: clusters.length,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return jsonResponse({
|
|
392
|
+
groupBy,
|
|
393
|
+
conflicts,
|
|
394
|
+
count: conflicts.length,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** GET /v1/conflicts/audit — summarize conflict attention and priority clusters */
|
|
399
|
+
async function handleConflictAudit(request: Request): Promise<Response> {
|
|
400
|
+
const url = new URL(request.url);
|
|
401
|
+
const status = parseConflictStatus(url.searchParams.get("status"));
|
|
402
|
+
const canonicalKey = url.searchParams.get("canonicalKey") || undefined;
|
|
403
|
+
const limit = clampInt(url.searchParams.get("limit"), 100, 1, 500);
|
|
404
|
+
const top = clampInt(url.searchParams.get("top"), 5, 1, 20);
|
|
405
|
+
const records = await conflictStore.listRecent({ status, canonicalKey, limit });
|
|
406
|
+
return jsonResponse(buildConflictAuditSummary(records, top));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** POST /v1/conflicts/escalate — preview or apply conflict aging / escalation policy */
|
|
410
|
+
async function handleEscalateConflicts(request: Request): Promise<Response> {
|
|
411
|
+
const body = await readJson(request);
|
|
412
|
+
try {
|
|
413
|
+
const result = await escalateConflicts({
|
|
414
|
+
conflictStore,
|
|
415
|
+
}, body);
|
|
416
|
+
return jsonResponse(result);
|
|
417
|
+
} catch (err) {
|
|
418
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
419
|
+
return errorResponse(400, message);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** POST /v1/conflicts/resolve — resolve an open conflict candidate */
|
|
424
|
+
async function handleResolveConflict(request: Request): Promise<Response> {
|
|
425
|
+
const body = await readJson(request);
|
|
426
|
+
try {
|
|
427
|
+
const { store, embedder } = getComponents();
|
|
428
|
+
const result = await resolveConflictCandidate({
|
|
429
|
+
store,
|
|
430
|
+
embedder,
|
|
431
|
+
conflictStore,
|
|
432
|
+
}, body);
|
|
433
|
+
return jsonResponse(result);
|
|
434
|
+
} catch (err) {
|
|
435
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
436
|
+
return errorResponse(400, message);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** POST /v1/checkpoint — persist current session state in the checkpoint store */
|
|
441
|
+
async function handleCheckpoint(request: Request): Promise<Response> {
|
|
442
|
+
const body = await readJson(request);
|
|
443
|
+
try {
|
|
444
|
+
const result = buildSessionCheckpointResult(body);
|
|
445
|
+
const stored = await checkpointStore.save(result.record);
|
|
446
|
+
await saveManagedObservation(buildManagedCheckpointObservation({
|
|
447
|
+
...result,
|
|
448
|
+
record: stored,
|
|
449
|
+
}));
|
|
450
|
+
return jsonResponse(stored, 201);
|
|
451
|
+
} catch (err) {
|
|
452
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
453
|
+
return errorResponse(400, message);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** POST /v1/workflow-observe — persist a workflow observation outside durable memory */
|
|
458
|
+
async function handleWorkflowObserve(request: Request): Promise<Response> {
|
|
459
|
+
const body = await readJson(request);
|
|
460
|
+
try {
|
|
461
|
+
const record = buildWorkflowObservationRecord(body);
|
|
462
|
+
const stored = await workflowObservationStore.save(record);
|
|
463
|
+
return jsonResponse(stored, 201);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
466
|
+
return errorResponse(400, message);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** GET /v1/workflow-health — inspect one workflow or return a dashboard */
|
|
471
|
+
async function handleWorkflowHealth(request: Request): Promise<Response> {
|
|
472
|
+
const url = new URL(request.url);
|
|
473
|
+
const workflowId = url.searchParams.get("workflowId") || undefined;
|
|
474
|
+
const scope = url.searchParams.get("scope") || undefined;
|
|
475
|
+
const limit = clampInt(url.searchParams.get("limit"), 10, 1, 30);
|
|
476
|
+
|
|
477
|
+
if (workflowId) {
|
|
478
|
+
return jsonResponse(await inspectWorkflowHealth(workflowObservationStore, { workflowId, scope }));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return jsonResponse({
|
|
482
|
+
dashboard: await inspectWorkflowDashboard(workflowObservationStore, { scope, limit }),
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** GET /v1/workflow-evidence — build an evidence pack for a workflow primitive */
|
|
487
|
+
async function handleWorkflowEvidence(request: Request): Promise<Response> {
|
|
488
|
+
const url = new URL(request.url);
|
|
489
|
+
const workflowId = url.searchParams.get("workflowId") || undefined;
|
|
490
|
+
if (!workflowId) {
|
|
491
|
+
return errorResponse(400, "workflowId is required");
|
|
492
|
+
}
|
|
493
|
+
const scope = url.searchParams.get("scope") || undefined;
|
|
494
|
+
const limit = clampInt(url.searchParams.get("limit"), 5, 1, 20);
|
|
495
|
+
return jsonResponse(await buildWorkflowEvidence(workflowObservationStore, { workflowId, scope, limit }));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** GET /v1/checkpoint/latest — fetch the latest checkpoint for a scope or session */
|
|
499
|
+
async function handleLatestCheckpoint(request: Request): Promise<Response> {
|
|
500
|
+
const url = new URL(request.url);
|
|
501
|
+
const sessionId = url.searchParams.get("sessionId") || undefined;
|
|
502
|
+
const scope = url.searchParams.get("scope") || undefined;
|
|
503
|
+
const latest = await checkpointStore.getLatest({ sessionId, scope });
|
|
504
|
+
return jsonResponse({
|
|
505
|
+
checkpoint: latest,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** POST /v1/resume — compose startup context for a fresh window */
|
|
510
|
+
async function handleResume(request: Request): Promise<Response> {
|
|
511
|
+
const body = await readJson(request);
|
|
512
|
+
try {
|
|
513
|
+
const profileName = typeof body.profile === "string" ? body.profile : undefined;
|
|
514
|
+
const { retriever, profile } = getComponents(profileName);
|
|
515
|
+
const scopeSelection = resolveScopeSelection({
|
|
516
|
+
scope: typeof body.scope === "string" ? body.scope : undefined,
|
|
517
|
+
sessionId: typeof body.sessionId === "string" ? body.sessionId : undefined,
|
|
518
|
+
operation: "api:/v1/resume",
|
|
519
|
+
allowUnscoped: true,
|
|
520
|
+
});
|
|
521
|
+
const context = await composeResumeContext({
|
|
522
|
+
retriever,
|
|
523
|
+
checkpointStore,
|
|
524
|
+
}, {
|
|
525
|
+
...body,
|
|
526
|
+
scope: scopeSelection.resolvedScope,
|
|
527
|
+
profile: profile.name,
|
|
528
|
+
});
|
|
529
|
+
await saveManagedObservation(buildManagedResumeObservation({
|
|
530
|
+
task: typeof body.task === "string" ? body.task : undefined,
|
|
531
|
+
scope: typeof body.scope === "string" ? body.scope : undefined,
|
|
532
|
+
sessionId: typeof body.sessionId === "string" ? body.sessionId : undefined,
|
|
533
|
+
}, context));
|
|
534
|
+
|
|
535
|
+
return jsonResponse({
|
|
536
|
+
...context,
|
|
537
|
+
profile: profile.name,
|
|
538
|
+
});
|
|
539
|
+
} catch (err) {
|
|
540
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
541
|
+
return errorResponse(400, message);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** POST /v1/search — advanced search with full detail */
|
|
546
|
+
async function handleSearch(request: Request): Promise<Response> {
|
|
547
|
+
const body = await readJson(request);
|
|
548
|
+
const query = body.query;
|
|
549
|
+
if (!query || typeof query !== "string") {
|
|
550
|
+
return errorResponse(400, "query is required");
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const limit = clampInt(body.limit, 5, 1, 20);
|
|
554
|
+
const minScore = clampFloat(body.minScore, 0, 0, 1);
|
|
555
|
+
const category = parseDurableMemoryCategory(body.category);
|
|
556
|
+
const profileName = typeof body.profile === "string" ? body.profile : undefined;
|
|
557
|
+
|
|
558
|
+
const { retriever, profile } = getComponents(profileName);
|
|
559
|
+
const results = await retriever.retrieve(buildRetrievalContext({
|
|
560
|
+
query,
|
|
561
|
+
limit,
|
|
562
|
+
category,
|
|
563
|
+
scope: typeof body.scope === "string" ? body.scope : undefined,
|
|
564
|
+
sessionId: typeof body.sessionId === "string" ? body.sessionId : undefined,
|
|
565
|
+
allScopes: body.allScopes === true,
|
|
566
|
+
}, {
|
|
567
|
+
operation: "api:/v1/search",
|
|
568
|
+
}));
|
|
569
|
+
|
|
570
|
+
const filtered = minScore > 0 ? results.filter((r) => r.score >= minScore) : results;
|
|
571
|
+
|
|
572
|
+
return jsonResponse({
|
|
573
|
+
results: filtered.map((r) => serializeRetrievalResult(r)),
|
|
574
|
+
query,
|
|
575
|
+
profile: profile.name,
|
|
576
|
+
count: filtered.length,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** GET /v1/stats — memory statistics */
|
|
581
|
+
async function handleStats(): Promise<Response> {
|
|
582
|
+
const { store } = getComponents();
|
|
583
|
+
const stats = await store.stats();
|
|
584
|
+
|
|
585
|
+
return jsonResponse({
|
|
586
|
+
totalMemories: stats.totalCount,
|
|
587
|
+
byScope: stats.scopeCounts,
|
|
588
|
+
byCategory: stats.categoryCounts,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** GET /v1/health — health check */
|
|
593
|
+
async function handleHealth(): Promise<Response> {
|
|
594
|
+
try {
|
|
595
|
+
const { store } = getComponents();
|
|
596
|
+
const stats = await store.stats();
|
|
597
|
+
return jsonResponse({
|
|
598
|
+
status: "ok",
|
|
599
|
+
version: "1.0.0",
|
|
600
|
+
totalMemories: stats.totalCount,
|
|
601
|
+
timestamp: new Date().toISOString(),
|
|
602
|
+
});
|
|
603
|
+
} catch (err) {
|
|
604
|
+
return jsonResponse(
|
|
605
|
+
{
|
|
606
|
+
status: "error",
|
|
607
|
+
error: err instanceof Error ? err.message : String(err),
|
|
608
|
+
timestamp: new Date().toISOString(),
|
|
609
|
+
},
|
|
610
|
+
503,
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ============================================================================
|
|
616
|
+
// Server
|
|
617
|
+
// ============================================================================
|
|
618
|
+
|
|
619
|
+
const port = clampInt(process.env.RECALLNEST_API_PORT, 4318, 1, 65535);
|
|
620
|
+
|
|
621
|
+
const server = Bun.serve({
|
|
622
|
+
port,
|
|
623
|
+
hostname: "127.0.0.1",
|
|
624
|
+
async fetch(request) {
|
|
625
|
+
const url = new URL(request.url);
|
|
626
|
+
const { pathname } = url;
|
|
627
|
+
const method = request.method;
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
// POST endpoints
|
|
631
|
+
if (method === "POST") {
|
|
632
|
+
if (pathname === "/v1/recall") return await handleRecall(request);
|
|
633
|
+
if (pathname === "/v1/auto-recall") return await handleAutoRecall(request);
|
|
634
|
+
if (pathname === "/v1/store") return await handleStore(request);
|
|
635
|
+
if (pathname === "/v1/capture") return await handleCapture(request);
|
|
636
|
+
if (pathname === "/v1/pattern") return await handlePattern(request);
|
|
637
|
+
if (pathname === "/v1/case") return await handleCase(request);
|
|
638
|
+
if (pathname === "/v1/promote") return await handlePromote(request);
|
|
639
|
+
if (pathname === "/v1/conflicts/resolve") return await handleResolveConflict(request);
|
|
640
|
+
if (pathname === "/v1/conflicts/escalate") return await handleEscalateConflicts(request);
|
|
641
|
+
if (pathname === "/v1/checkpoint") return await handleCheckpoint(request);
|
|
642
|
+
if (pathname === "/v1/workflow-observe") return await handleWorkflowObserve(request);
|
|
643
|
+
if (pathname === "/v1/resume") return await handleResume(request);
|
|
644
|
+
if (pathname === "/v1/search") return await handleSearch(request);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// GET endpoints
|
|
648
|
+
if (method === "GET") {
|
|
649
|
+
if (pathname === "/v1/conflicts/audit") return await handleConflictAudit(request);
|
|
650
|
+
if (pathname === "/v1/conflicts") return await handleConflicts(request);
|
|
651
|
+
if (pathname === "/v1/checkpoint/latest") return await handleLatestCheckpoint(request);
|
|
652
|
+
if (pathname === "/v1/workflow-health") return await handleWorkflowHealth(request);
|
|
653
|
+
if (pathname === "/v1/workflow-evidence") return await handleWorkflowEvidence(request);
|
|
654
|
+
if (pathname === "/v1/stats") return await handleStats();
|
|
655
|
+
if (pathname === "/v1/health") return await handleHealth();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return errorResponse(404, "Not Found");
|
|
659
|
+
} catch (err) {
|
|
660
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
661
|
+
console.error(`[API] ${method} ${pathname} error:`, message);
|
|
662
|
+
return errorResponse(500, `Internal error: ${message}`);
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
console.log(`RecallNest API running at http://localhost:${server.port}`);
|