@coffer-org/plugin-orchestrator 1.4.0 → 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/runtime/index.d.ts +4 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/live-message.d.ts +14 -0
- package/dist/runtime/live-message.js +83 -0
- package/dist/runtime/pipeline.d.ts +0 -1
- package/dist/runtime/pipeline.js +50 -82
- package/dist/runtime/system-assembly.d.ts +1 -0
- package/dist/runtime/system-assembly.js +4 -1
- package/dist/runtime/types.d.ts +16 -11
- package/dist/schema.js +74 -28
- package/package.json +4 -4
package/dist/runtime/index.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/runtime/index.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/runtime/pipeline.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { runAgent, agentSystemBase } from '@coffer-org/plugin-claude-agent/runtime';
|
|
2
|
-
import { chunk } from "./format.js";
|
|
3
2
|
import { join } from 'node:path';
|
|
4
3
|
import { loadAllowed, saveAllowed, isAllowed, addAllowed, makeThrottle } from "./allow.js";
|
|
5
4
|
import { loadGatePolicy } from "./config.js";
|
|
6
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;
|
|
@@ -22,6 +20,15 @@ function passThrottle(connectorId) {
|
|
|
22
20
|
}
|
|
23
21
|
let cachedBase;
|
|
24
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
|
+
}
|
|
25
32
|
async function safeCall(fn, fallback, label) {
|
|
26
33
|
try {
|
|
27
34
|
return await fn();
|
|
@@ -34,7 +41,6 @@ async function safeCall(fn, fallback, label) {
|
|
|
34
41
|
export async function handleIncoming(connector, conversation, deps) {
|
|
35
42
|
const agent = deps?.runAgent ?? runAgent;
|
|
36
43
|
const db = deps && 'logDb' in deps ? deps.logDb : logDb;
|
|
37
|
-
const throttleMs = deps?.throttleMs ?? DEFAULT_STREAM_THROTTLE_MS;
|
|
38
44
|
const policy = deps?.policy ?? await loadGatePolicy();
|
|
39
45
|
const agentBase = deps?.agentBase ?? cachedAgentBase;
|
|
40
46
|
const { connectorId, chatId, sender, messages } = conversation;
|
|
@@ -50,10 +56,14 @@ export async function handleIncoming(connector, conversation, deps) {
|
|
|
50
56
|
if (last.content.trim() === policy.accessPassword) {
|
|
51
57
|
allow = addAllowed(allow, sender.id, Date.now());
|
|
52
58
|
saveAllowed(allowFile, allow);
|
|
53
|
-
|
|
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)');
|
|
54
62
|
}
|
|
55
63
|
else if (passThrottle(connectorId)(sender.id)) {
|
|
56
|
-
|
|
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)');
|
|
57
67
|
}
|
|
58
68
|
return;
|
|
59
69
|
}
|
|
@@ -67,88 +77,46 @@ export async function handleIncoming(connector, conversation, deps) {
|
|
|
67
77
|
? messages.map((m, i) => (i === messages.length - 1 ? { ...m, content: queryText } : m))
|
|
68
78
|
: messages;
|
|
69
79
|
const base = await agentBase();
|
|
70
|
-
const system = buildSystem({
|
|
80
|
+
const system = buildSystem({
|
|
81
|
+
base,
|
|
82
|
+
channelSystem: conversation.channelSystem,
|
|
83
|
+
volatileSystem: conversation.volatileSystem,
|
|
84
|
+
});
|
|
71
85
|
db?.logTurn({ connector: connectorId, chatId, userId: sender.id, role: 'user', text: queryText, tokensIn: null, tokensOut: null, ms: null });
|
|
72
86
|
const startedAt = Date.now();
|
|
73
|
-
const
|
|
74
|
-
|
|
87
|
+
const ch = openChannel(connector, chatId, { parentMsgId: incomingMsgId });
|
|
88
|
+
if (!ch)
|
|
89
|
+
return;
|
|
75
90
|
let result;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
:
|
|
81
|
-
|
|
82
|
-
let editPromise = Promise.resolve();
|
|
83
|
-
const timer = setInterval(() => {
|
|
84
|
-
if (closed || editing)
|
|
85
|
-
return;
|
|
86
|
-
const t = pending.slice(0, max);
|
|
87
|
-
if (!t || t === lastEdited)
|
|
88
|
-
return;
|
|
89
|
-
editing = true;
|
|
90
|
-
if (!placeholderId) {
|
|
91
|
-
editPromise = safeCall(() => connector.sendMessage(chatId, t), null, 'sendMessage(stream-create)')
|
|
92
|
-
.then((s) => { placeholderId = s?.msgId ?? null; lastEdited = t; })
|
|
93
|
-
.finally(() => { editing = false; });
|
|
94
|
-
return;
|
|
91
|
+
try {
|
|
92
|
+
result = await agent({
|
|
93
|
+
system,
|
|
94
|
+
messages: agentMessages,
|
|
95
|
+
onDelta: (acc) => { try {
|
|
96
|
+
ch.update(acc);
|
|
95
97
|
}
|
|
96
|
-
|
|
97
|
-
.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
lastEdited = '';
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
finally {
|
|
117
|
-
closed = true;
|
|
118
|
-
clearInterval(timer);
|
|
119
|
-
await editPromise;
|
|
120
|
-
stopTyping?.();
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
const stopTyping = connector.capabilities.typing && connector.startTyping
|
|
125
|
-
? await safeCall(async () => connector.startTyping(chatId), undefined, 'startTyping')
|
|
126
|
-
: undefined;
|
|
127
|
-
try {
|
|
128
|
-
result = await agent({ system, messages: agentMessages });
|
|
129
|
-
}
|
|
130
|
-
finally {
|
|
131
|
-
stopTyping?.();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
let botMsgId = null;
|
|
135
|
-
const out = result.text ?? '⚠️ the agent returned no response';
|
|
136
|
-
const parts = chunk(out, max);
|
|
137
|
-
if (canStream && placeholderId) {
|
|
138
|
-
await safeCall(() => connector.editMessage(chatId, placeholderId, parts[0]), undefined, 'editMessage(final)');
|
|
139
|
-
botMsgId = placeholderId;
|
|
140
|
-
for (const p of parts.slice(1)) {
|
|
141
|
-
const s = await safeCall(() => connector.sendMessage(chatId, p), null, 'sendMessage(overflow)');
|
|
142
|
-
botMsgId = s?.msgId ?? botMsgId;
|
|
143
|
-
}
|
|
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
|
+
});
|
|
144
114
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const s = await safeCall(() => connector.sendMessage(chatId, p), null, 'sendMessage(deliver)');
|
|
148
|
-
botMsgId = s?.msgId ?? botMsgId;
|
|
149
|
-
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
log.error(`agent error: ${err instanceof Error ? err.message : String(err)}`);
|
|
150
117
|
}
|
|
151
|
-
|
|
118
|
+
const botMsgId = await safeCall(() => ch.finish({ text: result?.text ?? null, reasoning: result?.reasoning ?? null }), null, 'finish');
|
|
119
|
+
if (result?.text)
|
|
152
120
|
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
|
|
121
|
+
await safeCall(() => connector.recordAssistant({ parentMsgId: incomingMsgId, botMsgId, text: result?.text ?? '', reasoning: result?.reasoning ?? null }), undefined, 'recordAssistant');
|
|
154
122
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export function buildSystem(input) {
|
|
2
2
|
const text = [input.base, input.channelSystem].filter(Boolean).join('\n\n');
|
|
3
|
-
|
|
3
|
+
const layers = [{ text, stable: true }];
|
|
4
|
+
if (input.volatileSystem?.trim())
|
|
5
|
+
layers.push({ text: input.volatileSystem, stable: false });
|
|
6
|
+
return layers;
|
|
4
7
|
}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
export type { ConvMessage, SystemLayer, AgentRequest, AgentResult } from '@coffer-org/plugin-claude-agent/runtime';
|
|
2
2
|
import type { ConvMessage } from '@coffer-org/plugin-claude-agent/runtime';
|
|
3
|
-
export interface ConnectorCapabilities {
|
|
4
|
-
streaming: boolean;
|
|
5
|
-
typing: boolean;
|
|
6
|
-
maxMessageLength: number;
|
|
7
|
-
}
|
|
8
3
|
export interface IncomingConversation {
|
|
9
4
|
connectorId: string;
|
|
10
5
|
chatId: string;
|
|
11
6
|
channelSystem?: string;
|
|
7
|
+
volatileSystem?: string;
|
|
12
8
|
sender: {
|
|
13
9
|
id: string;
|
|
14
10
|
displayName?: string;
|
|
15
11
|
};
|
|
16
12
|
messages: ConvMessage[];
|
|
17
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>;
|
|
26
|
+
}
|
|
18
27
|
export interface Connector {
|
|
19
28
|
id: string;
|
|
20
|
-
|
|
21
|
-
sendMessage(chatId: string, text: string): Promise<{
|
|
22
|
-
msgId: string;
|
|
23
|
-
} | null>;
|
|
24
|
-
editMessage?(chatId: string, msgId: string, text: string): Promise<void>;
|
|
25
|
-
startTyping?(chatId: string): () => void;
|
|
29
|
+
reply(chatId: string, ctx: ReplyContext): ReplyChannel;
|
|
26
30
|
recordAssistant(m: {
|
|
27
31
|
parentMsgId: string | null;
|
|
28
32
|
botMsgId: string | null;
|
|
29
33
|
text: string;
|
|
34
|
+
reasoning: string | null;
|
|
30
35
|
}): Promise<void>;
|
|
31
36
|
}
|
|
32
37
|
export interface GatePolicy {
|
package/dist/schema.js
CHANGED
|
@@ -4609,6 +4609,25 @@ function internalApiToken(o) {
|
|
|
4609
4609
|
]
|
|
4610
4610
|
});
|
|
4611
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
|
+
}
|
|
4612
4631
|
var SLUG_RE = /^[a-z0-9-]+$/;
|
|
4613
4632
|
function slug(raw) {
|
|
4614
4633
|
const o = normalizeOpts(raw);
|
|
@@ -5038,7 +5057,8 @@ var presets = {
|
|
|
5038
5057
|
weight,
|
|
5039
5058
|
dimensions,
|
|
5040
5059
|
country,
|
|
5041
|
-
internalApiToken
|
|
5060
|
+
internalApiToken,
|
|
5061
|
+
internalOauthGrants
|
|
5042
5062
|
};
|
|
5043
5063
|
//#endregion
|
|
5044
5064
|
//#region ../../node_modules/iso-639-1/src/data.js
|
|
@@ -5865,7 +5885,7 @@ function group(o) {
|
|
|
5865
5885
|
required: o.required,
|
|
5866
5886
|
unique: r.unique,
|
|
5867
5887
|
label: o.label,
|
|
5868
|
-
icon:
|
|
5888
|
+
icon: o.icon,
|
|
5869
5889
|
display: v.display ?? "wrap",
|
|
5870
5890
|
kind: v.kind,
|
|
5871
5891
|
fixed: r.fixed,
|
|
@@ -5876,11 +5896,9 @@ function group(o) {
|
|
|
5876
5896
|
function row(o) {
|
|
5877
5897
|
return group({
|
|
5878
5898
|
label: o.label,
|
|
5899
|
+
icon: o.icon,
|
|
5879
5900
|
fields: o.fields,
|
|
5880
|
-
view: {
|
|
5881
|
-
...o.view,
|
|
5882
|
-
display: "scroll"
|
|
5883
|
-
}
|
|
5901
|
+
view: { display: "scroll" }
|
|
5884
5902
|
});
|
|
5885
5903
|
}
|
|
5886
5904
|
/** Tabular collection (array of rows → <table>, aligned by columns). Sugar. */
|
|
@@ -5888,14 +5906,12 @@ function table(o) {
|
|
|
5888
5906
|
return group({
|
|
5889
5907
|
key: o.key,
|
|
5890
5908
|
label: o.label,
|
|
5909
|
+
icon: o.icon,
|
|
5891
5910
|
fields: o.fields,
|
|
5892
5911
|
multiple: true,
|
|
5893
5912
|
required: o.required,
|
|
5894
5913
|
rules: o.rules,
|
|
5895
|
-
view: {
|
|
5896
|
-
...o.view,
|
|
5897
|
-
display: "table"
|
|
5898
|
-
}
|
|
5914
|
+
view: { display: "table" }
|
|
5899
5915
|
});
|
|
5900
5916
|
}
|
|
5901
5917
|
/**
|
|
@@ -5911,11 +5927,9 @@ function sheet(o) {
|
|
|
5911
5927
|
});
|
|
5912
5928
|
return group({
|
|
5913
5929
|
label: o.label,
|
|
5930
|
+
icon: o.icon,
|
|
5914
5931
|
fields: flat,
|
|
5915
|
-
view: {
|
|
5916
|
-
...o.view,
|
|
5917
|
-
display: "sheet"
|
|
5918
|
-
}
|
|
5932
|
+
view: { display: "sheet" }
|
|
5919
5933
|
});
|
|
5920
5934
|
}
|
|
5921
5935
|
/**
|
|
@@ -5947,8 +5961,8 @@ function keyed(o) {
|
|
|
5947
5961
|
return {
|
|
5948
5962
|
...(o.container ?? group)({
|
|
5949
5963
|
label: o.label,
|
|
5950
|
-
|
|
5951
|
-
|
|
5964
|
+
icon: o.icon,
|
|
5965
|
+
fields: [o.by, ...o.fields]
|
|
5952
5966
|
}),
|
|
5953
5967
|
key: o.key,
|
|
5954
5968
|
multiple: true,
|
|
@@ -6384,7 +6398,7 @@ function relation(raw) {
|
|
|
6384
6398
|
},
|
|
6385
6399
|
relation: {
|
|
6386
6400
|
library: raw.options.library,
|
|
6387
|
-
|
|
6401
|
+
shelf: raw.options.shelf
|
|
6388
6402
|
},
|
|
6389
6403
|
...multi && { json: true },
|
|
6390
6404
|
zod: optionalize(s, required)
|
|
@@ -6764,15 +6778,44 @@ function period(raw) {
|
|
|
6764
6778
|
zod: optionalize(s, required)
|
|
6765
6779
|
});
|
|
6766
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
|
+
}
|
|
6767
6815
|
function makeFile(kind, raw) {
|
|
6768
6816
|
const o = normalizeOpts(raw);
|
|
6769
6817
|
const required = o.required ?? false;
|
|
6770
6818
|
const multiple = o.multiple ?? false;
|
|
6771
|
-
const rowSchema = object({
|
|
6772
|
-
name: string$1().min(1),
|
|
6773
|
-
mime: string$1().optional(),
|
|
6774
|
-
size: number$1().optional()
|
|
6775
|
-
});
|
|
6776
6819
|
const s = unknown().superRefine((raw, ctx) => {
|
|
6777
6820
|
if (typeof raw === "string") try {
|
|
6778
6821
|
JSON.parse(raw);
|
|
@@ -6785,12 +6828,15 @@ function makeFile(kind, raw) {
|
|
|
6785
6828
|
}
|
|
6786
6829
|
const parsed = jsonValue(raw);
|
|
6787
6830
|
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
6788
|
-
for (const it of items)
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
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
|
+
}
|
|
6794
6840
|
}
|
|
6795
6841
|
});
|
|
6796
6842
|
return wrapKey(o, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/plugin-orchestrator",
|
|
3
|
-
"version": "
|
|
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": "^
|
|
29
|
-
"@coffer-org/server": "^
|
|
30
|
-
"@coffer-org/plugin-claude-agent": "^
|
|
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"
|