@spilno/herald-mcp 1.36.0 → 1.36.2
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 +4 -4
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +5 -8
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +39 -15
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/login.d.ts +1 -0
- package/dist/cli/login.d.ts.map +1 -1
- package/dist/cli/login.js +15 -2
- package/dist/cli/login.js.map +1 -1
- package/dist/cli/templates/claude-md.d.ts +3 -3
- package/dist/cli/templates/claude-md.d.ts.map +1 -1
- package/dist/cli/templates/claude-md.js +9 -9
- package/dist/cli/templates/claude-md.js.map +1 -1
- package/dist/cli.js +345 -30
- package/dist/cli.js.map +1 -1
- package/dist/sanitization.spec.js +1 -1
- package/dist/sanitization.spec.js.map +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +74 -2
- package/dist/sdk.js.map +1 -1
- package/dist/session-tools.spec.js +3 -3
- package/dist/session-tools.spec.js.map +1 -1
- package/package.json +3 -3
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2822
- package/dist/index.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -26,11 +26,106 @@ import { sanitize, previewSanitization } from "./sanitization.js";
|
|
|
26
26
|
const CEDA_API_URL = process.env.CEDA_URL || process.env.HERALD_API_URL || "https://getceda.com";
|
|
27
27
|
// CEDA_TOKEN is the primary auth (from app.getceda.com OAuth)
|
|
28
28
|
// HERALD_API_TOKEN kept for backwards compatibility
|
|
29
|
-
|
|
29
|
+
// CEDA-FIX: Also check ~/.herald/token.json for global token (one key per user)
|
|
30
|
+
const TOKEN_FILE_PATH = join(homedir(), ".herald", "token.json");
|
|
31
|
+
function loadGlobalToken() {
|
|
32
|
+
try {
|
|
33
|
+
if (existsSync(TOKEN_FILE_PATH)) {
|
|
34
|
+
const data = JSON.parse(readFileSync(TOKEN_FILE_PATH, "utf-8"));
|
|
35
|
+
return {
|
|
36
|
+
token: data.token || undefined,
|
|
37
|
+
refreshToken: data.refreshToken || undefined,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Ignore errors reading global token
|
|
43
|
+
}
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
function isTokenExpired(token) {
|
|
47
|
+
try {
|
|
48
|
+
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
|
49
|
+
if (!payload.exp)
|
|
50
|
+
return true;
|
|
51
|
+
// Consider expired if less than 60s remaining
|
|
52
|
+
return payload.exp * 1000 < Date.now() + 60_000;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const globalTokens = loadGlobalToken();
|
|
59
|
+
let CEDA_API_TOKEN = process.env.CEDA_TOKEN || process.env.HERALD_API_TOKEN || globalTokens.token;
|
|
60
|
+
let CEDA_REFRESH_TOKEN = globalTokens.refreshToken;
|
|
61
|
+
// Mutex: serialize concurrent refresh attempts so only one network call fires
|
|
62
|
+
let refreshInFlight = null;
|
|
63
|
+
/**
|
|
64
|
+
* Refresh the access token using the stored refresh token.
|
|
65
|
+
* Updates in-memory token and writes back to ~/.herald/token.json.
|
|
66
|
+
* Returns true on success, false on failure.
|
|
67
|
+
* Concurrent calls coalesce into a single network request.
|
|
68
|
+
*/
|
|
69
|
+
async function refreshAccessToken() {
|
|
70
|
+
if (refreshInFlight)
|
|
71
|
+
return refreshInFlight;
|
|
72
|
+
refreshInFlight = doRefreshAccessToken().finally(() => { refreshInFlight = null; });
|
|
73
|
+
return refreshInFlight;
|
|
74
|
+
}
|
|
75
|
+
async function doRefreshAccessToken() {
|
|
76
|
+
if (!CEDA_REFRESH_TOKEN)
|
|
77
|
+
return false;
|
|
78
|
+
try {
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
const timeoutId = setTimeout(() => controller.abort(), 10_000);
|
|
81
|
+
const response = await fetch(`${CEDA_API_URL}/api/auth/refresh`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Content-Type': 'application/json' },
|
|
84
|
+
body: JSON.stringify({ refreshToken: CEDA_REFRESH_TOKEN }),
|
|
85
|
+
signal: controller.signal,
|
|
86
|
+
});
|
|
87
|
+
clearTimeout(timeoutId);
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
console.error(`[Herald] Token refresh failed: HTTP ${response.status}`);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const data = await response.json();
|
|
93
|
+
CEDA_API_TOKEN = data.accessToken;
|
|
94
|
+
CEDA_REFRESH_TOKEN = data.refreshToken;
|
|
95
|
+
// Write updated tokens back to disk
|
|
96
|
+
try {
|
|
97
|
+
if (existsSync(TOKEN_FILE_PATH)) {
|
|
98
|
+
const existing = JSON.parse(readFileSync(TOKEN_FILE_PATH, "utf-8"));
|
|
99
|
+
existing.token = data.accessToken;
|
|
100
|
+
existing.refreshToken = data.refreshToken;
|
|
101
|
+
// Update expiresAt from new token
|
|
102
|
+
try {
|
|
103
|
+
const payload = JSON.parse(Buffer.from(data.accessToken.split('.')[1], 'base64').toString());
|
|
104
|
+
existing.expiresAt = payload.exp
|
|
105
|
+
? new Date(payload.exp * 1000).toISOString()
|
|
106
|
+
: new Date(Date.now() + data.expiresIn * 1000).toISOString();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
existing.expiresAt = new Date(Date.now() + data.expiresIn * 1000).toISOString();
|
|
110
|
+
}
|
|
111
|
+
writeFileSync(TOKEN_FILE_PATH, JSON.stringify(existing, null, 2), "utf-8");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Non-fatal: token works in memory even if disk write fails
|
|
116
|
+
}
|
|
117
|
+
console.error("[Herald] Token refreshed");
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`[Herald] Token refresh error: ${error}`);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
30
125
|
const CEDA_API_USER = process.env.HERALD_API_USER;
|
|
31
126
|
const CEDA_API_PASS = process.env.HERALD_API_PASS;
|
|
32
127
|
// CEDA-70: Zero-config context - everything auto-derived, nothing required
|
|
33
|
-
// User is ALWAYS known (whoami).
|
|
128
|
+
// User is ALWAYS known (whoami). Org/project inferred from path as tags.
|
|
34
129
|
function deriveUser() {
|
|
35
130
|
// Priority: git user > env var > OS user
|
|
36
131
|
// Git user is trusted (immutable identity from git config)
|
|
@@ -729,8 +824,55 @@ async function callCedaAPI(endpoint, method = "GET", body) {
|
|
|
729
824
|
signal: controller.signal,
|
|
730
825
|
});
|
|
731
826
|
clearTimeout(timeoutId);
|
|
827
|
+
if (response.status === 401 && CEDA_REFRESH_TOKEN) {
|
|
828
|
+
// Drain original response body to avoid leak
|
|
829
|
+
await response.text().catch(() => { });
|
|
830
|
+
// Attempt auto-refresh and retry once
|
|
831
|
+
const refreshed = await refreshAccessToken();
|
|
832
|
+
if (refreshed) {
|
|
833
|
+
const retryHeaders = { ...headers, Authorization: `Bearer ${CEDA_API_TOKEN}` };
|
|
834
|
+
const retryController = new AbortController();
|
|
835
|
+
const retryTimeoutId = setTimeout(() => retryController.abort(), CEDA_REQUEST_TIMEOUT_MS);
|
|
836
|
+
try {
|
|
837
|
+
const retryResponse = await fetch(url, {
|
|
838
|
+
method,
|
|
839
|
+
headers: retryHeaders,
|
|
840
|
+
body: enrichedBody ? JSON.stringify(enrichedBody) : undefined,
|
|
841
|
+
signal: retryController.signal,
|
|
842
|
+
});
|
|
843
|
+
clearTimeout(retryTimeoutId);
|
|
844
|
+
if (!retryResponse.ok) {
|
|
845
|
+
try {
|
|
846
|
+
const errorBody = await retryResponse.json();
|
|
847
|
+
return { success: false, error: `HTTP ${retryResponse.status}: ${errorBody.message || errorBody.error || retryResponse.statusText}`, details: errorBody };
|
|
848
|
+
}
|
|
849
|
+
catch {
|
|
850
|
+
return { success: false, error: `HTTP ${retryResponse.status}: ${retryResponse.statusText}` };
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return await retryResponse.json();
|
|
854
|
+
}
|
|
855
|
+
catch (retryError) {
|
|
856
|
+
clearTimeout(retryTimeoutId);
|
|
857
|
+
return { success: false, error: `Retry after refresh failed: ${retryError}` };
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
// Refresh failed — fall through with a synthetic 401 error
|
|
861
|
+
return { success: false, error: `HTTP 401: Unauthorized (token refresh failed)` };
|
|
862
|
+
}
|
|
732
863
|
if (!response.ok) {
|
|
733
|
-
|
|
864
|
+
// CEDA-FIX: Include response body for better error messages
|
|
865
|
+
try {
|
|
866
|
+
const errorBody = await response.json();
|
|
867
|
+
return {
|
|
868
|
+
success: false,
|
|
869
|
+
error: `HTTP ${response.status}: ${errorBody.message || errorBody.error || response.statusText}`,
|
|
870
|
+
details: errorBody
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
catch {
|
|
874
|
+
return { success: false, error: `HTTP ${response.status}: ${response.statusText}` };
|
|
875
|
+
}
|
|
734
876
|
}
|
|
735
877
|
return await response.json();
|
|
736
878
|
}
|
|
@@ -953,7 +1095,7 @@ const tools = [
|
|
|
953
1095
|
},
|
|
954
1096
|
{
|
|
955
1097
|
name: "herald_context",
|
|
956
|
-
description: `Get or refresh Herald's context (
|
|
1098
|
+
description: `Get or refresh Herald's context (org/project/user).
|
|
957
1099
|
|
|
958
1100
|
Context is derived from git (trusted) or path (fallback).
|
|
959
1101
|
Use refresh=true after cloning a repo or changing directories to update context.
|
|
@@ -1118,7 +1260,7 @@ Example flow:
|
|
|
1118
1260
|
// CEDA-49: Session Management Tools
|
|
1119
1261
|
{
|
|
1120
1262
|
name: "herald_session_list",
|
|
1121
|
-
description: "List sessions for a
|
|
1263
|
+
description: "List sessions for a org with optional filters. Returns session summaries including id, status, created/updated timestamps.",
|
|
1122
1264
|
inputSchema: {
|
|
1123
1265
|
type: "object",
|
|
1124
1266
|
properties: {
|
|
@@ -1235,6 +1377,52 @@ Shows what would be redacted and final transmitted text.`,
|
|
|
1235
1377
|
required: ["session", "feeling", "insight"],
|
|
1236
1378
|
},
|
|
1237
1379
|
},
|
|
1380
|
+
// Capture raw observation (Level 0) - feeds into graduation pipeline
|
|
1381
|
+
{
|
|
1382
|
+
name: "herald_observe",
|
|
1383
|
+
description: `Capture a raw observation (Level 0) for later pattern graduation.
|
|
1384
|
+
|
|
1385
|
+
Unlike herald_reflect (Level 1, pre-validated), observations are raw signals that
|
|
1386
|
+
feed into CEDA's clustering pipeline. When enough similar observations accumulate,
|
|
1387
|
+
they auto-graduate into patterns.
|
|
1388
|
+
|
|
1389
|
+
WHEN TO USE:
|
|
1390
|
+
- You notice something interesting but aren't sure if it's a pattern yet
|
|
1391
|
+
- Recording a data point without committing to "pattern" or "antipattern"
|
|
1392
|
+
- Logging what happened for later analysis
|
|
1393
|
+
- "That's interesting..." moments
|
|
1394
|
+
|
|
1395
|
+
WHEN TO USE herald_reflect INSTEAD:
|
|
1396
|
+
- You're confident it IS a pattern or antipattern
|
|
1397
|
+
- User explicitly says "capture this" / "log this pattern"
|
|
1398
|
+
- Clear success or failure signal
|
|
1399
|
+
|
|
1400
|
+
Example flow:
|
|
1401
|
+
1. User tries a new approach → herald_observe (raw signal)
|
|
1402
|
+
2. Same approach works 3x → CEDA auto-graduates to pattern
|
|
1403
|
+
3. User confirms it's reliable → herald_reflect (validated)
|
|
1404
|
+
|
|
1405
|
+
TRIGGER WORDS: "observe", "note this", "interesting", "log observation", "raw signal"`,
|
|
1406
|
+
inputSchema: {
|
|
1407
|
+
type: "object",
|
|
1408
|
+
properties: {
|
|
1409
|
+
input: {
|
|
1410
|
+
type: "string",
|
|
1411
|
+
description: "What was observed — the raw signal or event"
|
|
1412
|
+
},
|
|
1413
|
+
outcome: {
|
|
1414
|
+
type: "string",
|
|
1415
|
+
enum: ["accepted", "modified", "rejected"],
|
|
1416
|
+
description: "How it turned out: accepted (worked), modified (worked with changes), rejected (didn't work)"
|
|
1417
|
+
},
|
|
1418
|
+
feedback: {
|
|
1419
|
+
type: "string",
|
|
1420
|
+
description: "Optional context or notes about the observation"
|
|
1421
|
+
},
|
|
1422
|
+
},
|
|
1423
|
+
required: ["input", "outcome"],
|
|
1424
|
+
},
|
|
1425
|
+
},
|
|
1238
1426
|
// Query learned patterns - Claude reads this to avoid repeating mistakes
|
|
1239
1427
|
{
|
|
1240
1428
|
name: "herald_patterns",
|
|
@@ -1359,8 +1547,8 @@ call with outcome="helped" to reinforce that pattern.`,
|
|
|
1359
1547
|
description: `Share an insight with other Herald contexts using scope control.
|
|
1360
1548
|
|
|
1361
1549
|
Scopes:
|
|
1362
|
-
- "parent": Share with parent project/
|
|
1363
|
-
- "siblings": Share with sibling projects in same
|
|
1550
|
+
- "parent": Share with parent project/org (escalate learning)
|
|
1551
|
+
- "siblings": Share with sibling projects in same org
|
|
1364
1552
|
- "all": Share globally across all contexts
|
|
1365
1553
|
|
|
1366
1554
|
Use this to propagate valuable patterns beyond the current context.
|
|
@@ -1400,7 +1588,7 @@ Use this when:
|
|
|
1400
1588
|
Parameters:
|
|
1401
1589
|
- pattern_id: Delete a specific pattern by ID
|
|
1402
1590
|
- session_id: Delete all patterns from a session
|
|
1403
|
-
- all: Delete ALL patterns for current context (
|
|
1591
|
+
- all: Delete ALL patterns for current context (org/project/user)
|
|
1404
1592
|
|
|
1405
1593
|
WARNING: This action is irreversible. Data will be permanently deleted.`,
|
|
1406
1594
|
inputSchema: {
|
|
@@ -1432,7 +1620,7 @@ Use this when:
|
|
|
1432
1620
|
- Migrating data between systems
|
|
1433
1621
|
- Compliance audit requires data export
|
|
1434
1622
|
|
|
1435
|
-
Returns all patterns for the current context (
|
|
1623
|
+
Returns all patterns for the current context (org/project/user) in the specified format.`,
|
|
1436
1624
|
inputSchema: {
|
|
1437
1625
|
type: "object",
|
|
1438
1626
|
properties: {
|
|
@@ -1456,11 +1644,11 @@ async function fetchPatternsWithCascade() {
|
|
|
1456
1644
|
const seenInsights = new Set();
|
|
1457
1645
|
const patterns = [];
|
|
1458
1646
|
const antipatterns = [];
|
|
1459
|
-
// CEDA-102: Use
|
|
1647
|
+
// CEDA-102: Use org param for backwards compat with deployed CEDA server
|
|
1460
1648
|
const queries = [
|
|
1461
|
-
{ scope: "user", url: `/api/herald/reflections?
|
|
1462
|
-
{ scope: "project", url: `/api/herald/reflections?
|
|
1463
|
-
{ scope: "org", url: `/api/herald/reflections?
|
|
1649
|
+
{ scope: "user", url: `/api/herald/reflections?org=${HERALD_ORG}&project=${HERALD_PROJECT}&user=${HERALD_USER}&limit=100` },
|
|
1650
|
+
{ scope: "project", url: `/api/herald/reflections?org=${HERALD_ORG}&project=${HERALD_PROJECT}&limit=100` },
|
|
1651
|
+
{ scope: "org", url: `/api/herald/reflections?org=${HERALD_ORG}&limit=100` },
|
|
1464
1652
|
];
|
|
1465
1653
|
for (const { scope, url } of queries) {
|
|
1466
1654
|
try {
|
|
@@ -1499,7 +1687,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
1499
1687
|
{
|
|
1500
1688
|
uri: "herald://context",
|
|
1501
1689
|
name: "Herald Context",
|
|
1502
|
-
description: "Current Herald context configuration (
|
|
1690
|
+
description: "Current Herald context configuration (org/project/user)",
|
|
1503
1691
|
mimeType: "application/json",
|
|
1504
1692
|
},
|
|
1505
1693
|
];
|
|
@@ -1582,7 +1770,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1582
1770
|
Welcome to Herald - your AI-native interface to CEDA (Cognitive Event-Driven Architecture).
|
|
1583
1771
|
|
|
1584
1772
|
## Current Context
|
|
1585
|
-
-
|
|
1773
|
+
- Org: ${HERALD_ORG}
|
|
1586
1774
|
- Project: ${HERALD_PROJECT}
|
|
1587
1775
|
- User: ${HERALD_USER}
|
|
1588
1776
|
|
|
@@ -1603,7 +1791,7 @@ Welcome to Herald - your AI-native interface to CEDA (Cognitive Event-Driven Arc
|
|
|
1603
1791
|
- \`herald_session\` - View session history (legacy)
|
|
1604
1792
|
|
|
1605
1793
|
**Session Management (CEDA-49):**
|
|
1606
|
-
- \`herald_session_list\` - List sessions with filters (
|
|
1794
|
+
- \`herald_session_list\` - List sessions with filters (org, project, user, status)
|
|
1607
1795
|
- \`herald_session_get\` - Get detailed session info including prediction state
|
|
1608
1796
|
- \`herald_session_history\` - View version history for a session
|
|
1609
1797
|
- \`herald_session_rollback\` - Restore a session to a previous version
|
|
@@ -1960,6 +2148,7 @@ Herald will:
|
|
|
1960
2148
|
}
|
|
1961
2149
|
const synced = [];
|
|
1962
2150
|
const failed = [];
|
|
2151
|
+
const errors = []; // CEDA-FIX: Track error details
|
|
1963
2152
|
for (const item of buffer) {
|
|
1964
2153
|
try {
|
|
1965
2154
|
let result;
|
|
@@ -1987,13 +2176,23 @@ Herald will:
|
|
|
1987
2176
|
}
|
|
1988
2177
|
if (result.error) {
|
|
1989
2178
|
failed.push(item);
|
|
2179
|
+
// CEDA-FIX: Capture error details
|
|
2180
|
+
const details = result.details;
|
|
2181
|
+
const errorMsg = String(details?.message || result.error);
|
|
2182
|
+
if (!errors.includes(errorMsg)) {
|
|
2183
|
+
errors.push(errorMsg);
|
|
2184
|
+
}
|
|
1990
2185
|
}
|
|
1991
2186
|
else {
|
|
1992
2187
|
synced.push(item);
|
|
1993
2188
|
}
|
|
1994
2189
|
}
|
|
1995
|
-
catch {
|
|
2190
|
+
catch (err) {
|
|
1996
2191
|
failed.push(item);
|
|
2192
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2193
|
+
if (!errors.includes(errorMsg)) {
|
|
2194
|
+
errors.push(errorMsg);
|
|
2195
|
+
}
|
|
1997
2196
|
}
|
|
1998
2197
|
}
|
|
1999
2198
|
// Save only failed items back to buffer
|
|
@@ -2007,6 +2206,8 @@ Herald will:
|
|
|
2007
2206
|
synced: synced.length,
|
|
2008
2207
|
failed: failed.length,
|
|
2009
2208
|
remainingBuffer: failed.length,
|
|
2209
|
+
// CEDA-FIX: Include error details so users know WHY sync failed
|
|
2210
|
+
...(errors.length > 0 && { errors }),
|
|
2010
2211
|
}, null, 2)
|
|
2011
2212
|
}],
|
|
2012
2213
|
};
|
|
@@ -2029,13 +2230,13 @@ Herald will:
|
|
|
2029
2230
|
}
|
|
2030
2231
|
// CEDA-49: Session Management Tools
|
|
2031
2232
|
case "herald_session_list": {
|
|
2032
|
-
const
|
|
2233
|
+
const org = args?.org;
|
|
2033
2234
|
const project = args?.project;
|
|
2034
2235
|
const user = args?.user;
|
|
2035
2236
|
const status = args?.status;
|
|
2036
2237
|
const limit = args?.limit;
|
|
2037
2238
|
const params = new URLSearchParams();
|
|
2038
|
-
params.set("
|
|
2239
|
+
params.set("org", org || HERALD_ORG);
|
|
2039
2240
|
if (project)
|
|
2040
2241
|
params.set("project", project);
|
|
2041
2242
|
if (user)
|
|
@@ -2249,8 +2450,120 @@ Herald will:
|
|
|
2249
2450
|
};
|
|
2250
2451
|
}
|
|
2251
2452
|
}
|
|
2453
|
+
case "herald_observe": {
|
|
2454
|
+
const input = args?.input;
|
|
2455
|
+
const outcome = args?.outcome;
|
|
2456
|
+
const feedback = args?.feedback;
|
|
2457
|
+
if (!input || !outcome) {
|
|
2458
|
+
return {
|
|
2459
|
+
content: [{ type: "text", text: JSON.stringify({ error: "Missing required fields: input and outcome" }) }],
|
|
2460
|
+
isError: true,
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
// Sanitize before transmission
|
|
2464
|
+
const inputPreview = previewSanitization(input);
|
|
2465
|
+
const feedbackPreview = feedback ? previewSanitization(feedback) : null;
|
|
2466
|
+
if (inputPreview.wouldBlock || feedbackPreview?.wouldBlock) {
|
|
2467
|
+
return {
|
|
2468
|
+
content: [{
|
|
2469
|
+
type: "text",
|
|
2470
|
+
text: JSON.stringify({
|
|
2471
|
+
success: false,
|
|
2472
|
+
error: "Content contains restricted data that cannot be transmitted",
|
|
2473
|
+
blockReason: inputPreview.blockReason || feedbackPreview?.blockReason,
|
|
2474
|
+
hint: "Remove private keys, credentials, or other restricted data before observing.",
|
|
2475
|
+
}, null, 2)
|
|
2476
|
+
}],
|
|
2477
|
+
isError: true,
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
try {
|
|
2481
|
+
const result = await callCedaAPI("/api/observations", "POST", {
|
|
2482
|
+
input: inputPreview.sanitized,
|
|
2483
|
+
outcome,
|
|
2484
|
+
feedback: feedbackPreview?.sanitized,
|
|
2485
|
+
source: "herald",
|
|
2486
|
+
org: HERALD_ORG,
|
|
2487
|
+
project: HERALD_PROJECT,
|
|
2488
|
+
user: HERALD_USER,
|
|
2489
|
+
});
|
|
2490
|
+
if (result.error) {
|
|
2491
|
+
// Buffer locally if cloud unavailable
|
|
2492
|
+
bufferInsight({
|
|
2493
|
+
insight: inputPreview.sanitized,
|
|
2494
|
+
session: `observation: ${outcome}`,
|
|
2495
|
+
feeling: outcome === "rejected" ? "stuck" : "success",
|
|
2496
|
+
method: "observe",
|
|
2497
|
+
type: "observation",
|
|
2498
|
+
topic: "observation",
|
|
2499
|
+
org: HERALD_ORG,
|
|
2500
|
+
project: HERALD_PROJECT,
|
|
2501
|
+
user: HERALD_USER,
|
|
2502
|
+
});
|
|
2503
|
+
return {
|
|
2504
|
+
content: [{
|
|
2505
|
+
type: "text",
|
|
2506
|
+
text: JSON.stringify({
|
|
2507
|
+
success: true,
|
|
2508
|
+
mode: "local",
|
|
2509
|
+
message: "Observation buffered locally (cloud unavailable)",
|
|
2510
|
+
level: 0,
|
|
2511
|
+
hint: "Use herald_sync to flush buffer when cloud recovers.",
|
|
2512
|
+
buffered: true,
|
|
2513
|
+
}, null, 2)
|
|
2514
|
+
}],
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
return {
|
|
2518
|
+
content: [{
|
|
2519
|
+
type: "text",
|
|
2520
|
+
text: JSON.stringify({
|
|
2521
|
+
success: true,
|
|
2522
|
+
mode: "cloud",
|
|
2523
|
+
level: 0,
|
|
2524
|
+
message: `Observation captured: "${input.substring(0, 80)}"`,
|
|
2525
|
+
outcome,
|
|
2526
|
+
observationId: result.observationId,
|
|
2527
|
+
context: {
|
|
2528
|
+
org: HERALD_ORG,
|
|
2529
|
+
project: HERALD_PROJECT,
|
|
2530
|
+
},
|
|
2531
|
+
graduation: "Will auto-graduate to pattern when similar observations cluster (3+ needed)",
|
|
2532
|
+
...result,
|
|
2533
|
+
}, null, 2)
|
|
2534
|
+
}],
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
catch {
|
|
2538
|
+
// Network error - buffer locally
|
|
2539
|
+
bufferInsight({
|
|
2540
|
+
insight: inputPreview.sanitized,
|
|
2541
|
+
session: `observation: ${outcome}`,
|
|
2542
|
+
feeling: outcome === "rejected" ? "stuck" : "success",
|
|
2543
|
+
method: "observe",
|
|
2544
|
+
type: "observation",
|
|
2545
|
+
topic: "observation",
|
|
2546
|
+
org: HERALD_ORG,
|
|
2547
|
+
project: HERALD_PROJECT,
|
|
2548
|
+
user: HERALD_USER,
|
|
2549
|
+
});
|
|
2550
|
+
return {
|
|
2551
|
+
content: [{
|
|
2552
|
+
type: "text",
|
|
2553
|
+
text: JSON.stringify({
|
|
2554
|
+
success: true,
|
|
2555
|
+
mode: "local",
|
|
2556
|
+
message: "Observation buffered locally (network error)",
|
|
2557
|
+
level: 0,
|
|
2558
|
+
hint: "Use herald_sync when cloud recovers.",
|
|
2559
|
+
buffered: true,
|
|
2560
|
+
}, null, 2)
|
|
2561
|
+
}],
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2252
2565
|
case "herald_patterns": {
|
|
2253
|
-
// Query learned patterns with inheritance: user → project →
|
|
2566
|
+
// Query learned patterns with inheritance: user → project → org
|
|
2254
2567
|
// More specific patterns take precedence over broader ones
|
|
2255
2568
|
try {
|
|
2256
2569
|
// Helper to dedupe patterns by insight text (first occurrence wins)
|
|
@@ -2264,17 +2577,15 @@ Herald will:
|
|
|
2264
2577
|
return true;
|
|
2265
2578
|
}).map(item => ({ ...item, scope }));
|
|
2266
2579
|
};
|
|
2267
|
-
//
|
|
2268
|
-
// CEDA-102: User reflections now saved with level=1, so minLevel=1 is appropriate
|
|
2269
|
-
// But we also query without minLevel to catch level=0 reflections from before the fix
|
|
2580
|
+
// Level-gated cascade: user sees own (any level), project sees level>=2, org sees level>=3
|
|
2270
2581
|
const queries = [
|
|
2271
|
-
{ scope: "user", url: `/api/herald/reflections?
|
|
2272
|
-
{ scope: "project", url: `/api/herald/reflections?
|
|
2273
|
-
{ scope: "org", url: `/api/herald/reflections?
|
|
2582
|
+
{ scope: "user", url: `/api/herald/reflections?org=${HERALD_ORG}&project=${HERALD_PROJECT}&user=${HERALD_USER}&limit=50` },
|
|
2583
|
+
{ scope: "project", url: `/api/herald/reflections?org=${HERALD_ORG}&project=${HERALD_PROJECT}&minLevel=2&limit=50` },
|
|
2584
|
+
{ scope: "org", url: `/api/herald/reflections?org=${HERALD_ORG}&minLevel=3&limit=50` },
|
|
2274
2585
|
];
|
|
2275
2586
|
const patterns = [];
|
|
2276
2587
|
const antipatterns = [];
|
|
2277
|
-
// Query each level, dedupe as we go (user patterns win over project, project over
|
|
2588
|
+
// Query each level, dedupe as we go (user patterns win over project, project over org)
|
|
2278
2589
|
for (const { scope, url } of queries) {
|
|
2279
2590
|
try {
|
|
2280
2591
|
const result = await callCedaAPI(url);
|
|
@@ -2577,7 +2888,7 @@ Herald will:
|
|
|
2577
2888
|
insight,
|
|
2578
2889
|
scope,
|
|
2579
2890
|
topic,
|
|
2580
|
-
|
|
2891
|
+
sourceOrg: HERALD_ORG,
|
|
2581
2892
|
sourceProject: HERALD_PROJECT,
|
|
2582
2893
|
sourceUser: HERALD_USER,
|
|
2583
2894
|
sourceVault: HERALD_VAULT || undefined,
|
|
@@ -2595,7 +2906,7 @@ Herald will:
|
|
|
2595
2906
|
};
|
|
2596
2907
|
}
|
|
2597
2908
|
const scopeDescription = {
|
|
2598
|
-
parent: "parent project/
|
|
2909
|
+
parent: "parent project/org",
|
|
2599
2910
|
siblings: "sibling projects",
|
|
2600
2911
|
all: "all contexts globally",
|
|
2601
2912
|
};
|
|
@@ -2837,7 +3148,7 @@ async function verifyWithCeda() {
|
|
|
2837
3148
|
const ctx = result.context;
|
|
2838
3149
|
VERIFIED_CONTEXT = {
|
|
2839
3150
|
verified: true,
|
|
2840
|
-
org: (ctx.org || ctx.
|
|
3151
|
+
org: (ctx.org || ctx.org),
|
|
2841
3152
|
project: ctx.project,
|
|
2842
3153
|
trust: ctx.trust,
|
|
2843
3154
|
tags: ctx.tags,
|
|
@@ -2890,6 +3201,10 @@ async function sendStartupHeartbeat() {
|
|
|
2890
3201
|
async function runMCP() {
|
|
2891
3202
|
const transport = new StdioServerTransport();
|
|
2892
3203
|
await server.connect(transport);
|
|
3204
|
+
// Auto-refresh expired access token before verification
|
|
3205
|
+
if (CEDA_API_TOKEN && isTokenExpired(CEDA_API_TOKEN) && CEDA_REFRESH_TOKEN) {
|
|
3206
|
+
await refreshAccessToken();
|
|
3207
|
+
}
|
|
2893
3208
|
// CEDA-82: Verify trust with CEDA server before announcing
|
|
2894
3209
|
// This may upgrade trust if user is registered
|
|
2895
3210
|
await verifyWithCeda();
|