frinus-mcp 3.7.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/client/cp-client.d.ts +8 -1
- package/dist/client/cp-client.js +10 -0
- package/dist/client/memory-client.d.ts +13 -0
- package/dist/client/memory-client.js +21 -0
- package/dist/index.js +84 -1
- package/dist/notifications.d.ts +19 -5
- package/dist/notifications.js +61 -13
- package/dist/tools/boot-state.d.ts +56 -0
- package/dist/tools/boot-state.js +115 -0
- package/dist/tools/definitions.js +134 -12
- package/dist/tools/handlers.js +214 -22
- package/dist/types/index.d.ts +24 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.js +9 -0
- package/manifest.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ It does **not** run on its own. It stores nothing locally: every tool call is fo
|
|
|
11
11
|
|
|
12
12
|
With those in place, the server exposes **70 tools** spanning cognitive memory, working memory, sessions, agents, the L0–L3 knowledge hierarchy, orchestration tasks, and training pipelines. It speaks stdio and works with any MCP-aware client.
|
|
13
13
|
|
|
14
|
-
- **Version:** 3.
|
|
14
|
+
- **Version:** 3.8.0
|
|
15
15
|
- **Tools:** 70 (see [Tools Reference](#tools-reference))
|
|
16
16
|
- **Platform:** https://frinus.rdxsec.com.br (hosted SaaS — the backend this server talks to)
|
|
17
17
|
- **npm package:** [`frinus-mcp`](https://www.npmjs.com/package/frinus-mcp)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CpClientInterface, NotificationInbox } from "../types/index.js";
|
|
1
|
+
import type { CpClientInterface, NotificationInbox, OwnerNotificationCreate } from "../types/index.js";
|
|
2
2
|
export declare class CpClient implements CpClientInterface {
|
|
3
3
|
private client;
|
|
4
4
|
constructor(baseURL: string, apiKey: string);
|
|
@@ -19,6 +19,13 @@ export declare class CpClient implements CpClientInterface {
|
|
|
19
19
|
markDelivered?: boolean;
|
|
20
20
|
}): Promise<NotificationInbox>;
|
|
21
21
|
ackNotification(id: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Publish an organization-scoped notice (owner only). The Control Plane forces
|
|
24
|
+
* scope='organization' and target_org_id to the caller's org; the owner only
|
|
25
|
+
* supplies the message and optional role/user targeting. Uses the normal 30s
|
|
26
|
+
* timeout: unlike the inbox poll, this is a deliberate user action.
|
|
27
|
+
*/
|
|
28
|
+
createOrgNotification(data: OwnerNotificationCreate): Promise<any>;
|
|
22
29
|
storeCredential(integrationRef: string, data: Record<string, unknown>, metadata?: Record<string, unknown>): Promise<any>;
|
|
23
30
|
getCredential(integrationRef: string): Promise<any>;
|
|
24
31
|
listCredentials(): Promise<any>;
|
package/dist/client/cp-client.js
CHANGED
|
@@ -62,6 +62,16 @@ export class CpClient {
|
|
|
62
62
|
timeout: NOTIFICATION_TIMEOUT_MS,
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Publish an organization-scoped notice (owner only). The Control Plane forces
|
|
67
|
+
* scope='organization' and target_org_id to the caller's org; the owner only
|
|
68
|
+
* supplies the message and optional role/user targeting. Uses the normal 30s
|
|
69
|
+
* timeout: unlike the inbox poll, this is a deliberate user action.
|
|
70
|
+
*/
|
|
71
|
+
async createOrgNotification(data) {
|
|
72
|
+
const response = await this.client.post(`/api/v1/notifications/manage`, data);
|
|
73
|
+
return response.data;
|
|
74
|
+
}
|
|
65
75
|
// =========================================================================
|
|
66
76
|
// Credential Vault
|
|
67
77
|
// =========================================================================
|
|
@@ -38,9 +38,22 @@ export declare class MemoryClient implements MemoryClientInterface {
|
|
|
38
38
|
created_by_user_id?: string;
|
|
39
39
|
metadata?: Record<string, unknown>;
|
|
40
40
|
context_id?: string;
|
|
41
|
+
context_path?: string;
|
|
41
42
|
credential_ref?: string;
|
|
42
43
|
universe_id?: string;
|
|
43
44
|
}): Promise<any>;
|
|
45
|
+
/**
|
|
46
|
+
* hierarchy_find — read-only preview of the hierarchy nodes that best match a
|
|
47
|
+
* free-text query. Reuses the write-time classifier's node index (never
|
|
48
|
+
* writes). The agent calls it before memory_store (to copy a context_path) and
|
|
49
|
+
* before a tree-specific search. Universe permission is enforced server-side;
|
|
50
|
+
* an explicit universe_id the caller cannot read returns 403.
|
|
51
|
+
*/
|
|
52
|
+
findHierarchy(data: {
|
|
53
|
+
query_text: string;
|
|
54
|
+
universe_id?: string;
|
|
55
|
+
limit?: number;
|
|
56
|
+
}): Promise<any>;
|
|
44
57
|
searchMemories(data: {
|
|
45
58
|
query_text: string;
|
|
46
59
|
agent_id?: string;
|
|
@@ -98,6 +98,11 @@ export class MemoryClient {
|
|
|
98
98
|
payload.created_by_user_id = data.created_by_user_id;
|
|
99
99
|
if (data.context_id)
|
|
100
100
|
payload.context_id = data.context_id;
|
|
101
|
+
// Anchor by NAME (Onda 3): the backend resolves the breadcrumb to a node and
|
|
102
|
+
// uses it as the anchor target / classifier hint. Precedence is enforced by
|
|
103
|
+
// the backend: context_id (UUID) > context_path (name) > universe_id.
|
|
104
|
+
if (data.context_path)
|
|
105
|
+
payload.context_path = data.context_path;
|
|
101
106
|
if (data.credential_ref)
|
|
102
107
|
payload.credential_ref = data.credential_ref;
|
|
103
108
|
if (data.universe_id)
|
|
@@ -105,6 +110,22 @@ export class MemoryClient {
|
|
|
105
110
|
const response = await this.client.post("/memories", payload);
|
|
106
111
|
return response.data;
|
|
107
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* hierarchy_find — read-only preview of the hierarchy nodes that best match a
|
|
115
|
+
* free-text query. Reuses the write-time classifier's node index (never
|
|
116
|
+
* writes). The agent calls it before memory_store (to copy a context_path) and
|
|
117
|
+
* before a tree-specific search. Universe permission is enforced server-side;
|
|
118
|
+
* an explicit universe_id the caller cannot read returns 403.
|
|
119
|
+
*/
|
|
120
|
+
async findHierarchy(data) {
|
|
121
|
+
const payload = { query_text: data.query_text };
|
|
122
|
+
if (data.universe_id)
|
|
123
|
+
payload.universe_id = data.universe_id;
|
|
124
|
+
if (typeof data.limit === "number")
|
|
125
|
+
payload.limit = data.limit;
|
|
126
|
+
const response = await this.client.post("/graph/classify-preview", payload);
|
|
127
|
+
return response.data;
|
|
128
|
+
}
|
|
108
129
|
async searchMemories(data) {
|
|
109
130
|
const response = await this.client.post("/memories/search", {
|
|
110
131
|
query_text: data.query_text,
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,20 @@ import { getResolvedClient, setResolvedClient } from "./client/client-info.js";
|
|
|
22
22
|
import { InteractionCapture } from "./capture/interaction-capture.js";
|
|
23
23
|
import { TOOLS } from "./tools/definitions.js";
|
|
24
24
|
import { dispatchTool } from "./tools/handlers.js";
|
|
25
|
+
import { getSessionId, noteToolCall, reorientBanner, setSessionId } from "./tools/boot-state.js";
|
|
26
|
+
import { MCP_VERSION } from "./version.js";
|
|
27
|
+
// Irreducible boot rules injected into the client's system prompt via the MCP
|
|
28
|
+
// `initialize` handshake (Claude Code places server `instructions` there). These
|
|
29
|
+
// are the rules that MUST survive even if a single tool response is truncated or
|
|
30
|
+
// skimmed — the structural backstop behind the Boot Contract block.
|
|
31
|
+
const SERVER_INSTRUCTIONS = [
|
|
32
|
+
"Frinus MCP — regras de boot que nunca podem cair:",
|
|
33
|
+
"1. No início da sessão, chame agent_bootstrap(agent_id=...) e adote o \"Contrato de Boot\" no TOPO da resposta: identidade, ORGANIZAÇÃO (grave memórias só nela), usuário, session_id e a cadência de reorient. O system prompt longo vem depois do contrato.",
|
|
34
|
+
"2. Quando QUALQUER resposta de tool trouxer \"⚠️ REORIENT DEVIDO\", chame agent_reorient(agent_id=...) antes de continuar. O MCP conta as tool calls por você.",
|
|
35
|
+
"3. Confirme a organização do Contrato de Boot ANTES da primeira escrita de memória — nunca grave na org errada.",
|
|
36
|
+
"4. A sessão é gerenciada pelo MCP (iniciada no agent_bootstrap, encerrada no fim do processo). Não precisa fechá-la à mão.",
|
|
37
|
+
"Se o MCP/bootstrap estiver indisponível, avise \"MCP Frinus offline — capacidades reduzidas.\" e opere com cautela.",
|
|
38
|
+
].join("\n");
|
|
25
39
|
// Defaults point at the Frinus SaaS so `npx -y frinus-mcp` works with only an
|
|
26
40
|
// API key. Local development overrides these via env (MEMORY_SERVICE_URL, etc.).
|
|
27
41
|
const MEMORY_SERVICE_URL = process.env.MEMORY_SERVICE_URL || "https://frinus-memory.rdxsec.com.br";
|
|
@@ -95,11 +109,60 @@ async function main() {
|
|
|
95
109
|
console.error(`[Auth] API key validated. User: ${getResolvedUserEmail()}, Tenant: ${getResolvedTenantOrgId() || 'none'}`);
|
|
96
110
|
const server = new Server({
|
|
97
111
|
name: "frinus",
|
|
98
|
-
version:
|
|
112
|
+
version: MCP_VERSION,
|
|
99
113
|
}, {
|
|
100
114
|
capabilities: {
|
|
101
115
|
tools: {},
|
|
102
116
|
},
|
|
117
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
118
|
+
});
|
|
119
|
+
// --- Session lifecycle: close on process exit ---------------------------
|
|
120
|
+
// The session is OPENED by agent_bootstrap (the MCP owns it, not the model).
|
|
121
|
+
// Here we guarantee it is CLOSED when the process goes away: Claude Code
|
|
122
|
+
// closing stdin, a SIGTERM/SIGINT, or the event loop draining. We run
|
|
123
|
+
// session_summary then session_end under a short timeout so a slow/unreachable
|
|
124
|
+
// Memory Engine can never hang shutdown. Idempotent via the `closing` guard.
|
|
125
|
+
let closing = false;
|
|
126
|
+
const SHUTDOWN_TIMEOUT_MS = 3000;
|
|
127
|
+
const closeSession = async (reason) => {
|
|
128
|
+
if (closing)
|
|
129
|
+
return;
|
|
130
|
+
closing = true;
|
|
131
|
+
const sid = getSessionId();
|
|
132
|
+
if (!sid)
|
|
133
|
+
return;
|
|
134
|
+
console.error(`[Session] Closing ${sid} (${reason})`);
|
|
135
|
+
const work = (async () => {
|
|
136
|
+
try {
|
|
137
|
+
await memoryClient.getSessionSummary(sid);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.error(`[Session] summary failed: ${err?.message || err}`);
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
await memoryClient.endSession(sid);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
console.error(`[Session] end failed: ${err?.message || err}`);
|
|
147
|
+
}
|
|
148
|
+
})();
|
|
149
|
+
await Promise.race([
|
|
150
|
+
work,
|
|
151
|
+
new Promise((resolve) => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS)),
|
|
152
|
+
]);
|
|
153
|
+
setSessionId(null);
|
|
154
|
+
};
|
|
155
|
+
const shutdownAndExit = (reason) => {
|
|
156
|
+
void closeSession(reason).finally(() => process.exit(0));
|
|
157
|
+
};
|
|
158
|
+
process.once("SIGTERM", () => shutdownAndExit("SIGTERM"));
|
|
159
|
+
process.once("SIGINT", () => shutdownAndExit("SIGINT"));
|
|
160
|
+
// stdin close = the MCP client (Claude Code) disconnected.
|
|
161
|
+
process.stdin.on("close", () => shutdownAndExit("stdin-close"));
|
|
162
|
+
// Last-resort backstop when the loop drains without a signal. Does not force
|
|
163
|
+
// exit (lets the natural exit proceed after a best-effort close).
|
|
164
|
+
process.once("beforeExit", () => {
|
|
165
|
+
void closeSession("beforeExit");
|
|
103
166
|
});
|
|
104
167
|
// List tools handler (all tools always visible; backend enforces auth)
|
|
105
168
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -125,6 +188,26 @@ async function main() {
|
|
|
125
188
|
catch (err) {
|
|
126
189
|
console.error('[MCP] Capture failed:', err.message);
|
|
127
190
|
}
|
|
191
|
+
// Deterministic reorient cadence (see tools/boot-state.ts): count this call,
|
|
192
|
+
// then, if the agent is overdue, append the REORIENT DUE banner to the result
|
|
193
|
+
// so the model cannot miss it. Repeats on every call until agent_reorient
|
|
194
|
+
// resets the counter. Never throws into the response path.
|
|
195
|
+
try {
|
|
196
|
+
noteToolCall(name);
|
|
197
|
+
const banner = reorientBanner();
|
|
198
|
+
if (banner) {
|
|
199
|
+
const last = toolResult.content?.[toolResult.content.length - 1];
|
|
200
|
+
if (last && last.type === "text") {
|
|
201
|
+
last.text += banner;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
toolResult.content = [...(toolResult.content || []), { type: "text", text: banner.trimStart() }];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
console.error('[MCP] Reorient cadence check failed:', err?.message || err);
|
|
210
|
+
}
|
|
128
211
|
return toolResult;
|
|
129
212
|
});
|
|
130
213
|
// Resolve the connecting MCP client's identity for the X-Client header.
|
package/dist/notifications.d.ts
CHANGED
|
@@ -9,13 +9,18 @@
|
|
|
9
9
|
* Design rules:
|
|
10
10
|
* - NEVER throws. A 404, timeout or any network error yields '' plus a stderr
|
|
11
11
|
* log. The agent's real work must never be blocked by the news channel.
|
|
12
|
-
* - In-process state, per MCP process (= per session):
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* - In-process session state, per MCP process (= per session): WHEN each item
|
|
13
|
+
* was last shown, and when we last hit the inbox. Dedupe is a sliding WINDOW,
|
|
14
|
+
* not "forever": a long-lived process re-presents an un-acked item once the
|
|
15
|
+
* reprompt window elapses, so an ignored notice resurfaces on a later
|
|
16
|
+
* heartbeat instead of vanishing for the whole session. The server still caps
|
|
17
|
+
* total deliveries (counted at most once/day per user), so this cannot spam.
|
|
15
18
|
* - Env toggles: FRINUS_NOTIFICATIONS=off disables everything;
|
|
16
|
-
* FRINUS_NOTIFICATIONS_INTERVAL_MIN overrides the throttle
|
|
19
|
+
* FRINUS_NOTIFICATIONS_INTERVAL_MIN overrides the FETCH throttle (default 60);
|
|
20
|
+
* FRINUS_NOTIFICATIONS_REPROMPT_MIN overrides how long an already-shown item
|
|
21
|
+
* stays suppressed before it may be re-presented (default 1440 = 24h).
|
|
17
22
|
*/
|
|
18
|
-
import type { CpClientInterface } from "./types/index.js";
|
|
23
|
+
import type { CpClientInterface, NotificationItem } from "./types/index.js";
|
|
19
24
|
/** Reset module state. Test-only; not part of the runtime surface. */
|
|
20
25
|
export declare function __resetNotificationsState(): void;
|
|
21
26
|
export interface FetchBlockOptions {
|
|
@@ -28,3 +33,12 @@ export interface FetchBlockOptions {
|
|
|
28
33
|
* failure). Guaranteed not to throw.
|
|
29
34
|
*/
|
|
30
35
|
export declare function fetchNotificationsBlock(cpClient: CpClientInterface, opts?: FetchBlockOptions): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Render the markdown block the agent relays to the user. Exported so another
|
|
38
|
+
* frente can insert it at the TOP of the boot contract (the preferred place —
|
|
39
|
+
* appended at the end, a ~15k-token bootstrap buries it and the model skips it).
|
|
40
|
+
*
|
|
41
|
+
* The block is self-contained: it tells the agent to relay briefly, to ack on
|
|
42
|
+
* the user's confirmation, and — for `critical` items — to relay IMMEDIATELY.
|
|
43
|
+
*/
|
|
44
|
+
export declare function renderBlock(items: NotificationItem[]): string;
|
package/dist/notifications.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
/** Max characters of a notification body rendered inline. */
|
|
2
2
|
const BODY_LIMIT = 600;
|
|
3
|
-
/** Default throttle window for non-forced checks (minutes). */
|
|
3
|
+
/** Default FETCH throttle window for non-forced checks (minutes). */
|
|
4
4
|
const DEFAULT_INTERVAL_MIN = 60;
|
|
5
|
+
/** Default reprompt window: how long a shown item stays suppressed (minutes). */
|
|
6
|
+
const DEFAULT_REPROMPT_MIN = 1440; // 24h — aligns with the server's per-day delivery count.
|
|
5
7
|
// ---------------------------------------------------------------------------
|
|
6
8
|
// In-process session state
|
|
7
9
|
// ---------------------------------------------------------------------------
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Epoch ms each notification id was last SHOWN in this session. Used for a
|
|
12
|
+
* windowed dedupe: an item is re-presentable once `now - shownAt > reprompt`.
|
|
13
|
+
*/
|
|
14
|
+
const shownAt = new Map();
|
|
10
15
|
/** Epoch ms of the last inbox check (forced or throttled). 0 = never. */
|
|
11
16
|
let lastCheckAt = 0;
|
|
12
17
|
/** Reset module state. Test-only; not part of the runtime surface. */
|
|
13
18
|
export function __resetNotificationsState() {
|
|
14
|
-
|
|
19
|
+
shownAt.clear();
|
|
15
20
|
lastCheckAt = 0;
|
|
16
21
|
}
|
|
17
22
|
function isDisabled() {
|
|
@@ -23,6 +28,12 @@ function throttleMs() {
|
|
|
23
28
|
const minutes = Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_INTERVAL_MIN;
|
|
24
29
|
return minutes * 60_000;
|
|
25
30
|
}
|
|
31
|
+
function repromptMs() {
|
|
32
|
+
const raw = process.env.FRINUS_NOTIFICATIONS_REPROMPT_MIN;
|
|
33
|
+
const parsed = raw !== undefined ? Number.parseInt(raw, 10) : NaN;
|
|
34
|
+
const minutes = Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_REPROMPT_MIN;
|
|
35
|
+
return minutes * 60_000;
|
|
36
|
+
}
|
|
26
37
|
/**
|
|
27
38
|
* Fetch the inbox and render the "Novidades do Frinus" block, or '' when there
|
|
28
39
|
* is nothing new to show (disabled, throttled, empty, already-shown, or any
|
|
@@ -50,19 +61,47 @@ export async function fetchNotificationsBlock(cpClient, opts = {}) {
|
|
|
50
61
|
console.error(`[Notifications] inbox fetch failed: ${err?.message || err}`);
|
|
51
62
|
return "";
|
|
52
63
|
}
|
|
53
|
-
const
|
|
64
|
+
const now2 = Date.now();
|
|
65
|
+
const window = repromptMs();
|
|
66
|
+
const fresh = (inbox?.items || []).filter((it) => {
|
|
67
|
+
if (!it || typeof it.id !== "string")
|
|
68
|
+
return false;
|
|
69
|
+
const last = shownAt.get(it.id);
|
|
70
|
+
// Unseen this session, or last shown long enough ago to re-present.
|
|
71
|
+
return last === undefined || now2 - last >= window;
|
|
72
|
+
});
|
|
54
73
|
if (fresh.length === 0)
|
|
55
74
|
return "";
|
|
56
75
|
for (const it of fresh)
|
|
57
|
-
|
|
76
|
+
shownAt.set(it.id, now2);
|
|
58
77
|
return renderBlock(fresh);
|
|
59
78
|
}
|
|
60
|
-
/**
|
|
61
|
-
function
|
|
79
|
+
/** Human label for a notification scope, used in the rendered header. */
|
|
80
|
+
function scopeLabel(scope) {
|
|
81
|
+
if (scope === "organization")
|
|
82
|
+
return "Organização";
|
|
83
|
+
if (scope === "system")
|
|
84
|
+
return "Sistema";
|
|
85
|
+
return "Sistema"; // default/unknown: treat as a platform notice.
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Render the markdown block the agent relays to the user. Exported so another
|
|
89
|
+
* frente can insert it at the TOP of the boot contract (the preferred place —
|
|
90
|
+
* appended at the end, a ~15k-token bootstrap buries it and the model skips it).
|
|
91
|
+
*
|
|
92
|
+
* The block is self-contained: it tells the agent to relay briefly, to ack on
|
|
93
|
+
* the user's confirmation, and — for `critical` items — to relay IMMEDIATELY.
|
|
94
|
+
*/
|
|
95
|
+
export function renderBlock(items) {
|
|
96
|
+
const hasCritical = items.some((it) => it.severity === "critical");
|
|
62
97
|
let out = `## 📣 Novidades do Frinus\n\n`;
|
|
63
98
|
for (const it of items) {
|
|
64
|
-
const
|
|
65
|
-
|
|
99
|
+
const label = scopeLabel(it.scope);
|
|
100
|
+
const sev = it.severity ? ` · _${it.severity}_` : "";
|
|
101
|
+
out += `### [${label}] ${it.title}${sev}\n`;
|
|
102
|
+
if (it.severity === "critical") {
|
|
103
|
+
out += `> ⚠️ CRÍTICO — repasse ao usuário IMEDIATAMENTE, antes de seguir com a tarefa.\n`;
|
|
104
|
+
}
|
|
66
105
|
let body = (it.body || "").trim();
|
|
67
106
|
if (body.length > BODY_LIMIT) {
|
|
68
107
|
body = body.slice(0, BODY_LIMIT).trimEnd() + "…";
|
|
@@ -78,9 +117,18 @@ function renderBlock(items) {
|
|
|
78
117
|
out += `\n_id: ${it.id}_\n\n`;
|
|
79
118
|
}
|
|
80
119
|
out += `---\n`;
|
|
120
|
+
if (hasCritical) {
|
|
121
|
+
out +=
|
|
122
|
+
`Há um aviso CRÍTICO acima: repasse-o ao usuário IMEDIATAMENTE, no início ` +
|
|
123
|
+
`da sua próxima resposta. `;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
out += `Avise o usuário destas novidades de forma breve (1–3 linhas cada) no ` +
|
|
127
|
+
`início da sua próxima resposta, sem interromper a tarefa. `;
|
|
128
|
+
}
|
|
81
129
|
out +=
|
|
82
|
-
`
|
|
83
|
-
`
|
|
84
|
-
`
|
|
130
|
+
`Avisos marcados [Sistema] vêm do Frinus; [Organização] vêm da própria ` +
|
|
131
|
+
`organização do usuário. Quando ele confirmar ciência ou pedir para não ` +
|
|
132
|
+
`ver mais, chame notification_ack(id).\n`;
|
|
85
133
|
return out;
|
|
86
134
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-contract state for the MCP process (one instance per stdio session).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS
|
|
5
|
+
* ---------------
|
|
6
|
+
* The thin-client boot protocol used to depend entirely on the model
|
|
7
|
+
* remembering to call `agent_reorient` "every N answers". Models forget. This
|
|
8
|
+
* module makes the reorient cadence DETERMINISTIC and server-side (here, inside
|
|
9
|
+
* the MCP process) instead of a hope:
|
|
10
|
+
*
|
|
11
|
+
* - `agent_bootstrap` calls `markBooted()` with the agent's reorient config
|
|
12
|
+
* (read from the authoritative Control Plane row via the bootstrap payload).
|
|
13
|
+
* - Every subsequent tool call increments a counter (`noteToolCall`).
|
|
14
|
+
* - Once the counter reaches `reorientEveryN`, `reorientBanner()` returns a
|
|
15
|
+
* "REORIENT DUE" notice that `index.ts` appends to EVERY tool result until
|
|
16
|
+
* `agent_reorient` is actually called, which resets the counter.
|
|
17
|
+
*
|
|
18
|
+
* The context-percentage trigger (`reorientOnContextPct`) cannot be enforced
|
|
19
|
+
* here — the MCP has no visibility into the model's token window — so it stays a
|
|
20
|
+
* model-side heuristic documented in the Boot Contract and the server
|
|
21
|
+
* `instructions`. The N-count is the half we CAN guarantee, and we guarantee it.
|
|
22
|
+
*
|
|
23
|
+
* State is per-process (= per session). Nothing here is persisted.
|
|
24
|
+
*/
|
|
25
|
+
/** Reorientation cadence knobs, mirrored from `org_agents` via the bootstrap. */
|
|
26
|
+
export interface ReorientConfig {
|
|
27
|
+
reorientEveryN: number;
|
|
28
|
+
reorientOnContextPct: number;
|
|
29
|
+
}
|
|
30
|
+
/** Mark the process as booted and (re)seed the reorient cadence + session id. */
|
|
31
|
+
export declare function markBooted(opts: {
|
|
32
|
+
agentId: string;
|
|
33
|
+
sessionId?: string | null;
|
|
34
|
+
config?: Partial<ReorientConfig>;
|
|
35
|
+
}): void;
|
|
36
|
+
/** Reset the cadence counter (called when the agent actually reorients). */
|
|
37
|
+
export declare function resetReorientCounter(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Record a tool call. Exempt tools do not increment; `agent_reorient` resets.
|
|
40
|
+
* No-op until the process has booted, so pre-boot probing never arms the banner.
|
|
41
|
+
*/
|
|
42
|
+
export declare function noteToolCall(name: string): void;
|
|
43
|
+
/** True once at least `reorientEveryN` counted tool calls have elapsed. */
|
|
44
|
+
export declare function reorientDue(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The "REORIENT DUE" banner, or null when not due. Deliberately imperative and
|
|
47
|
+
* self-explaining so it survives even if the model ignored the Boot Contract.
|
|
48
|
+
*/
|
|
49
|
+
export declare function reorientBanner(): string | null;
|
|
50
|
+
export declare function getBootAgentId(): string | null;
|
|
51
|
+
export declare function getSessionId(): string | null;
|
|
52
|
+
export declare function setSessionId(id: string | null): void;
|
|
53
|
+
export declare function isBooted(): boolean;
|
|
54
|
+
export declare function getReorientConfig(): ReorientConfig;
|
|
55
|
+
/** Test-only: reset all module state. Not part of the runtime surface. */
|
|
56
|
+
export declare function __resetBootState(): void;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-contract state for the MCP process (one instance per stdio session).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS
|
|
5
|
+
* ---------------
|
|
6
|
+
* The thin-client boot protocol used to depend entirely on the model
|
|
7
|
+
* remembering to call `agent_reorient` "every N answers". Models forget. This
|
|
8
|
+
* module makes the reorient cadence DETERMINISTIC and server-side (here, inside
|
|
9
|
+
* the MCP process) instead of a hope:
|
|
10
|
+
*
|
|
11
|
+
* - `agent_bootstrap` calls `markBooted()` with the agent's reorient config
|
|
12
|
+
* (read from the authoritative Control Plane row via the bootstrap payload).
|
|
13
|
+
* - Every subsequent tool call increments a counter (`noteToolCall`).
|
|
14
|
+
* - Once the counter reaches `reorientEveryN`, `reorientBanner()` returns a
|
|
15
|
+
* "REORIENT DUE" notice that `index.ts` appends to EVERY tool result until
|
|
16
|
+
* `agent_reorient` is actually called, which resets the counter.
|
|
17
|
+
*
|
|
18
|
+
* The context-percentage trigger (`reorientOnContextPct`) cannot be enforced
|
|
19
|
+
* here — the MCP has no visibility into the model's token window — so it stays a
|
|
20
|
+
* model-side heuristic documented in the Boot Contract and the server
|
|
21
|
+
* `instructions`. The N-count is the half we CAN guarantee, and we guarantee it.
|
|
22
|
+
*
|
|
23
|
+
* State is per-process (= per session). Nothing here is persisted.
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_CONFIG = {
|
|
26
|
+
reorientEveryN: 10,
|
|
27
|
+
reorientOnContextPct: 70,
|
|
28
|
+
};
|
|
29
|
+
let config = { ...DEFAULT_CONFIG };
|
|
30
|
+
let booted = false;
|
|
31
|
+
let bootAgentId = null;
|
|
32
|
+
let sessionId = null;
|
|
33
|
+
let toolCallsSinceReorient = 0;
|
|
34
|
+
/**
|
|
35
|
+
* Tools that must never count toward the cadence: `agent_bootstrap` seeds it and
|
|
36
|
+
* `agent_reorient` IS the reorientation (and resets the counter).
|
|
37
|
+
*/
|
|
38
|
+
const EXEMPT_TOOLS = new Set(["agent_bootstrap", "agent_reorient"]);
|
|
39
|
+
/** Mark the process as booted and (re)seed the reorient cadence + session id. */
|
|
40
|
+
export function markBooted(opts) {
|
|
41
|
+
booted = true;
|
|
42
|
+
bootAgentId = opts.agentId;
|
|
43
|
+
if (opts.sessionId)
|
|
44
|
+
sessionId = opts.sessionId;
|
|
45
|
+
if (opts.config) {
|
|
46
|
+
const n = opts.config.reorientEveryN;
|
|
47
|
+
if (typeof n === "number" && Number.isFinite(n) && n >= 1) {
|
|
48
|
+
config.reorientEveryN = Math.floor(n);
|
|
49
|
+
}
|
|
50
|
+
const pct = opts.config.reorientOnContextPct;
|
|
51
|
+
if (typeof pct === "number" && Number.isFinite(pct) && pct >= 1 && pct <= 100) {
|
|
52
|
+
config.reorientOnContextPct = Math.floor(pct);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
toolCallsSinceReorient = 0;
|
|
56
|
+
}
|
|
57
|
+
/** Reset the cadence counter (called when the agent actually reorients). */
|
|
58
|
+
export function resetReorientCounter() {
|
|
59
|
+
toolCallsSinceReorient = 0;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Record a tool call. Exempt tools do not increment; `agent_reorient` resets.
|
|
63
|
+
* No-op until the process has booted, so pre-boot probing never arms the banner.
|
|
64
|
+
*/
|
|
65
|
+
export function noteToolCall(name) {
|
|
66
|
+
if (!booted)
|
|
67
|
+
return;
|
|
68
|
+
if (EXEMPT_TOOLS.has(name)) {
|
|
69
|
+
if (name === "agent_reorient")
|
|
70
|
+
resetReorientCounter();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
toolCallsSinceReorient += 1;
|
|
74
|
+
}
|
|
75
|
+
/** True once at least `reorientEveryN` counted tool calls have elapsed. */
|
|
76
|
+
export function reorientDue() {
|
|
77
|
+
return booted && toolCallsSinceReorient >= config.reorientEveryN;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The "REORIENT DUE" banner, or null when not due. Deliberately imperative and
|
|
81
|
+
* self-explaining so it survives even if the model ignored the Boot Contract.
|
|
82
|
+
*/
|
|
83
|
+
export function reorientBanner() {
|
|
84
|
+
if (!reorientDue())
|
|
85
|
+
return null;
|
|
86
|
+
const id = bootAgentId ?? "<agent_id>";
|
|
87
|
+
return (`\n\n---\n` +
|
|
88
|
+
`⚠️ REORIENT DEVIDO: ${toolCallsSinceReorient} tool calls desde o último ` +
|
|
89
|
+
`boot/reorient (limite ${config.reorientEveryN}). Chame ` +
|
|
90
|
+
`\`agent_reorient(agent_id="${id}")\` agora para se re-fundamentar antes de ` +
|
|
91
|
+
`continuar. Este aviso repete a cada tool call até você reorientar.`);
|
|
92
|
+
}
|
|
93
|
+
export function getBootAgentId() {
|
|
94
|
+
return bootAgentId;
|
|
95
|
+
}
|
|
96
|
+
export function getSessionId() {
|
|
97
|
+
return sessionId;
|
|
98
|
+
}
|
|
99
|
+
export function setSessionId(id) {
|
|
100
|
+
sessionId = id;
|
|
101
|
+
}
|
|
102
|
+
export function isBooted() {
|
|
103
|
+
return booted;
|
|
104
|
+
}
|
|
105
|
+
export function getReorientConfig() {
|
|
106
|
+
return { ...config };
|
|
107
|
+
}
|
|
108
|
+
/** Test-only: reset all module state. Not part of the runtime surface. */
|
|
109
|
+
export function __resetBootState() {
|
|
110
|
+
config = { ...DEFAULT_CONFIG };
|
|
111
|
+
booted = false;
|
|
112
|
+
bootAgentId = null;
|
|
113
|
+
sessionId = null;
|
|
114
|
+
toolCallsSinceReorient = 0;
|
|
115
|
+
}
|
|
@@ -9,6 +9,19 @@ export const TOOLS = [
|
|
|
9
9
|
Use this to record important information, learnings, decisions, or procedures
|
|
10
10
|
that should be remembered for future reference.
|
|
11
11
|
|
|
12
|
+
ANCHORING (do this first): memory lives in a tree — Universe > Concept > Theme >
|
|
13
|
+
Topic > Point. Before storing, call hierarchy_find("<subject>") and copy the best
|
|
14
|
+
candidate's context_path (BY NAME, e.g. "Plataforma Frinus > Memory Engine") into
|
|
15
|
+
context_path below. The path raises the classifier's confidence and anchors the
|
|
16
|
+
memory on the right node. Never invent a UUID — you get names right, UUIDs wrong.
|
|
17
|
+
|
|
18
|
+
PRECEDENCE of the placement arguments (highest first):
|
|
19
|
+
context_id (UUID) > context_path (name) > universe_id > the agent's own
|
|
20
|
+
universe > auto-classification > the org's "Geral" (floor, never NULL).
|
|
21
|
+
A context_path that is ambiguous or whose segments don't all match never creates
|
|
22
|
+
a node and never fails the write: it anchors to the deepest segment that matched
|
|
23
|
+
and the response carries a non-fatal context_path_warning saying so.
|
|
24
|
+
|
|
12
25
|
The backend classifies the memory by content at write time and proposes which
|
|
13
26
|
universe/context node it belongs to. When the tenant has auto-classification
|
|
14
27
|
enabled and the proposal's confidence meets the tenant threshold (the platform
|
|
@@ -17,11 +30,11 @@ is moved without review. Below the threshold (or with auto-classification off)
|
|
|
17
30
|
the proposal stays a PENDING suggestion: review it with
|
|
18
31
|
memory_classification_pending and apply with memory_classification_approve.
|
|
19
32
|
|
|
20
|
-
Passing universe_id or context_id anchors the memory
|
|
21
|
-
"manual"): auto-classification is skipped
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
Passing context_path, universe_id or context_id anchors the memory accordingly
|
|
34
|
+
(status "manual" for UUIDs): auto-classification is skipped and nothing may later
|
|
35
|
+
move it. The response always reports the destination the memory actually landed
|
|
36
|
+
in — a suggestion that is still pending is labelled as such and has NOT been
|
|
37
|
+
applied.
|
|
25
38
|
|
|
26
39
|
Memory types:
|
|
27
40
|
- episodic: Specific experiences and events (what happened)
|
|
@@ -88,7 +101,11 @@ Scopes:
|
|
|
88
101
|
},
|
|
89
102
|
context_id: {
|
|
90
103
|
type: "string",
|
|
91
|
-
description: "Optional UUID of a hierarchy node (universe, concept, theme, topic, or point). When provided, the memory is automatically linked to this node and all its ancestors.
|
|
104
|
+
description: "Optional UUID of a hierarchy node (universe, concept, theme, topic, or point). When provided, the memory is automatically linked to this node and all its ancestors. Highest-precedence placement argument (above context_path and universe_id).",
|
|
105
|
+
},
|
|
106
|
+
context_path: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "Anchor the memory BY NAME with a breadcrumb like 'Universe > Concept > Theme' (separators /, > or › all work). Call hierarchy_find first and copy the best candidate's context_path here — this is the preferred way to place a memory. The backend resolves names to a node deterministically (never creates nodes). Takes precedence over universe_id, but not over context_id. If a segment is ambiguous or unknown the write still succeeds, anchors to the deepest matched segment, and returns a context_path_warning.",
|
|
92
109
|
},
|
|
93
110
|
credential_ref: {
|
|
94
111
|
type: "string",
|
|
@@ -98,6 +115,43 @@ Scopes:
|
|
|
98
115
|
required: ["agent_id", "content"],
|
|
99
116
|
},
|
|
100
117
|
},
|
|
118
|
+
{
|
|
119
|
+
name: "hierarchy_find",
|
|
120
|
+
description: `Find where something belongs in the memory tree (Universe >
|
|
121
|
+
Concept > Theme > Topic > Point).
|
|
122
|
+
|
|
123
|
+
Read-only: it never writes a memory or a node. It runs the same classifier the
|
|
124
|
+
backend uses at write time and returns the top candidate nodes for your query,
|
|
125
|
+
each with its full path BY NAME and a confidence in [0,1].
|
|
126
|
+
|
|
127
|
+
Call it in two moments:
|
|
128
|
+
- BEFORE memory_store — copy the best candidate's context_path (by name) into
|
|
129
|
+
memory_store(context_path=...) so the memory anchors on the right node and
|
|
130
|
+
clears the 0.60 auto-anchor threshold instead of landing in the review queue.
|
|
131
|
+
- BEFORE a tree-specific search — to confirm which branch to look in.
|
|
132
|
+
|
|
133
|
+
Copy the context_path (the names), never the context_id (UUID). Universe
|
|
134
|
+
permission is enforced: a restricted caller only sees candidates in universes it
|
|
135
|
+
may read, and an explicit universe_id it cannot read is rejected (403).`,
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
query_text: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "What to situate in the tree — the subject of the memory you are about to store, or the search you are about to run.",
|
|
142
|
+
},
|
|
143
|
+
universe_id: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "Optional UUID to restrict candidates to one universe (must be one you may read). Omit to search every universe you can access.",
|
|
146
|
+
},
|
|
147
|
+
limit: {
|
|
148
|
+
type: "integer",
|
|
149
|
+
description: "Max candidates to return (default 5, max 25).",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ["query_text"],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
101
155
|
{
|
|
102
156
|
name: "memory_search",
|
|
103
157
|
description: `Search memories by semantic similarity.
|
|
@@ -860,7 +914,11 @@ Memories created by the agent are NOT deleted.`,
|
|
|
860
914
|
name: "agent_bootstrap",
|
|
861
915
|
description: `Bootstrap completo do agente — chame no início da sessão para carregar identidade, system prompt e contexto runtime. O agente thin-client chama isto como regra única de boot.
|
|
862
916
|
|
|
863
|
-
|
|
917
|
+
A resposta começa por um "Contrato de Boot" no TOPO com o que não pode se perder: identidade (id), organização (grave memórias só nela), usuário, versão do MCP, session_id, persona_version e a cadência de reorient. O system prompt longo vem depois.
|
|
918
|
+
|
|
919
|
+
Efeitos colaterais: inicia a sessão de memória (session_id devolvido no contrato; o MCP a encerra no fim do processo) e arma a cadência de reorient — a partir daqui o MCP conta as tool calls e anexa um aviso "REORIENT DEVIDO" quando vencer.
|
|
920
|
+
|
|
921
|
+
Retorna identity, system_prompt, runtime (working_memory, recent_memories, skills, delegate_digest), config (reorient_every_n, reorient_on_context_pct, max_memories) e persona_version.
|
|
864
922
|
|
|
865
923
|
O roster de especialistas NÃO vem no bootstrap — apenas um resumo (quantos e em quais universos). Use \`agent_list\` no momento da delegação para obter nomes e IDs.`,
|
|
866
924
|
inputSchema: {
|
|
@@ -1856,15 +1914,16 @@ granted it and when. No secret data.`,
|
|
|
1856
1914
|
},
|
|
1857
1915
|
},
|
|
1858
1916
|
// ==========================================================================
|
|
1859
|
-
// Platform Notifications (
|
|
1917
|
+
// Platform Notifications (3)
|
|
1860
1918
|
// ==========================================================================
|
|
1861
1919
|
{
|
|
1862
1920
|
name: "notifications_list",
|
|
1863
|
-
description: `List pending Frinus
|
|
1921
|
+
description: `List pending Frinus notifications for the current user.
|
|
1864
1922
|
|
|
1865
|
-
|
|
1866
|
-
tips
|
|
1867
|
-
|
|
1923
|
+
Two scopes appear here: (Sistema) = platform-authored (new features, maintenance,
|
|
1924
|
+
tips, alerts), and (Organização) = authored by an owner of the user's own org.
|
|
1925
|
+
This is a READ-ONLY peek — it does not mark anything as delivered or acknowledged,
|
|
1926
|
+
so items stay in the inbox until the user sees them.
|
|
1868
1927
|
|
|
1869
1928
|
Each item includes an \`id\` you can pass to notification_ack once the user has
|
|
1870
1929
|
read it. Use this when the user asks "what's new" or to re-check news that was
|
|
@@ -1894,4 +1953,67 @@ Frinus" block or from notifications_list.`,
|
|
|
1894
1953
|
required: ["id"],
|
|
1895
1954
|
},
|
|
1896
1955
|
},
|
|
1956
|
+
{
|
|
1957
|
+
name: "notification_create",
|
|
1958
|
+
description: `Publish an ORGANIZATION notification to the members of your org (OWNER only).
|
|
1959
|
+
|
|
1960
|
+
Use this when an org owner wants to broadcast something to their team through the
|
|
1961
|
+
Frinus channel: a process change, a reminder, a heads-up. The notice is delivered
|
|
1962
|
+
on each member's next agent_bootstrap / heartbeat and leaves their inbox when they
|
|
1963
|
+
acknowledge it (or after max_deliveries days of being shown-and-ignored).
|
|
1964
|
+
|
|
1965
|
+
Scope is forced server-side: the notice is bound to YOUR org and cannot target
|
|
1966
|
+
other orgs (that is a platform_admin "system" notice, created elsewhere). Optional
|
|
1967
|
+
targeting narrows WITHIN your org by role and/or specific users. Requires owner
|
|
1968
|
+
role — a 403 means the account is not an owner. Use severity "critical" only for
|
|
1969
|
+
things the member must see immediately (it is relayed before other work).`,
|
|
1970
|
+
inputSchema: {
|
|
1971
|
+
type: "object",
|
|
1972
|
+
properties: {
|
|
1973
|
+
title: { type: "string", description: "Short headline (required, 1–200 chars)" },
|
|
1974
|
+
body: {
|
|
1975
|
+
type: "string",
|
|
1976
|
+
description: "Message body, markdown allowed (optional, up to 2000 chars)",
|
|
1977
|
+
},
|
|
1978
|
+
kind: {
|
|
1979
|
+
type: "string",
|
|
1980
|
+
enum: ["feature", "announcement", "maintenance", "tip", "alert"],
|
|
1981
|
+
description: "Category (default 'announcement')",
|
|
1982
|
+
},
|
|
1983
|
+
severity: {
|
|
1984
|
+
type: "string",
|
|
1985
|
+
enum: ["info", "success", "warning", "critical"],
|
|
1986
|
+
description: "Visual/priority level (default 'info'; 'critical' is relayed immediately)",
|
|
1987
|
+
},
|
|
1988
|
+
target_roles: {
|
|
1989
|
+
type: "array",
|
|
1990
|
+
items: { type: "string" },
|
|
1991
|
+
description: "Optional: only members with one of these roles (e.g. ['trainer']). Empty = all members.",
|
|
1992
|
+
},
|
|
1993
|
+
target_user_keys: {
|
|
1994
|
+
type: "array",
|
|
1995
|
+
items: { type: "string" },
|
|
1996
|
+
description: "Optional: only these specific users (user id/sub or email). Empty = all members.",
|
|
1997
|
+
},
|
|
1998
|
+
cta_label: { type: "string", description: "Optional call-to-action label" },
|
|
1999
|
+
cta_url: {
|
|
2000
|
+
type: "string",
|
|
2001
|
+
description: "Optional call-to-action URL (must be https://)",
|
|
2002
|
+
},
|
|
2003
|
+
starts_at: {
|
|
2004
|
+
type: "string",
|
|
2005
|
+
description: "Optional ISO-8601 start time (default: now)",
|
|
2006
|
+
},
|
|
2007
|
+
expires_at: {
|
|
2008
|
+
type: "string",
|
|
2009
|
+
description: "Optional ISO-8601 expiry time (default: never)",
|
|
2010
|
+
},
|
|
2011
|
+
max_deliveries: {
|
|
2012
|
+
type: "number",
|
|
2013
|
+
description: "How many days an unseen notice is shown before it drops off (default 3, 1–100)",
|
|
2014
|
+
},
|
|
2015
|
+
},
|
|
2016
|
+
required: ["title"],
|
|
2017
|
+
},
|
|
2018
|
+
},
|
|
1897
2019
|
];
|
package/dist/tools/handlers.js
CHANGED
|
@@ -8,6 +8,8 @@ import axios from "axios";
|
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { getResolvedTenantOrgId } from "../client/memory-client.js";
|
|
10
10
|
import { fetchNotificationsBlock } from "../notifications.js";
|
|
11
|
+
import { MCP_VERSION } from "../version.js";
|
|
12
|
+
import { getSessionId, markBooted, resetReorientCounter } from "./boot-state.js";
|
|
11
13
|
// ---------------------------------------------------------------------------
|
|
12
14
|
// Credential delivery — server-side broker, never by value
|
|
13
15
|
// ---------------------------------------------------------------------------
|
|
@@ -330,6 +332,12 @@ function renderStoreResult(result) {
|
|
|
330
332
|
const id = result?.id;
|
|
331
333
|
const status = result?.classification_status;
|
|
332
334
|
const head = `✓ Salva`;
|
|
335
|
+
// Non-fatal note when a supplied context_path did not fully resolve by name
|
|
336
|
+
// (ambiguous or missing segment). The memory WAS stored; this is appended to
|
|
337
|
+
// whatever destination line we render below so the caller can fix the path.
|
|
338
|
+
const warning = typeof result?.context_path_warning === "string" && result.context_path_warning
|
|
339
|
+
? `\n ⚠ context_path: ${result.context_path_warning}`
|
|
340
|
+
: "";
|
|
333
341
|
const applied = appliedLabel(result);
|
|
334
342
|
const pending = status === "proposed" || status === "universe_only";
|
|
335
343
|
// Anchored: a real destination exists. Report it, and only then mention a
|
|
@@ -342,7 +350,7 @@ function renderStoreResult(result) {
|
|
|
342
350
|
out += `\n ↳ sugestão pendente (NÃO aplicada): ${sug} — revise com memory_classification_pending`;
|
|
343
351
|
}
|
|
344
352
|
}
|
|
345
|
-
return out;
|
|
353
|
+
return out + warning;
|
|
346
354
|
}
|
|
347
355
|
// Not anchored anywhere: the suggestion is all we have to offer.
|
|
348
356
|
if (pending) {
|
|
@@ -350,12 +358,12 @@ function renderStoreResult(result) {
|
|
|
350
358
|
const conf = confidenceLabel(result?.classification_confidence);
|
|
351
359
|
if (sug) {
|
|
352
360
|
const confTxt = conf ? `, confiança ${conf}` : "";
|
|
353
|
-
return `${head} · sem destino aplicado · sugestão: ${sug} (auto${confTxt}) — aprove com memory_classification_approve
|
|
361
|
+
return `${head} · sem destino aplicado · sugestão: ${sug} (auto${confTxt}) — aprove com memory_classification_approve` + warning;
|
|
354
362
|
}
|
|
355
|
-
return `${head} · sem destino aplicado e sem sugestão confiável — use memory_classification_pending para revisar
|
|
363
|
+
return `${head} · sem destino aplicado e sem sugestão confiável — use memory_classification_pending para revisar` + warning;
|
|
356
364
|
}
|
|
357
365
|
// Defensive: older backend without classification fields, or no hierarchy at all.
|
|
358
|
-
return `${head}\nID: ${id}\nType: ${result?.memory_type}\nScope: ${result?.scope}
|
|
366
|
+
return `${head}\nID: ${id}\nType: ${result?.memory_type}\nScope: ${result?.scope}` + warning;
|
|
359
367
|
}
|
|
360
368
|
const handlers = {
|
|
361
369
|
// ==========================================================================
|
|
@@ -387,6 +395,7 @@ const handlers = {
|
|
|
387
395
|
created_by_user_id: createdByUser,
|
|
388
396
|
metadata: args.metadata,
|
|
389
397
|
context_id: args.context_id,
|
|
398
|
+
context_path: args.context_path,
|
|
390
399
|
credential_ref: args.credential_ref,
|
|
391
400
|
universe_id: universeId,
|
|
392
401
|
scope: args.scope,
|
|
@@ -400,6 +409,43 @@ const handlers = {
|
|
|
400
409
|
throw error;
|
|
401
410
|
}
|
|
402
411
|
},
|
|
412
|
+
async hierarchy_find(args, { memoryClient }) {
|
|
413
|
+
let result;
|
|
414
|
+
try {
|
|
415
|
+
result = await memoryClient.findHierarchy({
|
|
416
|
+
query_text: args.query_text,
|
|
417
|
+
universe_id: args.universe_id,
|
|
418
|
+
limit: args.limit,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
const apiErr = handleApiError(error);
|
|
423
|
+
if (apiErr)
|
|
424
|
+
return apiErr;
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
const candidates = Array.isArray(result?.candidates) ? result.candidates : [];
|
|
428
|
+
if (candidates.length === 0) {
|
|
429
|
+
return {
|
|
430
|
+
content: [{
|
|
431
|
+
type: "text",
|
|
432
|
+
text: "Nenhum nó candidato para esta consulta. Salve mesmo assim (o backend classifica ou cai no universo/Geral), ou refine a consulta.",
|
|
433
|
+
}],
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
let formatted = "Nós candidatos (copie o caminho POR NOME para memory_store(context_path=...)):\n\n";
|
|
437
|
+
candidates.forEach((c, i) => {
|
|
438
|
+
const path = typeof c?.context_path === "string" ? c.context_path : "(sem caminho)";
|
|
439
|
+
const conf = confidenceLabel(c?.confidence);
|
|
440
|
+
const confTxt = typeof c?.confidence === "number"
|
|
441
|
+
? ` (confiança ${c.confidence.toFixed(2)}${conf ? ` · ${conf}` : ""})`
|
|
442
|
+
: "";
|
|
443
|
+
const level = c?.level ? ` [${c.level}]` : "";
|
|
444
|
+
formatted += `${i + 1}. ${path}${level}${confTxt}\n`;
|
|
445
|
+
});
|
|
446
|
+
formatted += "\nCopie o caminho (os nomes), não o context_id. Confiança ≥ 0,60 ancora sozinho; abaixo disso vai para a fila de revisão.";
|
|
447
|
+
return { content: [{ type: "text", text: formatted }] };
|
|
448
|
+
},
|
|
403
449
|
// ==========================================================================
|
|
404
450
|
// Auto-classification (human-in-the-loop)
|
|
405
451
|
// ==========================================================================
|
|
@@ -1254,7 +1300,7 @@ const handlers = {
|
|
|
1254
1300
|
}],
|
|
1255
1301
|
};
|
|
1256
1302
|
},
|
|
1257
|
-
async agent_bootstrap(args, { agentClient, cpClient }) {
|
|
1303
|
+
async agent_bootstrap(args, { agentClient, cpClient, memoryClient, capture, resolvedUserEmail, resolvedUserId }) {
|
|
1258
1304
|
// Kick off the notifications fetch concurrently with the bootstrap call so
|
|
1259
1305
|
// it adds no latency on the critical path. fetchNotificationsBlock never
|
|
1260
1306
|
// throws and self-limits to a 4s timeout, so bootstrap can neither fail nor
|
|
@@ -1264,20 +1310,69 @@ const handlers = {
|
|
|
1264
1310
|
const result = await agentClient.bootstrapAgent(args.agent_id);
|
|
1265
1311
|
const identity = result.identity || {};
|
|
1266
1312
|
const runtime = result.runtime || {};
|
|
1313
|
+
const bootConfig = result.config || {};
|
|
1314
|
+
const agentIdStr = String(identity.id || args.agent_id || "");
|
|
1315
|
+
// --- Session lifecycle: the MCP owns it ------------------------------
|
|
1316
|
+
// The boot protocol must not depend on the model remembering to call
|
|
1317
|
+
// session_start / session_end. We start the formal session HERE (once per
|
|
1318
|
+
// process) and record it in the boot state; the process-exit hooks in
|
|
1319
|
+
// index.ts close it (session_summary + session_end). Best-effort: a
|
|
1320
|
+
// Memory Engine hiccup must never make bootstrap fail.
|
|
1321
|
+
let activeSessionId = getSessionId();
|
|
1322
|
+
if (!activeSessionId) {
|
|
1323
|
+
try {
|
|
1324
|
+
const s = await memoryClient.startSession({ agent_id: agentIdStr });
|
|
1325
|
+
activeSessionId = s?.session_id || null;
|
|
1326
|
+
if (activeSessionId)
|
|
1327
|
+
capture.sessionId = activeSessionId;
|
|
1328
|
+
}
|
|
1329
|
+
catch (err) {
|
|
1330
|
+
console.error("[MCP] bootstrap session_start failed:", err?.message || err);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
// Arm the deterministic reorient cadence from the authoritative config.
|
|
1334
|
+
const reorientEveryN = Number(bootConfig.reorient_every_n) || 10;
|
|
1335
|
+
const reorientPct = Number(bootConfig.reorient_on_context_pct) || 70;
|
|
1336
|
+
markBooted({
|
|
1337
|
+
agentId: agentIdStr,
|
|
1338
|
+
sessionId: activeSessionId,
|
|
1339
|
+
config: { reorientEveryN, reorientOnContextPct: reorientPct },
|
|
1340
|
+
});
|
|
1341
|
+
// --- Boot Contract (TOP: highest-attention, never-truncated-away) -----
|
|
1342
|
+
// Everything the agent cannot afford to lose goes here, ABOVE the long
|
|
1343
|
+
// system prompt (which the model tends to skim at the tail). Identity,
|
|
1344
|
+
// the org it may write to, the user, the session, and the reorient
|
|
1345
|
+
// cadence. The long persona/protocol text follows.
|
|
1346
|
+
const orgId = identity.org_id || getResolvedTenantOrgId() || "unknown";
|
|
1347
|
+
const orgName = identity.org_name ? `${identity.org_name} ` : "";
|
|
1348
|
+
const userEmail = resolvedUserEmail || "desconhecido";
|
|
1349
|
+
const userId = resolvedUserId ? ` \`${resolvedUserId}\`` : "";
|
|
1350
|
+
const roleTxt = identity.role || identity.specialization;
|
|
1351
|
+
// Notifications (system + org) go at the very TOP, above the contract:
|
|
1352
|
+
// a critical notice must be the first thing the agent reads. The fetch
|
|
1353
|
+
// ran concurrently with bootstrapAgent, so awaiting here adds no latency.
|
|
1354
|
+
const notif = await notifPromise;
|
|
1267
1355
|
let formatted = `# Agent Bootstrap\n\n`;
|
|
1268
|
-
formatted +=
|
|
1269
|
-
if (
|
|
1270
|
-
formatted +=
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
if (
|
|
1274
|
-
formatted +=
|
|
1356
|
+
formatted += `<!-- ALERTS_SLOT -->\n`;
|
|
1357
|
+
if (notif)
|
|
1358
|
+
formatted += `${notif.trimEnd()}\n\n`;
|
|
1359
|
+
formatted += `## ⚙️ Contrato de Boot (CRÍTICO — leia e adote ANTES de tudo)\n`;
|
|
1360
|
+
formatted += `- Agente: ${identity.name || "?"} \`${agentIdStr}\`\n`;
|
|
1361
|
+
if (roleTxt)
|
|
1362
|
+
formatted += `- Papel: ${roleTxt}\n`;
|
|
1363
|
+
formatted += `- Organização: ${orgName}\`${orgId}\` — grave memórias SOMENTE nesta org (confira antes da 1ª escrita)\n`;
|
|
1364
|
+
formatted += `- Usuário: ${userEmail}${userId}\n`;
|
|
1365
|
+
formatted += `- MCP: frinus v${MCP_VERSION}`;
|
|
1366
|
+
if (activeSessionId)
|
|
1367
|
+
formatted += ` · Sessão: ${activeSessionId}`;
|
|
1368
|
+
if (result.persona_version !== undefined)
|
|
1369
|
+
formatted += ` · Persona: ${result.persona_version}`;
|
|
1370
|
+
formatted += `\n`;
|
|
1371
|
+
formatted += `- Reorient: a cada ${reorientEveryN} tool calls OU ~${reorientPct}% de contexto → \`agent_reorient(agent_id="${agentIdStr}")\`. O MCP conta as chamadas e anexa um aviso automático quando vencer — não dependa da sua memória.\n`;
|
|
1275
1372
|
if (identity.universe_id)
|
|
1276
|
-
formatted +=
|
|
1373
|
+
formatted += `- Universe: \`${identity.universe_id}\`\n`;
|
|
1277
1374
|
if (identity.team_id)
|
|
1278
|
-
formatted +=
|
|
1279
|
-
if (result.persona_version !== undefined)
|
|
1280
|
-
formatted += `Persona Version: ${result.persona_version}\n`;
|
|
1375
|
+
formatted += `- Team: \`${identity.team_id}\`\n`;
|
|
1281
1376
|
formatted += `\n`;
|
|
1282
1377
|
if (result.system_prompt) {
|
|
1283
1378
|
formatted += `## System Prompt\n${result.system_prompt}\n\n`;
|
|
@@ -1295,7 +1390,14 @@ const handlers = {
|
|
|
1295
1390
|
if (recentMemories.length > 0) {
|
|
1296
1391
|
formatted += `\n### Recent Memories (${recentMemories.length})\n`;
|
|
1297
1392
|
recentMemories.forEach((mem, i) => {
|
|
1298
|
-
|
|
1393
|
+
// The server sends `type` (not `memory_type`); accept both so the
|
|
1394
|
+
// label is real instead of a constant "[memory]". Surface the id so
|
|
1395
|
+
// the agent can reinforce/weaken/get it, and the importance so it can
|
|
1396
|
+
// weight it. Content cut raised from 150 to 400 chars.
|
|
1397
|
+
const t = mem.type || mem.memory_type || "memory";
|
|
1398
|
+
const idTxt = mem.id ? ` (id: ${mem.id})` : "";
|
|
1399
|
+
const imp = typeof mem.importance === "number" ? ` (importance: ${mem.importance.toFixed(2)})` : "";
|
|
1400
|
+
formatted += `${i + 1}. [${t}]${idTxt}${imp} ${mem.content?.slice(0, 400)}\n`;
|
|
1299
1401
|
});
|
|
1300
1402
|
}
|
|
1301
1403
|
const skills = runtime.skills || [];
|
|
@@ -1309,8 +1411,14 @@ const handlers = {
|
|
|
1309
1411
|
skills.forEach((s) => {
|
|
1310
1412
|
const name = s.skill_name || s.name || s.slug || s;
|
|
1311
1413
|
const slug = s.slug ? ` \`${s.slug}\`` : "";
|
|
1414
|
+
// version/permission come from the server (skill_version / permission)
|
|
1415
|
+
// and were previously discarded. They tell the agent which revision it
|
|
1416
|
+
// holds and whether it may edit the playbook.
|
|
1417
|
+
const ver = s.version || s.skill_version;
|
|
1418
|
+
const verTxt = ver ? ` v${ver}` : "";
|
|
1419
|
+
const perm = s.permission ? ` [${s.permission}]` : "";
|
|
1312
1420
|
const desc = s.description ? ` — ${s.description}` : "";
|
|
1313
|
-
formatted += `- ${name}${slug}${desc}\n`;
|
|
1421
|
+
formatted += `- ${name}${slug}${verTxt}${perm}${desc}\n`;
|
|
1314
1422
|
});
|
|
1315
1423
|
formatted += `</skills_catalog>\n`;
|
|
1316
1424
|
}
|
|
@@ -1323,9 +1431,19 @@ const handlers = {
|
|
|
1323
1431
|
if (delegateDigest && delegateDigest.count > 0 && delegateDigest.summary) {
|
|
1324
1432
|
formatted += `\n### Delegate Targets\n${delegateDigest.summary}\n`;
|
|
1325
1433
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1434
|
+
// Shallow tree map (universe → concept) for orientation. The lines are
|
|
1435
|
+
// already composed and truncated server-side; print them verbatim. Use
|
|
1436
|
+
// hierarchy_find(query) for the exact node, universe_hierarchy for the
|
|
1437
|
+
// full tree.
|
|
1438
|
+
const hierarchyMap = runtime.hierarchy_map || [];
|
|
1439
|
+
if (Array.isArray(hierarchyMap) && hierarchyMap.length > 0) {
|
|
1440
|
+
formatted += `\n### Árvore (navegação)\n`;
|
|
1441
|
+
formatted += `Mapa raso para orientação; use hierarchy_find(query) para achar o nó exato e universe_hierarchy para a árvore completa.\n`;
|
|
1442
|
+
hierarchyMap.forEach((line) => {
|
|
1443
|
+
if (typeof line === "string" && line)
|
|
1444
|
+
formatted += `- ${line}\n`;
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1329
1447
|
return { content: [{ type: "text", text: formatted }] };
|
|
1330
1448
|
}
|
|
1331
1449
|
catch (error) {
|
|
@@ -1338,6 +1456,10 @@ const handlers = {
|
|
|
1338
1456
|
async agent_reorient(args, { memoryClient }) {
|
|
1339
1457
|
try {
|
|
1340
1458
|
const result = await memoryClient.reorientAgent(args.agent_id, args.focus);
|
|
1459
|
+
// Deterministic cadence: reorienting clears the "reorient due" counter so
|
|
1460
|
+
// the auto-appended banner stops until the next N tool calls elapse. Done
|
|
1461
|
+
// regardless of what the server returns.
|
|
1462
|
+
resetReorientCounter();
|
|
1341
1463
|
const identity = result.identity || {};
|
|
1342
1464
|
let formatted = `# Agent Reorient\n\n`;
|
|
1343
1465
|
formatted += `## Identity\n`;
|
|
@@ -2352,7 +2474,8 @@ const handlers = {
|
|
|
2352
2474
|
let text = `${items.length} pending Frinus notification${items.length === 1 ? "" : "s"}:\n\n`;
|
|
2353
2475
|
items.forEach((it) => {
|
|
2354
2476
|
const sev = it.severity ? `[${it.severity}] ` : "";
|
|
2355
|
-
|
|
2477
|
+
const scope = it.scope === "organization" ? "(Organização) " : "(Sistema) ";
|
|
2478
|
+
text += `- ${scope}${sev}${it.title} (id: ${it.id})\n`;
|
|
2356
2479
|
let body = (it.body || "").trim();
|
|
2357
2480
|
if (body.length > 300)
|
|
2358
2481
|
body = body.slice(0, 300).trimEnd() + "…";
|
|
@@ -2407,6 +2530,75 @@ const handlers = {
|
|
|
2407
2530
|
throw error;
|
|
2408
2531
|
}
|
|
2409
2532
|
},
|
|
2533
|
+
async notification_create(args, { cpClient }) {
|
|
2534
|
+
const title = args.title?.trim() || "";
|
|
2535
|
+
if (!title) {
|
|
2536
|
+
return {
|
|
2537
|
+
content: [{ type: "text", text: "notification_create requires a non-empty 'title'." }],
|
|
2538
|
+
isError: true,
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
// Owner scope only: the Control Plane forces scope='organization' and binds
|
|
2542
|
+
// the notice to the caller's own org — the owner cannot target other orgs.
|
|
2543
|
+
const payload = { title };
|
|
2544
|
+
if (typeof args.body === "string")
|
|
2545
|
+
payload.body = args.body;
|
|
2546
|
+
if (typeof args.kind === "string")
|
|
2547
|
+
payload.kind = args.kind;
|
|
2548
|
+
if (typeof args.severity === "string")
|
|
2549
|
+
payload.severity = args.severity;
|
|
2550
|
+
if (Array.isArray(args.target_roles))
|
|
2551
|
+
payload.target_roles = args.target_roles;
|
|
2552
|
+
if (Array.isArray(args.target_user_keys))
|
|
2553
|
+
payload.target_user_keys = args.target_user_keys;
|
|
2554
|
+
if (typeof args.cta_label === "string")
|
|
2555
|
+
payload.cta_label = args.cta_label;
|
|
2556
|
+
if (typeof args.cta_url === "string")
|
|
2557
|
+
payload.cta_url = args.cta_url;
|
|
2558
|
+
if (typeof args.starts_at === "string")
|
|
2559
|
+
payload.starts_at = args.starts_at;
|
|
2560
|
+
if (typeof args.expires_at === "string")
|
|
2561
|
+
payload.expires_at = args.expires_at;
|
|
2562
|
+
if (typeof args.max_deliveries === "number")
|
|
2563
|
+
payload.max_deliveries = args.max_deliveries;
|
|
2564
|
+
try {
|
|
2565
|
+
const created = await cpClient.createOrgNotification(payload);
|
|
2566
|
+
return {
|
|
2567
|
+
content: [{
|
|
2568
|
+
type: "text",
|
|
2569
|
+
text: `Organization notification created (id: ${created?.id ?? "?"}). It will reach the ` +
|
|
2570
|
+
`members of your org on their next bootstrap/heartbeat, newest-critical first, ` +
|
|
2571
|
+
`and leaves each member's inbox on ack or after max_deliveries days unseen.`,
|
|
2572
|
+
}],
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
catch (error) {
|
|
2576
|
+
const status = error?.response?.status;
|
|
2577
|
+
if (status === 403) {
|
|
2578
|
+
return {
|
|
2579
|
+
content: [{
|
|
2580
|
+
type: "text",
|
|
2581
|
+
text: "Only an org owner can create organization notifications. This account is not an owner of the org.",
|
|
2582
|
+
}],
|
|
2583
|
+
isError: true,
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
if (status === 422) {
|
|
2587
|
+
const detail = error?.response?.data?.detail;
|
|
2588
|
+
return {
|
|
2589
|
+
content: [{
|
|
2590
|
+
type: "text",
|
|
2591
|
+
text: `notification_create rejected: ${typeof detail === "string" ? detail : JSON.stringify(detail)}`,
|
|
2592
|
+
}],
|
|
2593
|
+
isError: true,
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
const apiErr = handleApiError(error);
|
|
2597
|
+
if (apiErr)
|
|
2598
|
+
return apiErr;
|
|
2599
|
+
throw error;
|
|
2600
|
+
}
|
|
2601
|
+
},
|
|
2410
2602
|
};
|
|
2411
2603
|
// ---------------------------------------------------------------------------
|
|
2412
2604
|
// Dispatcher: looks up the handler and executes it with error handling
|
package/dist/types/index.d.ts
CHANGED
|
@@ -66,11 +66,28 @@ export interface NotificationItem {
|
|
|
66
66
|
body: string;
|
|
67
67
|
kind: string;
|
|
68
68
|
severity: string;
|
|
69
|
+
/** "system" (platform-wide) or "organization" (owner of this org). */
|
|
70
|
+
scope?: string;
|
|
69
71
|
cta_label?: string | null;
|
|
70
72
|
cta_url?: string | null;
|
|
71
73
|
starts_at?: string | null;
|
|
72
74
|
expires_at?: string | null;
|
|
73
75
|
}
|
|
76
|
+
/** Payload an org owner sends to publish an organization-scoped notice. */
|
|
77
|
+
export interface OwnerNotificationCreate {
|
|
78
|
+
title: string;
|
|
79
|
+
body?: string;
|
|
80
|
+
kind?: string;
|
|
81
|
+
severity?: string;
|
|
82
|
+
target_roles?: string[];
|
|
83
|
+
target_user_keys?: string[];
|
|
84
|
+
cta_label?: string | null;
|
|
85
|
+
cta_url?: string | null;
|
|
86
|
+
starts_at?: string | null;
|
|
87
|
+
expires_at?: string | null;
|
|
88
|
+
max_deliveries?: number;
|
|
89
|
+
is_active?: boolean;
|
|
90
|
+
}
|
|
74
91
|
/** Response shape of GET /api/v1/notifications/inbox. */
|
|
75
92
|
export interface NotificationInbox {
|
|
76
93
|
items: NotificationItem[];
|
|
@@ -95,6 +112,7 @@ export interface CpClientInterface {
|
|
|
95
112
|
markDelivered?: boolean;
|
|
96
113
|
}): Promise<NotificationInbox>;
|
|
97
114
|
ackNotification(id: string): Promise<void>;
|
|
115
|
+
createOrgNotification(data: OwnerNotificationCreate): Promise<any>;
|
|
98
116
|
storeCredential(integrationRef: string, data: Record<string, unknown>, metadata?: Record<string, unknown>): Promise<any>;
|
|
99
117
|
getCredential(integrationRef: string): Promise<any>;
|
|
100
118
|
listCredentials(): Promise<any>;
|
|
@@ -132,6 +150,7 @@ export interface MemoryClientInterface {
|
|
|
132
150
|
created_by_user_id?: string;
|
|
133
151
|
metadata?: Record<string, unknown>;
|
|
134
152
|
context_id?: string;
|
|
153
|
+
context_path?: string;
|
|
135
154
|
credential_ref?: string;
|
|
136
155
|
universe_id?: string;
|
|
137
156
|
}): Promise<any>;
|
|
@@ -141,6 +160,11 @@ export interface MemoryClientInterface {
|
|
|
141
160
|
memory_types?: string[];
|
|
142
161
|
limit?: number;
|
|
143
162
|
}): Promise<any>;
|
|
163
|
+
findHierarchy(data: {
|
|
164
|
+
query_text: string;
|
|
165
|
+
universe_id?: string;
|
|
166
|
+
limit?: number;
|
|
167
|
+
}): Promise<any>;
|
|
144
168
|
getMemory(memoryId: string): Promise<any>;
|
|
145
169
|
getAgentMemories(agentId: string, memoryType?: string, limit?: number): Promise<any>;
|
|
146
170
|
deleteMemory(memoryId: string): Promise<any>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the MCP server version.
|
|
3
|
+
*
|
|
4
|
+
* Imported by `index.ts` (server `name`/`version` handshake) and by the
|
|
5
|
+
* `agent_bootstrap` Boot Contract block so the version the user sees always
|
|
6
|
+
* matches the running build. Keep in sync with `package.json` and
|
|
7
|
+
* `manifest.json` at release time.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MCP_VERSION = "3.8.0";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the MCP server version.
|
|
3
|
+
*
|
|
4
|
+
* Imported by `index.ts` (server `name`/`version` handshake) and by the
|
|
5
|
+
* `agent_bootstrap` Boot Contract block so the version the user sees always
|
|
6
|
+
* matches the running build. Keep in sync with `package.json` and
|
|
7
|
+
* `manifest.json` at release time.
|
|
8
|
+
*/
|
|
9
|
+
export const MCP_VERSION = "3.8.0";
|
package/manifest.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "frinus",
|
|
4
4
|
"display_name": "Frinus — Memória Cognitiva",
|
|
5
|
-
"version": "3.
|
|
6
|
-
"description": "Memória cognitiva de longo prazo para o Claude (φρήν + νοῦς).
|
|
5
|
+
"version": "3.8.0",
|
|
6
|
+
"description": "Memória cognitiva de longo prazo para o Claude (φρήν + νοῦς). 91 ferramentas de memória, sessões, agentes e conhecimento.",
|
|
7
7
|
"long_description": "O Frinus dá ao Claude memória de longo prazo, identidade e inteligência persistente entre conversas. Conecta o Claude Desktop à plataforma Frinus (Memory Engine, Control Plane e Agent Service) via MCP. Basta colar sua chave de API pessoal — nada para instalar ou compilar.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Frinus",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frinus-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Frinus - MCP Server for Cognitive Memory (φρήν + νοῦς). Long-term memory, sessions, agents and knowledge for Claude and any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|