@arkimedehq/recordare-client 0.2.0 → 0.4.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 +33 -7
- package/README_it.md +36 -9
- package/dist/client.d.ts +94 -4
- package/dist/client.js +116 -5
- package/dist/contract.d.ts +185 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/links.d.ts +10 -0
- package/dist/links.js +16 -0
- package/dist/mcp.js +1 -1
- package/dist/people.d.ts +5 -3
- package/dist/people.js +6 -2
- package/dist/tools.d.ts +4 -4
- package/dist/tools.js +4 -4
- package/package.json +1 -1
- package/src/client.ts +142 -8
- package/src/contract.ts +128 -0
- package/src/index.ts +1 -0
- package/src/links.ts +20 -0
- package/src/mcp.ts +1 -1
- package/src/people.ts +11 -5
- package/src/tools.ts +4 -4
package/src/contract.ts
CHANGED
|
@@ -37,6 +37,11 @@ export interface Me {
|
|
|
37
37
|
scopes: string[];
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** `DELETE api/v1/me`: what erasing the whole memory took, by kind. */
|
|
41
|
+
export interface Erased {
|
|
42
|
+
erased: { conversations: number; messages: number; episodes: number; facts: number; notes: number; digests: number; sources: number; contacts: number };
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
/** `PATCH api/v1/me`: the person's settings from their platform. */
|
|
41
46
|
export interface MeSettings {
|
|
42
47
|
/** Follows the platform's profile: send it again whenever the user renames themselves. */
|
|
@@ -74,9 +79,20 @@ export interface IngestConversation {
|
|
|
74
79
|
source?: ConversationSource;
|
|
75
80
|
channel?: string;
|
|
76
81
|
title?: string;
|
|
82
|
+
/** A one-to-one chat (true) or a group (false), when the platform knows: a link code is never accepted in a group (D51). */
|
|
83
|
+
direct?: boolean;
|
|
77
84
|
participants?: IngestParticipant[];
|
|
78
85
|
}
|
|
79
86
|
|
|
87
|
+
/** A one-time code the holder sends from a channel to link that identity to themselves (D51; ten minutes, one use). */
|
|
88
|
+
export interface LinkCode { code: string; expiresAt: string }
|
|
89
|
+
|
|
90
|
+
/** The identities that speak as the memory's "I", and the claims waiting for the holder's confirmation (D51). */
|
|
91
|
+
export interface HolderIdentities {
|
|
92
|
+
linked: Array<{ id: string; channel: string | null; client: string | null; externalId: string; linkedAt: string }>;
|
|
93
|
+
pending: Array<{ id: string; channel: string | null; client: string | null; externalId: string; displayName: string | null; claimedAt: string }>;
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
export interface IngestMessage {
|
|
81
97
|
/** The platform's own stable id: Recordare deduplicates on it, so a resend never duplicates. */
|
|
82
98
|
externalId: string;
|
|
@@ -111,6 +127,43 @@ export interface IngestResult {
|
|
|
111
127
|
conflicts: string[];
|
|
112
128
|
}
|
|
113
129
|
|
|
130
|
+
/** One voice of a room transcript (continuous listening, `ingestRoom`). */
|
|
131
|
+
export interface RoomSpeaker {
|
|
132
|
+
/** The transcript-local reference messages use in `speakerRef`. */
|
|
133
|
+
ref: string;
|
|
134
|
+
displayName?: string;
|
|
135
|
+
/** The platform's user id of a speaker with an account: in their own memory their words are theirs. */
|
|
136
|
+
user?: string;
|
|
137
|
+
/** Otherwise (or as well) a channel identity, e.g. a voice profile: a contact in every recipient's memory. */
|
|
138
|
+
identity?: ParticipantIdentity;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* What a home device heard in a room (WORK_PLAN 6.5 (4)), fanned out by Recordare to the memories of `recipients` (the
|
|
143
|
+
* platform's user ids of the people present whose memory is on — the platform's switch, D50): each gets it as an
|
|
144
|
+
* `ambient` conversation with the same external id, so edits and deletions go through the per-user calls.
|
|
145
|
+
*/
|
|
146
|
+
export interface RoomRequest {
|
|
147
|
+
conversation: { externalId: string; channel?: string; title?: string };
|
|
148
|
+
speakers: RoomSpeaker[];
|
|
149
|
+
recipients: string[];
|
|
150
|
+
messages: Array<{
|
|
151
|
+
externalId: string;
|
|
152
|
+
/** Missing when the device could not tell who spoke: "someone" in every memory. */
|
|
153
|
+
speakerRef?: string;
|
|
154
|
+
content: string;
|
|
155
|
+
/** ISO 8601 with offset. */
|
|
156
|
+
sentAt: string;
|
|
157
|
+
upsert?: boolean;
|
|
158
|
+
}>;
|
|
159
|
+
hints?: { conversationEnded?: boolean };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface RoomResult {
|
|
163
|
+
/** One per recipient, in the request's order; `conversationId` is that memory's conversation. */
|
|
164
|
+
recipients: Array<{ user: string; conversationId: string | null; accepted: number; duplicates: number; conflicts: string[] }>;
|
|
165
|
+
}
|
|
166
|
+
|
|
114
167
|
// ── The diary: read / write API for host UIs (API.md §4) ─────────────────────────────────────────────────────────
|
|
115
168
|
|
|
116
169
|
export type PlanStatus = 'open' | 'confirmed' | 'cancelled' | 'rescheduled' | 'unresolved';
|
|
@@ -182,11 +235,64 @@ export interface Note {
|
|
|
182
235
|
pinned: boolean;
|
|
183
236
|
pending: boolean;
|
|
184
237
|
inferred: boolean;
|
|
238
|
+
/** A recurring pattern the memory drew from its episodes (inferred, never pending; WORK_PLAN 5.4). */
|
|
239
|
+
pattern: boolean;
|
|
185
240
|
authorRole: string;
|
|
186
241
|
supportCount: number;
|
|
187
242
|
recordedAt: string;
|
|
188
243
|
}
|
|
189
244
|
|
|
245
|
+
/** The messages behind a fact or a note; text only from the client's own conversations (`otherClient` otherwise). */
|
|
246
|
+
export type ItemEvidence = Array<{ messageId?: string; conversation?: string; role: string; author?: string | null; sentAt: string; text?: string; otherClient?: true }>;
|
|
247
|
+
|
|
248
|
+
export interface FactDetail extends Fact {
|
|
249
|
+
authorRole: string;
|
|
250
|
+
/** The fact this one corrected (a correction by the person). */
|
|
251
|
+
corrects: string | null;
|
|
252
|
+
recordedAt: string;
|
|
253
|
+
evidence: ItemEvidence;
|
|
254
|
+
/** Episodes the fact was drawn from. */
|
|
255
|
+
episodeIds: string[];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface NoteDetail extends Note {
|
|
259
|
+
status: 'current' | 'superseded' | 'corrected';
|
|
260
|
+
supersedes: string | null;
|
|
261
|
+
corrects: string | null;
|
|
262
|
+
evidence: ItemEvidence;
|
|
263
|
+
episodeIds: string[];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** One entry of the notes change feed; `note` is the note as it is now, null once forgotten. */
|
|
267
|
+
export interface NoteChange {
|
|
268
|
+
seq: number;
|
|
269
|
+
noteId: string;
|
|
270
|
+
change: 'created' | 'updated' | 'corrected' | 'confirmed' | 'forgotten' | 'retired';
|
|
271
|
+
at: string;
|
|
272
|
+
/** For `updated` / `corrected`: the note this one took the place of (a copy of it should follow), else null. */
|
|
273
|
+
replaces: string | null;
|
|
274
|
+
note: (Note & { status: string }) | null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface MemorySettings {
|
|
278
|
+
locale: string;
|
|
279
|
+
timezone: string;
|
|
280
|
+
/** null: the installation default (`effectiveQualityProfile`). */
|
|
281
|
+
qualityProfile: 'economy' | 'balanced' | 'full' | null;
|
|
282
|
+
effectiveQualityProfile: 'economy' | 'balanced' | 'full';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface UsageTotals { calls: number; inputTokens: number; cachedInputTokens: number; outputTokens: number; failed: number }
|
|
286
|
+
|
|
287
|
+
export interface Usage extends UsageTotals {
|
|
288
|
+
from: string;
|
|
289
|
+
to: string;
|
|
290
|
+
byPrompt: Array<UsageTotals & { prompt: string }>;
|
|
291
|
+
byDay: Array<UsageTotals & { day: string }>;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface ExportJob { id: string; status: 'running' | 'done' | 'failed'; createdAt: string; finishedAt: string | null; bytes: number | null }
|
|
295
|
+
|
|
190
296
|
/** Size of the parts `learnSource` sends a big text in (UTF-8 bytes; the service's default request limit is 16 MB). */
|
|
191
297
|
export const SOURCE_PART_BYTES = 4 * 1024 * 1024;
|
|
192
298
|
|
|
@@ -234,6 +340,28 @@ export interface Source {
|
|
|
234
340
|
}
|
|
235
341
|
|
|
236
342
|
/** `POST api/v1/context`: the memories relevant to the message a host is about to answer (WORK_PLAN 5.7). */
|
|
343
|
+
/** `POST api/v1/context` options. */
|
|
344
|
+
export interface ContextOptions {
|
|
345
|
+
/** Open the block with the self card — who I am: current facts and notes about me. Off by default. */
|
|
346
|
+
card?: boolean;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** `GET api/v1/cards`: a person as the memory knows them now (WORK_PLAN 8.13). */
|
|
350
|
+
export interface Card {
|
|
351
|
+
personId: string;
|
|
352
|
+
name: string;
|
|
353
|
+
aliases: string[];
|
|
354
|
+
facts: Array<{ id: string; key: string; value: string | null; validFrom: string | null }>;
|
|
355
|
+
notes: Array<{ id: string; category: string; content: string; pinned: boolean; pattern: boolean }>;
|
|
356
|
+
/** The day of the latest episode about this contact (null for the self). */
|
|
357
|
+
lastMentioned: string | null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export interface Cards {
|
|
361
|
+
self: Card;
|
|
362
|
+
contacts: Card[];
|
|
363
|
+
}
|
|
364
|
+
|
|
237
365
|
export interface MemoryContext {
|
|
238
366
|
/** A fenced `<memory-context>` block to append to the prompt, or null (nothing relevant, or off). */
|
|
239
367
|
block: string | null;
|
package/src/index.ts
CHANGED
|
@@ -9,4 +9,5 @@ export { RecordareClient } from './client.js';
|
|
|
9
9
|
export { RecordareMcp, type McpOptions, type Tool, type ToolResult } from './mcp.js';
|
|
10
10
|
export { PersonDirectory, type Person, type PersonDirectoryOptions } from './people.js';
|
|
11
11
|
export { TOOLS } from './tools.js';
|
|
12
|
+
export { isLinkCode, LINK_CODE, LINK_CODE_INSTRUCTION } from './links.js';
|
|
12
13
|
export { afterFailure, backoffMs, DEFAULT_DELIVERY, type DeliveryPolicy, type Next } from './delivery.js';
|
package/src/links.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright © 2026 Andrea Genovese
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* D51 — linking the holder's channel identity by one-time code. The holder makes a code (`RecordareClient.linkCode`),
|
|
6
|
+
* sends it to the agent from the channel, then confirms the claim (`holderIdentities` / `confirmHolderClaim`). Recordare
|
|
7
|
+
* never stores the code's message; a host that sees the message before its model can tell the model what it is.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** A message that is only a link code (dashes and a final dot optional, any case) — the service's own pattern. */
|
|
11
|
+
export const LINK_CODE = /^\s*RCD-?[0-9A-Z]{4}-?[0-9A-Z]{4}\.?\s*$/i;
|
|
12
|
+
|
|
13
|
+
export function isLinkCode(text: string): boolean {
|
|
14
|
+
return LINK_CODE.test(text);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** What a host adds for its model when the user's message is a link code (the model would otherwise try to answer it). */
|
|
18
|
+
export const LINK_CODE_INSTRUCTION = 'The user\'s last message is a one-time code that links their account on this channel to their '
|
|
19
|
+
+ 'memory (Recordare); it is handled automatically. Reply only, briefly and in the user\'s language, that the code was '
|
|
20
|
+
+ 'received and that they now confirm the link where they created the code. Do not repeat the code.';
|
package/src/mcp.ts
CHANGED
|
@@ -99,7 +99,7 @@ export class RecordareMcp {
|
|
|
99
99
|
},
|
|
100
100
|
requestInit: { headers: { ...this.http.headers(user), [CONVERSATION_HEADER]: conversation } },
|
|
101
101
|
});
|
|
102
|
-
const client = new Client(this.options.clientInfo ?? { name: 'recordare-client', version: '0.
|
|
102
|
+
const client = new Client(this.options.clientInfo ?? { name: 'recordare-client', version: '0.4.0' });
|
|
103
103
|
await client.connect(transport);
|
|
104
104
|
return client;
|
|
105
105
|
}
|
package/src/people.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* Who each of the platform's users is in Recordare, cached: the memory (memoryId), its mode and the Atlas
|
|
6
6
|
* address. Recordare has no consent flag (D50): the platform's own switch (`enabled`) decides whether it is contacted.
|
|
7
7
|
* The synchronous readers never wait on Recordare: they answer from the cache (even if stale) and refresh in the
|
|
8
|
-
* background. Each lookup also keeps the person's name in sync with the platform's profile.
|
|
8
|
+
* background. Each lookup also keeps the person's name and gender in sync with the platform's profile.
|
|
9
9
|
*/
|
|
10
|
-
import { type MemoryMode } from './contract.js';
|
|
10
|
+
import { type MeSettings, type MemoryGender, type MemoryMode } from './contract.js';
|
|
11
11
|
import { type RecordareClient } from './client.js';
|
|
12
12
|
|
|
13
13
|
export interface Person {
|
|
@@ -23,6 +23,8 @@ export interface PlatformUser {
|
|
|
23
23
|
enabled: boolean;
|
|
24
24
|
/** The current profile name: the person's name in Recordare follows it. */
|
|
25
25
|
name?: string | null;
|
|
26
|
+
/** The profile's gender: the first person's gender in gendered languages follows it (not set = left as it is). */
|
|
27
|
+
gender?: MemoryGender | null;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface PersonDirectoryOptions {
|
|
@@ -32,7 +34,7 @@ export interface PersonDirectoryOptions {
|
|
|
32
34
|
user: (user: string) => Promise<PlatformUser | null>;
|
|
33
35
|
/** A person resolved (e.g. to store the memoryId with the platform's user). */
|
|
34
36
|
onResolved?: (user: string, person: Person) => void | Promise<void>;
|
|
35
|
-
/** Called when a lookup or a
|
|
37
|
+
/** Called when a lookup or a settings update fails (the cache keeps the last known person). */
|
|
36
38
|
onError?: (user: string, err: unknown) => void;
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -85,8 +87,12 @@ export class PersonDirectory {
|
|
|
85
87
|
if (!platform?.enabled) return remember({ memoryId: last?.memoryId ?? null, mode: last?.mode ?? null, atlasUrl: last?.atlasUrl ?? null });
|
|
86
88
|
const me = await this.client.me(user);
|
|
87
89
|
const name = platform.name?.trim().slice(0, 100);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
const changed: MeSettings = {
|
|
91
|
+
...(name && me.displayName !== name ? { displayName: name } : {}),
|
|
92
|
+
...(platform.gender && me.gender !== platform.gender ? { gender: platform.gender } : {}),
|
|
93
|
+
};
|
|
94
|
+
if (Object.keys(changed).length) {
|
|
95
|
+
await this.client.updateMe(user, changed).catch((err) => this.options.onError?.(user, err));
|
|
90
96
|
}
|
|
91
97
|
const person: Person = { memoryId: me.memoryId, mode: me.mode, atlasUrl: me.atlasUrl ?? null };
|
|
92
98
|
await this.options.onResolved?.(user, person);
|
package/src/tools.ts
CHANGED
|
@@ -101,7 +101,7 @@ export const TOOLS = [
|
|
|
101
101
|
{
|
|
102
102
|
"name": "log_episode",
|
|
103
103
|
"title": "Note something that happened or is planned",
|
|
104
|
-
"description": "Explicit capture (\"note that today I serviced the car\"). Resolve the date yourself (ISO).",
|
|
104
|
+
"description": "Explicit capture (\"note that today I serviced the car\"). Resolve the date yourself (ISO). Conversations are remembered automatically: use it only when explicitly asked, and never ask whether to remember or save something.",
|
|
105
105
|
"inputSchema": {
|
|
106
106
|
"type": "object",
|
|
107
107
|
"properties": {
|
|
@@ -151,7 +151,7 @@ export const TOOLS = [
|
|
|
151
151
|
{
|
|
152
152
|
"name": "remember",
|
|
153
153
|
"title": "Remember something",
|
|
154
|
-
"description": "Explicit \"remember that…\" about preferences, habits, values, knowledge.",
|
|
154
|
+
"description": "Explicit \"remember that…\" about preferences, habits, values, knowledge. Conversations are remembered automatically: use it only when explicitly asked, and never ask whether to remember or save something.",
|
|
155
155
|
"inputSchema": {
|
|
156
156
|
"type": "object",
|
|
157
157
|
"properties": {
|
|
@@ -197,7 +197,7 @@ export const TOOLS = [
|
|
|
197
197
|
{
|
|
198
198
|
"name": "search_episodes",
|
|
199
199
|
"title": "Search what happened",
|
|
200
|
-
"description": "Search your memory of what happened: what you lived, did, planned or learned, and what happened to the people you know (events, plans, changes), with dates and status. Each item has a subject: you (written in the first person), a person by name, someone, or undecided. Use `from`/`to` (ISO dates, inclusive; see resolve_period) for questions about a period. mode: \"search\" = most relevant, \"list\" = chronological in the period (overviews, counting), \"latest\" = most recent first (\"when did I last…\").",
|
|
200
|
+
"description": "Search your memory of what happened: what you lived, did, planned or learned, and what happened to the people you know (events, plans, changes), with dates and status. Each item has a subject: you (written in the first person), a person by name, someone, or undecided. Use `from`/`to` (ISO dates, inclusive; see resolve_period) for questions about a period; a vague span (\"lately\", \"these weeks\") is not a period: leave them out. mode: \"search\" = most relevant, \"list\" = chronological in the period (overviews, counting), \"latest\" = most recent first (\"when did I last…\"). An item or chat excerpt marked `heard` was overheard in the room by a listening device, not said to you: tell it as heard at home, and its speaker may have been misrecognised — what it says of you may be someone else's. A chat excerpt by `assistant` is your own earlier reply: what you told them then (advice, a recipe, a name), not something they lived.",
|
|
201
201
|
"inputSchema": {
|
|
202
202
|
"type": "object",
|
|
203
203
|
"properties": {
|
|
@@ -261,7 +261,7 @@ export const TOOLS = [
|
|
|
261
261
|
{
|
|
262
262
|
"name": "search_memory",
|
|
263
263
|
"title": "Search what you know about yourself and others",
|
|
264
|
-
"description": "Search your memory of preferences, habits, values, relationships, knowledge and current state (car, home, job…) — yours and of the people you know; each item has its subject. Use `as_of` (ISO date) for \"what was it on that date\" questions; each fact comes with its history.",
|
|
264
|
+
"description": "Search your memory of preferences, habits, values, relationships, knowledge and current state (car, home, job…) — yours and of the people you know; each item has its subject. Use `as_of` (ISO date) for \"what was it on that date\" questions; each fact comes with its history. A note with `pattern` is a habit you noticed yourself in repeated episodes, not something said: tell it as such.",
|
|
265
265
|
"inputSchema": {
|
|
266
266
|
"type": "object",
|
|
267
267
|
"properties": {
|