@coffer-org/plugin-orchestrator 1.3.1 → 2.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.
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;
@@ -2,5 +2,8 @@ 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 { makeLiveChannel, plainRender } from './live-message.ts';
6
+ export type { LiveChannelOps, LiveChannelOpts, RenderFn } from './live-message.ts';
7
+ export { chunk } from './format.ts';
8
+ export type { Connector, IncomingConversation, GatePolicy, ReplyContext, ReplyPayload, ReplyChannel, ConvMessage, SystemLayer, AgentRequest, AgentResult, } from './types.ts';
6
9
  export declare const serverHooks: PluginHooks;
@@ -2,6 +2,8 @@ import { openLogDb } from "./db.js";
2
2
  import { setLogDb } from "./pipeline.js";
3
3
  export { handleIncoming } from "./pipeline.js";
4
4
  export { buildPolicy, loadGatePolicy } from "./config.js";
5
+ export { makeLiveChannel, plainRender } from "./live-message.js";
6
+ export { chunk } from "./format.js";
5
7
  let db;
6
8
  export const serverHooks = {
7
9
  init: () => { db = openLogDb(); setLogDb(db); },
@@ -0,0 +1,14 @@
1
+ import type { ReplyChannel, ReplyPayload } from './types.ts';
2
+ export interface LiveChannelOps {
3
+ send(text: string): Promise<string | null>;
4
+ edit(msgId: string, text: string): Promise<void>;
5
+ }
6
+ export type RenderFn = (r: ReplyPayload) => string[];
7
+ export interface LiveChannelOpts {
8
+ ops: LiveChannelOps;
9
+ throttleMs: number;
10
+ render: RenderFn;
11
+ maxLength: number;
12
+ }
13
+ export declare function plainRender(max: number): RenderFn;
14
+ export declare function makeLiveChannel(o: LiveChannelOpts): ReplyChannel;
@@ -0,0 +1,83 @@
1
+ import { chunk } from "./format.js";
2
+ import { getLogger } from '@coffer-org/sdk/logger';
3
+ const log = getLogger('orchestrator');
4
+ export function plainRender(max) {
5
+ return (r) => chunk((r.text ?? '').trim() || '⚠️ the agent returned no response', max);
6
+ }
7
+ async function swallow(fn, fallback, label) {
8
+ try {
9
+ return await fn();
10
+ }
11
+ catch (err) {
12
+ log.error(`live-message ${label}: ${err instanceof Error ? err.message : String(err)}`);
13
+ return fallback;
14
+ }
15
+ }
16
+ export function makeLiveChannel(o) {
17
+ let msgId = null;
18
+ let pending = '';
19
+ let lastSent = '';
20
+ let busy = false;
21
+ let closed = false;
22
+ let gen = 0;
23
+ let inFlight = Promise.resolve();
24
+ const timer = setInterval(() => {
25
+ if (closed || busy)
26
+ return;
27
+ const t = pending.slice(0, o.maxLength);
28
+ if (!t || t === lastSent)
29
+ return;
30
+ busy = true;
31
+ const myGen = gen;
32
+ if (!msgId) {
33
+ inFlight = o.ops.send(t)
34
+ .then((id) => { if (gen === myGen) {
35
+ msgId = id;
36
+ lastSent = t;
37
+ } })
38
+ .catch((err) => { log.error(`live-message send: ${err instanceof Error ? err.message : String(err)}`); })
39
+ .finally(() => { busy = false; });
40
+ return;
41
+ }
42
+ const id = msgId;
43
+ inFlight = o.ops.edit(id, t)
44
+ .then(() => { if (gen === myGen)
45
+ lastSent = t; })
46
+ .catch((err) => { log.error(`live-message edit: ${err instanceof Error ? err.message : String(err)}`); })
47
+ .finally(() => { busy = false; });
48
+ }, o.throttleMs);
49
+ if (typeof timer.unref === 'function')
50
+ timer.unref();
51
+ return {
52
+ update(text) {
53
+ pending = text;
54
+ },
55
+ segment() {
56
+ gen++;
57
+ msgId = null;
58
+ pending = '';
59
+ lastSent = '';
60
+ },
61
+ async finish(r) {
62
+ closed = true;
63
+ clearInterval(timer);
64
+ await inFlight;
65
+ const parts = o.render(r);
66
+ let last = null;
67
+ const [head, ...rest] = parts;
68
+ if (msgId && head !== undefined) {
69
+ const id = msgId;
70
+ await swallow(() => o.ops.edit(id, head), undefined, 'edit(final)');
71
+ last = id;
72
+ }
73
+ else if (head !== undefined) {
74
+ last = await swallow(() => o.ops.send(head), null, 'send(final)');
75
+ }
76
+ for (const p of rest) {
77
+ const id = await swallow(() => o.ops.send(p), null, 'send(overflow)');
78
+ last = id ?? last;
79
+ }
80
+ return last;
81
+ },
82
+ };
83
+ }
@@ -1,12 +1,12 @@
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;
6
6
  export interface PipelineDeps {
7
7
  runAgent?: RunAgentFn;
8
8
  logDb?: LogDb | null;
9
- throttleMs?: number;
10
9
  policy?: GatePolicy;
10
+ agentBase?: () => Promise<string>;
11
11
  }
12
- export declare function handleIncoming(connector: Connector, payload: IncomingPayload, deps?: PipelineDeps): Promise<void>;
12
+ export declare function handleIncoming(connector: Connector, conversation: IncomingConversation, deps?: PipelineDeps): Promise<void>;
@@ -1,12 +1,10 @@
1
- import { runAgent } from '@coffer-org/plugin-agent/runtime';
2
- import { chunk } from "./format.js";
1
+ import { runAgent, agentSystemBase } from '@coffer-org/plugin-claude-agent/runtime';
3
2
  import { join } from 'node:path';
4
- import { loadThreads, saveThreads, resolveParent, getSession, recordSession, pruneThreads, } from "./threads.js";
5
3
  import { loadAllowed, saveAllowed, isAllowed, addAllowed, makeThrottle } from "./allow.js";
6
4
  import { loadGatePolicy } from "./config.js";
5
+ import { buildSystem } from "./system-assembly.js";
7
6
  import { getLogger } from '@coffer-org/sdk/logger';
8
7
  const log = getLogger('orchestrator');
9
- const DEFAULT_STREAM_THROTTLE_MS = 2000;
10
8
  const stateDir = () => process.env['ORCHESTRATOR_STATE_DIR']
11
9
  ?? join(new URL('../../runtime/state', import.meta.url).pathname);
12
10
  let logDb;
@@ -20,6 +18,17 @@ function passThrottle(connectorId) {
20
18
  }
21
19
  return t;
22
20
  }
