@coffer-org/server 7.2.0 → 7.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.
Files changed (74) hide show
  1. package/dist/auth-api.d.ts +4 -0
  2. package/dist/auth-api.js +52 -0
  3. package/dist/auth-store.d.ts +2 -0
  4. package/dist/auth-store.js +1 -0
  5. package/dist/compute-unit.js +0 -3
  6. package/dist/conversation-store.d.ts +61 -0
  7. package/dist/conversation-store.js +223 -0
  8. package/dist/entity-schema.d.ts +5 -2
  9. package/dist/entity-schema.js +35 -18
  10. package/dist/identity-link.d.ts +15 -0
  11. package/dist/identity-link.js +76 -0
  12. package/dist/identity-providers.d.ts +17 -0
  13. package/dist/identity-providers.js +14 -0
  14. package/dist/index.js +8 -1
  15. package/dist/mcp-contract/schema.d.ts +0 -1
  16. package/dist/mcp-contract/schema.js +0 -2
  17. package/dist/mcp-http.js +7 -3
  18. package/dist/mcp-tools.d.ts +4 -3
  19. package/dist/mcp-tools.js +84 -84
  20. package/dist/media/image.d.ts +23 -0
  21. package/dist/media/image.js +103 -0
  22. package/dist/media/index.d.ts +1 -0
  23. package/dist/media/index.js +1 -0
  24. package/dist/migrations.js +1 -1
  25. package/dist/mutate.js +0 -15
  26. package/dist/orchestrator/agent-capabilities.d.ts +2 -2
  27. package/dist/orchestrator/agent-capabilities.js +3 -3
  28. package/dist/orchestrator/allow.d.ts +1 -16
  29. package/dist/orchestrator/allow.js +3 -53
  30. package/dist/orchestrator/config.js +0 -1
  31. package/dist/orchestrator/context-facts.d.ts +27 -0
  32. package/dist/orchestrator/context-facts.js +89 -0
  33. package/dist/orchestrator/conversation-access.d.ts +9 -0
  34. package/dist/orchestrator/conversation-access.js +12 -0
  35. package/dist/orchestrator/draft-message.d.ts +18 -0
  36. package/dist/orchestrator/draft-message.js +85 -0
  37. package/dist/orchestrator/environment.d.ts +1 -0
  38. package/dist/orchestrator/environment.js +10 -0
  39. package/dist/orchestrator/file-inspection.d.ts +2 -2
  40. package/dist/orchestrator/file-inspection.js +41 -19
  41. package/dist/orchestrator/index.d.ts +14 -9
  42. package/dist/orchestrator/index.js +6 -7
  43. package/dist/orchestrator/live-message.d.ts +7 -4
  44. package/dist/orchestrator/live-message.js +52 -32
  45. package/dist/orchestrator/pipeline.d.ts +22 -4
  46. package/dist/orchestrator/pipeline.js +316 -115
  47. package/dist/orchestrator/registry.d.ts +2 -2
  48. package/dist/orchestrator/registry.js +1 -1
  49. package/dist/orchestrator/system-areas.d.ts +12 -0
  50. package/dist/orchestrator/system-areas.js +63 -0
  51. package/dist/orchestrator/system-capabilities.js +1 -1
  52. package/dist/orchestrator/turn-context.d.ts +18 -0
  53. package/dist/orchestrator/turn-context.js +39 -0
  54. package/dist/orchestrator/types.d.ts +100 -49
  55. package/dist/plugin-hooks.d.ts +38 -1
  56. package/dist/plugin-hooks.js +4 -0
  57. package/dist/plugin-http-mounts.d.ts +18 -0
  58. package/dist/plugin-http-mounts.js +94 -0
  59. package/dist/plugin-runtime.d.ts +4 -0
  60. package/dist/plugin-runtime.js +9 -5
  61. package/dist/plugins-api.d.ts +8 -2
  62. package/dist/plugins-api.js +4 -2
  63. package/dist/records-api.js +15 -3
  64. package/dist/settings-write.d.ts +1 -2
  65. package/dist/settings-write.js +1 -2
  66. package/dist/system-settings.js +0 -1
  67. package/dist/temporal.js +8 -3
  68. package/dist/thread-state.d.ts +14 -0
  69. package/dist/thread-state.js +71 -11
  70. package/dist/thread-store.d.ts +5 -3
  71. package/dist/thread-store.js +12 -9
  72. package/dist/turn-gate.d.ts +8 -0
  73. package/dist/turn-gate.js +39 -0
  74. package/package.json +7 -2
