@coffer-org/server 4.0.0 → 6.0.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.
@@ -1 +1 @@
1
- export declare function frontendInstructions(): Promise<string | null>;
1
+ export declare function frontendInstructions(siteUrl: string): Promise<string | null>;
@@ -10,9 +10,8 @@ import { readFile } from 'node:fs/promises';
10
10
  import { join } from 'node:path';
11
11
  import { pathToFileURL } from 'node:url';
12
12
  import { getLogger } from '@coffer-org/sdk/logger';
13
- import { configuredPublicUrl } from "./public-url.js";
14
13
  const log = getLogger('frontend-agent');
15
- export async function frontendInstructions() {
14
+ export async function frontendInstructions(siteUrl) {
16
15
  const dist = process.env['WEB_DIST'];
17
16
  if (!dist)
18
17
  return null;
@@ -27,7 +26,7 @@ export async function frontendInstructions() {
27
26
  log.warn(`frontend ${rel} exports no agent() — no site-link instructions`);
28
27
  return null;
29
28
  }
30
- const text = await mod.agent({ siteUrl: await configuredPublicUrl() });
29
+ const text = await mod.agent({ siteUrl });
31
30
  return typeof text === 'string' && text.trim() ? text : null;
32
31
  }
33
32
  catch (e) {
@@ -0,0 +1,9 @@
1
+ export interface LocaleResolver {
2
+ lang: string;
3
+ resolve(key: string): string | undefined;
4
+ }
5
+ export declare function loadComposedLocales(opts?: {
6
+ dirs?: string[];
7
+ lang?: string;
8
+ }): Promise<LocaleResolver>;
9
+ export declare function clearComposedLocaleCache(): void;
@@ -0,0 +1,51 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { pluginPackageNames } from "./plugin-discovery.js";
4
+ import { systemLanguage } from "./plugin-i18n.js";
5
+ function deepMerge(into, from) {
6
+ for (const [k, v] of Object.entries(from)) {
7
+ const prev = into[k];
8
+ if (v && typeof v === 'object' && !Array.isArray(v) && prev && typeof prev === 'object' && !Array.isArray(prev)) {
9
+ deepMerge(prev, v);
10
+ }
11
+ else {
12
+ into[k] = v;
13
+ }
14
+ }
15
+ }
16
+ async function pluginLocaleDirs() {
17
+ const nm = join(process.cwd(), 'node_modules');
18
+ return (await pluginPackageNames(nm)).map((name) => join(nm, name, 'locales'));
19
+ }
20
+ function makeResolver(lang, dict) {
21
+ return {
22
+ lang,
23
+ resolve(key) {
24
+ const value = key
25
+ .split('.')
26
+ .reduce((node, part) => (node == null ? undefined : node[part]), dict);
27
+ return typeof value === 'string' ? value : undefined;
28
+ },
29
+ };
30
+ }
31
+ let cached = null;
32
+ export async function loadComposedLocales(opts = {}) {
33
+ const lang = opts.lang ?? (await systemLanguage('uk'));
34
+ if (!opts.dirs && cached?.lang === lang)
35
+ return makeResolver(lang, cached.dict);
36
+ const dirs = opts.dirs ?? (await pluginLocaleDirs());
37
+ const dict = {};
38
+ for (const dir of dirs) {
39
+ try {
40
+ deepMerge(dict, JSON.parse(await readFile(join(dir, `${lang}.json`), 'utf8')));
41
+ }
42
+ catch {
43
+ }
44
+ }
45
+ if (!opts.dirs)
46
+ cached = { lang, dict };
47
+ return makeResolver(lang, dict);
48
+ }
49
+ export function clearComposedLocaleCache() {
50
+ cached = null;
51
+ }
@@ -1,3 +1,4 @@
1
+ import type { LocaleResolver } from '../locale-registry.ts';
1
2
  export interface ShelfIndexEntry {
2
3
  library: string;
3
4
  libraryLabel: string;
@@ -9,6 +10,7 @@ export interface FieldInfo {
9
10
  kind: string;
10
11
  prim: string;
11
12
  required: boolean;
13
+ agent?: string;
12
14
  relation?: {
13
15
  library: string;
14
16
  shelf: string;
@@ -27,6 +29,7 @@ export interface ShelfDescription {
27
29
  library: string;
28
30
  shelf: string;
29
31
  label: string;
32
+ agent?: string;
30
33
  fields: FieldInfo[];
31
34
  }
32
35
  interface SchemaClient {
@@ -35,8 +38,10 @@ interface SchemaClient {
35
38
  export declare function flattenFields(items: unknown[]): FieldInfo[];
36
39
  export declare class SchemaCache {
37
40
  private client;
41
+ private locales?;
38
42
  private cached?;
39
- constructor(client: SchemaClient);
43
+ constructor(client: SchemaClient, locales?: LocaleResolver | undefined);
44
+ private display;
40
45
  refresh(): void;
41
46
  private load;
42
47
  index(): Promise<ShelfIndexEntry[]>;
@@ -34,6 +34,7 @@ export function flattenFields(items) {
34
34
  kind: t.kind,
35
35
  prim: t.prim,
36
36
  required: t.required,
37
+ ...(t.agent ? { agent: t.agent } : {}),
37
38
  ...(t.relation ? { relation: t.relation } : {}),
38
39
  ...(t.options ? { options: t.options } : {}),
39
40
  ...(t.prim === 'file' ? { write: FILE_WRITE_HINT } : {}),
@@ -50,6 +51,7 @@ export function flattenFields(items) {
50
51
  kind: 'group',
51
52
  prim: 'group',
52
53
  required: it.required === true,
54
+ ...(it.agent ? { agent: it.agent } : {}),
53
55
  ...(it.multiple ? { multiple: true } : {}),
54
56
  fields: flattenFields(it.fields),
55
57
  });
@@ -60,9 +62,14 @@ export function flattenFields(items) {
60
62
  }
61
63
  export class SchemaCache {
62
64
  client;
65
+ locales;
63
66
  cached;
64
- constructor(client) {
67
+ constructor(client, locales) {
65
68
  this.client = client;
69
+ this.locales = locales;
70
+ }
71
+ display(labelKey, id) {
72
+ return (labelKey ? this.locales?.resolve(labelKey) : undefined) ?? id;
66
73
  }
67
74
  refresh() {
68
75
  this.cached = undefined;
@@ -74,7 +81,12 @@ export class SchemaCache {
74
81
  }
75
82
  async index() {
76
83
  const libraries = await this.load();
77
- return libraries.flatMap((v) => v.shelves.map((m) => ({ library: v.id, libraryLabel: v.label, shelf: m.shelf, label: m.label })));
84
+ return libraries.flatMap((v) => v.shelves.map((m) => ({
85
+ library: v.id,
86
+ libraryLabel: this.display(v.label, v.id),
87
+ shelf: m.shelf,
88
+ label: this.display(m.label, m.shelf),
89
+ })));
78
90
  }
79
91
  async describeShelf(library, shelf) {
80
92
  let m = this.find(await this.load(), library, shelf);
@@ -82,7 +94,13 @@ export class SchemaCache {
82
94
  m = this.find(await this.load(true), library, shelf);
83
95
  if (!m)
84
96
  throw new Error(`unknown_shelf ${library}/${shelf}`);
85
- return { library, shelf, label: m.label, fields: flattenFields(m.fields) };
97
+ return {
98
+ library,
99
+ shelf,
100
+ label: this.display(m.label, m.shelf),
101
+ ...(m.agent ? { agent: m.agent } : {}),
102
+ fields: flattenFields(m.fields),
103
+ };
86
104
  }
87
105
  find(libraries, library, shelf) {
88
106
  return libraries.find((v) => v.id === library)?.shelves.find((m) => m.shelf === shelf);
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { type CofferClientApi } from './client.ts';
3
3
  import { SchemaCache } from './schema.ts';
4
+ import type { LocaleResolver } from '../locale-registry.ts';
4
5
  export interface ToolResult {
5
6
  content: {
6
7
  type: 'text';
@@ -16,4 +17,4 @@ export interface ToolDef {
16
17
  }
17
18
  export declare const LIST_DEFAULT_LIMIT = 25;
18
19
  export declare const LIST_MAX_LIMIT = 200;
19
- export declare function buildTools(client: CofferClientApi, cache: SchemaCache): ToolDef[];
20
+ export declare function buildTools(client: CofferClientApi, cache: SchemaCache, locales?: LocaleResolver): ToolDef[];
@@ -40,7 +40,7 @@ async function guard(fn) {
40
40
  }
41
41
  export const LIST_DEFAULT_LIMIT = 25;
42
42
  export const LIST_MAX_LIMIT = 200;
43
- export function buildTools(client, cache) {
43
+ export function buildTools(client, cache, locales) {
44
44
  const library = z.string().describe('Library identifier, e.g. "things"');
45
45
  const shelf = z.string().describe('Shelf identifier, e.g. "item"');
46
46
  const id = z.coerce.number().int().describe('record id');
@@ -53,12 +53,38 @@ export function buildTools(client, cache) {
53
53
  },
54
54
  {
55
55
  name: 'describe_shelf',
56
- description: 'Detailed shelf schema: fields with kind/prim/required and relation/options. A collection is one entry with ' +
57
- 'multiple:true and its row shape in fields[]. A field with derived:true is computed by the server and rejects ' +
58
- 'writes — omit it when writing. Call BEFORE create_record/update_record.',
56
+ description: 'Detailed shelf schema: the shelf note (agent), then fields with kind/prim/required, relation/options, and ' +
57
+ 'a per-field note (agent) saying what that field means. A collection is one entry with multiple:true and its ' +
58
+ 'row shape in fields[]. A field with derived:true is computed by the server and rejects writes — omit it when ' +
59
+ 'writing. Call BEFORE create_record/update_record.',
59
60
  inputSchema: { library, shelf },
60
61
  handler: (a) => guard(() => cache.describeShelf(a.library, a.shelf)),
61
62
  },
63
+ {
64
+ name: 'translate',
65
+ description: "Resolve interface text keys (dotted, e.g. 'things.item.options.condition.good') to their words in this " +
66
+ "instance's language. Use it when a payload hands you a key instead of a word — an option's title, an " +
67
+ 'extra field-set name — and you need to say that thing to the user. It is for NAMING things in your answer: ' +
68
+ 'tool arguments still take machine ids, so never send a translated word back as a library, shelf, field or ' +
69
+ 'value. A key this instance cannot resolve comes back under "missing" — say you do not have a name for it ' +
70
+ 'rather than inventing one.',
71
+ inputSchema: {
72
+ keys: z.array(z.string()).describe('Dotted interface text keys to resolve'),
73
+ },
74
+ handler: async (a) => {
75
+ const keys = Array.isArray(a.keys) ? a.keys.map((k) => String(k)) : [];
76
+ const resolved = {};
77
+ const missing = [];
78
+ for (const k of keys) {
79
+ const v = locales?.resolve(k);
80
+ if (v === undefined)
81
+ missing.push(k);
82
+ else
83
+ resolved[k] = v;
84
+ }
85
+ return ok({ resolved, missing });
86
+ },
87
+ },
62
88
  {
63
89
  name: 'refresh_schema',
64
90
  description: 'Re-read the schema from the server. Call after enabling/installing a plugin (server restart required).',
package/dist/mcp-http.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
- import { collectMcpTools, resolveRagDeps, buildDomainSections, buildMcpInstructions } from "./mcp-tools.js";
3
+ import { collectMcpTools, resolveRagDeps, buildDomainSections, buildMcpInstructions, siteSection, } from "./mcp-tools.js";
4
+ import { configuredPublicUrl } from "./public-url.js";
4
5
  import { getLogger } from "./log.js";
5
6
  const log = getLogger('mcp-http');
6
7
  export async function buildMcpServer(role, actor) {
7
8
  const rag = await resolveRagDeps();
8
9
  const tools = (await collectMcpTools({ rag, includeAdmin: role === 'admin', actor })).filter((t) => role === 'admin' || t.role === 'member');
9
- const sections = await buildDomainSections();
10
+ const site = await siteSection(await configuredPublicUrl());
11
+ const sections = [...(await buildDomainSections()), ...(site ? [site] : [])];
10
12
  const server = new McpServer({ name: 'coffer', version: '1.0.0' }, { instructions: buildMcpInstructions(sections) });
11
13
  const registerTool = server.registerTool.bind(server);
12
14
  for (const t of tools) {
@@ -1,6 +1,7 @@
1
1
  import type { EntityManager } from '@mikro-orm/core';
2
2
  import { z } from 'zod';
3
3
  import { type ToolResult } from './mcp-contract/tools.ts';
4
+ import { type LocaleResolver } from './locale-registry.ts';
4
5
  import { type AuthRole, type PluginHooks } from './plugin-hooks.ts';
5
6
  import { type Condition } from '@coffer-org/sdk/condition';
6
7
  import { type RagHit } from './rag-search.ts';
@@ -31,23 +32,24 @@ export declare function collectPluginInstructions(hooks?: Record<string, PluginH
31
32
  type SingleShelf = {
32
33
  library: string;
33
34
  shelf: string;
34
- claude: string;
35
+ agent: string;
35
36
  };
36
37
  export declare function collectSingleShelves(reg?: {
37
38
  shelves: {
38
39
  library: string;
39
40
  shelf: string;
40
41
  single?: boolean;
41
- claude?: string;
42
+ agent?: string;
42
43
  }[];
43
44
  }): SingleShelf[];
44
45
  type LibraryPurpose = {
45
46
  id: string;
47
+ name: string;
46
48
  agent: string;
47
49
  extends: {
48
50
  id: string;
49
51
  shelf: string;
50
- claude: string;
52
+ agent: string;
51
53
  showWhen?: Condition;
52
54
  }[];
53
55
  };
@@ -55,28 +57,31 @@ export declare function collectLibraryPurposes(reg?: {
55
57
  libraries: {
56
58
  meta: {
57
59
  id: string;
60
+ label?: string;
58
61
  agent?: string;
59
62
  };
60
63
  }[];
61
64
  extends_: {
62
65
  id: string;
63
- claude?: string;
66
+ agent?: string;
64
67
  showWhen?: Condition;
65
68
  attachTo: {
66
69
  library: string;
67
70
  shelf: string;
68
71
  }[];
69
72
  }[];
70
- }): LibraryPurpose[];
71
- export declare function buildDomainSections(): Promise<string[]>;
73
+ }, locales?: LocaleResolver): LibraryPurpose[];
74
+ export declare function siteSection(siteUrl: string): Promise<string | null>;
75
+ export declare function buildDomainSections(locales?: LocaleResolver): Promise<string[]>;
72
76
  export declare function collectStarterHints(hooks?: Record<string, PluginHooks>, emFactory?: () => EntityManager, reg?: {
73
77
  libraries: {
74
78
  meta: {
75
79
  id: string;
80
+ label?: string;
76
81
  starterHint?: string;
77
82
  };
78
83
  }[];
79
- }): Promise<{
84
+ }, locales?: LocaleResolver): Promise<{
80
85
  combined: string;
81
86
  hash: string;
82
87
  }>;
package/dist/mcp-tools.js CHANGED
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
  import { buildTools } from "./mcp-contract/tools.js";
4
4
  import { SchemaCache } from "./mcp-contract/schema.js";
5
5
  import { LocalClient } from "./mcp-local.js";
6
+ import { loadComposedLocales } from "./locale-registry.js";
6
7
  import { frontendInstructions } from "./frontend-agent.js";
7
8
  import { mintUploadTicket } from "./upload-ticket.js";
8
9
  import { pluginHooks, pluginCtx } from "./plugin-hooks.js";
@@ -48,8 +49,9 @@ export async function resolveRagDeps() {
48
49
  export async function collectMcpTools(opts = {}) {
49
50
  const out = [];
50
51
  const client = new LocalClient();
51
- const cache = new SchemaCache(client);
52
- for (const t of buildTools(client, cache)) {
52
+ const locales = await loadComposedLocales();
53
+ const cache = new SchemaCache(client, locales);
54
+ for (const t of buildTools(client, cache, locales)) {
53
55
  out.push({
54
56
  server: 'coffer',
55
57
  bareName: t.name,
@@ -278,10 +280,10 @@ export function collectSingleShelves(reg) {
278
280
  if (!registry)
279
281
  return [];
280
282
  return registry.shelves
281
- .filter((s) => s.single && s.claude)
282
- .map((s) => ({ library: s.library, shelf: s.shelf, claude: s.claude }));
283
+ .filter((s) => s.single && s.agent)
284
+ .map((s) => ({ library: s.library, shelf: s.shelf, agent: s.agent }));
283
285
  }
284
- export function collectLibraryPurposes(reg) {
286
+ export function collectLibraryPurposes(reg, locales) {
285
287
  let registry = reg;
286
288
  if (!registry) {
287
289
  try {
@@ -297,27 +299,33 @@ export function collectLibraryPurposes(reg) {
297
299
  .filter((v) => v.meta.agent)
298
300
  .map((v) => ({
299
301
  id: v.meta.id,
302
+ name: (v.meta.label ? locales?.resolve(v.meta.label) : undefined) ?? v.meta.id,
300
303
  agent: v.meta.agent,
301
- extends: registry.extends_.flatMap((e) => e.claude
304
+ extends: registry.extends_.flatMap((e) => e.agent
302
305
  ? e.attachTo
303
306
  .filter((a) => a.library === v.meta.id)
304
- .map((a) => ({ id: e.id, shelf: a.shelf, claude: e.claude, showWhen: e.showWhen }))
307
+ .map((a) => ({ id: e.id, shelf: a.shelf, agent: e.agent, showWhen: e.showWhen }))
305
308
  : []),
306
309
  }));
307
310
  }
308
- export async function buildDomainSections() {
309
- const libraries = collectLibraryPurposes();
311
+ export async function siteSection(siteUrl) {
312
+ const site = await frontendInstructions(siteUrl);
313
+ return site ? `## web\n${site}` : null;
314
+ }
315
+ export async function buildDomainSections(locales) {
316
+ const i18n = locales ?? (await loadComposedLocales());
317
+ const libraries = collectLibraryPurposes(undefined, i18n);
310
318
  let overview = null;
311
319
  if (libraries.length) {
312
320
  const blocks = libraries.map((v) => {
313
- let s = `### ${v.id} — ${v.agent}`;
321
+ let s = v.name === v.id ? `### ${v.id} — ${v.agent}` : `### ${v.id} ("${v.name}") — ${v.agent}`;
314
322
  if (v.extends.length) {
315
323
  s +=
316
324
  '\nExtra field-sets some records carry (which one depends on the record):\n' +
317
325
  v.extends
318
326
  .map((e) => {
319
327
  const when = describeCondition(e.showWhen, (f) => f).join(' and ');
320
- return `- ${e.id} (on ${e.shelf}${when ? `, when ${when}` : ''}): ${e.claude.replace(/\s*\n\s*/g, ' ')}`;
328
+ return `- ${e.id} (on ${e.shelf}${when ? `, when ${when}` : ''}): ${e.agent.replace(/\s*\n\s*/g, ' ')}`;
321
329
  })
322
330
  .join('\n');
323
331
  }
@@ -329,7 +337,7 @@ export async function buildDomainSections() {
329
337
  const singles = collectSingleShelves();
330
338
  const singleSection = singles.length
331
339
  ? '## Single-record shelves (one document each — read the record, do not search the shelf)\n\n' +
332
- singles.map((s) => `- ${s.library}/${s.shelf}: ${s.claude.replace(/\s*\n\s*/g, ' ')}`).join('\n')
340
+ singles.map((s) => `- ${s.library}/${s.shelf}: ${s.agent.replace(/\s*\n\s*/g, ' ')}`).join('\n')
333
341
  : null;
334
342
  const dataModel = '## Data model\n' +
335
343
  'Library (top-level area) → shelf (a kind of record, e.g. things/item) → record (addressed library/shelf/id) → fields. ' +
@@ -341,16 +349,9 @@ export async function buildDomainSections() {
341
349
  'For duplicates, read both records completely, compare fields/collections/extends/attachments, recommend the less complete record for trash, and offer a field-by-field merge first. ' +
342
350
  'Use list_trash and restore_record for recovery; purge_record is irreversible and requires explicit confirmation.';
343
351
  const rules = (await collectPluginInstructions()).map(({ id, instructions }) => `## ${id}\n${instructions}`);
344
- const site = await frontendInstructions();
345
- return [
346
- dataModel,
347
- ...(overview ? [overview] : []),
348
- ...(singleSection ? [singleSection] : []),
349
- ...(site ? [`## web\n${site}`] : []),
350
- ...rules,
351
- ];
352
+ return [dataModel, ...(overview ? [overview] : []), ...(singleSection ? [singleSection] : []), ...rules];
352
353
  }
353
- export async function collectStarterHints(hooks = pluginHooks, emFactory = () => getEm().fork(), reg) {
354
+ export async function collectStarterHints(hooks = pluginHooks, emFactory = () => getEm().fork(), reg, locales) {
354
355
  const parts = [];
355
356
  for (const [id, h] of Object.entries(hooks)) {
356
357
  const hint = h.agent?.starterHint;
@@ -374,9 +375,12 @@ export async function collectStarterHints(hooks = pluginHooks, emFactory = () =>
374
375
  registry = undefined;
375
376
  }
376
377
  }
378
+ const i18n = locales ?? (await loadComposedLocales());
377
379
  for (const v of registry?.libraries ?? []) {
378
- if (v.meta.starterHint)
379
- parts.push(`- ${v.meta.id}: ${v.meta.starterHint}`);
380
+ if (!v.meta.starterHint)
381
+ continue;
382
+ const name = v.meta.label ? i18n.resolve(v.meta.label) : undefined;
383
+ parts.push(`- ${v.meta.id}${name ? ` ("${name}")` : ''}: ${v.meta.starterHint}`);
380
384
  }
381
385
  const combined = parts.join('\n');
382
386
  const hash = createHash('sha1').update(combined).digest('hex');
@@ -1,3 +1,2 @@
1
1
  export declare function systemTimeZone(): string;
2
2
  export declare function todayDateString(now?: Date, timeZone?: string): string;
3
- export declare function currentEnvironment(now?: Date, timeZone?: string): string;
@@ -12,19 +12,3 @@ function part(now, timeZone, locale, options) {
12
12
  export function todayDateString(now = new Date(), timeZone = systemTimeZone()) {
13
13
  return part(now, timeZone, 'en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' });
14
14
  }
15
- export function currentEnvironment(now = new Date(), timeZone = systemTimeZone()) {
16
- let zone = timeZone;
17
- try {
18
- new Intl.DateTimeFormat('en-CA', { timeZone: zone }).format(now);
19
- }
20
- catch {
21
- zone = 'UTC';
22
- }
23
- const date = part(now, zone, 'en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' });
24
- const weekday = part(now, zone, 'en-US', { weekday: 'long' });
25
- const time = part(now, zone, 'en-GB', { hour: '2-digit', minute: '2-digit', hour12: false });
26
- return [
27
- `Current moment, refreshed on every turn: ${date} (${weekday}), ${time}, timezone ${zone}.`,
28
- 'Resolve every relative date against this value — "today", "tomorrow", "this week", ages, and expiry, warranty or due-date checks. Never infer the date from your training data and never tell the user you cannot know it.',
29
- ].join('\n');
30
- }
@@ -8,7 +8,7 @@ export { makeAttachmentCapabilities } from './agent-capabilities.ts';
8
8
  export { makeSystemCapabilities } from './system-capabilities.ts';
9
9
  export { makeSuggestionCapabilities } from './suggestion-capabilities.ts';
10
10
  export type { SuggestionCapability } from './suggestion-capabilities.ts';
11
- export { currentEnvironment, systemTimeZone } from './environment.ts';
11
+ export { systemTimeZone } from './environment.ts';
12
12
  export { inspectUpload, isAgentToolContentResult } from './file-inspection.ts';
13
13
  export type { PipelineDeps, RunAgentFn } from './pipeline.ts';
14
14
  export { buildPolicy, loadGatePolicy, loadAgentId } from './config.ts';
@@ -17,7 +17,7 @@ export { isSenderAllowed } from './allow.ts';
17
17
  export { makeLiveChannel, plainRender } from './live-message.ts';
18
18
  export type { LiveChannelOps, LiveChannelOpts, RenderFn } from './live-message.ts';
19
19
  export { chunk } from './format.ts';
20
- export type { Connector, IncomingConversation, GatePolicy, ReplyContext, ReplyPayload, ReplyChannel, AttachmentRef, AttachmentMaterializer, AgentToolDefinition, AgentToolProvider, ConvMessage, SystemLayer, AgentRequest, AgentResult, AgentRuntime, AgentCapabilities, AgentPreset, AgentDescriptor, ConnectorRegistration, AgentToolContentBlock, AgentToolContentResult, } from './types.ts';
20
+ export type { Connector, IncomingConversation, GatePolicy, ReplyContext, ReplyPayload, ReplyChannel, AttachmentRef, AttachmentMaterializer, AgentToolDefinition, AgentToolProvider, ConvMessage, AgentRequest, AgentResult, AgentRuntime, AgentCapabilities, AgentPreset, AgentDescriptor, ConnectorRegistration, AgentToolContentBlock, AgentToolContentResult, } from './types.ts';
21
21
  export declare function startOrchestrator(): void;
22
22
  export declare function stopOrchestrator(): void;
23
23
  export declare const orchestratorStartersTask: BackgroundTask;
@@ -12,7 +12,7 @@ export { attachmentMaterializer } from "./attachments.js";
12
12
  export { makeAttachmentCapabilities } from "./agent-capabilities.js";
13
13
  export { makeSystemCapabilities } from "./system-capabilities.js";
14
14
  export { makeSuggestionCapabilities } from "./suggestion-capabilities.js";
15
- export { currentEnvironment, systemTimeZone } from "./environment.js";
15
+ export { systemTimeZone } from "./environment.js";
16
16
  export { inspectUpload, isAgentToolContentResult } from "./file-inspection.js";
17
17
  export { buildPolicy, loadGatePolicy, loadAgentId } from "./config.js";
18
18
  export { getConversationStarters, refreshSystemStarters } from "./starters.js";
@@ -101,15 +101,15 @@ export async function handleIncoming(connector, conversation, deps) {
101
101
  const preparedMessages = conversation.prepareAttachments
102
102
  ? await safeCall(() => conversation.prepareAttachments(attachmentMaterializer), messages, 'prepareAttachments')
103
103
  : messages;
104
- const agentMessages = policy.triggerPrefix
105
- ? preparedMessages.map((m, i) => (i === preparedMessages.length - 1 ? { ...m, content: queryText } : m))
106
- : preparedMessages;
104
+ const agentMessages = preparedMessages.map((m, i) => i === preparedMessages.length - 1
105
+ ? {
106
+ ...m,
107
+ ...(policy.triggerPrefix ? { content: queryText } : {}),
108
+ ...(conversation.turnContext ? { context: conversation.turnContext } : {}),
109
+ }
110
+ : m);
107
111
  const base = await agentBase();
108
- const system = buildSystem({
109
- base,
110
- channelSystem: conversation.channelSystem,
111
- volatileSystem: conversation.volatileSystem,
112
- });
112
+ const system = buildSystem({ base, channelSystem: conversation.channelSystem });
113
113
  db?.logTurn({
114
114
  connector: connectorId,
115
115
  chatId,
@@ -1,7 +1,4 @@
1
- import type { SystemLayer } from './types.ts';
2
1
  export declare function buildSystem(input: {
3
2
  base: string;
4
3
  channelSystem?: string;
5
- volatileSystem?: string;
6
- environment?: string;
7
- }): SystemLayer[];
4
+ }): string;
@@ -1,13 +1,3 @@
1
- import { currentEnvironment } from "./environment.js";
2
1
  export function buildSystem(input) {
3
- const text = [input.base, input.channelSystem].filter(Boolean).join('\n\n');
4
- const layers = [{ text, stable: true }];
5
- const environment = input.environment ?? currentEnvironment();
6
- const volatile = [environment, input.volatileSystem]
7
- .map((part) => part?.trim())
8
- .filter(Boolean)
9
- .join('\n\n');
10
- if (volatile)
11
- layers.push({ text: volatile, stable: false });
12
- return layers;
2
+ return [input.base, input.channelSystem].filter(Boolean).join('\n\n');
13
3
  }
@@ -34,15 +34,12 @@ export interface AgentToolContentResult {
34
34
  export interface ConvMessage {
35
35
  role: 'user' | 'assistant';
36
36
  content: string;
37
+ context?: string;
37
38
  attachments?: AttachmentRef[];
38
39
  sender?: string | null;
39
40
  msgId: string;
40
41
  ts: number;
41
42
  }
42
- export interface SystemLayer {
43
- text: string;
44
- stable: boolean;
45
- }
46
43
  export interface AgentCapabilities {
47
44
  vision?: boolean;
48
45
  documents?: boolean;
@@ -64,7 +61,7 @@ export interface AgentDescriptor {
64
61
  presets: AgentPreset[];
65
62
  }
66
63
  export interface AgentRequest {
67
- system: SystemLayer[];
64
+ system: string;
68
65
  messages: ConvMessage[];
69
66
  toolProvider?: AgentToolProvider;
70
67
  presetId?: string;
@@ -103,7 +100,7 @@ export interface IncomingConversation {
103
100
  presetId?: string;
104
101
  chatId: string;
105
102
  channelSystem?: string;
106
- volatileSystem?: string;
103
+ turnContext?: string;
107
104
  sender: {
108
105
  id: string;
109
106
  displayName?: string;
@@ -1,5 +1,6 @@
1
1
  import type { PluginManifest } from '@coffer-org/sdk/plugin';
2
2
  import type { PluginHooks } from './plugin-hooks.ts';
3
+ export declare function pluginPackageNames(nm: string): Promise<string[]>;
3
4
  export interface PluginAssetPaths {
4
5
  schema: string;
5
6
  web?: string;
@@ -11,7 +11,7 @@ import { join, sep } from 'node:path';
11
11
  import { pathToFileURL } from 'node:url';
12
12
  import { getLogger } from '@coffer-org/sdk/logger';
13
13
  const log = getLogger('discovery');
14
- async function pkgNames(nm) {
14
+ export async function pluginPackageNames(nm) {
15
15
  const names = [];
16
16
  try {
17
17
  for (const d of await readdir(join(nm, '@coffer-org'))) {
@@ -58,7 +58,7 @@ export async function discoverPluginAssets() {
58
58
  if (assetsCache)
59
59
  return assetsCache;
60
60
  const nm = join(process.cwd(), 'node_modules');
61
- const names = await pkgNames(nm);
61
+ const names = await pluginPackageNames(nm);
62
62
  const out = [];
63
63
  for (const name of names) {
64
64
  try {
@@ -121,7 +121,7 @@ async function loadManifest(nm, name) {
121
121
  }
122
122
  export async function discoverPlugins() {
123
123
  const nm = join(process.cwd(), 'node_modules');
124
- const names = await pkgNames(nm);
124
+ const names = await pluginPackageNames(nm);
125
125
  const manifests = [];
126
126
  for (const name of names) {
127
127
  const m = await loadManifest(nm, name);
@@ -132,7 +132,7 @@ export async function discoverPlugins() {
132
132
  }
133
133
  export async function loadServerHooks() {
134
134
  const nm = join(process.cwd(), 'node_modules');
135
- const names = await pkgNames(nm);
135
+ const names = await pluginPackageNames(nm);
136
136
  const hooks = {};
137
137
  for (const name of names) {
138
138
  try {
@@ -1,3 +1,4 @@
1
+ export declare function systemLanguage(fallback: string): Promise<string>;
1
2
  export interface PluginI18n {
2
3
  lang: string;
3
4
  t(key: string, fallback?: string): string;
@@ -1,6 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  const cache = new Map();
3
- async function systemLanguage(fallback) {
3
+ export async function systemLanguage(fallback) {
4
4
  try {
5
5
  const { getPluginSettings } = await import("./plugin-runtime.js");
6
6
  const { SYSTEM_SETTINGS_ID } = await import("./system-settings.js");
@@ -12,14 +12,14 @@ export const SYSTEM_SETTINGS = defineSettings({
12
12
  label: 'core.settings.language',
13
13
  default: 'uk',
14
14
  strict: true,
15
- view: { noSearch: true },
15
+ noSearch: true,
16
16
  options: [
17
17
  { value: 'uk', title: 'Українська' },
18
18
  { value: 'ru', title: 'Русский' },
19
19
  { value: 'en', title: 'English' },
20
20
  ],
21
21
  }),
22
- agent_id: field.string({ label: 'core.settings.agent_id', strict: true, view: { noSearch: true } }),
22
+ agent_id: field.string({ label: 'core.settings.agent_id', strict: true, noSearch: true }),
23
23
  access_password: field.password({ label: 'core.settings.access_password' }),
24
24
  trigger_prefix: field.string({ label: 'core.settings.trigger_prefix' }),
25
25
  reply_window: field.int({ label: 'core.settings.reply_window', default: 1800 }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/server",
3
- "version": "4.0.0",
3
+ "version": "6.0.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": "^4.0.0",
39
+ "@coffer-org/sdk": "^6.0.0",
40
40
  "@extractus/oembed-extractor": "^4.1.0",
41
41
  "@fastify/cors": "^11.2.0",
42
42
  "@fastify/multipart": "^10.0.0",