@frankieai/server 0.2.5 → 0.2.6
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/dist/{chunk-SQ5NJKFE.js → chunk-EYR3BV7U.js} +505 -35
- package/dist/chunk-EYR3BV7U.js.map +1 -0
- package/dist/{chunk-HPYT6ZIU.js → chunk-WEHJH4FL.js} +610 -54
- package/dist/chunk-WEHJH4FL.js.map +1 -0
- package/dist/control-server.js +5 -5
- package/dist/control-server.js.map +1 -1
- package/dist/index.d.ts +180 -2
- package/dist/index.js +2 -2
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-HPYT6ZIU.js.map +0 -1
- package/dist/chunk-SQ5NJKFE.js.map +0 -1
package/dist/control-server.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createControlApp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EYR3BV7U.js";
|
|
5
5
|
import {
|
|
6
6
|
createFrankieApp,
|
|
7
7
|
resolveStores
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-WEHJH4FL.js";
|
|
9
9
|
|
|
10
10
|
// src/control-server.ts
|
|
11
11
|
import { serve } from "@hono/node-server";
|
|
@@ -17,10 +17,10 @@ async function main() {
|
|
|
17
17
|
console.error("[frankie] Set FRANKIE_ADMIN_TOKEN to run the control plane.");
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
|
-
const { tenants: store, sessions, describe } = await resolveStores();
|
|
20
|
+
const { tenants: store, sessions, knowledge, describe } = await resolveStores();
|
|
21
21
|
const app = new Hono();
|
|
22
|
-
app.route("/", createControlApp({ store, sessions }));
|
|
23
|
-
app.route("/", createFrankieApp({ tenants: store, sessions }));
|
|
22
|
+
app.route("/", createControlApp({ store, sessions, knowledge }));
|
|
23
|
+
app.route("/", createFrankieApp({ tenants: store, sessions, knowledge }));
|
|
24
24
|
serve({ fetch: app.fetch, port, hostname: host }, () => {
|
|
25
25
|
console.log(
|
|
26
26
|
JSON.stringify({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/control-server.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The control plane, as its own process: the admin API the dashboard and the\n * setup CLI talk to. It writes tenants into FRANKIE_TENANTS_DIR — the same\n * directory the data-plane server reads — so what you create here is served\n * there with no further wiring.\n *\n * FRANKIE_ADMIN_TOKEN=… FRANKIE_TENANTS_DIR=./.frankie/tenants \\\n * pnpm --filter @frankieai/server start:control\n */\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport { createFrankieApp } from \"./app.js\";\nimport { createControlApp } from \"./control/app.js\";\nimport { resolveStores } from \"./control/resolve-store.js\";\n\nasync function main(): Promise<void> {\n const port = Number(process.env[\"PORT\"] ?? process.env[\"CONTROL_PORT\"] ?? 8081);\n const host = process.env[\"HOST\"] ?? \"0.0.0.0\";\n\n if (!process.env[\"FRANKIE_ADMIN_TOKEN\"]) {\n console.error(\"[frankie] Set FRANKIE_ADMIN_TOKEN to run the control plane.\");\n process.exit(1);\n }\n\n const { tenants: store, sessions, describe } = await resolveStores();\n const app = new Hono();\n app.route(\"/\", createControlApp({ store, sessions }));\n app.route(\"/\", createFrankieApp({ tenants: store, sessions }));\n\n serve({ fetch: app.fetch, port, hostname: host }, () => {\n console.log(\n JSON.stringify({\n ts: new Date().toISOString(),\n level: \"info\",\n event: \"control_listening\",\n store: describe,\n url: `http://${host}:${port}`,\n }),\n );\n });\n}\n\nvoid main();\n"],"mappings":";;;;;;;;;;AAUA,SAAS,aAAa;AACtB,SAAS,YAAY;AAKrB,eAAe,OAAsB;AACnC,QAAM,OAAO,OAAO,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,cAAc,KAAK,IAAI;AAC9E,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAEpC,MAAI,CAAC,QAAQ,IAAI,qBAAqB,GAAG;AACvC,YAAQ,MAAM,6DAA6D;AAC3E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,SAAS,OAAO,UAAU,SAAS,IAAI,MAAM,cAAc;
|
|
1
|
+
{"version":3,"sources":["../src/control-server.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The control plane, as its own process: the admin API the dashboard and the\n * setup CLI talk to. It writes tenants into FRANKIE_TENANTS_DIR — the same\n * directory the data-plane server reads — so what you create here is served\n * there with no further wiring.\n *\n * FRANKIE_ADMIN_TOKEN=… FRANKIE_TENANTS_DIR=./.frankie/tenants \\\n * pnpm --filter @frankieai/server start:control\n */\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport { createFrankieApp } from \"./app.js\";\nimport { createControlApp } from \"./control/app.js\";\nimport { resolveStores } from \"./control/resolve-store.js\";\n\nasync function main(): Promise<void> {\n const port = Number(process.env[\"PORT\"] ?? process.env[\"CONTROL_PORT\"] ?? 8081);\n const host = process.env[\"HOST\"] ?? \"0.0.0.0\";\n\n if (!process.env[\"FRANKIE_ADMIN_TOKEN\"]) {\n console.error(\"[frankie] Set FRANKIE_ADMIN_TOKEN to run the control plane.\");\n process.exit(1);\n }\n\n const { tenants: store, sessions, knowledge, describe } = await resolveStores();\n const app = new Hono();\n app.route(\"/\", createControlApp({ store, sessions, knowledge }));\n app.route(\"/\", createFrankieApp({ tenants: store, sessions, knowledge }));\n\n serve({ fetch: app.fetch, port, hostname: host }, () => {\n console.log(\n JSON.stringify({\n ts: new Date().toISOString(),\n level: \"info\",\n event: \"control_listening\",\n store: describe,\n url: `http://${host}:${port}`,\n }),\n );\n });\n}\n\nvoid main();\n"],"mappings":";;;;;;;;;;AAUA,SAAS,aAAa;AACtB,SAAS,YAAY;AAKrB,eAAe,OAAsB;AACnC,QAAM,OAAO,OAAO,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,cAAc,KAAK,IAAI;AAC9E,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAEpC,MAAI,CAAC,QAAQ,IAAI,qBAAqB,GAAG;AACvC,YAAQ,MAAM,6DAA6D;AAC3E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,SAAS,OAAO,UAAU,WAAW,SAAS,IAAI,MAAM,cAAc;AAC9E,QAAM,MAAM,IAAI,KAAK;AACrB,MAAI,MAAM,KAAK,iBAAiB,EAAE,OAAO,UAAU,UAAU,CAAC,CAAC;AAC/D,MAAI,MAAM,KAAK,iBAAiB,EAAE,SAAS,OAAO,UAAU,UAAU,CAAC,CAAC;AAExE,QAAM,EAAE,OAAO,IAAI,OAAO,MAAM,UAAU,KAAK,GAAG,MAAM;AACtD,YAAQ;AAAA,MACN,KAAK,UAAU;AAAA,QACb,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC3B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,KAAK,UAAU,IAAI,IAAI,IAAI;AAAA,MAC7B,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAEA,KAAK,KAAK;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -112,6 +112,26 @@ declare function safeEqual(a: string, b: string): boolean;
|
|
|
112
112
|
/** Stable pseudonymous id for an anonymous visitor. */
|
|
113
113
|
declare function anonymousUser(seed: string): VerifiedUser;
|
|
114
114
|
|
|
115
|
+
/** One recorded hand-off to the workspace's team. Stored on the conversation. */
|
|
116
|
+
interface EscalationRecord {
|
|
117
|
+
id: string;
|
|
118
|
+
at: number;
|
|
119
|
+
/** Why the agent escalated (its own words / the visitor's ask). */
|
|
120
|
+
reason: string;
|
|
121
|
+
/** A short recap of what the visitor needs and what was already tried. */
|
|
122
|
+
summary: string;
|
|
123
|
+
/** How to reach the visitor, when they provided it. */
|
|
124
|
+
email?: string;
|
|
125
|
+
name?: string;
|
|
126
|
+
}
|
|
127
|
+
/** What the tool hands back to the app: the record plus who/where it came from. */
|
|
128
|
+
interface EscalationEvent extends EscalationRecord {
|
|
129
|
+
tenantId: string;
|
|
130
|
+
conversationId: string;
|
|
131
|
+
userId: string;
|
|
132
|
+
}
|
|
133
|
+
type EscalationSink = (event: EscalationEvent) => Promise<void>;
|
|
134
|
+
|
|
115
135
|
interface ChatSession {
|
|
116
136
|
id: string;
|
|
117
137
|
tenantId: string;
|
|
@@ -130,7 +150,17 @@ interface ChatSession {
|
|
|
130
150
|
lastActiveAt: number;
|
|
131
151
|
messageCount: number;
|
|
132
152
|
costUsd: number;
|
|
153
|
+
/** True while a chat turn holds this session; see `busySince` for how long. */
|
|
133
154
|
busy: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* When the running turn took the lock. A turn can only hold the session for
|
|
157
|
+
* as long as its timeout, so a lock older than that is one whose release
|
|
158
|
+
* never landed (the instance was frozen or restarted mid-turn) and is free
|
|
159
|
+
* for the taking — otherwise the session would answer 409 forever.
|
|
160
|
+
*/
|
|
161
|
+
busySince?: number;
|
|
162
|
+
/** Human hand-offs the agent made in this conversation (the escalation inbox). */
|
|
163
|
+
escalations?: EscalationRecord[];
|
|
134
164
|
}
|
|
135
165
|
interface SessionStore {
|
|
136
166
|
create(session: ChatSession): Promise<void>;
|
|
@@ -163,6 +193,41 @@ declare class MemorySessionStore implements SessionStore {
|
|
|
163
193
|
}
|
|
164
194
|
declare function newSessionId(): string;
|
|
165
195
|
|
|
196
|
+
/** One indexed passage of a document, with its embedding vector. */
|
|
197
|
+
interface KnowledgeChunk {
|
|
198
|
+
id: string;
|
|
199
|
+
url: string;
|
|
200
|
+
title: string;
|
|
201
|
+
text: string;
|
|
202
|
+
embedding: number[];
|
|
203
|
+
}
|
|
204
|
+
/** A retrieval result: the passage and how close it was to the query. */
|
|
205
|
+
interface KnowledgeHit {
|
|
206
|
+
url: string;
|
|
207
|
+
title: string;
|
|
208
|
+
text: string;
|
|
209
|
+
score: number;
|
|
210
|
+
}
|
|
211
|
+
/** What the dashboard shows about a workspace's index. */
|
|
212
|
+
interface KnowledgeStats {
|
|
213
|
+
chunks: number;
|
|
214
|
+
pages: number;
|
|
215
|
+
sources: string[];
|
|
216
|
+
ingestedAt?: number;
|
|
217
|
+
}
|
|
218
|
+
interface KnowledgeStore {
|
|
219
|
+
/** Replace a workspace's whole index with a freshly ingested set (atomic swap). */
|
|
220
|
+
replace(tenantId: string, chunks: KnowledgeChunk[], meta: {
|
|
221
|
+
sources: string[];
|
|
222
|
+
pages: number;
|
|
223
|
+
ingestedAt: number;
|
|
224
|
+
}): Promise<void>;
|
|
225
|
+
/** Top-k passages by cosine similarity to the query vector. */
|
|
226
|
+
search(tenantId: string, queryVector: readonly number[], k: number): Promise<KnowledgeHit[]>;
|
|
227
|
+
stats(tenantId: string): Promise<KnowledgeStats>;
|
|
228
|
+
clear(tenantId: string): Promise<void>;
|
|
229
|
+
}
|
|
230
|
+
|
|
166
231
|
declare class ModelResolutionError extends Error {
|
|
167
232
|
}
|
|
168
233
|
interface ResolvedModel {
|
|
@@ -249,7 +314,7 @@ declare class SingleTenantStore implements TenantStore {
|
|
|
249
314
|
declare class MemoryTenantStore implements TenantStore {
|
|
250
315
|
private readonly byKey;
|
|
251
316
|
constructor(tenants?: ReadonlyArray<ResolvedConfig>);
|
|
252
|
-
/** Register (or replace) a tenant
|
|
317
|
+
/** Register (or replace) a tenant, under its base key and each persona's key. */
|
|
253
318
|
add(resolved: ResolvedConfig): void;
|
|
254
319
|
byPublishableKey(key: string): Promise<ResolvedConfig | null>;
|
|
255
320
|
}
|
|
@@ -260,6 +325,8 @@ interface CreateAppOptions {
|
|
|
260
325
|
/** Many tenants, each resolved by publishable key at request time — the SaaS case. */
|
|
261
326
|
tenants?: TenantStore | TenantRegistry;
|
|
262
327
|
sessions?: SessionStore;
|
|
328
|
+
/** Knowledge-base index for the docs-search tool; in-memory when omitted. */
|
|
329
|
+
knowledge?: KnowledgeStore;
|
|
263
330
|
rateLimiter?: RateLimiter;
|
|
264
331
|
logger?: (event: Record<string, unknown>) => void;
|
|
265
332
|
}
|
|
@@ -275,6 +342,8 @@ interface McpHostOptions {
|
|
|
275
342
|
logger?: (event: Record<string, unknown>) => void;
|
|
276
343
|
/** Injected in tests so generated API tools hit a stub rather than the network. */
|
|
277
344
|
fetchImpl?: typeof fetch;
|
|
345
|
+
/** Shared with the chat API; each request counts against the tenant's message budget. */
|
|
346
|
+
rateLimiter?: RateLimiter;
|
|
278
347
|
}
|
|
279
348
|
declare function createMcpHostApp(options: McpHostOptions): Hono;
|
|
280
349
|
|
|
@@ -302,6 +371,10 @@ interface RepositoryInstallRecord {
|
|
|
302
371
|
pullRequestState?: "draft" | "open" | "closed" | "merged";
|
|
303
372
|
checkRollup?: "success" | "pending" | "failure" | "none";
|
|
304
373
|
checkedAt?: string;
|
|
374
|
+
/** The current integration stage (see apps/web/lib/integration-status.ts), so Overview renders it on first paint. */
|
|
375
|
+
stage?: string;
|
|
376
|
+
/** The fully-integrated checklist rows that are done (apps/web/lib/integration-checklist.ts) — the funnel position. */
|
|
377
|
+
checklist?: string[];
|
|
305
378
|
updatedAt: string;
|
|
306
379
|
}
|
|
307
380
|
/**
|
|
@@ -322,6 +395,21 @@ interface VercelLinkRecord {
|
|
|
322
395
|
}
|
|
323
396
|
/** `VercelLinkRecord` with the token stripped — what the dashboard sees. */
|
|
324
397
|
type PublicVercelLink = Omit<VercelLinkRecord, "accessToken">;
|
|
398
|
+
/** What the installer read from the repository's own styling (see @frankieai/core inferTheme). */
|
|
399
|
+
interface SuggestedThemeRecord {
|
|
400
|
+
theme: {
|
|
401
|
+
primary: string;
|
|
402
|
+
background?: string;
|
|
403
|
+
foreground?: string;
|
|
404
|
+
radius?: string;
|
|
405
|
+
};
|
|
406
|
+
sources: Array<{
|
|
407
|
+
field: "primary" | "background" | "foreground" | "radius";
|
|
408
|
+
path: string;
|
|
409
|
+
}>;
|
|
410
|
+
repository: string;
|
|
411
|
+
inferredAt: string;
|
|
412
|
+
}
|
|
325
413
|
interface TenantRecord {
|
|
326
414
|
apiSpecSync?: Record<string, {
|
|
327
415
|
repository: string;
|
|
@@ -343,8 +431,19 @@ interface TenantRecord {
|
|
|
343
431
|
sessionSecret: string;
|
|
344
432
|
/** When true, the workspace is protected: it cannot be deleted until unlocked. */
|
|
345
433
|
locked?: boolean;
|
|
434
|
+
/** When true, the dashboard applies a changed repository spec on its own (new operations still land unapproved). */
|
|
435
|
+
apiAutoRefresh?: boolean;
|
|
436
|
+
/** A widget theme inferred from the repository at install time, awaiting Accept / Keep in the Theme editor. */
|
|
437
|
+
suggestedTheme?: SuggestedThemeRecord;
|
|
346
438
|
/** Durable repository-install state surfaced in the dashboard and setup chat. */
|
|
347
439
|
repositoryInstall?: RepositoryInstallRecord;
|
|
440
|
+
/**
|
|
441
|
+
* Per-persona repository-install state, keyed by persona id. The primary
|
|
442
|
+
* agent keeps `repositoryInstall`; each embedded persona (see
|
|
443
|
+
* `config.personas`) gets its own install record here, so a workspace can
|
|
444
|
+
* open a separate scoped-install PR per agent.
|
|
445
|
+
*/
|
|
446
|
+
personaInstalls?: Record<string, RepositoryInstallRecord>;
|
|
348
447
|
/** Deployment provider connection, when the workspace has connected Vercel. */
|
|
349
448
|
vercel?: VercelLinkRecord;
|
|
350
449
|
createdAt: string;
|
|
@@ -407,14 +506,23 @@ interface PublicTenant {
|
|
|
407
506
|
}>;
|
|
408
507
|
/** Protected from deletion until unlocked. */
|
|
409
508
|
locked: boolean;
|
|
509
|
+
/** Apply a changed repository spec without a click (see TenantRecord). */
|
|
510
|
+
apiAutoRefresh: boolean;
|
|
511
|
+
suggestedTheme?: SuggestedThemeRecord;
|
|
410
512
|
/** A submitted or in-progress SDK installation for this workspace. */
|
|
411
513
|
repositoryInstall?: RepositoryInstallRecord;
|
|
514
|
+
/** A submitted or in-progress scoped-install PR per persona id (`config.personas`). */
|
|
515
|
+
personaInstalls?: Record<string, RepositoryInstallRecord>;
|
|
412
516
|
/** The connected Vercel installation and matched project — never its token. */
|
|
413
517
|
vercel?: PublicVercelLink;
|
|
414
518
|
/** The customer's brand theme for the widget, so the dashboard can edit it. */
|
|
415
519
|
theme: TenantRecord["config"]["theme"];
|
|
416
520
|
/** Opt-in chat capabilities (attachments, model picker) for the dashboard to edit. */
|
|
417
521
|
features: TenantRecord["config"]["features"];
|
|
522
|
+
/** Documentation sources indexed for the knowledge-base agent, for the dashboard to edit. */
|
|
523
|
+
knowledgeBase: TenantRecord["config"]["knowledgeBase"];
|
|
524
|
+
/** Embedded page-scoped personas (extra agents) on this workspace, for the dashboard to edit. Their publishable keys are public. */
|
|
525
|
+
personas: TenantRecord["config"]["personas"];
|
|
418
526
|
/** Editable chat copy: the greeting and the starter cards shown on the empty screen. */
|
|
419
527
|
agent: {
|
|
420
528
|
greeting: string;
|
|
@@ -453,6 +561,27 @@ interface PlatformAdminRecord {
|
|
|
453
561
|
grantedBy: string;
|
|
454
562
|
createdAt: string;
|
|
455
563
|
}
|
|
564
|
+
/** Billing plan tiers. Free is the default; Pro lifts the workspace cap. */
|
|
565
|
+
type Plan = "free" | "pro";
|
|
566
|
+
/**
|
|
567
|
+
* A per-org (owner id) billing subscription. One org, one subscription,
|
|
568
|
+
* covering all its workspaces and members — matching the model where inviting
|
|
569
|
+
* someone grants access to every workspace. The Stripe ids are written only by
|
|
570
|
+
* the webhook (platform admin), never by the org itself, so an org can't
|
|
571
|
+
* self-upgrade. Absent record ⇒ the Free plan.
|
|
572
|
+
*/
|
|
573
|
+
interface SubscriptionRecord {
|
|
574
|
+
/** The org's owner id, e.g. "github:42". */
|
|
575
|
+
org: string;
|
|
576
|
+
plan: Plan;
|
|
577
|
+
/** Mirrors the Stripe subscription status; anything but active/trialing is treated as Free. */
|
|
578
|
+
status: "active" | "trialing" | "past_due" | "canceled" | "incomplete";
|
|
579
|
+
stripeCustomerId?: string;
|
|
580
|
+
stripeSubscriptionId?: string;
|
|
581
|
+
/** ISO timestamp the current paid period ends, for display. */
|
|
582
|
+
currentPeriodEnd?: string;
|
|
583
|
+
updatedAt: string;
|
|
584
|
+
}
|
|
456
585
|
interface ControlStore extends TenantStore {
|
|
457
586
|
listOrganizations(): Promise<string[]>;
|
|
458
587
|
listPlatformAdmins(): Promise<PlatformAdminRecord[]>;
|
|
@@ -473,6 +602,10 @@ interface ControlStore extends TenantStore {
|
|
|
473
602
|
deleteMember(org: string, login: string): Promise<void>;
|
|
474
603
|
/** Every org membership held by a given login, across all orgs. */
|
|
475
604
|
membershipsForLogin(login: string): Promise<MembershipRecord[]>;
|
|
605
|
+
/** The org's billing subscription, or null for an org on the Free plan. */
|
|
606
|
+
getSubscription(org: string): Promise<SubscriptionRecord | null>;
|
|
607
|
+
/** Create or replace an org's subscription. Written by the Stripe webhook. */
|
|
608
|
+
putSubscription(subscription: SubscriptionRecord): Promise<void>;
|
|
476
609
|
}
|
|
477
610
|
/**
|
|
478
611
|
* The control plane's persistence, and at the same time a `TenantStore` the
|
|
@@ -495,6 +628,7 @@ declare class FileTenantStore implements ControlStore {
|
|
|
495
628
|
private index;
|
|
496
629
|
private byId;
|
|
497
630
|
private members;
|
|
631
|
+
private subscriptions;
|
|
498
632
|
private loaded;
|
|
499
633
|
private static memberKey;
|
|
500
634
|
constructor(options: {
|
|
@@ -508,6 +642,9 @@ declare class FileTenantStore implements ControlStore {
|
|
|
508
642
|
putMember(member: MembershipRecord): Promise<void>;
|
|
509
643
|
deleteMember(org: string, login: string): Promise<void>;
|
|
510
644
|
membershipsForLogin(login: string): Promise<MembershipRecord[]>;
|
|
645
|
+
private persistSubscriptions;
|
|
646
|
+
getSubscription(org: string): Promise<SubscriptionRecord | null>;
|
|
647
|
+
putSubscription(subscription: SubscriptionRecord): Promise<void>;
|
|
511
648
|
private remember;
|
|
512
649
|
byPublishableKey(key: string): Promise<ResolvedConfig | null>;
|
|
513
650
|
/** All tenants, or — when an owner is given — only that owner's. */
|
|
@@ -515,6 +652,12 @@ declare class FileTenantStore implements ControlStore {
|
|
|
515
652
|
get(id: string): Promise<TenantRecord | null>;
|
|
516
653
|
put(record: TenantRecord): Promise<void>;
|
|
517
654
|
compareAndSwap(previous: TenantRecord, next: TenantRecord): Promise<boolean>;
|
|
655
|
+
/**
|
|
656
|
+
* A record holds the workspace's signing and session secrets and source
|
|
657
|
+
* credentials: owner-only permissions, and written whole (temp file, then
|
|
658
|
+
* rename) so a crash never leaves half a record behind.
|
|
659
|
+
*/
|
|
660
|
+
private write;
|
|
518
661
|
delete(id: string): Promise<void>;
|
|
519
662
|
}
|
|
520
663
|
|
|
@@ -557,6 +700,7 @@ declare class PostgresTenantStore implements ControlStore {
|
|
|
557
700
|
private ensureReady;
|
|
558
701
|
/** Create the table if it isn't there yet. Safe to call repeatedly. */
|
|
559
702
|
init(): Promise<void>;
|
|
703
|
+
private subscriptionRow;
|
|
560
704
|
private memberRow;
|
|
561
705
|
private adminRow;
|
|
562
706
|
listOrganizations(): Promise<string[]>;
|
|
@@ -567,6 +711,8 @@ declare class PostgresTenantStore implements ControlStore {
|
|
|
567
711
|
listMembers(org: string): Promise<MembershipRecord[]>;
|
|
568
712
|
putMember(member: MembershipRecord): Promise<void>;
|
|
569
713
|
deleteMember(org: string, login: string): Promise<void>;
|
|
714
|
+
getSubscription(org: string): Promise<SubscriptionRecord | null>;
|
|
715
|
+
putSubscription(subscription: SubscriptionRecord): Promise<void>;
|
|
570
716
|
membershipsForLogin(login: string): Promise<MembershipRecord[]>;
|
|
571
717
|
private parse;
|
|
572
718
|
byPublishableKey(key: string): Promise<ResolvedConfig | null>;
|
|
@@ -584,6 +730,7 @@ interface ResolvedStore {
|
|
|
584
730
|
interface ResolvedStores {
|
|
585
731
|
tenants: ControlStore;
|
|
586
732
|
sessions: SessionStore;
|
|
733
|
+
knowledge: KnowledgeStore;
|
|
587
734
|
describe: string;
|
|
588
735
|
}
|
|
589
736
|
/**
|
|
@@ -641,7 +788,8 @@ interface SkillGenerationInput {
|
|
|
641
788
|
*/
|
|
642
789
|
toolDetails?: Array<{
|
|
643
790
|
id: string;
|
|
644
|
-
description: string;
|
|
791
|
+
description: string; /** What the tool takes (`describeInputs`), when the inventory knows. */
|
|
792
|
+
inputs?: string;
|
|
645
793
|
}>;
|
|
646
794
|
/** Present when refining an existing skill rather than starting fresh. */
|
|
647
795
|
instructions?: string;
|
|
@@ -655,6 +803,8 @@ interface ControlOptions {
|
|
|
655
803
|
store: ControlStore;
|
|
656
804
|
/** Durable session store shared with the data plane when Postgres is configured. */
|
|
657
805
|
sessions?: SessionStore;
|
|
806
|
+
/** Knowledge index shared with the data plane, so a dashboard-triggered ingest is what the docs-search tool reads. */
|
|
807
|
+
knowledge?: KnowledgeStore;
|
|
658
808
|
/** Bearer token that gates the admin API. A stand-in for real user auth. */
|
|
659
809
|
adminToken?: string;
|
|
660
810
|
/**
|
|
@@ -679,6 +829,26 @@ interface ControlOptions {
|
|
|
679
829
|
*/
|
|
680
830
|
declare function createControlApp(options: ControlOptions): Hono;
|
|
681
831
|
|
|
832
|
+
/** Where and how to compute embeddings — an OpenAI-compatible `/embeddings` endpoint. */
|
|
833
|
+
interface EmbeddingConfig {
|
|
834
|
+
endpoint: string;
|
|
835
|
+
apiKey: string;
|
|
836
|
+
model: string;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Embed a batch of texts. Sends them in bounded requests so a large ingest
|
|
840
|
+
* doesn't build one enormous body. Returns one vector per input, in order.
|
|
841
|
+
*/
|
|
842
|
+
declare function embedTexts(texts: string[], config: EmbeddingConfig, fetchImpl?: typeof fetch, batchSize?: number): Promise<number[][]>;
|
|
843
|
+
|
|
844
|
+
/** Everything the retrieval tool needs, injected by the runner from the app. */
|
|
845
|
+
interface KnowledgeDeps {
|
|
846
|
+
store: KnowledgeStore;
|
|
847
|
+
embedding: EmbeddingConfig;
|
|
848
|
+
/** Override the embed call in tests. */
|
|
849
|
+
embedImpl?: typeof embedTexts;
|
|
850
|
+
}
|
|
851
|
+
|
|
682
852
|
/**
|
|
683
853
|
* The per-turn handle an API tool uses to run itself in the user's browser
|
|
684
854
|
* instead of on Frankie's server. `available` says whether a live widget
|
|
@@ -713,7 +883,11 @@ interface RunTurnParams {
|
|
|
713
883
|
signal: AbortSignal;
|
|
714
884
|
/** Called with the full transcript once the turn settles, to persist it. */
|
|
715
885
|
onTranscript?: (messages: AgentMessage[]) => void;
|
|
886
|
+
/** Receives a human-escalation when the built-in escalation tool is called. */
|
|
887
|
+
onEscalation?: EscalationSink;
|
|
716
888
|
onWarning?: (message: string, detail: Record<string, unknown>) => void;
|
|
889
|
+
/** Knowledge-base index + embedding config for the docs-search tool. */
|
|
890
|
+
knowledge?: KnowledgeDeps;
|
|
717
891
|
}
|
|
718
892
|
/**
|
|
719
893
|
* Run one user turn and stream it back as widget-facing events.
|
|
@@ -808,6 +982,10 @@ interface CollectToolsOptions {
|
|
|
808
982
|
accountApiUrl?: string;
|
|
809
983
|
/** Browser channel for API sources marked `execution: "client"`. */
|
|
810
984
|
clientBridge?: ClientToolBridge;
|
|
985
|
+
/** Knowledge-base index + embedding config for the docs-search tool. */
|
|
986
|
+
knowledge?: KnowledgeDeps;
|
|
987
|
+
/** Receives a human-escalation when the built-in escalation tool is called. */
|
|
988
|
+
onEscalation?: EscalationSink;
|
|
811
989
|
onWarning?: (message: string, detail: Record<string, unknown>) => void;
|
|
812
990
|
}
|
|
813
991
|
interface CollectedTools {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createControlApp
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EYR3BV7U.js";
|
|
4
4
|
import {
|
|
5
5
|
ConfigError,
|
|
6
6
|
applyEnvOverrides,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
runTurn,
|
|
43
43
|
safeEqual,
|
|
44
44
|
toPublicTenant
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-WEHJH4FL.js";
|
|
46
46
|
export {
|
|
47
47
|
API_TOOL_PREFIX,
|
|
48
48
|
AuthError,
|
package/dist/server.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
ModelResolutionError,
|
|
8
8
|
createFrankieApp,
|
|
9
9
|
resolveStores
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-WEHJH4FL.js";
|
|
11
11
|
|
|
12
12
|
// src/server.ts
|
|
13
13
|
import { serve } from "@hono/node-server";
|
|
@@ -21,7 +21,7 @@ async function main() {
|
|
|
21
21
|
if (multiTenant) {
|
|
22
22
|
const resolved = await resolveStores();
|
|
23
23
|
describe = `multi-tenant (${resolved.describe})`;
|
|
24
|
-
app = createFrankieApp({ tenants: resolved.tenants, sessions: resolved.sessions });
|
|
24
|
+
app = createFrankieApp({ tenants: resolved.tenants, sessions: resolved.sessions, knowledge: resolved.knowledge });
|
|
25
25
|
} else {
|
|
26
26
|
app = createFrankieApp({ resolved: await loadResolvedConfig() });
|
|
27
27
|
}
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { serve } from \"@hono/node-server\";\nimport { createFrankieApp } from \"./app.js\";\nimport { ModelResolutionError } from \"./agent/models.js\";\nimport { ConfigError, loadResolvedConfig } from \"./config.js\";\nimport { resolveStores } from \"./control/resolve-store.js\";\n\nasync function main(): Promise<void> {\n const port = Number(process.env[\"PORT\"] ?? 8080);\n const host = process.env[\"HOST\"] ?? \"0.0.0.0\";\n\n // Two shapes, one binary. With DATABASE_URL (or FRANKIE_TENANTS_DIR) set, this\n // serves every tenant the control plane wrote, resolved by publishable key —\n // the hosted, multi-tenant shape. Otherwise it loads one frankie.config.json,\n // the self-hosted, single-tenant shape.\n const multiTenant = Boolean(process.env[\"DATABASE_URL\"] || process.env[\"FRANKIE_TENANTS_DIR\"]);\n let app;\n let describe = \"single-tenant\";\n try {\n if (multiTenant) {\n const resolved = await resolveStores();\n describe = `multi-tenant (${resolved.describe})`;\n app = createFrankieApp({ tenants: resolved.tenants, sessions: resolved.sessions });\n } else {\n app = createFrankieApp({ resolved: await loadResolvedConfig() });\n }\n } catch (error) {\n if (error instanceof ConfigError || error instanceof ModelResolutionError) {\n console.error(`[frankie] ${error.message}`);\n process.exit(1);\n }\n throw error;\n }\n\n serve({ fetch: app.fetch, port, hostname: host }, () => {\n console.log(\n JSON.stringify({\n ts: new Date().toISOString(),\n level: \"info\",\n event: \"listening\",\n mode: describe,\n url: `http://${host}:${port}`,\n }),\n );\n });\n}\n\nvoid main();\n"],"mappings":";;;;;;;;;;;;AACA,SAAS,aAAa;AAMtB,eAAe,OAAsB;AACnC,QAAM,OAAO,OAAO,QAAQ,IAAI,MAAM,KAAK,IAAI;AAC/C,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAMpC,QAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc,KAAK,QAAQ,IAAI,qBAAqB,CAAC;AAC7F,MAAI;AACJ,MAAI,WAAW;AACf,MAAI;AACF,QAAI,aAAa;AACf,YAAM,WAAW,MAAM,cAAc;AACrC,iBAAW,iBAAiB,SAAS,QAAQ;AAC7C,YAAM,iBAAiB,EAAE,SAAS,SAAS,SAAS,UAAU,SAAS,SAAS,CAAC;AAAA,
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { serve } from \"@hono/node-server\";\nimport { createFrankieApp } from \"./app.js\";\nimport { ModelResolutionError } from \"./agent/models.js\";\nimport { ConfigError, loadResolvedConfig } from \"./config.js\";\nimport { resolveStores } from \"./control/resolve-store.js\";\n\nasync function main(): Promise<void> {\n const port = Number(process.env[\"PORT\"] ?? 8080);\n const host = process.env[\"HOST\"] ?? \"0.0.0.0\";\n\n // Two shapes, one binary. With DATABASE_URL (or FRANKIE_TENANTS_DIR) set, this\n // serves every tenant the control plane wrote, resolved by publishable key —\n // the hosted, multi-tenant shape. Otherwise it loads one frankie.config.json,\n // the self-hosted, single-tenant shape.\n const multiTenant = Boolean(process.env[\"DATABASE_URL\"] || process.env[\"FRANKIE_TENANTS_DIR\"]);\n let app;\n let describe = \"single-tenant\";\n try {\n if (multiTenant) {\n const resolved = await resolveStores();\n describe = `multi-tenant (${resolved.describe})`;\n app = createFrankieApp({ tenants: resolved.tenants, sessions: resolved.sessions, knowledge: resolved.knowledge });\n } else {\n app = createFrankieApp({ resolved: await loadResolvedConfig() });\n }\n } catch (error) {\n if (error instanceof ConfigError || error instanceof ModelResolutionError) {\n console.error(`[frankie] ${error.message}`);\n process.exit(1);\n }\n throw error;\n }\n\n serve({ fetch: app.fetch, port, hostname: host }, () => {\n console.log(\n JSON.stringify({\n ts: new Date().toISOString(),\n level: \"info\",\n event: \"listening\",\n mode: describe,\n url: `http://${host}:${port}`,\n }),\n );\n });\n}\n\nvoid main();\n"],"mappings":";;;;;;;;;;;;AACA,SAAS,aAAa;AAMtB,eAAe,OAAsB;AACnC,QAAM,OAAO,OAAO,QAAQ,IAAI,MAAM,KAAK,IAAI;AAC/C,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAMpC,QAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc,KAAK,QAAQ,IAAI,qBAAqB,CAAC;AAC7F,MAAI;AACJ,MAAI,WAAW;AACf,MAAI;AACF,QAAI,aAAa;AACf,YAAM,WAAW,MAAM,cAAc;AACrC,iBAAW,iBAAiB,SAAS,QAAQ;AAC7C,YAAM,iBAAiB,EAAE,SAAS,SAAS,SAAS,UAAU,SAAS,UAAU,WAAW,SAAS,UAAU,CAAC;AAAA,IAClH,OAAO;AACL,YAAM,iBAAiB,EAAE,UAAU,MAAM,mBAAmB,EAAE,CAAC;AAAA,IACjE;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,eAAe,iBAAiB,sBAAsB;AACzE,cAAQ,MAAM,aAAa,MAAM,OAAO,EAAE;AAC1C,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM;AAAA,EACR;AAEA,QAAM,EAAE,OAAO,IAAI,OAAO,MAAM,UAAU,KAAK,GAAG,MAAM;AACtD,YAAQ;AAAA,MACN,KAAK,UAAU;AAAA,QACb,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC3B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,QACN,KAAK,UAAU,IAAI,IAAI,IAAI;AAAA,MAC7B,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAEA,KAAK,KAAK;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frankieai/server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Frankie cloud agent server — auth, sessions, MCP wiring and streaming for the embedded onboarding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"yaml": "^2.9.0",
|
|
29
29
|
"zod": "^4.0.0",
|
|
30
30
|
"pg": "^8.13.0",
|
|
31
|
-
"@frankieai/core": "0.2.
|
|
31
|
+
"@frankieai/core": "0.2.6"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^22.10.0",
|