@coffer-org/server 6.0.0 → 7.1.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/dist/embed-openai.d.ts +2 -2
- package/dist/embed-openai.js +4 -3
- package/dist/embeddings.d.ts +1 -0
- package/dist/embeddings.js +8 -0
- package/dist/index.js +2 -0
- package/dist/instance-api.d.ts +5 -0
- package/dist/instance-api.js +4 -0
- package/dist/mcp-tools.d.ts +8 -5
- package/dist/mcp-tools.js +18 -12
- package/dist/orchestrator/starter-sources.d.ts +2 -0
- package/dist/orchestrator/starter-sources.js +22 -0
- package/dist/orchestrator/starters.d.ts +7 -3
- package/dist/orchestrator/starters.js +168 -38
- package/dist/orchestrator/types.d.ts +2 -2
- package/dist/plugin-hooks.d.ts +2 -1
- package/dist/plugin-hooks.js +2 -2
- package/package.json +2 -2
package/dist/embed-openai.d.ts
CHANGED
|
@@ -14,11 +14,11 @@ export declare class OpenAIEmbeddingError extends Error {
|
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
export declare function embeddingFailureMessage(error: unknown): string;
|
|
17
|
-
export declare function embedBatch(texts: string[], apiKey: string, fetchImpl?: typeof fetch): Promise<{
|
|
17
|
+
export declare function embedBatch(texts: string[], apiKey: string, fetchImpl?: typeof fetch, signal?: AbortSignal): Promise<{
|
|
18
18
|
vectors: number[][];
|
|
19
19
|
tokens: number;
|
|
20
20
|
}>;
|
|
21
|
-
export declare function embedOne(text: string, apiKey: string, fetchImpl?: typeof fetch): Promise<{
|
|
21
|
+
export declare function embedOne(text: string, apiKey: string, fetchImpl?: typeof fetch, signal?: AbortSignal): Promise<{
|
|
22
22
|
vector: number[];
|
|
23
23
|
tokens: number;
|
|
24
24
|
}>;
|
package/dist/embed-openai.js
CHANGED
|
@@ -54,7 +54,7 @@ export function embeddingFailureMessage(error) {
|
|
|
54
54
|
return `OpenAI embeddings are unavailable${error.status == null ? '' : ` (HTTP ${error.status})`}. RAG will retry; records are still saved.`;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
export async function embedBatch(texts, apiKey, fetchImpl = fetch) {
|
|
57
|
+
export async function embedBatch(texts, apiKey, fetchImpl = fetch, signal) {
|
|
58
58
|
if (texts.length === 0)
|
|
59
59
|
return { vectors: [], tokens: 0 };
|
|
60
60
|
if (!apiKey)
|
|
@@ -65,6 +65,7 @@ export async function embedBatch(texts, apiKey, fetchImpl = fetch) {
|
|
|
65
65
|
method: 'POST',
|
|
66
66
|
headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}` },
|
|
67
67
|
body: JSON.stringify({ model: EMBED_MODEL, input: texts, dimensions: EMBED_DIM }),
|
|
68
|
+
...(signal ? { signal } : {}),
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
71
|
catch (cause) {
|
|
@@ -79,8 +80,8 @@ export async function embedBatch(texts, apiKey, fetchImpl = fetch) {
|
|
|
79
80
|
const vectors = [...json.data].sort((a, b) => a.index - b.index).map((d) => d.embedding);
|
|
80
81
|
return { vectors, tokens: json.usage?.total_tokens ?? 0 };
|
|
81
82
|
}
|
|
82
|
-
export async function embedOne(text, apiKey, fetchImpl = fetch) {
|
|
83
|
-
const { vectors, tokens } = await embedBatch([text], apiKey, fetchImpl);
|
|
83
|
+
export async function embedOne(text, apiKey, fetchImpl = fetch, signal) {
|
|
84
|
+
const { vectors, tokens } = await embedBatch([text], apiKey, fetchImpl, signal);
|
|
84
85
|
const vector = vectors[0];
|
|
85
86
|
if (!vector)
|
|
86
87
|
throw new Error('embeddings: empty response');
|
package/dist/embeddings.d.ts
CHANGED
|
@@ -39,3 +39,4 @@ export declare function searchEmbeddings(queryVec: number[], k: number, opts?: {
|
|
|
39
39
|
shelfKeys?: Set<string>;
|
|
40
40
|
}): Promise<EmbeddingHit[]>;
|
|
41
41
|
export declare function listEventsSince(lastId: number, limit: number): Promise<EventRow[]>;
|
|
42
|
+
export declare function latestEventId(): Promise<number>;
|
package/dist/embeddings.js
CHANGED
|
@@ -116,3 +116,11 @@ export async function listEventsSince(lastId, limit) {
|
|
|
116
116
|
}));
|
|
117
117
|
return rows;
|
|
118
118
|
}
|
|
119
|
+
export async function latestEventId() {
|
|
120
|
+
const em = getEm().fork();
|
|
121
|
+
const rows = (await em.find('_Event', {}, {
|
|
122
|
+
orderBy: { id: 'DESC' },
|
|
123
|
+
limit: 1,
|
|
124
|
+
}));
|
|
125
|
+
return rows[0]?.id ?? 0;
|
|
126
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import { registerPluginUserApi, registerPluginAdminApi } from "./plugin-user-api
|
|
|
22
22
|
import { registerAuthApi, resolveRequestUser, requireAdmin, PUBLIC_API_PATHS } from "./auth-api.js";
|
|
23
23
|
import { registerMcpHttp } from "./mcp-http.js";
|
|
24
24
|
import { registerOAuthApi } from "./oauth-api.js";
|
|
25
|
+
import { registerInstanceApi } from "./instance-api.js";
|
|
25
26
|
import { baseUrl } from "./public-url.js";
|
|
26
27
|
import { discoverPluginAssets, discoverRuntime } from "./plugin-discovery.js";
|
|
27
28
|
import { checkLatestVersion, resolveUpdateTarget, resolveAllUpdateTargets, resolveBaseTargets, resolveRuntimeTarget, runNpmInstall, } from "./plugin-updates.js";
|
|
@@ -146,6 +147,7 @@ app.get('/health', async (_req, reply) => {
|
|
|
146
147
|
return reply.code(503).send({ status: 'db_unavailable' });
|
|
147
148
|
}
|
|
148
149
|
});
|
|
150
|
+
registerInstanceApi(app);
|
|
149
151
|
let librariesPayload = null;
|
|
150
152
|
app.get('/api/libraries', async (req, reply) => {
|
|
151
153
|
if (!librariesPayload) {
|
package/dist/mcp-tools.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface McpToolDef {
|
|
|
13
13
|
inputSchema: z.ZodRawShape;
|
|
14
14
|
scope: 'crud' | 'plugin' | 'rag' | 'settings' | 'upload';
|
|
15
15
|
role: AuthRole;
|
|
16
|
-
handler: (args: Record<string, unknown
|
|
16
|
+
handler: (args: Record<string, unknown>, signal?: AbortSignal) => Promise<ToolResult>;
|
|
17
17
|
}
|
|
18
18
|
export interface RagDeps {
|
|
19
19
|
embeddingApiKey: string;
|
|
@@ -73,6 +73,12 @@ export declare function collectLibraryPurposes(reg?: {
|
|
|
73
73
|
}, locales?: LocaleResolver): LibraryPurpose[];
|
|
74
74
|
export declare function siteSection(siteUrl: string): Promise<string | null>;
|
|
75
75
|
export declare function buildDomainSections(locales?: LocaleResolver): Promise<string[]>;
|
|
76
|
+
export interface StarterHint {
|
|
77
|
+
id: string;
|
|
78
|
+
text: string;
|
|
79
|
+
hash: string;
|
|
80
|
+
}
|
|
81
|
+
export declare const CORE_STARTER_HINT: string;
|
|
76
82
|
export declare function collectStarterHints(hooks?: Record<string, PluginHooks>, emFactory?: () => EntityManager, reg?: {
|
|
77
83
|
libraries: {
|
|
78
84
|
meta: {
|
|
@@ -81,9 +87,6 @@ export declare function collectStarterHints(hooks?: Record<string, PluginHooks>,
|
|
|
81
87
|
starterHint?: string;
|
|
82
88
|
};
|
|
83
89
|
}[];
|
|
84
|
-
}, locales?: LocaleResolver): Promise<
|
|
85
|
-
combined: string;
|
|
86
|
-
hash: string;
|
|
87
|
-
}>;
|
|
90
|
+
}, locales?: LocaleResolver): Promise<StarterHint[]>;
|
|
88
91
|
export declare function buildMcpInstructions(sections: string[]): string;
|
|
89
92
|
export {};
|
package/dist/mcp-tools.js
CHANGED
|
@@ -130,9 +130,9 @@ export async function collectMcpTools(opts = {}) {
|
|
|
130
130
|
inputSchema: t.inputSchema,
|
|
131
131
|
scope: 'plugin',
|
|
132
132
|
role: t.role ?? 'member',
|
|
133
|
-
handler: async (args) => {
|
|
133
|
+
handler: async (args, signal) => {
|
|
134
134
|
try {
|
|
135
|
-
const data = await t.handler(args, pluginCtx(id, getEm().fork()));
|
|
135
|
+
const data = await t.handler(args, pluginCtx(id, getEm().fork(), signal));
|
|
136
136
|
return ok(data);
|
|
137
137
|
}
|
|
138
138
|
catch (e) {
|
|
@@ -162,7 +162,7 @@ export async function collectMcpTools(opts = {}) {
|
|
|
162
162
|
},
|
|
163
163
|
scope: 'rag',
|
|
164
164
|
role: 'member',
|
|
165
|
-
handler: async (args) => {
|
|
165
|
+
handler: async (args, signal) => {
|
|
166
166
|
const library = args.library;
|
|
167
167
|
const shelf = args.shelf;
|
|
168
168
|
if (shelf && !library) {
|
|
@@ -180,7 +180,7 @@ export async function collectMcpTools(opts = {}) {
|
|
|
180
180
|
}
|
|
181
181
|
let vector = null;
|
|
182
182
|
try {
|
|
183
|
-
vector = (await embedOne(args.query, embeddingApiKey)).vector;
|
|
183
|
+
vector = (await embedOne(args.query, embeddingApiKey, undefined, signal)).vector;
|
|
184
184
|
}
|
|
185
185
|
catch (e) {
|
|
186
186
|
log.warn(`embedding failed, text-only search — ${embeddingFailureMessage(e)}`);
|
|
@@ -351,16 +351,23 @@ export async function buildDomainSections(locales) {
|
|
|
351
351
|
const rules = (await collectPluginInstructions()).map(({ id, instructions }) => `## ${id}\n${instructions}`);
|
|
352
352
|
return [dataModel, ...(overview ? [overview] : []), ...(singleSection ? [singleSection] : []), ...rules];
|
|
353
353
|
}
|
|
354
|
+
export const CORE_STARTER_HINT = 'what this Coffer instance itself holds — which libraries are present and what kinds of ' +
|
|
355
|
+
'questions the stored data can answer';
|
|
356
|
+
function hintHash(text) {
|
|
357
|
+
return createHash('sha1').update(text).digest('hex');
|
|
358
|
+
}
|
|
354
359
|
export async function collectStarterHints(hooks = pluginHooks, emFactory = () => getEm().fork(), reg, locales) {
|
|
355
|
-
const
|
|
360
|
+
const out = [{ id: 'system', text: CORE_STARTER_HINT, hash: hintHash(CORE_STARTER_HINT) }];
|
|
356
361
|
for (const [id, h] of Object.entries(hooks)) {
|
|
357
362
|
const hint = h.agent?.starterHint;
|
|
358
363
|
if (hint == null)
|
|
359
364
|
continue;
|
|
360
365
|
try {
|
|
361
|
-
const
|
|
362
|
-
if (
|
|
363
|
-
|
|
366
|
+
const hintText = typeof hint === 'function' ? await hint(pluginCtx(id, emFactory())) : hint;
|
|
367
|
+
if (hintText) {
|
|
368
|
+
const text = `${id}: ${hintText}`;
|
|
369
|
+
out.push({ id: `plugin:${id}`, text, hash: hintHash(text) });
|
|
370
|
+
}
|
|
364
371
|
}
|
|
365
372
|
catch (e) {
|
|
366
373
|
log.warn(`plugin ${id}: starterHint skipped — ${e.message}`);
|
|
@@ -380,11 +387,10 @@ export async function collectStarterHints(hooks = pluginHooks, emFactory = () =>
|
|
|
380
387
|
if (!v.meta.starterHint)
|
|
381
388
|
continue;
|
|
382
389
|
const name = v.meta.label ? i18n.resolve(v.meta.label) : undefined;
|
|
383
|
-
|
|
390
|
+
const text = `${v.meta.id}${name ? ` ("${name}")` : ''}: ${v.meta.starterHint}`;
|
|
391
|
+
out.push({ id: `library:${v.meta.id}`, text, hash: hintHash(text) });
|
|
384
392
|
}
|
|
385
|
-
|
|
386
|
-
const hash = createHash('sha1').update(combined).digest('hex');
|
|
387
|
-
return { combined, hash };
|
|
393
|
+
return out;
|
|
388
394
|
}
|
|
389
395
|
export function buildMcpInstructions(sections) {
|
|
390
396
|
const base = [
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getActiveRegistry } from "../registry-context.js";
|
|
2
|
+
export function shelfSources(slug, getShelfOwner) {
|
|
3
|
+
const parts = slug.split('/');
|
|
4
|
+
if (parts.length !== 2)
|
|
5
|
+
return [];
|
|
6
|
+
const [library, shelf] = parts;
|
|
7
|
+
if (!library || !shelf)
|
|
8
|
+
return [];
|
|
9
|
+
const out = [`library:${library}`];
|
|
10
|
+
const owner = getShelfOwner(library, shelf);
|
|
11
|
+
if (owner)
|
|
12
|
+
out.push(`plugin:${owner}`);
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
export function defaultShelfSources(slug) {
|
|
16
|
+
try {
|
|
17
|
+
return shelfSources(slug, getActiveRegistry().getShelfOwner);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -3,6 +3,7 @@ import { getPluginState, setPluginState } from '../plugin-state.ts';
|
|
|
3
3
|
import { collectStarterHints } from '../mcp-tools.ts';
|
|
4
4
|
import { loadAgentId } from './config.ts';
|
|
5
5
|
import { getDefaultAgentId } from './registry.ts';
|
|
6
|
+
import { listEventsSince, latestEventId } from '../embeddings.ts';
|
|
6
7
|
import type { AgentRuntime } from './types.ts';
|
|
7
8
|
export interface StartersDeps {
|
|
8
9
|
getPluginSettings: typeof getPluginSettings;
|
|
@@ -12,8 +13,11 @@ export interface StartersDeps {
|
|
|
12
13
|
loadAgentId: typeof loadAgentId;
|
|
13
14
|
resolveAgent: (id?: string) => AgentRuntime;
|
|
14
15
|
getDefaultAgentId: typeof getDefaultAgentId;
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
now: () => number;
|
|
17
|
+
shuffle: <T>(xs: T[]) => T[];
|
|
18
|
+
listEventsSince: typeof listEventsSince;
|
|
19
|
+
latestEventId: typeof latestEventId;
|
|
20
|
+
shelfSources: (slug: string) => string[];
|
|
17
21
|
}
|
|
18
|
-
export declare function getConversationStarters(deps?: StartersDeps): Promise<string[]>;
|
|
19
22
|
export declare function refreshSystemStarters(deps?: StartersDeps): Promise<void>;
|
|
23
|
+
export declare function getConversationStarters(deps?: StartersDeps): Promise<string[]>;
|
|
@@ -3,12 +3,30 @@ import { SYSTEM_SETTINGS_ID } from "../system-settings.js";
|
|
|
3
3
|
import { getPluginState, setPluginState } from "../plugin-state.js";
|
|
4
4
|
import { collectStarterHints } from "../mcp-tools.js";
|
|
5
5
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
6
|
-
import { todayDateString } from "./environment.js";
|
|
7
6
|
import { loadAgentId } from "./config.js";
|
|
8
7
|
import { resolveAgent, getDefaultAgentId } from "./registry.js";
|
|
8
|
+
import { listEventsSince, latestEventId } from "../embeddings.js";
|
|
9
|
+
import { defaultShelfSources } from "./starter-sources.js";
|
|
9
10
|
const log = getLogger('orchestrator');
|
|
10
11
|
const PLUGIN = 'orchestrator';
|
|
11
12
|
const STATE_KEY = 'system_starters';
|
|
13
|
+
const POOL_VERSION = 2;
|
|
14
|
+
const ENTRY_TTL_MS = 24 * 60 * 60_000;
|
|
15
|
+
const EARLY_DEBOUNCE_MS = 60 * 60_000;
|
|
16
|
+
const EARLY_COOLDOWN_MS = 6 * 60 * 60_000;
|
|
17
|
+
const EARLY_BUDGET_PER_DAY = 10;
|
|
18
|
+
const SAMPLE_MIN = 4;
|
|
19
|
+
const SAMPLE_MAX = 5;
|
|
20
|
+
const PER_SOURCE_SOFT_CAP = 2;
|
|
21
|
+
const EVENT_BATCH = 5000;
|
|
22
|
+
function shuffled(xs) {
|
|
23
|
+
const out = [...xs];
|
|
24
|
+
for (let i = out.length - 1; i > 0; i--) {
|
|
25
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
26
|
+
[out[i], out[j]] = [out[j], out[i]];
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
12
30
|
const defaultDeps = {
|
|
13
31
|
getPluginSettings,
|
|
14
32
|
getPluginState,
|
|
@@ -17,65 +35,177 @@ const defaultDeps = {
|
|
|
17
35
|
loadAgentId,
|
|
18
36
|
resolveAgent,
|
|
19
37
|
getDefaultAgentId,
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
now: () => Date.now(),
|
|
39
|
+
shuffle: shuffled,
|
|
40
|
+
listEventsSince,
|
|
41
|
+
latestEventId,
|
|
42
|
+
shelfSources: defaultShelfSources,
|
|
22
43
|
};
|
|
23
44
|
async function currentLanguage(deps) {
|
|
24
45
|
const system = await deps.getPluginSettings(SYSTEM_SETTINGS_ID);
|
|
25
46
|
const v = system['language'];
|
|
26
47
|
return typeof v === 'string' && v ? v : 'en';
|
|
27
48
|
}
|
|
28
|
-
|
|
49
|
+
function isPool(v) {
|
|
50
|
+
if (typeof v !== 'object' || v === null)
|
|
51
|
+
return false;
|
|
52
|
+
const p = v;
|
|
53
|
+
return p.version === POOL_VERSION && typeof p.language === 'string' && typeof p.sources === 'object' && !!p.sources;
|
|
54
|
+
}
|
|
55
|
+
async function readPool(deps) {
|
|
29
56
|
const raw = await deps.getPluginState(PLUGIN, STATE_KEY);
|
|
30
57
|
if (!raw)
|
|
31
58
|
return null;
|
|
32
59
|
try {
|
|
33
|
-
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
return isPool(parsed) ? parsed : null;
|
|
34
62
|
}
|
|
35
63
|
catch {
|
|
36
64
|
return null;
|
|
37
65
|
}
|
|
38
66
|
}
|
|
39
|
-
|
|
40
|
-
return
|
|
67
|
+
function fresh(entry, nowMs) {
|
|
68
|
+
return nowMs - Date.parse(entry.generatedAt) < ENTRY_TTL_MS;
|
|
69
|
+
}
|
|
70
|
+
function dayKey(nowMs) {
|
|
71
|
+
return new Date(nowMs).toISOString().slice(0, 10);
|
|
72
|
+
}
|
|
73
|
+
function earlyBudgetLeft(pool, nowMs) {
|
|
74
|
+
const spent = pool.earlyRuns;
|
|
75
|
+
if (!spent || spent.date !== dayKey(nowMs))
|
|
76
|
+
return true;
|
|
77
|
+
return spent.count < EARLY_BUDGET_PER_DAY;
|
|
78
|
+
}
|
|
79
|
+
function countEarlyRun(pool, nowMs) {
|
|
80
|
+
const key = dayKey(nowMs);
|
|
81
|
+
pool.earlyRuns =
|
|
82
|
+
pool.earlyRuns?.date === key ? { date: key, count: pool.earlyRuns.count + 1 } : { date: key, count: 1 };
|
|
83
|
+
}
|
|
84
|
+
function pickEarly(pool, hints, nowMs) {
|
|
85
|
+
const at = (h) => Date.parse(pool.sources[h.id].generatedAt);
|
|
86
|
+
return hints
|
|
87
|
+
.filter((h) => {
|
|
88
|
+
const e = pool.sources[h.id];
|
|
89
|
+
if (!e?.dirtySince)
|
|
90
|
+
return false;
|
|
91
|
+
if (nowMs - Date.parse(e.dirtySince) < EARLY_DEBOUNCE_MS)
|
|
92
|
+
return false;
|
|
93
|
+
return nowMs - Date.parse(e.generatedAt) >= EARLY_COOLDOWN_MS;
|
|
94
|
+
})
|
|
95
|
+
.sort((a, b) => at(a) - at(b))[0];
|
|
96
|
+
}
|
|
97
|
+
function tryResolve(deps, agentId) {
|
|
98
|
+
try {
|
|
99
|
+
return deps.resolveAgent(agentId);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function markChangedSources(deps, pool, declared, nowMs) {
|
|
106
|
+
if (pool.eventCursor == null) {
|
|
107
|
+
pool.eventCursor = await deps.latestEventId();
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
const rows = await deps.listEventsSince(pool.eventCursor, EVENT_BATCH);
|
|
111
|
+
if (!rows.length)
|
|
112
|
+
return false;
|
|
113
|
+
for (const row of rows) {
|
|
114
|
+
for (const id of deps.shelfSources(String(row.type ?? ''))) {
|
|
115
|
+
if (!declared.has(id))
|
|
116
|
+
continue;
|
|
117
|
+
const entry = pool.sources[id];
|
|
118
|
+
if (!entry || entry.dirtySince)
|
|
119
|
+
continue;
|
|
120
|
+
entry.dirtySince = new Date(nowMs).toISOString();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
pool.eventCursor = rows[rows.length - 1].id;
|
|
124
|
+
return true;
|
|
41
125
|
}
|
|
42
126
|
export async function refreshSystemStarters(deps = defaultDeps) {
|
|
43
|
-
const date = deps.today();
|
|
44
127
|
const [language, hints] = await Promise.all([currentLanguage(deps), deps.collectStarterHints()]);
|
|
45
128
|
const agentId = (await deps.loadAgentId()) ?? deps.getDefaultAgentId();
|
|
46
129
|
if (!agentId)
|
|
47
130
|
return;
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
runtime = deps.resolveAgent(agentId);
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return;
|
|
131
|
+
const stored = await readPool(deps);
|
|
132
|
+
const pool = stored && stored.language === language ? stored : { version: POOL_VERSION, language, sources: {} };
|
|
133
|
+
let dirty = pool !== stored;
|
|
134
|
+
const declared = new Set(hints.map((h) => h.id));
|
|
135
|
+
for (const id of Object.keys(pool.sources)) {
|
|
136
|
+
if (!declared.has(id)) {
|
|
137
|
+
delete pool.sources[id];
|
|
138
|
+
dirty = true;
|
|
139
|
+
}
|
|
62
140
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
141
|
+
const nowMs = deps.now();
|
|
142
|
+
if (await markChangedSources(deps, pool, declared, nowMs))
|
|
143
|
+
dirty = true;
|
|
144
|
+
const stale = hints.find((h) => {
|
|
145
|
+
const e = pool.sources[h.id];
|
|
146
|
+
if (!e)
|
|
147
|
+
return true;
|
|
148
|
+
if (e.hintHash !== h.hash || e.agentId !== agentId)
|
|
149
|
+
return true;
|
|
150
|
+
return !fresh(e, nowMs);
|
|
151
|
+
});
|
|
152
|
+
const early = stale || !earlyBudgetLeft(pool, nowMs) ? undefined : pickEarly(pool, hints, nowMs);
|
|
153
|
+
const target = stale ?? early;
|
|
154
|
+
if (target) {
|
|
155
|
+
const runtime = tryResolve(deps, agentId);
|
|
156
|
+
if (runtime?.starters) {
|
|
157
|
+
try {
|
|
158
|
+
const messages = await runtime.starters(target.text);
|
|
159
|
+
pool.sources[target.id] = {
|
|
160
|
+
messages,
|
|
161
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
162
|
+
hintHash: target.hash,
|
|
163
|
+
agentId,
|
|
164
|
+
};
|
|
165
|
+
if (target === early)
|
|
166
|
+
countEarlyRun(pool, nowMs);
|
|
167
|
+
dirty = true;
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
log.warn(`starters for ${target.id} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
68
173
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
174
|
+
if (dirty)
|
|
175
|
+
await setPool(deps, pool);
|
|
176
|
+
}
|
|
177
|
+
async function setPool(deps, pool) {
|
|
178
|
+
await deps.setPluginState(PLUGIN, STATE_KEY, JSON.stringify(pool));
|
|
179
|
+
}
|
|
180
|
+
export async function getConversationStarters(deps = defaultDeps) {
|
|
181
|
+
const [pool, language] = await Promise.all([readPool(deps), currentLanguage(deps)]);
|
|
182
|
+
if (!pool)
|
|
183
|
+
return [];
|
|
184
|
+
if (pool.language !== language)
|
|
185
|
+
return [];
|
|
186
|
+
const nowMs = deps.now();
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
const primary = [];
|
|
189
|
+
const spare = [];
|
|
190
|
+
for (const entry of deps.shuffle(Object.values(pool.sources))) {
|
|
191
|
+
if (!fresh(entry, nowMs))
|
|
192
|
+
continue;
|
|
193
|
+
let taken = 0;
|
|
194
|
+
for (const message of deps.shuffle(entry.messages)) {
|
|
195
|
+
if (seen.has(message))
|
|
196
|
+
continue;
|
|
197
|
+
seen.add(message);
|
|
198
|
+
if (taken < PER_SOURCE_SOFT_CAP) {
|
|
199
|
+
primary.push(message);
|
|
200
|
+
taken++;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
spare.push(message);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
72
206
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
agentId,
|
|
78
|
-
starters,
|
|
79
|
-
generatedAt: deps.now(),
|
|
80
|
-
}));
|
|
207
|
+
const out = deps.shuffle(primary).slice(0, SAMPLE_MAX);
|
|
208
|
+
if (out.length < SAMPLE_MIN)
|
|
209
|
+
out.push(...deps.shuffle(spare).slice(0, SAMPLE_MAX - out.length));
|
|
210
|
+
return out;
|
|
81
211
|
}
|
|
@@ -8,7 +8,7 @@ export interface AgentToolDefinition {
|
|
|
8
8
|
name: string;
|
|
9
9
|
description: string;
|
|
10
10
|
inputSchema: Record<string, unknown>;
|
|
11
|
-
handler: (args: Record<string, unknown
|
|
11
|
+
handler: (args: Record<string, unknown>, signal?: AbortSignal) => Promise<unknown>;
|
|
12
12
|
forcedAfterAnswer?: boolean;
|
|
13
13
|
}
|
|
14
14
|
export interface AgentToolProvider {
|
|
@@ -83,7 +83,7 @@ export interface AgentRuntime {
|
|
|
83
83
|
run(request: AgentRequest): Promise<AgentResult>;
|
|
84
84
|
systemBase(): Promise<string>;
|
|
85
85
|
describe(): Promise<AgentDescriptor>;
|
|
86
|
-
starters?(): Promise<string[]>;
|
|
86
|
+
starters?(hint: string): Promise<string[]>;
|
|
87
87
|
}
|
|
88
88
|
export interface ConnectorRegistration {
|
|
89
89
|
id: string;
|
package/dist/plugin-hooks.d.ts
CHANGED
|
@@ -25,8 +25,9 @@ export interface Migration {
|
|
|
25
25
|
export interface PluginCtx {
|
|
26
26
|
em: EntityManager;
|
|
27
27
|
log: Logger;
|
|
28
|
+
signal?: AbortSignal;
|
|
28
29
|
}
|
|
29
|
-
export declare function pluginCtx(id: string, em: EntityManager): PluginCtx;
|
|
30
|
+
export declare function pluginCtx(id: string, em: EntityManager, signal?: AbortSignal): PluginCtx;
|
|
30
31
|
export interface Seed {
|
|
31
32
|
name: string;
|
|
32
33
|
run(ctx: PluginCtx): Promise<void> | void;
|
package/dist/plugin-hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
2
|
-
export function pluginCtx(id, em) {
|
|
3
|
-
return { em, log: getLogger(id) };
|
|
2
|
+
export function pluginCtx(id, em, signal) {
|
|
3
|
+
return { em, log: getLogger(id), ...(signal ? { signal } : {}) };
|
|
4
4
|
}
|
|
5
5
|
export class HttpError extends Error {
|
|
6
6
|
status;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"postpack": "node ../../scripts/swap-exports.mjs src"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@coffer-org/sdk": "^
|
|
39
|
+
"@coffer-org/sdk": "^7.1.0",
|
|
40
40
|
"@extractus/oembed-extractor": "^4.1.0",
|
|
41
41
|
"@fastify/cors": "^11.2.0",
|
|
42
42
|
"@fastify/multipart": "^10.0.0",
|