@@ -7,6 +7,10 @@ declare module 'fastify' {
7
7
  }
8
8
  export declare const PUBLIC_API_PATHS: string[];
9
9
  export declare function startPasswordSession(reply: FastifyReply, login: string, password: string): Promise<AuthUser | null>;
10
+ export declare function parseBasicCredential(header: string | undefined): {
11
+ login: string;
12
+ secret: string;
13
+ } | null;
10
14
  export declare function resolveRequestUser(req: FastifyRequest): Promise<AuthUser | null>;
11
15
  export declare function requireAdmin(req: FastifyRequest, reply: FastifyReply): boolean;
12
16
  export declare function registerAuthApi(app: FastifyInstance): Promise<void>;
package/dist/auth-api.js CHANGED
@@ -2,6 +2,7 @@ import { field } from '@coffer-org/sdk/fields';
2
2
  import { materializeTree } from '@coffer-org/sdk/materialize/pipeline';
3
3
  import { countUsers, createUser, findUserByLogin, findUserById, getPasswordHash, listUsers, updateUser, deleteUser, countAdmins, createSession, resolveSession, deleteSession, createApiToken, resolveApiToken, listApiTokens, revokeApiToken, } from "./auth-store.js";
4
4
  import { resolveAccessToken } from "./oauth-store.js";
5
+ import { listIdentityProviders, getIdentityProvider } from "./identity-providers.js";
5
6
  import { mcpResource } from "./public-url.js";
6
7
  import { verifyPassword } from "./auth-crypto.js";
7
8
  import { maskSecrets, preserveSecrets } from "./field-masking.js";
@@ -37,12 +38,32 @@ export async function startPasswordSession(reply, login, password) {
37
38
  setCookie(reply, raw, SESSION_TTL_MS / 1000);
38
39
  return row;
39
40
  }
41
+ export function parseBasicCredential(header) {
42
+ if (!header?.startsWith('Basic '))
43
+ return null;
44
+ let decoded;
45
+ try {
46
+ decoded = Buffer.from(header.slice('Basic '.length).trim(), 'base64').toString('utf8');
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ const at = decoded.indexOf(':');
52
+ if (at <= 0)
53
+ return null;
54
+ return { login: decoded.slice(0, at), secret: decoded.slice(at + 1) };
55
+ }
40
56
  export async function resolveRequestUser(req) {
41
57
  const auth = req.headers.authorization;
42
58
  if (auth?.startsWith('Bearer ')) {
43
59
  const raw = auth.slice('Bearer '.length);
44
60
  return (await resolveApiToken(raw)) ?? (await resolveAccessToken(raw, await mcpResource(req)));
45
61
  }
62
+ const basic = parseBasicCredential(auth);
63
+ if (basic) {
64
+ const user = await resolveApiToken(basic.secret);
65
+ return user && user.login === basic.login ? user : null;
66
+ }
46
67
  const sid = readCookie(req, COOKIE_NAME);
47
68
  if (!sid)
48
69
  return null;
@@ -208,4 +229,35 @@ export async function registerAuthApi(app) {
208
229
  return reply.code(404).send({ error: 'not_found' });
209
230
  return { ok: true };
210
231
  });
232
+ app.get('/api/auth/links', async (req, reply) => {
233
+ if (!req.user)
234
+ return reply.code(401).send({ error: 'unauthorized' });
235
+ const providers = listIdentityProviders();
236
+ const links = (await Promise.all(providers.map(async (p) => (await p.listLinks(req.user.id)).map((l) => ({ provider: p.id, ...l }))))).flat();
237
+ return { providers: providers.map(({ id, label, icon }) => ({ id, label, icon })), links };
238
+ });
239
+ app.post('/api/auth/links/:provider', async (req, reply) => {
240
+ if (!req.user)
241
+ return reply.code(401).send({ error: 'unauthorized' });
242
+ const { provider } = req.params;
243
+ const p = getIdentityProvider(provider);
244
+ if (!p)
245
+ return reply.code(404).send({ error: 'not_found' });
246
+ const started = await p.beginLink(req.user.id);
247
+ if (!started)
248
+ return reply.code(503).send({ error: 'unavailable' });
249
+ return started;
250
+ });
251
+ app.delete('/api/auth/links/:provider/:externalId', async (req, reply) => {
252
+ if (!req.user)
253
+ return reply.code(401).send({ error: 'unauthorized' });
254
+ const { provider, externalId } = req.params;
255
+ const p = getIdentityProvider(provider);
256
+ if (!p)
257
+ return reply.code(404).send({ error: 'not_found' });
258
+ const ok = await p.unlink(req.user.id, externalId);
259
+ if (!ok)
260
+ return reply.code(404).send({ error: 'not_found' });
261
+ return { ok: true };
262
+ });
211
263
  }
