instar 0.28.83 → 0.28.85
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/dashboard/index.html +239 -0
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/intent.d.ts.map +1 -1
- package/dist/commands/intent.js +29 -2
- package/dist/commands/intent.js.map +1 -1
- package/dist/commands/memoryBackfillEvidence.d.ts +69 -0
- package/dist/commands/memoryBackfillEvidence.d.ts.map +1 -0
- package/dist/commands/memoryBackfillEvidence.js +220 -0
- package/dist/commands/memoryBackfillEvidence.js.map +1 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +86 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/DecisionJournal.d.ts +66 -3
- package/dist/core/DecisionJournal.d.ts.map +1 -1
- package/dist/core/DecisionJournal.js +116 -3
- package/dist/core/DecisionJournal.js.map +1 -1
- package/dist/core/DispatchExecutor.d.ts +74 -1
- package/dist/core/DispatchExecutor.d.ts.map +1 -1
- package/dist/core/DispatchExecutor.js +120 -1
- package/dist/core/DispatchExecutor.js.map +1 -1
- package/dist/core/EvolutionManager.d.ts +133 -0
- package/dist/core/EvolutionManager.d.ts.map +1 -1
- package/dist/core/EvolutionManager.js +494 -0
- package/dist/core/EvolutionManager.js.map +1 -1
- package/dist/core/InitiativeTracker.d.ts +82 -15
- package/dist/core/InitiativeTracker.d.ts.map +1 -1
- package/dist/core/InitiativeTracker.js +502 -31
- package/dist/core/InitiativeTracker.js.map +1 -1
- package/dist/core/LearnSkillBridge.d.ts +119 -0
- package/dist/core/LearnSkillBridge.d.ts.map +1 -0
- package/dist/core/LearnSkillBridge.js +160 -0
- package/dist/core/LearnSkillBridge.js.map +1 -0
- package/dist/core/SharedStateLedger.d.ts.map +1 -1
- package/dist/core/SharedStateLedger.js +4 -0
- package/dist/core/SharedStateLedger.js.map +1 -1
- package/dist/core/types.d.ts +124 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +8 -0
- package/dist/core/types.js.map +1 -1
- package/dist/memory/EvidenceRenderer.d.ts +64 -0
- package/dist/memory/EvidenceRenderer.d.ts.map +1 -0
- package/dist/memory/EvidenceRenderer.js +119 -0
- package/dist/memory/EvidenceRenderer.js.map +1 -0
- package/dist/memory/SemanticMemory.d.ts +61 -1
- package/dist/memory/SemanticMemory.d.ts.map +1 -1
- package/dist/memory/SemanticMemory.js +381 -11
- package/dist/memory/SemanticMemory.js.map +1 -1
- package/dist/server/AgentServer.d.ts +4 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +2 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +8 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +500 -14
- package/dist/server/routes.js.map +1 -1
- package/dist/tasks/DivergenceChecker.d.ts +89 -0
- package/dist/tasks/DivergenceChecker.d.ts.map +1 -0
- package/dist/tasks/DivergenceChecker.js +255 -0
- package/dist/tasks/DivergenceChecker.js.map +1 -0
- package/dist/tasks/LruCache.d.ts +33 -0
- package/dist/tasks/LruCache.d.ts.map +1 -0
- package/dist/tasks/LruCache.js +88 -0
- package/dist/tasks/LruCache.js.map +1 -0
- package/dist/tasks/RateLimiter.d.ts +61 -0
- package/dist/tasks/RateLimiter.d.ts.map +1 -0
- package/dist/tasks/RateLimiter.js +108 -0
- package/dist/tasks/RateLimiter.js.map +1 -0
- package/dist/tasks/TaskFlowDueWaker.d.ts +29 -0
- package/dist/tasks/TaskFlowDueWaker.d.ts.map +1 -0
- package/dist/tasks/TaskFlowDueWaker.js +73 -0
- package/dist/tasks/TaskFlowDueWaker.js.map +1 -0
- package/dist/tasks/TaskFlowMaintenanceSweeper.d.ts +40 -0
- package/dist/tasks/TaskFlowMaintenanceSweeper.d.ts.map +1 -0
- package/dist/tasks/TaskFlowMaintenanceSweeper.js +131 -0
- package/dist/tasks/TaskFlowMaintenanceSweeper.js.map +1 -0
- package/dist/tasks/TaskFlowRegistry.d.ts +180 -0
- package/dist/tasks/TaskFlowRegistry.d.ts.map +1 -0
- package/dist/tasks/TaskFlowRegistry.js +791 -0
- package/dist/tasks/TaskFlowRegistry.js.map +1 -0
- package/dist/tasks/ThreadlineFlowBridge.d.ts +54 -0
- package/dist/tasks/ThreadlineFlowBridge.d.ts.map +1 -0
- package/dist/tasks/ThreadlineFlowBridge.js +129 -0
- package/dist/tasks/ThreadlineFlowBridge.js.map +1 -0
- package/dist/tasks/task-flow-registry.store.sqlite.d.ts +77 -0
- package/dist/tasks/task-flow-registry.store.sqlite.d.ts.map +1 -0
- package/dist/tasks/task-flow-registry.store.sqlite.js +350 -0
- package/dist/tasks/task-flow-registry.store.sqlite.js.map +1 -0
- package/dist/tasks/task-flow-types.d.ts +229 -0
- package/dist/tasks/task-flow-types.d.ts.map +1 -0
- package/dist/tasks/task-flow-types.js +128 -0
- package/dist/tasks/task-flow-types.js.map +1 -0
- package/dist/threadline/ThreadlineMCPServer.d.ts +32 -0
- package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
- package/dist/threadline/ThreadlineMCPServer.js +82 -5
- package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
- package/dist/threadline/mcp-stdio-entry.js +56 -0
- package/dist/threadline/mcp-stdio-entry.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +78 -78
- package/upgrades/0.28.84.md +89 -0
- package/upgrades/0.28.85.md +64 -0
- package/upgrades/side-effects/0.28.83.md +27 -0
- package/upgrades/side-effects/taskflow-phase1.md +130 -0
- package/upgrades/side-effects/taskflow-phase2.md +110 -0
- package/upgrades/side-effects/taskflow-phase3a.md +138 -0
- package/upgrades/side-effects/taskflow-phase4.md +377 -0
- package/upgrades/side-effects/taskflow-phase5.md +234 -0
- package/upgrades/side-effects/threadline-discover-relay-and-trust.md +122 -0
- package/upgrades/side-effects/wikiclaim-evidence-phase1.md +138 -0
- package/upgrades/side-effects/wikiclaim-evidence-phase2.md +148 -0
- package/upgrades/side-effects/wikiclaim-evidence-phase3.md +125 -0
- package/upgrades/side-effects/wikiclaim-evidence-phase4.md +129 -0
- package/upgrades/side-effects/wikiclaim-evidence-phase5.md +285 -0
package/dist/server/routes.js
CHANGED
|
@@ -2021,6 +2021,114 @@ export function createRoutes(ctx) {
|
|
|
2021
2021
|
res.status(500).json({ error: err instanceof Error ? err.message : 'Context generation failed' });
|
|
2022
2022
|
}
|
|
2023
2023
|
});
|
|
2024
|
+
// ── WikiClaim Phase 4 — Inverse-traceability HTTP endpoints ───────
|
|
2025
|
+
//
|
|
2026
|
+
// Per docs/specs/OPENCLAW-IMPORT-WIKICLAIM-EVIDENCE-SPEC.md § Phase 4
|
|
2027
|
+
// (line 343) and § Inverse traceability (line 291).
|
|
2028
|
+
//
|
|
2029
|
+
// Both routes are thin pass-throughs to SemanticMemory's already-filtered
|
|
2030
|
+
// typed methods (`getEvidence` / `findCitations`). Privacy enforcement
|
|
2031
|
+
// lives ONE place — the storage layer's read-time filter (Phase 1) plus
|
|
2032
|
+
// the EvidenceRenderer helper (Phase 5). These routes do not re-implement
|
|
2033
|
+
// the filter; they invoke the methods and serialize the result.
|
|
2034
|
+
//
|
|
2035
|
+
// viewerScope derivation: the single bearer-token auth model has one
|
|
2036
|
+
// principal (the agent itself), which sees its own data at `private`
|
|
2037
|
+
// scope by default. Callers can request a NARROWED view via the
|
|
2038
|
+
// `?viewerScope=shared-project|shared-topic|private` query param —
|
|
2039
|
+
// useful for "what would a topic-peer see?" preview rendering in the
|
|
2040
|
+
// dashboard. Widening above the auth principal is a no-op (the cap is
|
|
2041
|
+
// `private`). Per spec § Storage and Privacy line 315 — the renderer is
|
|
2042
|
+
// the privacy boundary; this route is a read endpoint with no
|
|
2043
|
+
// judgment surface.
|
|
2044
|
+
const VALID_EVIDENCE_KINDS = new Set([
|
|
2045
|
+
'feedback', 'commit', 'session', 'document', 'message',
|
|
2046
|
+
'job-run', 'ledger-entry', 'pattern-entity', 'external-url',
|
|
2047
|
+
'supersedes-evidence',
|
|
2048
|
+
]);
|
|
2049
|
+
const VALID_VIEWER_SCOPES = new Set(['shared-project', 'shared-topic', 'private']);
|
|
2050
|
+
function resolveViewerScope(raw) {
|
|
2051
|
+
if (typeof raw === 'string' && VALID_VIEWER_SCOPES.has(raw)) {
|
|
2052
|
+
return raw;
|
|
2053
|
+
}
|
|
2054
|
+
// Default: agent has full visibility into its own DB.
|
|
2055
|
+
return 'private';
|
|
2056
|
+
}
|
|
2057
|
+
// GET /memory/evidence/by-entity/:id
|
|
2058
|
+
// Returns the entity's evidence array, viewer-scope filtered.
|
|
2059
|
+
router.get('/memory/evidence/by-entity/:id', (req, res) => {
|
|
2060
|
+
if (!ctx.semanticMemory) {
|
|
2061
|
+
res.status(503).json({ error: 'Semantic memory not enabled' });
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
try {
|
|
2065
|
+
const entityId = req.params.id;
|
|
2066
|
+
if (!entityId || typeof entityId !== 'string') {
|
|
2067
|
+
res.status(400).json({ error: 'Missing entity id' });
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
const viewerScope = resolveViewerScope(req.query.viewerScope);
|
|
2071
|
+
// Existence + entity-level visibility check goes through the eager
|
|
2072
|
+
// helper, which returns null when the entity is hidden (or missing)
|
|
2073
|
+
// at the requested viewer scope. We deliberately collapse "entity
|
|
2074
|
+
// exists but entity-scope wider than viewer" and "entity does not
|
|
2075
|
+
// exist" into the same 404 — the spec's inverse-query non-leak rule
|
|
2076
|
+
// (§ Storage and Privacy line 316) extends to direct fetch: a viewer
|
|
2077
|
+
// at a narrower scope must not be able to probe entity existence by
|
|
2078
|
+
// diffing 404 vs 200-empty.
|
|
2079
|
+
const eager = ctx.semanticMemory.getEntityWithEvidence(entityId, viewerScope);
|
|
2080
|
+
if (!eager) {
|
|
2081
|
+
res.status(404).json({ error: 'Entity not found' });
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
res.json({
|
|
2085
|
+
entityId,
|
|
2086
|
+
viewerScope,
|
|
2087
|
+
evidence: eager.evidence,
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
catch (err) {
|
|
2091
|
+
res.status(500).json({
|
|
2092
|
+
error: err instanceof Error ? err.message : 'Evidence lookup failed',
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
});
|
|
2096
|
+
// GET /memory/entities/by-evidence?kind=feedback&sourceId=fb_123
|
|
2097
|
+
// Returns entities citing (kind, sourceId), viewer-scope filtered.
|
|
2098
|
+
router.get('/memory/entities/by-evidence', (req, res) => {
|
|
2099
|
+
if (!ctx.semanticMemory) {
|
|
2100
|
+
res.status(503).json({ error: 'Semantic memory not enabled' });
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
try {
|
|
2104
|
+
const kindRaw = req.query.kind;
|
|
2105
|
+
const sourceIdRaw = req.query.sourceId;
|
|
2106
|
+
const kind = typeof kindRaw === 'string' ? kindRaw : '';
|
|
2107
|
+
const sourceId = typeof sourceIdRaw === 'string' ? sourceIdRaw : '';
|
|
2108
|
+
if (!kind || !sourceId) {
|
|
2109
|
+
res.status(400).json({ error: 'Missing required query params: kind, sourceId' });
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
if (!VALID_EVIDENCE_KINDS.has(kind)) {
|
|
2113
|
+
res.status(400).json({ error: `Invalid evidence kind: ${kind}` });
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
const viewerScope = resolveViewerScope(req.query.viewerScope);
|
|
2117
|
+
const entities = ctx.semanticMemory.findCitations({ kind: kind, sourceId }, viewerScope);
|
|
2118
|
+
res.json({
|
|
2119
|
+
kind,
|
|
2120
|
+
sourceId,
|
|
2121
|
+
viewerScope,
|
|
2122
|
+
entities,
|
|
2123
|
+
totalResults: entities.length,
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
catch (err) {
|
|
2127
|
+
res.status(500).json({
|
|
2128
|
+
error: err instanceof Error ? err.message : 'Citation lookup failed',
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
});
|
|
2024
2132
|
// ── MEMORY.md Export (Phase 6) ─────────────────────────────────
|
|
2025
2133
|
router.post('/semantic/export-memory', async (req, res) => {
|
|
2026
2134
|
if (!ctx.semanticMemory) {
|
|
@@ -4728,7 +4836,7 @@ export function createRoutes(ctx) {
|
|
|
4728
4836
|
}
|
|
4729
4837
|
res.json(initiative);
|
|
4730
4838
|
});
|
|
4731
|
-
router.post('/initiatives', (req, res) => {
|
|
4839
|
+
router.post('/initiatives', async (req, res) => {
|
|
4732
4840
|
if (!ctx.initiativeTracker) {
|
|
4733
4841
|
res.status(503).json({ error: 'Initiative tracker not configured' });
|
|
4734
4842
|
return;
|
|
@@ -4751,7 +4859,7 @@ export function createRoutes(ctx) {
|
|
|
4751
4859
|
return;
|
|
4752
4860
|
}
|
|
4753
4861
|
try {
|
|
4754
|
-
const created = ctx.initiativeTracker.create({
|
|
4862
|
+
const created = await ctx.initiativeTracker.create({
|
|
4755
4863
|
id, title, description, phases, links, nextCheckAt,
|
|
4756
4864
|
needsUser, needsUserReason, blockers,
|
|
4757
4865
|
});
|
|
@@ -4761,7 +4869,7 @@ export function createRoutes(ctx) {
|
|
|
4761
4869
|
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
4762
4870
|
}
|
|
4763
4871
|
});
|
|
4764
|
-
router.patch('/initiatives/:id', (req, res) => {
|
|
4872
|
+
router.patch('/initiatives/:id', async (req, res) => {
|
|
4765
4873
|
if (!ctx.initiativeTracker) {
|
|
4766
4874
|
res.status(503).json({ error: 'Initiative tracker not configured' });
|
|
4767
4875
|
return;
|
|
@@ -4771,7 +4879,7 @@ export function createRoutes(ctx) {
|
|
|
4771
4879
|
return;
|
|
4772
4880
|
}
|
|
4773
4881
|
try {
|
|
4774
|
-
const updated = ctx.initiativeTracker.update(req.params.id, req.body ?? {});
|
|
4882
|
+
const updated = await ctx.initiativeTracker.update(req.params.id, req.body ?? {});
|
|
4775
4883
|
res.json(updated);
|
|
4776
4884
|
}
|
|
4777
4885
|
catch (err) {
|
|
@@ -4780,7 +4888,7 @@ export function createRoutes(ctx) {
|
|
|
4780
4888
|
res.status(status).json({ error: msg });
|
|
4781
4889
|
}
|
|
4782
4890
|
});
|
|
4783
|
-
router.post('/initiatives/:id/phase/:phaseId', (req, res) => {
|
|
4891
|
+
router.post('/initiatives/:id/phase/:phaseId', async (req, res) => {
|
|
4784
4892
|
if (!ctx.initiativeTracker) {
|
|
4785
4893
|
res.status(503).json({ error: 'Initiative tracker not configured' });
|
|
4786
4894
|
return;
|
|
@@ -4791,7 +4899,7 @@ export function createRoutes(ctx) {
|
|
|
4791
4899
|
return;
|
|
4792
4900
|
}
|
|
4793
4901
|
try {
|
|
4794
|
-
const updated = ctx.initiativeTracker.setPhaseStatus(req.params.id, req.params.phaseId, status);
|
|
4902
|
+
const updated = await ctx.initiativeTracker.setPhaseStatus(req.params.id, req.params.phaseId, status);
|
|
4795
4903
|
res.json(updated);
|
|
4796
4904
|
}
|
|
4797
4905
|
catch (err) {
|
|
@@ -4799,12 +4907,12 @@ export function createRoutes(ctx) {
|
|
|
4799
4907
|
res.status(msg.includes('not found') ? 404 : 400).json({ error: msg });
|
|
4800
4908
|
}
|
|
4801
4909
|
});
|
|
4802
|
-
router.delete('/initiatives/:id', (req, res) => {
|
|
4910
|
+
router.delete('/initiatives/:id', async (req, res) => {
|
|
4803
4911
|
if (!ctx.initiativeTracker) {
|
|
4804
4912
|
res.status(503).json({ error: 'Initiative tracker not configured' });
|
|
4805
4913
|
return;
|
|
4806
4914
|
}
|
|
4807
|
-
const removed = ctx.initiativeTracker.remove(req.params.id);
|
|
4915
|
+
const removed = await ctx.initiativeTracker.remove(req.params.id);
|
|
4808
4916
|
if (!removed) {
|
|
4809
4917
|
res.status(404).json({ error: 'initiative not found' });
|
|
4810
4918
|
return;
|
|
@@ -6723,12 +6831,12 @@ export function createRoutes(ctx) {
|
|
|
6723
6831
|
const applied = req.query.applied !== undefined ? req.query.applied === 'true' : undefined;
|
|
6724
6832
|
res.json({ learnings: ctx.evolution.listLearnings({ category, applied }) });
|
|
6725
6833
|
});
|
|
6726
|
-
router.post('/evolution/learnings', (req, res) => {
|
|
6834
|
+
router.post('/evolution/learnings', async (req, res) => {
|
|
6727
6835
|
if (!ctx.evolution) {
|
|
6728
6836
|
res.status(503).json({ error: 'Evolution system not configured' });
|
|
6729
6837
|
return;
|
|
6730
6838
|
}
|
|
6731
|
-
const { title, category, description, source, tags, evolutionRelevance } = req.body;
|
|
6839
|
+
const { title, category, description, source, tags, evolutionRelevance, context, documentFallback, evidence: explicitEvidence } = req.body;
|
|
6732
6840
|
if (!title || typeof title !== 'string' || title.length > 500) {
|
|
6733
6841
|
res.status(400).json({ error: '"title" must be a string under 500 characters' });
|
|
6734
6842
|
return;
|
|
@@ -6737,6 +6845,43 @@ export function createRoutes(ctx) {
|
|
|
6737
6845
|
res.status(400).json({ error: '"description" is required' });
|
|
6738
6846
|
return;
|
|
6739
6847
|
}
|
|
6848
|
+
// WikiClaim Phase 3 (spec § Producers line 268, § Migration Plan line 341):
|
|
6849
|
+
// /learn must cite at least one evidence row. Either:
|
|
6850
|
+
// (a) caller passes `evidence: MemoryEvidence[]` directly (advanced),
|
|
6851
|
+
// (b) caller passes `context: string` and we auto-derive sessions/messages,
|
|
6852
|
+
// (c) caller passes `documentFallback: {sourceId, path}` for prompted-source.
|
|
6853
|
+
// Combining (b)+(c) is allowed.
|
|
6854
|
+
let derivedEvidence = [];
|
|
6855
|
+
let externalReferences = [];
|
|
6856
|
+
let pendingDocumentRef;
|
|
6857
|
+
try {
|
|
6858
|
+
if (Array.isArray(explicitEvidence) && explicitEvidence.length > 0) {
|
|
6859
|
+
derivedEvidence = explicitEvidence;
|
|
6860
|
+
}
|
|
6861
|
+
else {
|
|
6862
|
+
const { buildLearnEvidence, LearnEvidenceError } = await import('../core/LearnSkillBridge.js');
|
|
6863
|
+
try {
|
|
6864
|
+
const built = buildLearnEvidence({
|
|
6865
|
+
context: typeof context === 'string' ? context : `${title}\n\n${description}`,
|
|
6866
|
+
documentFallback,
|
|
6867
|
+
});
|
|
6868
|
+
derivedEvidence = built.evidence;
|
|
6869
|
+
externalReferences = built.externalReferences;
|
|
6870
|
+
pendingDocumentRef = built.pendingDocumentRef;
|
|
6871
|
+
}
|
|
6872
|
+
catch (err) {
|
|
6873
|
+
if (err instanceof LearnEvidenceError) {
|
|
6874
|
+
res.status(400).json({ error: err.message });
|
|
6875
|
+
return;
|
|
6876
|
+
}
|
|
6877
|
+
throw err;
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
catch (err) {
|
|
6882
|
+
res.status(500).json({ error: err instanceof Error ? err.message : 'Failed to derive evidence' });
|
|
6883
|
+
return;
|
|
6884
|
+
}
|
|
6740
6885
|
const learning = ctx.evolution.addLearning({
|
|
6741
6886
|
title, category: category || 'general', description,
|
|
6742
6887
|
source: source || { discoveredAt: new Date().toISOString() },
|
|
@@ -6753,7 +6898,18 @@ export function createRoutes(ctx) {
|
|
|
6753
6898
|
soulNudge = 'This learning may be identity-relevant. Consider updating soul.md or running /reflect.';
|
|
6754
6899
|
}
|
|
6755
6900
|
}
|
|
6756
|
-
|
|
6901
|
+
// WikiClaim Phase 3: surface derived evidence + external references on
|
|
6902
|
+
// the response so callers can confirm the bridge resolved their context.
|
|
6903
|
+
// Spec § Producers line 228 — LearnSkill kinds are message|session;
|
|
6904
|
+
// externalReferences carry feedback/commit refs that the caller routes
|
|
6905
|
+
// through the appropriate downstream producer.
|
|
6906
|
+
res.status(201).json({
|
|
6907
|
+
...learning,
|
|
6908
|
+
soulNudge,
|
|
6909
|
+
evidence: derivedEvidence,
|
|
6910
|
+
externalReferences,
|
|
6911
|
+
...(pendingDocumentRef ? { pendingDocumentRef } : {}),
|
|
6912
|
+
});
|
|
6757
6913
|
});
|
|
6758
6914
|
router.patch('/evolution/learnings/:id/apply', (req, res) => {
|
|
6759
6915
|
if (!ctx.evolution) {
|
|
@@ -7408,14 +7564,46 @@ export function createRoutes(ctx) {
|
|
|
7408
7564
|
router.post('/intent/journal', async (req, res) => {
|
|
7409
7565
|
try {
|
|
7410
7566
|
const { DecisionJournal } = await import('../core/DecisionJournal.js');
|
|
7567
|
+
const { EvidencePolicyError } = await import('../memory/SemanticMemory.js');
|
|
7411
7568
|
const journal = new DecisionJournal(ctx.config.stateDir);
|
|
7412
|
-
const { sessionId, decision, ...rest } = req.body || {};
|
|
7569
|
+
const { sessionId, decision, evidence, ...rest } = req.body || {};
|
|
7413
7570
|
if (!sessionId || !decision) {
|
|
7414
7571
|
res.status(400).json({ error: 'sessionId and decision are required' });
|
|
7415
7572
|
return;
|
|
7416
7573
|
}
|
|
7417
|
-
|
|
7418
|
-
|
|
7574
|
+
// WikiClaim Phase 3 (spec § Producers line 258): every decision must
|
|
7575
|
+
// cite at least one evidence row. The route accepts an explicit
|
|
7576
|
+
// `evidence` array, OR — when omitted — synthesizes a minimum-viable
|
|
7577
|
+
// `session` evidence row from the request's `sessionId` (the auth-
|
|
7578
|
+
// context proxy available at this HTTP layer). Synthesis keeps the
|
|
7579
|
+
// legacy POST shape working while still satisfying the policy gate;
|
|
7580
|
+
// explicit evidence always overrides synthesis. Spec § Producers
|
|
7581
|
+
// line 227: `session` is in the DecisionJournal allowlist. Spec
|
|
7582
|
+
// § Storage and Privacy line 333: synthetic sourceIds are tolerated
|
|
7583
|
+
// (consumers handle dangling refs).
|
|
7584
|
+
let effectiveEvidence = Array.isArray(evidence) ? evidence : [];
|
|
7585
|
+
if (effectiveEvidence.length === 0) {
|
|
7586
|
+
effectiveEvidence = [{
|
|
7587
|
+
kind: 'session',
|
|
7588
|
+
sourceId: `session:${sessionId}`,
|
|
7589
|
+
weight: 0.5,
|
|
7590
|
+
confidence: 0.5,
|
|
7591
|
+
privacyTier: 'private',
|
|
7592
|
+
note: 'auto-synthesized from request session (no explicit evidence)',
|
|
7593
|
+
updatedAt: new Date().toISOString(),
|
|
7594
|
+
}];
|
|
7595
|
+
}
|
|
7596
|
+
try {
|
|
7597
|
+
const entry = journal.log({ sessionId, decision, ...rest }, effectiveEvidence);
|
|
7598
|
+
res.status(201).json(entry);
|
|
7599
|
+
}
|
|
7600
|
+
catch (err) {
|
|
7601
|
+
if (err instanceof EvidencePolicyError) {
|
|
7602
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
7603
|
+
return;
|
|
7604
|
+
}
|
|
7605
|
+
throw err;
|
|
7606
|
+
}
|
|
7419
7607
|
}
|
|
7420
7608
|
catch (err) {
|
|
7421
7609
|
res.status(500).json({ error: err instanceof Error ? err.message : 'Failed to log decision' });
|
|
@@ -9151,6 +9339,23 @@ export function createRoutes(ctx) {
|
|
|
9151
9339
|
}
|
|
9152
9340
|
}
|
|
9153
9341
|
}
|
|
9342
|
+
// ThreadlineFlowBridge: resume any TaskFlow flow waiting on a
|
|
9343
|
+
// cross-agent-callback whose correlationId matches this inbound. The
|
|
9344
|
+
// bridge runs after relay-accept (auth has passed), inspects only the
|
|
9345
|
+
// envelope, and never alters the HTTP response — failures or misses
|
|
9346
|
+
// are logged but not surfaced.
|
|
9347
|
+
if (ctx.threadlineFlowBridge) {
|
|
9348
|
+
try {
|
|
9349
|
+
const bridgeResult = await ctx.threadlineFlowBridge.consumeInbound(envelope);
|
|
9350
|
+
if (bridgeResult.resumed) {
|
|
9351
|
+
console.log(`[relay-agent] ThreadlineFlowBridge resumed ${bridgeResult.flowIds.length} flow(s): ${bridgeResult.flowIds.join(', ')}`);
|
|
9352
|
+
}
|
|
9353
|
+
}
|
|
9354
|
+
catch (err) {
|
|
9355
|
+
if (err instanceof Error)
|
|
9356
|
+
console.error('[routes] ThreadlineFlowBridge error:', err.message);
|
|
9357
|
+
}
|
|
9358
|
+
}
|
|
9154
9359
|
// If we have a ThreadlineRouter, route the message through it for
|
|
9155
9360
|
// session resume/spawn. We AWAIT the result so callers learn the real
|
|
9156
9361
|
// outcome (spawned / resumed / injected / handled:false). The message
|
|
@@ -10162,6 +10367,43 @@ export function createRoutes(ctx) {
|
|
|
10162
10367
|
});
|
|
10163
10368
|
}
|
|
10164
10369
|
});
|
|
10370
|
+
// ── Threadline Relay Discover ────────────────────────────────────────
|
|
10371
|
+
// Proxies a discover query through the agent server's relay WebSocket so
|
|
10372
|
+
// the MCP stdio subprocess can ask the relay for its live presence registry
|
|
10373
|
+
// (the MCP subprocess doesn't have its own relay client — same arrangement
|
|
10374
|
+
// as /threadline/relay-send above).
|
|
10375
|
+
router.post('/threadline/relay-discover', async (req, res) => {
|
|
10376
|
+
const relayClient = ctx.threadlineRelayClient;
|
|
10377
|
+
if (!relayClient || relayClient.connectionState !== 'connected') {
|
|
10378
|
+
res.status(503).json({
|
|
10379
|
+
success: false,
|
|
10380
|
+
error: 'Relay not connected',
|
|
10381
|
+
connectionState: relayClient?.connectionState ?? 'absent',
|
|
10382
|
+
});
|
|
10383
|
+
return;
|
|
10384
|
+
}
|
|
10385
|
+
const filter = (req.body && typeof req.body === 'object') ? req.body.filter : undefined;
|
|
10386
|
+
try {
|
|
10387
|
+
const agents = await relayClient.discover(filter);
|
|
10388
|
+
res.json({
|
|
10389
|
+
success: true,
|
|
10390
|
+
agents: agents.map(a => ({
|
|
10391
|
+
agentId: a.agentId,
|
|
10392
|
+
name: a.name,
|
|
10393
|
+
publicKey: Buffer.isBuffer(a.publicKey) ? a.publicKey.toString('hex') : a.publicKey,
|
|
10394
|
+
framework: a.framework,
|
|
10395
|
+
capabilities: a.capabilities,
|
|
10396
|
+
lastSeen: a.lastSeen,
|
|
10397
|
+
})),
|
|
10398
|
+
});
|
|
10399
|
+
}
|
|
10400
|
+
catch (err) {
|
|
10401
|
+
res.status(500).json({
|
|
10402
|
+
success: false,
|
|
10403
|
+
error: err instanceof Error ? err.message : 'Discover failed',
|
|
10404
|
+
});
|
|
10405
|
+
}
|
|
10406
|
+
});
|
|
10165
10407
|
// ── Response Review Pipeline (Coherence Gate) ────────────────────────
|
|
10166
10408
|
//
|
|
10167
10409
|
// Evaluates agent responses before delivery. Implements PEL + Gate + Specialist
|
|
@@ -11735,6 +11977,250 @@ export function createRoutes(ctx) {
|
|
|
11735
11977
|
idleExpiresAt: result.result.idleExpiresAt,
|
|
11736
11978
|
});
|
|
11737
11979
|
});
|
|
11980
|
+
// ─── TaskFlow registry (OpenClaw import — Phase 1) ──────────────────
|
|
11981
|
+
// Admin Bearer-token auth via global authMiddleware. The HTTP API exists for
|
|
11982
|
+
// debugging and out-of-process tooling; production controllers run
|
|
11983
|
+
// in-process inside the server and call the registry directly.
|
|
11984
|
+
// See docs/specs/OPENCLAW-IMPORT-TASKFLOW-SPEC.md.
|
|
11985
|
+
{
|
|
11986
|
+
const taskFlowError = (res, err) => {
|
|
11987
|
+
const e = err;
|
|
11988
|
+
if (e?.name !== 'TaskFlowError') {
|
|
11989
|
+
res.status(500).json({ error: 'internal_error', message: e?.message });
|
|
11990
|
+
return;
|
|
11991
|
+
}
|
|
11992
|
+
const status = {
|
|
11993
|
+
not_found: 404,
|
|
11994
|
+
revision_conflict: 409,
|
|
11995
|
+
already_terminal: 410,
|
|
11996
|
+
invalid_transition: 422,
|
|
11997
|
+
invalid_argument: 422,
|
|
11998
|
+
unauthorized_controller: 422,
|
|
11999
|
+
already_consumed: 422,
|
|
12000
|
+
wait_collision: 422,
|
|
12001
|
+
quota_exceeded: 429,
|
|
12002
|
+
}[e.code ?? ''] ?? 500;
|
|
12003
|
+
// Phase 5: rate-limit responses surface Retry-After header for HTTP-compliant
|
|
12004
|
+
// clients in addition to the in-body retryAfterMs hint. RFC 7231 § 7.1.3
|
|
12005
|
+
// specifies seconds; we round UP so a sub-second retry maps to 1.
|
|
12006
|
+
if (status === 429 && typeof e.detail?.retryAfterMs === 'number') {
|
|
12007
|
+
const retryAfterMs = e.detail.retryAfterMs;
|
|
12008
|
+
const seconds = Math.max(1, Math.ceil(retryAfterMs / 1000));
|
|
12009
|
+
try {
|
|
12010
|
+
res.setHeader('Retry-After', String(seconds));
|
|
12011
|
+
}
|
|
12012
|
+
catch { /* swallow */ }
|
|
12013
|
+
}
|
|
12014
|
+
res.status(status).json({ error: e.code, ...(e.detail ?? {}), message: e.message });
|
|
12015
|
+
};
|
|
12016
|
+
router.post('/flows', async (req, res) => {
|
|
12017
|
+
if (!ctx.taskFlowRegistry) {
|
|
12018
|
+
res.status(503).json({ error: 'taskflow_not_enabled' });
|
|
12019
|
+
return;
|
|
12020
|
+
}
|
|
12021
|
+
try {
|
|
12022
|
+
const out = await ctx.taskFlowRegistry.createFlow(req.body ?? {});
|
|
12023
|
+
res.status(out.created ? 201 : 200).json(out.flow);
|
|
12024
|
+
}
|
|
12025
|
+
catch (err) {
|
|
12026
|
+
if (err instanceof Error)
|
|
12027
|
+
taskFlowError(res, err);
|
|
12028
|
+
else
|
|
12029
|
+
res.status(500).json({ error: 'internal_error' });
|
|
12030
|
+
}
|
|
12031
|
+
});
|
|
12032
|
+
router.get('/flows/:flowId', (req, res) => {
|
|
12033
|
+
if (!ctx.taskFlowRegistry) {
|
|
12034
|
+
res.status(503).json({ error: 'taskflow_not_enabled' });
|
|
12035
|
+
return;
|
|
12036
|
+
}
|
|
12037
|
+
const flow = ctx.taskFlowRegistry.getFlow(req.params.flowId);
|
|
12038
|
+
if (!flow) {
|
|
12039
|
+
res.status(404).json({ error: 'not_found', flowId: req.params.flowId });
|
|
12040
|
+
return;
|
|
12041
|
+
}
|
|
12042
|
+
// The owning controller may identify itself via header to receive the
|
|
12043
|
+
// unredacted record. Otherwise we return the redacted shape (no
|
|
12044
|
+
// stateJson, waitJson reduced to {kind}).
|
|
12045
|
+
const callerControllerId = req.header('x-taskflow-controller-id');
|
|
12046
|
+
if (callerControllerId && callerControllerId === flow.controllerId) {
|
|
12047
|
+
res.json(flow);
|
|
12048
|
+
return;
|
|
12049
|
+
}
|
|
12050
|
+
res.json(ctx.taskFlowRegistry.getRedactedFlow(req.params.flowId));
|
|
12051
|
+
});
|
|
12052
|
+
router.get('/flows/waiting', (req, res) => {
|
|
12053
|
+
if (!ctx.taskFlowRegistry) {
|
|
12054
|
+
res.status(503).json({ error: 'taskflow_not_enabled' });
|
|
12055
|
+
return;
|
|
12056
|
+
}
|
|
12057
|
+
const channel = typeof req.query.channel === 'string' ? req.query.channel : undefined;
|
|
12058
|
+
const threadId = typeof req.query.threadId === 'string' ? req.query.threadId : undefined;
|
|
12059
|
+
const peer = typeof req.query.peer === 'string' ? req.query.peer : undefined;
|
|
12060
|
+
const correlationId = typeof req.query.correlationId === 'string' ? req.query.correlationId : undefined;
|
|
12061
|
+
const waitKind = typeof req.query.waitKind === 'string' ? req.query.waitKind : undefined;
|
|
12062
|
+
if (channel && threadId && peer) {
|
|
12063
|
+
res.json({
|
|
12064
|
+
matches: ctx.taskFlowRegistry.findWaitingByReply({ channel, threadId, peer }),
|
|
12065
|
+
});
|
|
12066
|
+
return;
|
|
12067
|
+
}
|
|
12068
|
+
if (correlationId && (waitKind === 'external-call' || waitKind === 'cross-agent-callback')) {
|
|
12069
|
+
res.json({
|
|
12070
|
+
matches: ctx.taskFlowRegistry.findWaitingByCorrelation({ waitKind, correlationId }),
|
|
12071
|
+
});
|
|
12072
|
+
return;
|
|
12073
|
+
}
|
|
12074
|
+
res.status(400).json({ error: 'invalid_query', message: 'specify (channel,threadId,peer) or (correlationId,waitKind)' });
|
|
12075
|
+
});
|
|
12076
|
+
const occMutationHandler = (op) => async (req, res) => {
|
|
12077
|
+
if (!ctx.taskFlowRegistry) {
|
|
12078
|
+
res.status(503).json({ error: 'taskflow_not_enabled' });
|
|
12079
|
+
return;
|
|
12080
|
+
}
|
|
12081
|
+
const body = (req.body ?? {});
|
|
12082
|
+
const flowId = req.params.flowId;
|
|
12083
|
+
const expectedRevision = Number(body.expectedRevision);
|
|
12084
|
+
if (!Number.isFinite(expectedRevision)) {
|
|
12085
|
+
res.status(422).json({ error: 'invalid_argument', field: 'expectedRevision' });
|
|
12086
|
+
return;
|
|
12087
|
+
}
|
|
12088
|
+
const principal = body.principal;
|
|
12089
|
+
if (!principal || typeof principal !== 'object' || !('scope' in principal)) {
|
|
12090
|
+
res.status(422).json({ error: 'invalid_argument', field: 'principal' });
|
|
12091
|
+
return;
|
|
12092
|
+
}
|
|
12093
|
+
try {
|
|
12094
|
+
let result;
|
|
12095
|
+
switch (op) {
|
|
12096
|
+
case 'startStep':
|
|
12097
|
+
result = await ctx.taskFlowRegistry.startStep({
|
|
12098
|
+
flowId,
|
|
12099
|
+
expectedRevision,
|
|
12100
|
+
principal: principal,
|
|
12101
|
+
currentStep: String(body.currentStep ?? ''),
|
|
12102
|
+
});
|
|
12103
|
+
break;
|
|
12104
|
+
case 'setFlowWaiting':
|
|
12105
|
+
result = await ctx.taskFlowRegistry.setFlowWaiting({
|
|
12106
|
+
flowId,
|
|
12107
|
+
expectedRevision,
|
|
12108
|
+
principal: principal,
|
|
12109
|
+
waitJson: body.waitJson,
|
|
12110
|
+
currentStep: body.currentStep,
|
|
12111
|
+
statePatch: body.statePatch,
|
|
12112
|
+
});
|
|
12113
|
+
break;
|
|
12114
|
+
case 'resumeFlow':
|
|
12115
|
+
result = await ctx.taskFlowRegistry.resumeFlow({
|
|
12116
|
+
flowId,
|
|
12117
|
+
expectedRevision,
|
|
12118
|
+
principal: principal,
|
|
12119
|
+
waitInstanceId: String(body.waitInstanceId ?? ''),
|
|
12120
|
+
currentStep: body.currentStep,
|
|
12121
|
+
statePatch: body.statePatch,
|
|
12122
|
+
});
|
|
12123
|
+
break;
|
|
12124
|
+
case 'finishFlow':
|
|
12125
|
+
result = await ctx.taskFlowRegistry.finishFlow({
|
|
12126
|
+
flowId,
|
|
12127
|
+
expectedRevision,
|
|
12128
|
+
principal: principal,
|
|
12129
|
+
result: body.result,
|
|
12130
|
+
});
|
|
12131
|
+
break;
|
|
12132
|
+
case 'failFlow':
|
|
12133
|
+
result = await ctx.taskFlowRegistry.failFlow({
|
|
12134
|
+
flowId,
|
|
12135
|
+
expectedRevision,
|
|
12136
|
+
principal: principal,
|
|
12137
|
+
failureReason: String(body.failureReason ?? ''),
|
|
12138
|
+
});
|
|
12139
|
+
break;
|
|
12140
|
+
case 'cancelFlow':
|
|
12141
|
+
result = await ctx.taskFlowRegistry.cancelFlow({
|
|
12142
|
+
flowId,
|
|
12143
|
+
expectedRevision,
|
|
12144
|
+
principal: principal,
|
|
12145
|
+
});
|
|
12146
|
+
break;
|
|
12147
|
+
case 'markLost':
|
|
12148
|
+
result = await ctx.taskFlowRegistry.markLost({
|
|
12149
|
+
flowId,
|
|
12150
|
+
expectedRevision,
|
|
12151
|
+
ledgerEntryId: String(body.ledgerEntryId ?? ''),
|
|
12152
|
+
reason: body.reason === 'stranded' ? 'stranded' : 'lost',
|
|
12153
|
+
});
|
|
12154
|
+
break;
|
|
12155
|
+
}
|
|
12156
|
+
res.json(result);
|
|
12157
|
+
}
|
|
12158
|
+
catch (err) {
|
|
12159
|
+
if (err instanceof Error)
|
|
12160
|
+
taskFlowError(res, err);
|
|
12161
|
+
else
|
|
12162
|
+
res.status(500).json({ error: 'internal_error' });
|
|
12163
|
+
}
|
|
12164
|
+
};
|
|
12165
|
+
router.post('/flows/:flowId/start-step', occMutationHandler('startStep'));
|
|
12166
|
+
router.post('/flows/:flowId/wait', occMutationHandler('setFlowWaiting'));
|
|
12167
|
+
router.post('/flows/:flowId/resume', occMutationHandler('resumeFlow'));
|
|
12168
|
+
router.post('/flows/:flowId/finish', occMutationHandler('finishFlow'));
|
|
12169
|
+
router.post('/flows/:flowId/fail', occMutationHandler('failFlow'));
|
|
12170
|
+
router.post('/flows/:flowId/cancel-flow', occMutationHandler('cancelFlow'));
|
|
12171
|
+
router.post('/flows/:flowId/mark-lost', occMutationHandler('markLost'));
|
|
12172
|
+
router.post('/flows/:flowId/cancel-request', async (req, res) => {
|
|
12173
|
+
if (!ctx.taskFlowRegistry) {
|
|
12174
|
+
res.status(503).json({ error: 'taskflow_not_enabled' });
|
|
12175
|
+
return;
|
|
12176
|
+
}
|
|
12177
|
+
const body = (req.body ?? {});
|
|
12178
|
+
const expectedRevision = Number(body.expectedRevision);
|
|
12179
|
+
if (!Number.isFinite(expectedRevision)) {
|
|
12180
|
+
res.status(422).json({ error: 'invalid_argument', field: 'expectedRevision' });
|
|
12181
|
+
return;
|
|
12182
|
+
}
|
|
12183
|
+
try {
|
|
12184
|
+
const result = await ctx.taskFlowRegistry.requestFlowCancel({
|
|
12185
|
+
flowId: req.params.flowId,
|
|
12186
|
+
expectedRevision,
|
|
12187
|
+
requesterOrigin: body.requesterOrigin,
|
|
12188
|
+
});
|
|
12189
|
+
res.json(result);
|
|
12190
|
+
}
|
|
12191
|
+
catch (err) {
|
|
12192
|
+
if (err instanceof Error)
|
|
12193
|
+
taskFlowError(res, err);
|
|
12194
|
+
else
|
|
12195
|
+
res.status(500).json({ error: 'internal_error' });
|
|
12196
|
+
}
|
|
12197
|
+
});
|
|
12198
|
+
router.post('/flows/:flowId/ping', async (req, res) => {
|
|
12199
|
+
if (!ctx.taskFlowRegistry) {
|
|
12200
|
+
res.status(503).json({ error: 'taskflow_not_enabled' });
|
|
12201
|
+
return;
|
|
12202
|
+
}
|
|
12203
|
+
const body = (req.body ?? {});
|
|
12204
|
+
const principal = body.principal;
|
|
12205
|
+
if (!principal || principal.scope !== 'controller') {
|
|
12206
|
+
res.status(422).json({ error: 'invalid_argument', field: 'principal' });
|
|
12207
|
+
return;
|
|
12208
|
+
}
|
|
12209
|
+
try {
|
|
12210
|
+
const flow = await ctx.taskFlowRegistry.pingFlow({
|
|
12211
|
+
flowId: req.params.flowId,
|
|
12212
|
+
principal,
|
|
12213
|
+
});
|
|
12214
|
+
res.json(flow);
|
|
12215
|
+
}
|
|
12216
|
+
catch (err) {
|
|
12217
|
+
if (err instanceof Error)
|
|
12218
|
+
taskFlowError(res, err);
|
|
12219
|
+
else
|
|
12220
|
+
res.status(500).json({ error: 'internal_error' });
|
|
12221
|
+
}
|
|
12222
|
+
});
|
|
12223
|
+
}
|
|
11738
12224
|
return router;
|
|
11739
12225
|
}
|
|
11740
12226
|
export function formatUptime(ms) {
|