@coffer-org/plugin-orchestrator 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { field } from '@coffer-org/sdk/fields';
4
4
  export default definePlugin({
5
5
  id: 'orchestrator',
6
6
  version: '1.0.0',
7
- dependsOn: ['agent'],
7
+ dependsOn: ['claude-agent'],
8
8
  settings: defineSettings({
9
9
  label: 'orchestrator.settings.label',
10
10
  fields: [
@@ -4,7 +4,6 @@ export type LogTurn = {
4
4
  userId: string;
5
5
  role: 'user' | 'assistant';
6
6
  text: string;
7
- sessionId: string | null;
8
7
  tokensIn: number | null;
9
8
  tokensOut: number | null;
10
9
  ms: number | null;
@@ -1,7 +1,9 @@
1
1
  import { logMessage } from '@coffer-org/server/msg-log';
2
+ import { getLogger } from '@coffer-org/sdk/logger';
3
+ const log = getLogger('orchestrator');
2
4
  export function openLogDb() {
3
5
  return {
4
- logTurn(row) { logMessage(row).catch((e) => console.error('[orchestrator] logTurn:', e)); },
6
+ logTurn(row) { logMessage(row).catch((e) => log.error('logTurn failed', e)); },
5
7
  close() { },
6
8
  };
7
9
  }
@@ -2,5 +2,5 @@ import type { PluginHooks } from '@coffer-org/server/plugin-hooks';
2
2
  export { handleIncoming } from './pipeline.ts';
3
3
  export type { PipelineDeps, RunAgentFn } from './pipeline.ts';
4
4
  export { buildPolicy, loadGatePolicy } from './config.ts';
5
- export type { Connector, ConnectorCapabilities, IncomingPayload, GatePolicy } from './types.ts';
5
+ export type { Connector, ConnectorCapabilities, IncomingConversation, GatePolicy, ConvMessage, SystemLayer, AgentRequest, AgentResult, } from './types.ts';
6
6
  export declare const serverHooks: PluginHooks;
@@ -1,5 +1,5 @@
1
- import { runAgent } from '@coffer-org/plugin-agent/runtime';
2
- import type { Connector, IncomingPayload, GatePolicy } from './types.ts';
1
+ import { runAgent } from '@coffer-org/plugin-claude-agent/runtime';
2
+ import type { Connector, IncomingConversation, GatePolicy } from './types.ts';
3
3
  import type { LogDb } from './db.ts';
4
4
  export declare function setLogDb(db: LogDb | undefined): void;
5
5
  export type RunAgentFn = typeof runAgent;
@@ -8,5 +8,6 @@ export interface PipelineDeps {
8
8
  logDb?: LogDb | null;
9
9
  throttleMs?: number;
10
10
  policy?: GatePolicy;
11
+ agentBase?: () => Promise<string>;
11
12
  }
12
- export declare function handleIncoming(connector: Connector, payload: IncomingPayload, deps?: PipelineDeps): Promise<void>;
13
+ export declare function handleIncoming(connector: Connector, conversation: IncomingConversation, deps?: PipelineDeps): Promise<void>;
@@ -1,9 +1,11 @@
1
- import { runAgent } from '@coffer-org/plugin-agent/runtime';
1
+ import { runAgent, agentSystemBase } from '@coffer-org/plugin-claude-agent/runtime';
2
2
  import { chunk } from "./format.js";
3
3
  import { join } from 'node:path';
4
- import { loadThreads, saveThreads, resolveParent, getSession, recordSession, pruneThreads, } from "./threads.js";
5
4
  import { loadAllowed, saveAllowed, isAllowed, addAllowed, makeThrottle } from "./allow.js";
6
5
  import { loadGatePolicy } from "./config.js";
6
+ import { buildSystem } from "./system-assembly.js";
7
+ import { getLogger } from '@coffer-org/sdk/logger';
8
+ const log = getLogger('orchestrator');
7
9
  const DEFAULT_STREAM_THROTTLE_MS = 2000;
8
10
  const stateDir = () => process.env['ORCHESTRATOR_STATE_DIR']
9
11
  ?? join(new URL('../../runtime/state', import.meta.url).pathname);
@@ -18,48 +20,55 @@ function passThrottle(connectorId) {
18
20
  }
19
21
  return t;
20
22
  }
23
+ let cachedBase;
24
+ function cachedAgentBase() { return (cachedBase ??= agentSystemBase()); }
21
25
  async function safeCall(fn, fallback, label) {
22
26
  try {
23
27
  return await fn();
24
28
  }
25
29
  catch (err) {
26
- console.error(`[orchestrator] connector error in ${label}:`, err instanceof Error ? err.message : String(err));
30
+ log.error(`connector error in ${label}: ${err instanceof Error ? err.message : String(err)}`);
27
31
  return fallback;
28
32
  }
29
33
  }
30
- export async function handleIncoming(connector, payload, deps) {
34
+ export async function handleIncoming(connector, conversation, deps) {
31
35
  const agent = deps?.runAgent ?? runAgent;
32
36
  const db = deps && 'logDb' in deps ? deps.logDb : logDb;
33
37
  const throttleMs = deps?.throttleMs ?? DEFAULT_STREAM_THROTTLE_MS;
34
38
  const policy = deps?.policy ?? await loadGatePolicy();
35
- const { chatId, userId, text, replyToId } = payload;
39
+ const agentBase = deps?.agentBase ?? cachedAgentBase;
40
+ const { connectorId, chatId, sender, messages } = conversation;
41
+ const last = messages[messages.length - 1];
42
+ if (!last || last.role !== 'user')
43
+ return;
44
+ const incomingMsgId = last.msgId;
36
45
  const dir = stateDir();
37
- const allowFile = join(dir, `${connector.id}.allowed.json`);
38
- const threadsFile = join(dir, `${connector.id}.threads.json`);
46
+ const allowFile = join(dir, `${connectorId}.allowed.json`);
39
47
  if (policy.accessPassword) {
40
48
  let allow = loadAllowed(allowFile);
41
- if (!isAllowed(allow, userId)) {
42
- if (text.trim() === policy.accessPassword) {
43
- allow = addAllowed(allow, userId, Date.now());
49
+ if (!isAllowed(allow, sender.id)) {
50
+ if (last.content.trim() === policy.accessPassword) {
51
+ allow = addAllowed(allow, sender.id, Date.now());
44
52
  saveAllowed(allowFile, allow);
45
53
  await safeCall(() => connector.sendMessage(chatId, '✅ Access granted. Send your requests.'), null, 'sendMessage(enroll)');
46
54
  }
47
- else if (passThrottle(connector.id)(userId)) {
55
+ else if (passThrottle(connectorId)(sender.id)) {
48
56
  await safeCall(() => connector.sendMessage(chatId, '🔒 Access locked. Send the password to gain access.'), null, 'sendMessage(locked)');
49
57
  }
50
58
  return;
51
59
  }
52
60
  }
53
- if (policy.triggerPrefix && !text.toLowerCase().startsWith(policy.triggerPrefix.toLowerCase()))
61
+ if (policy.triggerPrefix && !last.content.toLowerCase().startsWith(policy.triggerPrefix.toLowerCase()))
54
62
  return;
55
- const queryText = policy.triggerPrefix ? text.slice(policy.triggerPrefix.length).trim() : text.trim();
63
+ const queryText = policy.triggerPrefix ? last.content.slice(policy.triggerPrefix.length).trim() : last.content.trim();
56
64
  if (!queryText)
57
65
  return;
58
- const now = Date.now();
59
- let threads = loadThreads(threadsFile);
60
- const parent = resolveParent(threads, { replyToMsgId: replyToId, now }, policy.replyWindow);
61
- const parentSession = getSession(threads, parent);
62
- db?.logTurn({ connector: connector.id, chatId, userId, role: 'user', text: queryText, sessionId: parentSession, tokensIn: null, tokensOut: null, ms: null });
66
+ const agentMessages = policy.triggerPrefix
67
+ ? messages.map((m, i) => (i === messages.length - 1 ? { ...m, content: queryText } : m))
68
+ : messages;
69
+ const base = await agentBase();
70
+ const system = buildSystem({ base, channelSystem: conversation.channelSystem });
71
+ db?.logTurn({ connector: connectorId, chatId, userId: sender.id, role: 'user', text: queryText, tokensIn: null, tokensOut: null, ms: null });
63
72
  const startedAt = Date.now();
64
73
  const canStream = connector.capabilities.streaming && typeof connector.editMessage === 'function';
65
74
  const max = connector.capabilities.maxMessageLength;
@@ -89,9 +98,20 @@ export async function handleIncoming(connector, payload, deps) {
89
98
  .finally(() => { editing = false; });
90
99
  }, throttleMs);
91
100
  try {
92
- result = await agent({ prompt: queryText, sessionId: parentSession, onDelta: (acc) => { pending = acc; } });
93
- if (result.text == null && parentSession)
94
- result = await agent({ prompt: queryText, sessionId: null, onDelta: (acc) => { pending = acc; } });
101
+ result = await agent({
102
+ system,
103
+ messages: agentMessages,
104
+ onDelta: (acc) => { pending = acc; },
105
+ onSegment: () => {
106
+ const t = pending.slice(0, max);
107
+ if (placeholderId && t && t !== lastEdited) {
108
+ void safeCall(() => connector.editMessage(chatId, placeholderId, t), undefined, 'editMessage(segment)');
109
+ }
110
+ placeholderId = null;
111
+ pending = '';
112
+ lastEdited = '';
113
+ },
114
+ });
95
115
  }
96
116
  finally {
97
117
  closed = true;
@@ -105,9 +125,7 @@ export async function handleIncoming(connector, payload, deps) {
105
125
  ? await safeCall(async () => connector.startTyping(chatId), undefined, 'startTyping')
106
126
  : undefined;
107
127
  try {
108
- result = await agent({ prompt: queryText, sessionId: parentSession });
109
- if (result.text == null && parentSession)
110
- result = await agent({ prompt: queryText, sessionId: null });
128
+ result = await agent({ system, messages: agentMessages });
111
129
  }
112
130
  finally {
113
131
  stopTyping?.();
@@ -131,11 +149,6 @@ export async function handleIncoming(connector, payload, deps) {
131
149
  }
132
150
  }
133
151
  if (result.text)
134
- db?.logTurn({ connector: connector.id, chatId, userId, role: 'assistant', text: result.text, sessionId: result.sessionId, tokensIn: result.tokensIn, tokensOut: result.tokensOut, ms: Date.now() - startedAt });
135
- if (result.sessionId) {
136
- const incomingMsgId = replyToId ?? String(now);
137
- threads = recordSession(threads, { msgId: incomingMsgId, botMsgId, sessionId: result.sessionId, now });
138
- threads = pruneThreads(threads, now, 7);
139
- saveThreads(threadsFile, threads);
140
- }
152
+ db?.logTurn({ connector: connectorId, chatId, userId: sender.id, role: 'assistant', text: result.text, tokensIn: result.tokensIn, tokensOut: result.tokensOut, ms: Date.now() - startedAt });
153
+ await safeCall(() => connector.recordAssistant({ parentMsgId: incomingMsgId, botMsgId, text: result.text ?? '' }), undefined, 'recordAssistant');
141
154
  }
@@ -0,0 +1,5 @@
1
+ import type { SystemLayer } from './types.ts';
2
+ export declare function buildSystem(input: {
3
+ base: string;
4
+ channelSystem?: string;
5
+ }): SystemLayer[];
@@ -0,0 +1,4 @@
1
+ export function buildSystem(input) {
2
+ const text = [input.base, input.channelSystem].filter(Boolean).join('\n\n');
3
+ return [{ text, stable: true }];
4
+ }
@@ -1,8 +1,20 @@
1
+ export type { ConvMessage, SystemLayer, AgentRequest, AgentResult } from '@coffer-org/plugin-claude-agent/runtime';
2
+ import type { ConvMessage } from '@coffer-org/plugin-claude-agent/runtime';
1
3
  export interface ConnectorCapabilities {
2
4
  streaming: boolean;
3
5
  typing: boolean;
4
6
  maxMessageLength: number;
5
7
  }
8
+ export interface IncomingConversation {
9
+ connectorId: string;
10
+ chatId: string;
11
+ channelSystem?: string;
12
+ sender: {
13
+ id: string;
14
+ displayName?: string;
15
+ };
16
+ messages: ConvMessage[];
17
+ }
6
18
  export interface Connector {
7
19
  id: string;
8
20
  capabilities: ConnectorCapabilities;
@@ -11,12 +23,11 @@ export interface Connector {
11
23
  } | null>;
12
24
  editMessage?(chatId: string, msgId: string, text: string): Promise<void>;
13
25
  startTyping?(chatId: string): () => void;
14
- }
15
- export interface IncomingPayload {
16
- chatId: string;
17
- userId: string;
18
- text: string;
19
- replyToId: string | null;
26
+ recordAssistant(m: {
27
+ parentMsgId: string | null;
28
+ botMsgId: string | null;
29
+ text: string;
30
+ }): Promise<void>;
20
31
  }
21
32
  export interface GatePolicy {
22
33
  accessPassword: string;
package/dist/schema.js CHANGED
@@ -154,7 +154,10 @@ function assignProp(target, prop, value) {
154
154
  }
155
155
  function mergeDefs(...defs) {
156
156
  const mergedDescriptors = {};
157
- for (const def of defs) Object.assign(mergedDescriptors, Object.getOwnPropertyDescriptors(def));
157
+ for (const def of defs) {
158
+ const descriptors = Object.getOwnPropertyDescriptors(def);
159
+ Object.assign(mergedDescriptors, descriptors);
160
+ }
158
161
  return Object.defineProperties({}, mergedDescriptors);
159
162
  }
160
163
  function esc(str) {
@@ -476,7 +479,7 @@ var _parse = (_Err) => (schema, value, _ctx, _params) => {
476
479
  }, ctx);
477
480
  if (result instanceof Promise) throw new $ZodAsyncError();
478
481
  if (result.issues.length) {
479
- const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
482
+ const e = new ((_params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
480
483
  captureStackTrace(e, _params?.callee);
481
484
  throw e;
482
485
  }
@@ -493,7 +496,7 @@ var _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
493
496
  }, ctx);
494
497
  if (result instanceof Promise) result = await result;
495
498
  if (result.issues.length) {
496
- const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
499
+ const e = new ((params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
497
500
  captureStackTrace(e, params?.callee);
498
501
  throw e;
499
502
  }
@@ -1589,13 +1592,13 @@ var $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => {
1589
1592
  }
1590
1593
  return propValues;
1591
1594
  });
1592
- const isObject$1 = isObject;
1595
+ const isObject$2 = isObject;
1593
1596
  const catchall = def.catchall;
1594
1597
  let value;
1595
1598
  inst._zod.parse = (payload, ctx) => {
1596
1599
  value ?? (value = _normalized.value);
1597
1600
  const input = payload.value;
1598
- if (!isObject$1(input)) {
1601
+ if (!isObject$2(input)) {
1599
1602
  payload.issues.push({
1600
1603
  expected: "object",
1601
1604
  code: "invalid_type",
@@ -1718,7 +1721,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
1718
1721
  return (payload, ctx) => fn(shape, payload, ctx);
1719
1722
  };
1720
1723
  let fastpass;
1721
- const isObject$2 = isObject;
1724
+ const isObject$1 = isObject;
1722
1725
  const jit = !globalConfig.jitless;
1723
1726
  const fastEnabled = jit && allowsEval.value;
1724
1727
  const catchall = def.catchall;
@@ -1726,7 +1729,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
1726
1729
  inst._zod.parse = (payload, ctx) => {
1727
1730
  value ?? (value = _normalized.value);
1728
1731
  const input = payload.value;
1729
- if (!isObject$2(input)) {
1732
+ if (!isObject$1(input)) {
1730
1733
  payload.issues.push({
1731
1734
  expected: "object",
1732
1735
  code: "invalid_type",
@@ -1945,7 +1948,7 @@ var $ZodOptional = /*@__PURE__*/ $constructor("$ZodOptional", (inst, def) => {
1945
1948
  inst._zod.optin = "optional";
1946
1949
  inst._zod.optout = "optional";
1947
1950
  defineLazy(inst._zod, "values", () => {
1948
- return def.innerType._zod.values ? new Set([...def.innerType._zod.values, void 0]) : void 0;
1951
+ return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, void 0]) : void 0;
1949
1952
  });
1950
1953
  defineLazy(inst._zod, "pattern", () => {
1951
1954
  const pattern = def.innerType._zod.pattern;
@@ -1979,7 +1982,7 @@ var $ZodNullable = /*@__PURE__*/ $constructor("$ZodNullable", (inst, def) => {
1979
1982
  return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;
1980
1983
  });
1981
1984
  defineLazy(inst._zod, "values", () => {
1982
- return def.innerType._zod.values ? new Set([...def.innerType._zod.values, null]) : void 0;
1985
+ return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, null]) : void 0;
1983
1986
  });
1984
1987
  inst._zod.parse = (payload, ctx) => {
1985
1988
  if (payload.value === null) return payload;
@@ -4587,7 +4590,7 @@ function password(raw) {
4587
4590
  * Internal API-token list — keyValue-shaped collection (name + write-only
4588
4591
  * token + timestamps), custom renderer (`kind:'internalApiToken'`, own
4589
4592
  * create/revoke UI — same trick as url()/perWeekday()). "Internal" = not a
4590
- * general-purpose field type for plugin modules, only for the core account
4593
+ * general-purpose field type for plugin shelves, only for the core account
4591
4594
  * settings page. `token` is `kind:'password'` so maskSecrets/preserveTree
4592
4595
  * already mask/preserve it for free.
4593
4596
  */
@@ -4686,7 +4689,7 @@ var TEL_RE = /^\+?[\d\s()-]{4,}$/;
4686
4689
  /** Loose URL: будь-який scheme:// АБО host-з-крапкою (+опц. порт/шлях). Без пробілів. */
4687
4690
  var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
4688
4691
  /** CSS named colors (CSS Color Module L4) — для f.colorname. */
4689
- var CSS_COLOR_NAMES = new Set([
4692
+ var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
4690
4693
  "aliceblue",
4691
4694
  "antiquewhite",
4692
4695
  "aqua",
@@ -5147,7 +5150,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5147
5150
  },
5148
5151
  cs: {
5149
5152
  name: "Czech",
5150
- nativeName: "Čeština"
5153
+ nativeName: "čeština"
5151
5154
  },
5152
5155
  cu: {
5153
5156
  name: "Old Church Slavonic",
@@ -5611,7 +5614,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5611
5614
  },
5612
5615
  sk: {
5613
5616
  name: "Slovak",
5614
- nativeName: "Slovenčina"
5617
+ nativeName: "slovenčtina"
5615
5618
  },
5616
5619
  sl: {
5617
5620
  name: "Slovenian",
@@ -5989,6 +5992,34 @@ function wrapKey(opts, meta) {
5989
5992
  noEditControl: true
5990
5993
  }
5991
5994
  };
5995
+ if (opts.emphasis) m = {
5996
+ ...m,
5997
+ hints: {
5998
+ ...m.hints,
5999
+ emphasis: opts.emphasis
6000
+ }
6001
+ };
6002
+ if (opts.noLabel) m = {
6003
+ ...m,
6004
+ hints: {
6005
+ ...m.hints,
6006
+ noLabel: true
6007
+ }
6008
+ };
6009
+ if (opts.role) m = {
6010
+ ...m,
6011
+ hints: {
6012
+ ...m.hints,
6013
+ role: opts.role
6014
+ }
6015
+ };
6016
+ if (opts.pinned) m = {
6017
+ ...m,
6018
+ hints: {
6019
+ ...m.hints,
6020
+ pinned: true
6021
+ }
6022
+ };
5992
6023
  if (opts.default !== void 0) m = {
5993
6024
  ...m,
5994
6025
  default: opts.default
@@ -6352,8 +6383,8 @@ function relation(raw) {
6352
6383
  displayKey: o.displayKey ?? "name"
6353
6384
  },
6354
6385
  relation: {
6355
- vault: raw.options.vault,
6356
- type: raw.options.module
6386
+ library: raw.options.library,
6387
+ type: raw.options.shelf
6357
6388
  },
6358
6389
  ...multi && { json: true },
6359
6390
  zod: optionalize(s, required)
@@ -7003,7 +7034,7 @@ function toClient(field) {
7003
7034
  var src_default = definePlugin({
7004
7035
  id: "orchestrator",
7005
7036
  version: "1.0.0",
7006
- dependsOn: ["agent"],
7037
+ dependsOn: ["claude-agent"],
7007
7038
  settings: defineSettings({
7008
7039
  label: "orchestrator.settings.label",
7009
7040
  fields: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/plugin-orchestrator",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -25,9 +25,9 @@
25
25
  "postpack": "node ../../scripts/swap-exports.mjs src"
26
26
  },
27
27
  "dependencies": {
28
- "@coffer-org/sdk": "^1.3.0",
29
- "@coffer-org/server": "^1.3.0",
30
- "@coffer-org/plugin-agent": "^1.3.0"
28
+ "@coffer-org/sdk": "^1.5.0",
29
+ "@coffer-org/server": "^1.8.0",
30
+ "@coffer-org/plugin-claude-agent": "^1.4.0"
31
31
  },
32
32
  "coffer": {
33
33
  "schema": "dist/schema.js"