@@ -1,3 +1,5 @@
1
+ import { generateToken, hashToken } from './auth-crypto.ts';
2
+ export { generateToken, hashToken };
1
3
  export interface AuthUser {
2
4
  id: number;
3
5
  login: string;
@@ -1,5 +1,6 @@
1
1
  import { getEm } from "./db.js";
2
2
  import { hashPassword, generateToken, hashToken } from "./auth-crypto.js";
3
+ export { generateToken, hashToken };
3
4
  function toAuthUser(row) {
4
5
  return {
5
6
  id: row.id,
@@ -1,5 +1,4 @@
1
1
  import { hasChildren, isNamedField, isCollectionGroup, isEmbeddedGroup, isStorageGroup, } from '@coffer-org/sdk/fields';
2
- import { mandatedClientParts } from '@coffer-org/sdk/parts';
3
2
  import { PartContractError } from "./part-errors.js";
4
3
  import { createRefLoader } from "./part-ref.js";
5
4
  import { wholeClockColumn, partClockColumn } from "./cache-clock.js";
@@ -267,8 +266,6 @@ export function materializable(fm) {
267
266
  const parts = fm.parts ?? [];
268
267
  if (!parts.some((p) => p.mode === 'computedStored'))
269
268
  return false;
270
- if (mandatedClientParts(fm.required, parts).length > 0)
271
- return false;
272
269
  return true;
273
270
  }
274
271
  export function storedFilled(fm, value) {
@@ -0,0 +1,61 @@
1
+ export declare const HIDDEN_ROLES: readonly ["reasoning", "suggestions", "context"];
2
+ export declare const SIDECAR_ROLES: readonly ["reasoning", "suggestions"];
3
+ export declare const TITLE_MAX = 60;
4
+ export declare const DEFAULT_MAX_DEPTH = 40;
5
+ export declare const conversationEmitter: EventTarget;
6
+ export interface PutMessageInput {
7
+ conversationId: string;
8
+ msgId: string;
9
+ role: string;
10
+ sender?: string | null;
11
+ text: string;
12
+ attachments?: StoredAttachment[];
13
+ ts: number;
14
+ replyToId?: string | null;
15
+ }
16
+ export declare function onMessage(cb: (m: PutMessageInput) => void | Promise<void>): () => void;
17
+ export interface StoredMsg {
18
+ msgId: string;
19
+ role: string;
20
+ sender: string | null;
21
+ text: string;
22
+ attachments?: StoredAttachment[];
23
+ ts: number;
24
+ replyToId: string | null;
25
+ }
26
+ export interface StoredAttachment {
27
+ name: string;
28
+ mime?: string;
29
+ size?: number;
30
+ label?: string;
31
+ }
32
+ export interface Conversation {
33
+ id: string;
34
+ owner: string | null;
35
+ title: string | null;
36
+ agentId: string | null;
37
+ presetId: string | null;
38
+ visibility: 'private' | null;
39
+ updatedAt: string;
40
+ }
41
+ export interface ConversationSummary extends Conversation {
42
+ lastTs: number;
43
+ count: number;
44
+ firstUserText: string;
45
+ }
46
+ export declare function newConversationId(): string;
47
+ export declare function newMessageId(): string;
48
+ export declare function getConversation(id: string): Promise<Conversation>;
49
+ export declare function setConversation(id: string, patch: Partial<Omit<Conversation, 'id' | 'updatedAt'>>): Promise<void>;
50
+ export declare function readAndTouchConversation(id: string): Promise<Conversation>;
51
+ export declare function listConversations(viewerId: string): Promise<ConversationSummary[]>;
52
+ export declare function putMessage(m: PutMessageInput): Promise<void>;
53
+ export declare const insertMessage: typeof putMessage;
54
+ export declare function getMessage(conversationId: string, msgId: string): Promise<StoredMsg | null>;
55
+ export declare function readConversation(conversationId: string, opts?: {
56
+ limit?: number;
57
+ }): Promise<StoredMsg[]>;
58
+ export declare function readAll(conversationId: string): Promise<StoredMsg[]>;
59
+ export declare function countUserMessages(conversationId: string): Promise<number>;
60
+ export declare function lastMessageId(conversationId: string): Promise<string | null>;
61
+ export declare function pruneConversations(cutoffTs: number, cutoffIso: string): Promise<void>;
@@ -0,0 +1,223 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { getEm } from "./db.js";
3
+ import { mayRead } from "./orchestrator/conversation-access.js";
4
+ export const HIDDEN_ROLES = ['reasoning', 'suggestions', 'context'];
5
+ export const SIDECAR_ROLES = ['reasoning', 'suggestions'];
6
+ export const TITLE_MAX = 60;
7
+ export const DEFAULT_MAX_DEPTH = 40;
8
+ export const conversationEmitter = new EventTarget();
9
+ export function onMessage(cb) {
10
+ const handler = (e) => {
11
+ if (e instanceof CustomEvent) {
12
+ void cb(e.detail);
13
+ }
14
+ };
15
+ conversationEmitter.addEventListener('message', handler);
16
+ return () => {
17
+ conversationEmitter.removeEventListener('message', handler);
18
+ };
19
+ }
20
+ function capTitle(title) {
21
+ const t = title.trim();
22
+ return t.length <= TITLE_MAX ? t : `${t.slice(0, TITLE_MAX - 1)}…`;
23
+ }
24
+ function emptyConversation(id) {
25
+ return {
26
+ id,
27
+ owner: null,
28
+ title: null,
29
+ agentId: null,
30
+ presetId: null,
31
+ visibility: null,
32
+ updatedAt: '',
33
+ };
34
+ }
35
+ function toConversation(row) {
36
+ return {
37
+ id: row.id,
38
+ owner: row.owner,
39
+ title: row.title,
40
+ agentId: row.agent_id,
41
+ presetId: row.preset_id,
42
+ visibility: row.visibility === 'private' ? 'private' : null,
43
+ updatedAt: row.updated_at,
44
+ };
45
+ }
46
+ function toStored(r) {
47
+ let attachments;
48
+ if (r.attachments) {
49
+ try {
50
+ const parsed = JSON.parse(r.attachments);
51
+ if (Array.isArray(parsed)) {
52
+ const valid = parsed.filter((v) => typeof v === 'object' && v !== null && typeof v.name === 'string');
53
+ if (valid.length)
54
+ attachments = valid;
55
+ }
56
+ }
57
+ catch {
58
+ }
59
+ }
60
+ return {
61
+ msgId: r.msg_id,
62
+ role: r.role,
63
+ sender: r.sender,
64
+ text: r.text,
65
+ ...(attachments ? { attachments } : {}),
66
+ ts: r.ts,
67
+ replyToId: r.reply_to_id,
68
+ };
69
+ }
70
+ export function newConversationId() {
71
+ return randomUUID();
72
+ }
73
+ export function newMessageId() {
74
+ return randomUUID();
75
+ }
76
+ export async function getConversation(id) {
77
+ const em = getEm().fork();
78
+ const row = (await em.findOne('_Conversation', { id }));
79
+ if (!row)
80
+ return emptyConversation(id);
81
+ return toConversation(row);
82
+ }
83
+ export async function setConversation(id, patch) {
84
+ const em = getEm().fork();
85
+ const data = { updated_at: new Date().toISOString() };
86
+ if ('agentId' in patch)
87
+ data['agent_id'] = patch.agentId ?? null;
88
+ if ('presetId' in patch)
89
+ data['preset_id'] = patch.presetId ?? null;
90
+ if ('title' in patch)
91
+ data['title'] = patch.title ? capTitle(patch.title) : null;
92
+ if ('owner' in patch)
93
+ data['owner'] = patch.owner ?? null;
94
+ if ('visibility' in patch)
95
+ data['visibility'] = patch.visibility ?? null;
96
+ const existing = (await em.findOne('_Conversation', { id }));
97
+ if (existing) {
98
+ em.assign(existing, data);
99
+ }
100
+ else {
101
+ em.persist(em.create('_Conversation', {
102
+ id,
103
+ agent_id: data['agent_id'] ?? null,
104
+ preset_id: data['preset_id'] ?? null,
105
+ title: data['title'] ?? null,
106
+ owner: data['owner'] ?? null,
107
+ visibility: data['visibility'] ?? null,
108
+ updated_at: data['updated_at'],
109
+ }));
110
+ }
111
+ await em.flush();
112
+ conversationEmitter.dispatchEvent(new CustomEvent('conversation', { detail: { id, patch } }));
113
+ }
114
+ export async function readAndTouchConversation(id) {
115
+ const em = getEm().fork();
116
+ const row = (await em.findOne('_Conversation', { id }));
117
+ if (!row)
118
+ return emptyConversation(id);
119
+ const now = new Date().toISOString();
120
+ em.assign(row, { updated_at: now });
121
+ await em.flush();
122
+ return toConversation(row);
123
+ }
124
+ export async function listConversations(viewerId) {
125
+ const em = getEm().fork();
126
+ const convRows = (await em.find('_Conversation', {}));
127
+ const msgRows = (await em.find('_ConversationMessage', {}, { orderBy: { ts: 'asc', msg_id: 'asc' } }));
128
+ const msgSummaries = new Map();
129
+ for (const r of msgRows) {
130
+ if (HIDDEN_ROLES.includes(r.role))
131
+ continue;
132
+ const cur = msgSummaries.get(r.conversation_id);
133
+ if (!cur) {
134
+ msgSummaries.set(r.conversation_id, {
135
+ lastTs: r.ts,
136
+ count: 1,
137
+ firstUserText: r.role === 'user' ? r.text : '',
138
+ });
139
+ continue;
140
+ }
141
+ cur.count += 1;
142
+ if (r.ts > cur.lastTs)
143
+ cur.lastTs = r.ts;
144
+ if (!cur.firstUserText && r.role === 'user')
145
+ cur.firstUserText = r.text;
146
+ }
147
+ const convMap = new Map();
148
+ for (const r of convRows) {
149
+ convMap.set(r.id, toConversation(r));
150
+ }
151
+ const allIds = new Set([...convMap.keys(), ...msgSummaries.keys()]);
152
+ const out = [];
153
+ for (const id of allIds) {
154
+ const conv = convMap.get(id) ?? emptyConversation(id);
155
+ if (!mayRead(conv, viewerId))
156
+ continue;
157
+ const summary = msgSummaries.get(id);
158
+ out.push({
159
+ ...conv,
160
+ lastTs: summary?.lastTs ?? 0,
161
+ count: summary?.count ?? 0,
162
+ firstUserText: summary?.firstUserText ?? '',
163
+ });
164
+ }
165
+ return out.sort((a, b) => {
166
+ if (b.lastTs !== a.lastTs)
167
+ return b.lastTs - a.lastTs;
168
+ return (b.updatedAt || '').localeCompare(a.updatedAt || '') || b.id.localeCompare(a.id);
169
+ });
170
+ }
171
+ export async function putMessage(m) {
172
+ const em = getEm().fork();
173
+ await em.upsert('_ConversationMessage', {
174
+ conversation_id: m.conversationId,
175
+ msg_id: m.msgId,
176
+ role: m.role,
177
+ sender: m.sender ?? null,
178
+ attachments: m.attachments?.length ? JSON.stringify(m.attachments) : null,
179
+ text: m.text,
180
+ ts: m.ts,
181
+ reply_to_id: m.replyToId ?? null,
182
+ });
183
+ await em.flush();
184
+ conversationEmitter.dispatchEvent(new CustomEvent('message', { detail: m }));
185
+ }
186
+ export const insertMessage = putMessage;
187
+ export async function getMessage(conversationId, msgId) {
188
+ const em = getEm().fork();
189
+ const row = (await em.findOne('_ConversationMessage', { conversation_id: conversationId, msg_id: msgId }));
190
+ return row ? toStored(row) : null;
191
+ }
192
+ export async function readConversation(conversationId, opts) {
193
+ const limit = opts?.limit ?? DEFAULT_MAX_DEPTH;
194
+ const em = getEm().fork();
195
+ const visible = (await em.find('_ConversationMessage', { conversation_id: conversationId, role: { $nin: HIDDEN_ROLES } }, { orderBy: { ts: 'desc', msg_id: 'desc' }, limit }));
196
+ const oldest = visible.at(-1)?.ts;
197
+ const sidecars = oldest === undefined
198
+ ? []
199
+ : (await em.find('_ConversationMessage', { conversation_id: conversationId, role: { $in: SIDECAR_ROLES }, ts: { $gte: oldest - 1 } }, { orderBy: { ts: 'desc', msg_id: 'desc' } }));
200
+ return [...visible, ...sidecars]
201
+ .sort((a, b) => b.ts - a.ts || (a.msg_id < b.msg_id ? 1 : a.msg_id > b.msg_id ? -1 : 0))
202
+ .reverse()
203
+ .map(toStored);
204
+ }
205
+ export async function readAll(conversationId) {
206
+ const em = getEm().fork();
207
+ const rows = (await em.find('_ConversationMessage', { conversation_id: conversationId }, { orderBy: { ts: 'asc', msg_id: 'asc' } }));
208
+ return rows.map(toStored);
209
+ }
210
+ export async function countUserMessages(conversationId) {
211
+ const em = getEm().fork();
212
+ return em.count('_ConversationMessage', { conversation_id: conversationId, role: 'user' });
213
+ }
214
+ export async function lastMessageId(conversationId) {
215
+ const em = getEm().fork();
216
+ const row = (await em.findOne('_ConversationMessage', { conversation_id: conversationId, role: { $nin: HIDDEN_ROLES } }, { orderBy: { ts: 'desc', msg_id: 'desc' } }));
217
+ return row ? row.msg_id : null;
218
+ }
219
+ export async function pruneConversations(cutoffTs, cutoffIso) {
220
+ const em = getEm().fork();
221
+ await em.nativeDelete('_ConversationMessage', { ts: { $lt: cutoffTs } });
222
+ await em.nativeDelete('_Conversation', { updated_at: { $lt: cutoffIso }, owner: null, visibility: null });
223
+ }
@@ -3,10 +3,13 @@ import type { ShelfDef } from '@coffer-org/sdk/shelf';
3
3
  import type { ExtendDef } from '@coffer-org/sdk/extend';
4
4
  import type { SettingsDef } from '@coffer-org/sdk/settings';
5
5
  import type { PluginManifest } from '@coffer-org/sdk/plugin';
6
+ import type { PrivateTableDef } from './plugin-hooks.ts';
6
7
  export declare function shelfTableName(library: string, shelf: string): string;
7
8
  export declare function buildEntitySchema(m: ShelfDef): EntitySchema;
8
9
  export declare function buildExtendEntitySchema(e: ExtendDef): EntitySchema;
9
10
  export declare function buildSettingsEntitySchema(pluginId: string, settings: SettingsDef): EntitySchema;
11
+ export declare function privateTableName(pluginId: string, name: string): string;
12
+ export declare function buildPrivateTableEntity(pluginId: string, t: PrivateTableDef): EntitySchema;
10
13
  export declare function childTableName(library: string, shelf: string, key: string): string;
11
14
  export declare function buildCollectionEntities(m: ShelfDef): EntitySchema[];
12
15
  export declare function buildExtendCollectionEntities(e: ExtendDef): EntitySchema[];
@@ -18,8 +21,8 @@ export declare const SeedRowSchema: EntitySchema<any, never, import("@mikro-orm/
18
21
  export declare const EmbeddingSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
19
22
  export declare const PluginStateSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
20
23
  export declare const RecordActivitySchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
21
- export declare const ThreadMessageSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
22
- export declare const ThreadStateSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
24
+ export declare const ConversationMessageSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
25
+ export declare const ConversationSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
23
26
  export declare function buildPluginEntities(plugins: PluginManifest[]): EntitySchema[];
24
27
  export declare const MsgLogSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
25
28
  export declare const UserSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
@@ -23,14 +23,14 @@ function columnProp(field, nullable) {
23
23
  p['default'] = field.default;
24
24
  return p;
25
25
  }
26
- function addFieldProps(properties, fields, plainNullable) {
26
+ function addFieldProps(properties, fields) {
27
27
  for (const [key, field] of fieldEntries(fields)) {
28
28
  if (field.columns) {
29
29
  for (const [col, type] of storageColumns(key, field))
30
30
  properties[col] = { type: mikroType(type), nullable: true };
31
31
  }
32
32
  else {
33
- properties[key] = columnProp(field, plainNullable(field));
33
+ properties[key] = columnProp(field, true);
34
34
  }
35
35
  }
36
36
  for (const col of cacheClockColumns(fields))
@@ -46,7 +46,7 @@ export function buildEntitySchema(m) {
46
46
  updated_at: { type: 'text' },
47
47
  deleted_at: { type: 'text', nullable: true },
48
48
  };
49
- addFieldProps(properties, m.fields, (f) => !f.required);
49
+ addFieldProps(properties, m.fields);
50
50
  const name = shelfTableName(m.library, m.shelf);
51
51
  return new EntitySchema({ name, tableName: name, properties });
52
52
  }
@@ -54,7 +54,7 @@ export function buildExtendEntitySchema(e) {
54
54
  const properties = {
55
55
  base_id: { type: 'integer', primary: true },
56
56
  };
57
- addFieldProps(properties, e.fields, () => true);
57
+ addFieldProps(properties, e.fields);
58
58
  const name = `extend__${e.id}`;
59
59
  return new EntitySchema({ name, tableName: name, properties });
60
60
  }
@@ -62,10 +62,26 @@ export function buildSettingsEntitySchema(pluginId, settings) {
62
62
  const properties = {
63
63
  plugin_id: { type: 'text', primary: true },
64
64
  };
65
- addFieldProps(properties, settings.fields, (f) => !f.required);
65
+ addFieldProps(properties, settings.fields);
66
66
  const name = `_settings__${pluginId}`;
67
67
  return new EntitySchema({ name, tableName: name, properties });
68
68
  }
69
+ export function privateTableName(pluginId, name) {
70
+ return `_${pluginId}__${name}`;
71
+ }
72
+ export function buildPrivateTableEntity(pluginId, t) {
73
+ const properties = {
74
+ id: { type: 'integer', primary: true, autoincrement: true },
75
+ };
76
+ addFieldProps(properties, t.fields);
77
+ const name = privateTableName(pluginId, t.name);
78
+ return new EntitySchema({
79
+ name,
80
+ tableName: name,
81
+ properties,
82
+ uniques: (t.unique ?? []).map((properties) => ({ properties })),
83
+ });
84
+ }
69
85
  export function childTableName(library, shelf, key) {
70
86
  return `${library}__${shelf}__${key}`;
71
87
  }
@@ -75,7 +91,7 @@ function buildChildSchemaAt(prefix, c) {
75
91
  parent_id: { type: 'integer', index: true },
76
92
  position: { type: 'integer' },
77
93
  };
78
- addFieldProps(properties, c.group.fields, (f) => !f.required);
94
+ addFieldProps(properties, c.group.fields);
79
95
  const name = `${prefix}__${c.key}`;
80
96
  return new EntitySchema({ name, tableName: name, properties });
81
97
  }
@@ -186,12 +202,11 @@ export const RecordActivitySchema = new EntitySchema({
186
202
  last_viewed_at: { type: 'text', nullable: true },
187
203
  },
188
204
  });
189
- export const ThreadMessageSchema = new EntitySchema({
190
- name: '_ThreadMessage',
191
- tableName: '_thread_message',
205
+ export const ConversationMessageSchema = new EntitySchema({
206
+ name: '_ConversationMessage',
207
+ tableName: '_conversation_message',
192
208
  properties: {
193
- connector: { type: 'text', primary: true },
194
- chat_id: { type: 'text', primary: true },
209
+ conversation_id: { type: 'text', primary: true },
195
210
  msg_id: { type: 'text', primary: true },
196
211
  role: { type: 'text' },
197
212
  sender: { type: 'text', nullable: true },
@@ -201,14 +216,16 @@ export const ThreadMessageSchema = new EntitySchema({
201
216
  reply_to_id: { type: 'text', nullable: true },
202
217
  },
203
218
  });
204
- export const ThreadStateSchema = new EntitySchema({
205
- name: '_ThreadState',
206
- tableName: '_thread_state',
219
+ export const ConversationSchema = new EntitySchema({
220
+ name: '_Conversation',
221
+ tableName: '_conversation',
207
222
  properties: {
208
- connector: { type: 'text', primary: true },
209
- chat_id: { type: 'text', primary: true },
223
+ id: { type: 'text', primary: true },
210
224
  agent_id: { type: 'text', nullable: true },
211
225
  preset_id: { type: 'text', nullable: true },
226
+ title: { type: 'text', nullable: true },
227
+ owner: { type: 'text', nullable: true },
228
+ visibility: { type: 'text', nullable: true },
212
229
  updated_at: { type: 'text' },
213
230
  },
214
231
  });
@@ -329,7 +346,7 @@ export const systemEntities = [
329
346
  OAuthClientSchema,
330
347
  OAuthCodeSchema,
331
348
  OAuthTokenSchema,
332
- ThreadMessageSchema,
333
- ThreadStateSchema,
349
+ ConversationMessageSchema,
350
+ ConversationSchema,
334
351
  buildSettingsEntitySchema(SYSTEM_SETTINGS_ID, SYSTEM_SETTINGS),
335
352
  ];
@@ -0,0 +1,15 @@
1
+ export interface IdentityLink {
2
+ connector: string;
3
+ externalId: string;
4
+ userId: number;
5
+ linkedAt: string;
6
+ }
7
+ export declare function findLinkedUser(connector: string, externalId: string): Promise<number | null>;
8
+ export declare function listLinks(userId: number): Promise<IdentityLink[]>;
9
+ export declare function unlink(connector: string, externalId: string): Promise<boolean>;
10
+ export declare function mintLinkCode(userId: number, ttlMs: number): Promise<{
11
+ raw: string;
12
+ expiresAt: string;
13
+ }>;
14
+ export declare function redeemLinkCode(raw: string, connector: string, externalId: string): Promise<number | null>;
15
+ export declare function pruneLinkCodes(): Promise<void>;
@@ -0,0 +1,76 @@
1
+ import { getEm } from "./db.js";
2
+ import { generateToken, hashToken, findUserById } from "./auth-store.js";
3
+ export async function findLinkedUser(connector, externalId) {
4
+ const em = getEm().fork();
5
+ const row = (await em.findOne('_IdentityLink', {
6
+ connector,
7
+ external_id: externalId,
8
+ }));
9
+ if (!row)
10
+ return null;
11
+ const user = await findUserById(row.user_id);
12
+ return user && !user.disabled ? row.user_id : null;
13
+ }
14
+ export async function listLinks(userId) {
15
+ const em = getEm().fork();
16
+ const rows = (await em.find('_IdentityLink', { user_id: userId }));
17
+ return rows.map((r) => ({
18
+ connector: r.connector,
19
+ externalId: r.external_id,
20
+ userId: r.user_id,
21
+ linkedAt: r.linked_at,
22
+ }));
23
+ }
24
+ export async function unlink(connector, externalId) {
25
+ const em = getEm().fork();
26
+ const deleted = await em.nativeDelete('_IdentityLink', { connector, external_id: externalId });
27
+ return deleted > 0;
28
+ }
29
+ export async function mintLinkCode(userId, ttlMs) {
30
+ const em = getEm().fork();
31
+ const raw = generateToken();
32
+ const expiresAt = new Date(Date.now() + ttlMs).toISOString();
33
+ em.create('_LinkCode', {
34
+ code_hash: hashToken(raw),
35
+ user_id: userId,
36
+ expires_at: expiresAt,
37
+ created_at: new Date().toISOString(),
38
+ });
39
+ await em.flush();
40
+ return { raw, expiresAt };
41
+ }
42
+ async function setLink(em, connector, externalId, userId) {
43
+ const existing = (await em.findOne('_IdentityLink', {
44
+ connector,
45
+ external_id: externalId,
46
+ }));
47
+ const linkedAt = new Date().toISOString();
48
+ if (existing) {
49
+ em.assign(existing, { user_id: userId, linked_at: linkedAt });
50
+ }
51
+ else {
52
+ em.create('_IdentityLink', { connector, external_id: externalId, user_id: userId, linked_at: linkedAt });
53
+ }
54
+ await em.flush();
55
+ }
56
+ export async function redeemLinkCode(raw, connector, externalId) {
57
+ const em = getEm().fork();
58
+ const hash = hashToken(raw);
59
+ const row = (await em.findOne('_LinkCode', { code_hash: hash }));
60
+ if (!row)
61
+ return null;
62
+ const deleted = await em.nativeDelete('_LinkCode', { code_hash: hash });
63
+ if (deleted === 0)
64
+ return null;
65
+ if (row.expires_at < new Date().toISOString())
66
+ return null;
67
+ const owner = await findUserById(row.user_id);
68
+ if (!owner || owner.disabled)
69
+ return null;
70
+ await setLink(em, connector, externalId, row.user_id);
71
+ return row.user_id;
72
+ }
73
+ export async function pruneLinkCodes() {
74
+ const em = getEm().fork();
75
+ await em.nativeDelete('_LinkCode', { expires_at: { $lt: new Date().toISOString() } });
76
+ }
@@ -0,0 +1,17 @@
1
+ export interface IdentityProvider {
2
+ id: string;
3
+ label: string;
4
+ icon: string;
5
+ beginLink(userId: number): Promise<{
6
+ url?: string;
7
+ code?: string;
8
+ } | null>;
9
+ listLinks(userId: number): Promise<{
10
+ externalId: string;
11
+ linkedAt: string;
12
+ }[]>;
13
+ unlink(userId: number, externalId: string): Promise<boolean>;
14
+ }
15
+ export declare function registerIdentityProvider(p: IdentityProvider): () => void;
16
+ export declare function listIdentityProviders(): IdentityProvider[];
17
+ export declare function getIdentityProvider(id: string): IdentityProvider | undefined;
@@ -0,0 +1,14 @@
1
+ const providers = new Map();
2
+ export function registerIdentityProvider(p) {
3
+ providers.set(p.id, p);
4
+ return () => {
5
+ if (providers.get(p.id) === p)
6
+ providers.delete(p.id);
7
+ };
8
+ }
9
+ export function listIdentityProviders() {
10
+ return [...providers.values()].sort((a, b) => a.id.localeCompare(b.id));
11
+ }
12
+ export function getIdentityProvider(id) {
13
+ return providers.get(id);
14
+ }