21
+ let cachedBase;
22
+ function cachedAgentBase() { return (cachedBase ??= agentSystemBase()); }
23
+ function openChannel(connector, chatId, ctx) {
24
+ try {
25
+ return connector.reply(chatId, ctx);
26
+ }
27
+ catch (err) {
28
+ log.error(`connector error in reply: ${err instanceof Error ? err.message : String(err)}`);
29
+ return null;
30
+ }
31
+ }
23
32
  async function safeCall(fn, fallback, label) {
24
33
  try {
25
34
  return await fn();
@@ -29,115 +38,85 @@ async function safeCall(fn, fallback, label) {
29
38
  return fallback;
30
39
  }
31
40
  }
32
- export async function handleIncoming(connector, payload, deps) {
41
+ export async function handleIncoming(connector, conversation, deps) {
33
42
  const agent = deps?.runAgent ?? runAgent;
34
43
  const db = deps && 'logDb' in deps ? deps.logDb : logDb;
35
- const throttleMs = deps?.throttleMs ?? DEFAULT_STREAM_THROTTLE_MS;
36
44
  const policy = deps?.policy ?? await loadGatePolicy();
37
- const { chatId, userId, text, replyToId } = payload;
45
+ const agentBase = deps?.agentBase ?? cachedAgentBase;
46
+ const { connectorId, chatId, sender, messages } = conversation;
47
+ const last = messages[messages.length - 1];
48
+ if (!last || last.role !== 'user')
49
+ return;
50
+ const incomingMsgId = last.msgId;
38
51
  const dir = stateDir();
39
- const allowFile = join(dir, `${connector.id}.allowed.json`);
40
- const threadsFile = join(dir, `${connector.id}.threads.json`);
52
+ const allowFile = join(dir, `${connectorId}.allowed.json`);
41
53
  if (policy.accessPassword) {
42
54
  let allow = loadAllowed(allowFile);
43
- if (!isAllowed(allow, userId)) {
44
- if (text.trim() === policy.accessPassword) {
45
- allow = addAllowed(allow, userId, Date.now());
55
+ if (!isAllowed(allow, sender.id)) {
56
+ if (last.content.trim() === policy.accessPassword) {
57
+ allow = addAllowed(allow, sender.id, Date.now());
46
58
  saveAllowed(allowFile, allow);
47
- await safeCall(() => connector.sendMessage(chatId, '✅ Access granted. Send your requests.'), null, 'sendMessage(enroll)');
59
+ const ch = openChannel(connector, chatId, { parentMsgId: incomingMsgId });
60
+ if (ch)
61
+ await safeCall(() => ch.finish({ text: '✅ Access granted. Send your requests.', reasoning: null }), null, 'finish(enroll)');
48
62
  }
49
- else if (passThrottle(connector.id)(userId)) {
50
- await safeCall(() => connector.sendMessage(chatId, '🔒 Access locked. Send the password to gain access.'), null, 'sendMessage(locked)');
63
+ else if (passThrottle(connectorId)(sender.id)) {
64
+ const ch = openChannel(connector, chatId, { parentMsgId: incomingMsgId });
65
+ if (ch)
66
+ await safeCall(() => ch.finish({ text: '🔒 Access locked. Send the password to gain access.', reasoning: null }), null, 'finish(locked)');
51
67
  }
52
68
  return;
53
69
  }
54
70
  }
55
- if (policy.triggerPrefix && !text.toLowerCase().startsWith(policy.triggerPrefix.toLowerCase()))
71
+ if (policy.triggerPrefix && !last.content.toLowerCase().startsWith(policy.triggerPrefix.toLowerCase()))
56
72
  return;
57
- const queryText = policy.triggerPrefix ? text.slice(policy.triggerPrefix.length).trim() : text.trim();
73
+ const queryText = policy.triggerPrefix ? last.content.slice(policy.triggerPrefix.length).trim() : last.content.trim();
58
74
  if (!queryText)
59
75
  return;
60
- const now = Date.now();
61
- let threads = loadThreads(threadsFile);
62
- const parent = resolveParent(threads, { replyToMsgId: replyToId, now }, policy.replyWindow);
63
- const parentSession = getSession(threads, parent);
64
- db?.logTurn({ connector: connector.id, chatId, userId, role: 'user', text: queryText, sessionId: parentSession, tokensIn: null, tokensOut: null, ms: null });
76
+ const agentMessages = policy.triggerPrefix
77
+ ? messages.map((m, i) => (i === messages.length - 1 ? { ...m, content: queryText } : m))
78
+ : messages;
79
+ const base = await agentBase();
80
+ const system = buildSystem({
81
+ base,
82
+ channelSystem: conversation.channelSystem,
83
+ volatileSystem: conversation.volatileSystem,
84
+ });
85
+ db?.logTurn({ connector: connectorId, chatId, userId: sender.id, role: 'user', text: queryText, tokensIn: null, tokensOut: null, ms: null });
65
86
  const startedAt = Date.now();
66
- const canStream = connector.capabilities.streaming && typeof connector.editMessage === 'function';
67
- const max = connector.capabilities.maxMessageLength;
87
+ const ch = openChannel(connector, chatId, { parentMsgId: incomingMsgId });
88
+ if (!ch)
89
+ return;
68
90
  let result;
69
- let placeholderId = null;
70
- if (canStream) {
71
- const stopTyping = connector.capabilities.typing && connector.startTyping
72
- ? await safeCall(async () => connector.startTyping(chatId), undefined, 'startTyping')
73
- : undefined;
74
- let pending = '', lastEdited = '', editing = false, closed = false;
75
- let editPromise = Promise.resolve();
76
- const timer = setInterval(() => {
77
- if (closed || editing)
78
- return;
79
- const t = pending.slice(0, max);
80
- if (!t || t === lastEdited)
81
- return;
82
- editing = true;
83
- if (!placeholderId) {
84
- editPromise = safeCall(() => connector.sendMessage(chatId, t), null, 'sendMessage(stream-create)')
85
- .then((s) => { placeholderId = s?.msgId ?? null; lastEdited = t; })
86
- .finally(() => { editing = false; });
87
- return;
91
+ try {
92
+ result = await agent({
93
+ system,
94
+ messages: agentMessages,
95
+ onDelta: (acc) => { try {
96
+ ch.update(acc);
88
97
  }
89
- editPromise = safeCall(() => connector.editMessage(chatId, placeholderId, t), undefined, 'editMessage(stream)')
90
- .then(() => { lastEdited = t; })
91
- .finally(() => { editing = false; });
92
- }, throttleMs);
93
- try {
94
- result = await agent({ prompt: queryText, sessionId: parentSession, onDelta: (acc) => { pending = acc; } });
95
- if (result.text == null && parentSession)
96
- result = await agent({ prompt: queryText, sessionId: null, onDelta: (acc) => { pending = acc; } });
97
- }
98
- finally {
99
- closed = true;
100
- clearInterval(timer);
101
- await editPromise;
102
- stopTyping?.();
103
- }
104
- }
105
- else {
106
- const stopTyping = connector.capabilities.typing && connector.startTyping
107
- ? await safeCall(async () => connector.startTyping(chatId), undefined, 'startTyping')
108
- : undefined;
109
- try {
110
- result = await agent({ prompt: queryText, sessionId: parentSession });
111
- if (result.text == null && parentSession)
112
- result = await agent({ prompt: queryText, sessionId: null });
113
- }
114
- finally {
115
- stopTyping?.();
116
- }
117
- }
118
- let botMsgId = null;
119
- const out = result.text ?? '⚠️ the agent returned no response';
120
- const parts = chunk(out, max);
121
- if (canStream && placeholderId) {
122
- await safeCall(() => connector.editMessage(chatId, placeholderId, parts[0]), undefined, 'editMessage(final)');
123
- botMsgId = placeholderId;
124
- for (const p of parts.slice(1)) {
125
- const s = await safeCall(() => connector.sendMessage(chatId, p), null, 'sendMessage(overflow)');
126
- botMsgId = s?.msgId ?? botMsgId;
127
- }
128
- }
129
- else {
130
- for (const p of parts) {
131
- const s = await safeCall(() => connector.sendMessage(chatId, p), null, 'sendMessage(deliver)');
132
- botMsgId = s?.msgId ?? botMsgId;
133
- }
98
+ catch (err) {
99
+ log.error(`connector error in update: ${err instanceof Error ? err.message : String(err)}`);
100
+ } },
101
+ onReasoning: (acc) => { try {
102
+ ch.updateReasoning?.(acc);
103
+ }
104
+ catch (err) {
105
+ log.error(`connector error in updateReasoning: ${err instanceof Error ? err.message : String(err)}`);
106
+ } },
107
+ onSegment: () => { try {
108
+ ch.segment();
109
+ }
110
+ catch (err) {
111
+ log.error(`connector error in segment: ${err instanceof Error ? err.message : String(err)}`);
112
+ } },
113
+ });
134
114
  }
135
- if (result.text)
136
- 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 });
137
- if (result.sessionId) {
138
- const incomingMsgId = replyToId ?? String(now);
139
- threads = recordSession(threads, { msgId: incomingMsgId, botMsgId, sessionId: result.sessionId, now });
140
- threads = pruneThreads(threads, now, 7);
141
- saveThreads(threadsFile, threads);
115
+ catch (err) {
116
+ log.error(`agent error: ${err instanceof Error ? err.message : String(err)}`);
142
117
  }
118
+ const botMsgId = await safeCall(() => ch.finish({ text: result?.text ?? null, reasoning: result?.reasoning ?? null }), null, 'finish');
119
+ if (result?.text)
120
+ db?.logTurn({ connector: connectorId, chatId, userId: sender.id, role: 'assistant', text: result.text, tokensIn: result.tokensIn, tokensOut: result.tokensOut, ms: Date.now() - startedAt });
121
+ await safeCall(() => connector.recordAssistant({ parentMsgId: incomingMsgId, botMsgId, text: result?.text ?? '', reasoning: result?.reasoning ?? null }), undefined, 'recordAssistant');
143
122
  }
@@ -0,0 +1,6 @@
1
+ import type { SystemLayer } from './types.ts';
2
+ export declare function buildSystem(input: {
3
+ base: string;
4
+ channelSystem?: string;
5
+ volatileSystem?: string;
6
+ }): SystemLayer[];
@@ -0,0 +1,7 @@
1
+ export function buildSystem(input) {
2
+ const text = [input.base, input.channelSystem].filter(Boolean).join('\n\n');
3
+ const layers = [{ text, stable: true }];
4
+ if (input.volatileSystem?.trim())
5
+ layers.push({ text: input.volatileSystem, stable: false });
6
+ return layers;
7
+ }
@@ -1,22 +1,38 @@
1
- export interface ConnectorCapabilities {
2
- streaming: boolean;
3
- typing: boolean;
4
- maxMessageLength: number;
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';
3
+ export interface IncomingConversation {
4
+ connectorId: string;
5
+ chatId: string;
6
+ channelSystem?: string;
7
+ volatileSystem?: string;
8
+ sender: {
9
+ id: string;
10
+ displayName?: string;
11
+ };
12
+ messages: ConvMessage[];
13
+ }
14
+ export interface ReplyContext {
15
+ parentMsgId: string | null;
16
+ }
17
+ export interface ReplyPayload {
18
+ text: string | null;
19
+ reasoning: string | null;
20
+ }
21
+ export interface ReplyChannel {
22
+ update(text: string): void;
23
+ updateReasoning?(acc: string): void;
24
+ segment(): void;
25
+ finish(r: ReplyPayload): Promise<string | null>;
5
26
  }
6
27
  export interface Connector {
7
28
  id: string;
8
- capabilities: ConnectorCapabilities;
9
- sendMessage(chatId: string, text: string): Promise<{
10
- msgId: string;
11
- } | null>;
12
- editMessage?(chatId: string, msgId: string, text: string): Promise<void>;
13
- startTyping?(chatId: string): () => void;
14
- }
15
- export interface IncomingPayload {
16
- chatId: string;
17
- userId: string;
18
- text: string;
19
- replyToId: string | null;
29
+ reply(chatId: string, ctx: ReplyContext): ReplyChannel;
30
+ recordAssistant(m: {
31
+ parentMsgId: string | null;
32
+ botMsgId: string | null;
33
+ text: string;
34
+ reasoning: string | null;
35
+ }): Promise<void>;
20
36
  }
21
37
  export interface GatePolicy {
22
38
  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
  */
@@ -4606,6 +4609,25 @@ function internalApiToken(o) {
4606
4609
  ]
4607
4610
  });
4608
4611
  }
4612
+ /**
4613
+ * Internal "connected apps" list — the OAuth clients (claude.ai & co) the user
4614
+ * has authorized for MCP access. Same "internal" caveat and custom-renderer
4615
+ * trick as internalApiToken(): read-only rows + a revoke action, never part of
4616
+ * the parent form's save.
4617
+ */
4618
+ function internalOauthGrants(o) {
4619
+ return group({
4620
+ key: o.key,
4621
+ label: o.label ?? o.key,
4622
+ multiple: true,
4623
+ view: { kind: "internalOauthGrants" },
4624
+ fields: [
4625
+ string({ key: "clientName" }),
4626
+ string({ key: "createdAt" }),
4627
+ string({ key: "lastUsedAt" })
4628
+ ]
4629
+ });
4630
+ }
4609
4631
  var SLUG_RE = /^[a-z0-9-]+$/;
4610
4632
  function slug(raw) {
4611
4633
  const o = normalizeOpts(raw);
@@ -4686,7 +4708,7 @@ var TEL_RE = /^\+?[\d\s()-]{4,}$/;
4686
4708
  /** Loose URL: будь-який scheme:// АБО host-з-крапкою (+опц. порт/шлях). Без пробілів. */
4687
4709
  var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
4688
4710
  /** CSS named colors (CSS Color Module L4) — для f.colorname. */
4689
- var CSS_COLOR_NAMES = new Set([
4711
+ var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
4690
4712
  "aliceblue",
4691
4713
  "antiquewhite",
4692
4714
  "aqua",
@@ -5035,7 +5057,8 @@ var presets = {
5035
5057
  weight,
5036
5058
  dimensions,
5037
5059
  country,
5038
- internalApiToken
5060
+ internalApiToken,
5061
+ internalOauthGrants
5039
5062
  };
5040
5063
  //#endregion
5041
5064
  //#region ../../node_modules/iso-639-1/src/data.js
@@ -5147,7 +5170,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5147
5170
  },
5148
5171
  cs: {
5149
5172
  name: "Czech",
5150
- nativeName: "Čeština"
5173
+ nativeName: "čeština"
5151
5174
  },
5152
5175
  cu: {
5153
5176
  name: "Old Church Slavonic",
@@ -5611,7 +5634,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5611
5634
  },
5612
5635
  sk: {
5613
5636
  name: "Slovak",
5614
- nativeName: "Slovenčina"
5637
+ nativeName: "slovenčtina"
5615
5638
  },
5616
5639
  sl: {
5617
5640
  name: "Slovenian",
@@ -5862,7 +5885,7 @@ function group(o) {
5862
5885
  required: o.required,
5863
5886
  unique: r.unique,
5864
5887
  label: o.label,
5865
- icon: v.icon,
5888
+ icon: o.icon,
5866
5889
  display: v.display ?? "wrap",
5867
5890
  kind: v.kind,
5868
5891
  fixed: r.fixed,
@@ -5873,11 +5896,9 @@ function group(o) {
5873
5896
  function row(o) {
5874
5897
  return group({
5875
5898
  label: o.label,
5899
+ icon: o.icon,
5876
5900
  fields: o.fields,
5877
- view: {
5878
- ...o.view,
5879
- display: "scroll"
5880
- }
5901
+ view: { display: "scroll" }
5881
5902
  });
5882
5903
  }
5883
5904
  /** Tabular collection (array of rows → <table>, aligned by columns). Sugar. */
@@ -5885,14 +5906,12 @@ function table(o) {
5885
5906
  return group({
5886
5907
  key: o.key,
5887
5908
  label: o.label,
5909
+ icon: o.icon,
5888
5910
  fields: o.fields,
5889
5911
  multiple: true,
5890
5912
  required: o.required,
5891
5913
  rules: o.rules,
5892
- view: {
5893
- ...o.view,
5894
- display: "table"
5895
- }
5914
+ view: { display: "table" }
5896
5915
  });
5897
5916
  }
5898
5917
  /**
@@ -5908,11 +5927,9 @@ function sheet(o) {
5908
5927
  });
5909
5928
  return group({
5910
5929
  label: o.label,
5930
+ icon: o.icon,
5911
5931
  fields: flat,
5912
- view: {
5913
- ...o.view,
5914
- display: "sheet"
5915
- }
5932
+ view: { display: "sheet" }
5916
5933
  });
5917
5934
  }
5918
5935
  /**
@@ -5944,8 +5961,8 @@ function keyed(o) {
5944
5961
  return {
5945
5962
  ...(o.container ?? group)({
5946
5963
  label: o.label,
5947
- fields: [o.by, ...o.fields],
5948
- view: o.view
5964
+ icon: o.icon,
5965
+ fields: [o.by, ...o.fields]
5949
5966
  }),
5950
5967
  key: o.key,
5951
5968
  multiple: true,
@@ -5989,6 +6006,34 @@ function wrapKey(opts, meta) {
5989
6006
  noEditControl: true
5990
6007
  }
5991
6008
  };
6009
+ if (opts.emphasis) m = {
6010
+ ...m,
6011
+ hints: {
6012
+ ...m.hints,
6013
+ emphasis: opts.emphasis
6014
+ }
6015
+ };
6016
+ if (opts.noLabel) m = {
6017
+ ...m,
6018
+ hints: {
6019
+ ...m.hints,
6020
+ noLabel: true
6021
+ }
6022
+ };
6023
+ if (opts.role) m = {
6024
+ ...m,
6025
+ hints: {
6026
+ ...m.hints,
6027
+ role: opts.role
6028
+ }
6029
+ };
6030
+ if (opts.pinned) m = {
6031
+ ...m,
6032
+ hints: {
6033
+ ...m.hints,
6034
+ pinned: true
6035
+ }
6036
+ };
5992
6037
  if (opts.default !== void 0) m = {
5993
6038
  ...m,
5994
6039
  default: opts.default
@@ -6352,8 +6397,8 @@ function relation(raw) {
6352
6397
  displayKey: o.displayKey ?? "name"
6353
6398
  },
6354
6399
  relation: {
6355
- vault: raw.options.vault,
6356
- type: raw.options.module
6400
+ library: raw.options.library,
6401
+ shelf: raw.options.shelf
6357
6402
  },
6358
6403
  ...multi && { json: true },
6359
6404
  zod: optionalize(s, required)
@@ -6733,15 +6778,44 @@ function period(raw) {
6733
6778
  zod: optionalize(s, required)
6734
6779
  });
6735
6780
  }
6781
+ /** Keys a file entry may carry. Everything else is rejected — see fileEntryIssue. */
6782
+ var FILE_KEYS = /* @__PURE__ */ new Set([
6783
+ "name",
6784
+ "mime",
6785
+ "size"
6786
+ ]);
6787
+ /** Keys that mean "the author pasted a remote address" — the one mistake worth naming. */
6788
+ var FILE_URL_KEYS = [
6789
+ "url",
6790
+ "src",
6791
+ "href",
6792
+ "link"
6793
+ ];
6794
+ /**
6795
+ * A file entry points at a file already uploaded to the server: `{ name }`, where
6796
+ * name is the bare filename returned by POST /api/upload. Anything else — a remote
6797
+ * URL, an extra key, a path — is refused here, so a record can never hold a
6798
+ * reference the server cannot serve. `mime`/`size` are accepted (legacy payloads
6799
+ * and the web uploader send them) but the server overwrites them from disk.
6800
+ * Returns a vmsg code, or null when the entry is well-formed.
6801
+ */
6802
+ function fileEntryIssue(it) {
6803
+ if (typeof it !== "object" || it === null || Array.isArray(it)) return "file_structure";
6804
+ const rec = it;
6805
+ if (FILE_URL_KEYS.some((k) => k in rec)) return "file_remote_url";
6806
+ const name = rec["name"];
6807
+ if (typeof name !== "string" || name === "") return "file_structure";
6808
+ if (name.includes("://") || name.startsWith("//")) return "file_remote_url";
6809
+ if (/[\\/]/.test(name) || name.includes("..")) return "file_name";
6810
+ for (const k of Object.keys(rec)) if (!FILE_KEYS.has(k)) return "file_unknown_key";
6811
+ if (rec["mime"] !== void 0 && typeof rec["mime"] !== "string") return "file_structure";
6812
+ if (rec["size"] !== void 0 && typeof rec["size"] !== "number") return "file_structure";
6813
+ return null;
6814
+ }
6736
6815
  function makeFile(kind, raw) {
6737
6816
  const o = normalizeOpts(raw);
6738
6817
  const required = o.required ?? false;
6739
6818
  const multiple = o.multiple ?? false;
6740
- const rowSchema = object({
6741
- name: string$1().min(1),
6742
- mime: string$1().optional(),
6743
- size: number$1().optional()
6744
- });
6745
6819
  const s = unknown().superRefine((raw, ctx) => {
6746
6820
  if (typeof raw === "string") try {
6747
6821
  JSON.parse(raw);
@@ -6754,12 +6828,15 @@ function makeFile(kind, raw) {
6754
6828
  }
6755
6829
  const parsed = jsonValue(raw);
6756
6830
  const items = Array.isArray(parsed) ? parsed : [parsed];
6757
- for (const it of items) if (!rowSchema.safeParse(it).success) {
6758
- ctx.addIssue({
6759
- code: ZodIssueCode.custom,
6760
- message: vmsg("file_structure")
6761
- });
6762
- return;
6831
+ for (const it of items) {
6832
+ const code = fileEntryIssue(it);
6833
+ if (code) {
6834
+ ctx.addIssue({
6835
+ code: ZodIssueCode.custom,
6836
+ message: vmsg(code)
6837
+ });
6838
+ return;
6839
+ }
6763
6840
  }
6764
6841
  });
6765
6842
  return wrapKey(o, {
@@ -7003,7 +7080,7 @@ function toClient(field) {
7003
7080
  var src_default = definePlugin({
7004
7081
  id: "orchestrator",
7005
7082
  version: "1.0.0",
7006
- dependsOn: ["agent"],
7083
+ dependsOn: ["claude-agent"],
7007
7084
  settings: defineSettings({
7008
7085
  label: "orchestrator.settings.label",
7009
7086
  fields: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/plugin-orchestrator",
3
- "version": "1.3.1",
3
+ "version": "2.0.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.4.0",
29
- "@coffer-org/server": "^1.4.0",
30
- "@coffer-org/plugin-agent": "^1.3.1"
28
+ "@coffer-org/sdk": "^2.0.0",
29
+ "@coffer-org/server": "^2.0.1",
30
+ "@coffer-org/plugin-claude-agent": "^2.0.0"
31
31
  },
32
32
  "coffer": {
33
33
  "schema": "dist/schema.js"