@coffer-org/server 3.4.0 → 5.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/auth-api.js +4 -5
- package/dist/cache-clock.d.ts +8 -0
- package/dist/cache-clock.js +103 -0
- package/dist/cache-refresh.d.ts +7 -0
- package/dist/cache-refresh.js +322 -0
- package/dist/collection-io.d.ts +13 -0
- package/dist/collection-io.js +40 -29
- package/dist/compute-unit.d.ts +66 -0
- package/dist/compute-unit.js +369 -0
- package/dist/condition-where.js +1 -3
- package/dist/data-dir.d.ts +3 -0
- package/dist/data-dir.js +39 -0
- package/dist/entity-schema.js +5 -2
- package/dist/extend-table.d.ts +1 -1
- package/dist/extend-table.js +22 -8
- package/dist/file-fields.js +50 -23
- package/dist/frontend-agent.d.ts +1 -1
- package/dist/frontend-agent.js +2 -3
- package/dist/global-search.js +1 -1
- package/dist/index.js +20 -26
- package/dist/mcp-contract/client.d.ts +24 -0
- package/dist/mcp-contract/client.js +12 -0
- package/dist/mcp-contract/schema.d.ts +46 -0
- package/dist/mcp-contract/schema.js +90 -0
- package/dist/mcp-contract/tools.d.ts +19 -0
- package/dist/mcp-contract/tools.js +160 -0
- package/dist/mcp-http.js +4 -2
- package/dist/mcp-local.d.ts +1 -1
- package/dist/mcp-local.js +1 -1
- package/dist/mcp-tools.d.ts +13 -1
- package/dist/mcp-tools.js +46 -16
- package/dist/migrations.d.ts +5 -0
- package/dist/migrations.js +122 -0
- package/dist/mutate.js +77 -36
- package/dist/orchestrator/agent-capabilities.d.ts +2 -0
- package/dist/orchestrator/agent-capabilities.js +201 -0
- package/dist/orchestrator/allow.d.ts +16 -0
- package/dist/orchestrator/allow.js +65 -0
- package/dist/orchestrator/attachments.d.ts +2 -0
- package/dist/orchestrator/attachments.js +11 -0
- package/dist/orchestrator/config.d.ts +4 -0
- package/dist/orchestrator/config.js +19 -0
- package/dist/orchestrator/db.d.ts +17 -0
- package/dist/orchestrator/db.js +12 -0
- package/dist/orchestrator/diagnostics.d.ts +9 -0
- package/dist/orchestrator/diagnostics.js +13 -0
- package/dist/orchestrator/environment.d.ts +2 -0
- package/dist/orchestrator/environment.js +14 -0
- package/dist/orchestrator/file-inspection.d.ts +3 -0
- package/dist/orchestrator/file-inspection.js +61 -0
- package/dist/orchestrator/format.d.ts +1 -0
- package/dist/orchestrator/format.js +6 -0
- package/dist/orchestrator/index.d.ts +30 -0
- package/dist/orchestrator/index.js +56 -0
- package/dist/orchestrator/live-message.d.ts +14 -0
- package/dist/orchestrator/live-message.js +100 -0
- package/dist/orchestrator/pipeline.d.ts +11 -0
- package/dist/orchestrator/pipeline.js +198 -0
- package/dist/orchestrator/registry.d.ts +13 -0
- package/dist/orchestrator/registry.js +74 -0
- package/dist/orchestrator/starters.d.ts +19 -0
- package/dist/orchestrator/starters.js +81 -0
- package/dist/orchestrator/suggestion-capabilities.d.ts +5 -0
- package/dist/orchestrator/suggestion-capabilities.js +33 -0
- package/dist/orchestrator/system-assembly.d.ts +4 -0
- package/dist/orchestrator/system-assembly.js +3 -0
- package/dist/orchestrator/system-capabilities.d.ts +2 -0
- package/dist/orchestrator/system-capabilities.js +63 -0
- package/dist/orchestrator/types.d.ts +141 -0
- package/dist/orchestrator/types.js +1 -0
- package/dist/part-injection.d.ts +3 -4
- package/dist/part-injection.js +25 -368
- package/dist/plugin-hooks.d.ts +1 -0
- package/dist/plugin-i18n.js +2 -1
- package/dist/plugin-runtime.d.ts +2 -2
- package/dist/plugin-runtime.js +20 -11
- package/dist/plugin-updates.d.ts +2 -2
- package/dist/plugin-updates.js +6 -12
- package/dist/public-url.js +2 -1
- package/dist/recompute-derived.d.ts +2 -1
- package/dist/recompute-derived.js +111 -34
- package/dist/records-api.js +14 -16
- package/dist/registry-context.d.ts +1 -1
- package/dist/registry-context.js +3 -0
- package/dist/search-index.js +1 -1
- package/dist/search-indexer.d.ts +1 -1
- package/dist/search-indexer.js +3 -3
- package/dist/settings-groups.d.ts +14 -0
- package/dist/settings-groups.js +25 -0
- package/dist/settings-write.d.ts +4 -4
- package/dist/settings-write.js +23 -26
- package/dist/system-settings.d.ts +3 -0
- package/dist/system-settings.js +27 -0
- package/dist/thread-store.d.ts +3 -2
- package/dist/thread-store.js +6 -5
- package/dist/trigger-queue.d.ts +33 -0
- package/dist/trigger-queue.js +432 -0
- package/dist/uploads.js +6 -3
- package/dist/value-compare.d.ts +1 -0
- package/dist/value-compare.js +7 -0
- package/package.json +17 -3
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export interface AttachmentRef {
|
|
2
|
+
name: string;
|
|
3
|
+
mime?: string;
|
|
4
|
+
size?: number;
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AgentToolDefinition {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
inputSchema: Record<string, unknown>;
|
|
11
|
+
handler: (args: Record<string, unknown>) => Promise<unknown>;
|
|
12
|
+
forcedAfterAnswer?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentToolProvider {
|
|
15
|
+
tools: AgentToolDefinition[];
|
|
16
|
+
}
|
|
17
|
+
export type AgentToolContentBlock = {
|
|
18
|
+
type: 'text';
|
|
19
|
+
text: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'image';
|
|
22
|
+
mime: string;
|
|
23
|
+
data: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'document';
|
|
26
|
+
mime: 'application/pdf';
|
|
27
|
+
data: string;
|
|
28
|
+
};
|
|
29
|
+
export interface AgentToolContentResult {
|
|
30
|
+
__cofferToolContent: true;
|
|
31
|
+
content: AgentToolContentBlock[];
|
|
32
|
+
isError?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface ConvMessage {
|
|
35
|
+
role: 'user' | 'assistant';
|
|
36
|
+
content: string;
|
|
37
|
+
context?: string;
|
|
38
|
+
attachments?: AttachmentRef[];
|
|
39
|
+
sender?: string | null;
|
|
40
|
+
msgId: string;
|
|
41
|
+
ts: number;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentCapabilities {
|
|
44
|
+
vision?: boolean;
|
|
45
|
+
documents?: boolean;
|
|
46
|
+
tools?: boolean;
|
|
47
|
+
reasoning?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface AgentPreset {
|
|
50
|
+
id: string;
|
|
51
|
+
title: string;
|
|
52
|
+
hint?: string;
|
|
53
|
+
quality?: number;
|
|
54
|
+
time?: number;
|
|
55
|
+
default?: boolean;
|
|
56
|
+
capabilities?: AgentCapabilities;
|
|
57
|
+
}
|
|
58
|
+
export interface AgentDescriptor {
|
|
59
|
+
id: string;
|
|
60
|
+
title: string;
|
|
61
|
+
presets: AgentPreset[];
|
|
62
|
+
}
|
|
63
|
+
export interface AgentRequest {
|
|
64
|
+
system: string;
|
|
65
|
+
messages: ConvMessage[];
|
|
66
|
+
toolProvider?: AgentToolProvider;
|
|
67
|
+
presetId?: string;
|
|
68
|
+
onDelta?: (accumulated: string) => void;
|
|
69
|
+
onReasoning?: (accumulated: string) => void;
|
|
70
|
+
onSegment?: () => void;
|
|
71
|
+
}
|
|
72
|
+
export interface AgentResult {
|
|
73
|
+
text: string | null;
|
|
74
|
+
reasoning: string | null;
|
|
75
|
+
tokensIn: number | null;
|
|
76
|
+
tokensOut: number | null;
|
|
77
|
+
stopReason: string | null;
|
|
78
|
+
presetId: string | null;
|
|
79
|
+
suggestions?: string[] | null;
|
|
80
|
+
}
|
|
81
|
+
export interface AgentRuntime {
|
|
82
|
+
id: string;
|
|
83
|
+
run(request: AgentRequest): Promise<AgentResult>;
|
|
84
|
+
systemBase(): Promise<string>;
|
|
85
|
+
describe(): Promise<AgentDescriptor>;
|
|
86
|
+
starters?(): Promise<string[]>;
|
|
87
|
+
}
|
|
88
|
+
export interface ConnectorRegistration {
|
|
89
|
+
id: string;
|
|
90
|
+
}
|
|
91
|
+
export interface AttachmentMaterializer {
|
|
92
|
+
store(bytes: Uint8Array, opts?: {
|
|
93
|
+
originalName?: string;
|
|
94
|
+
mime?: string;
|
|
95
|
+
}): Promise<AttachmentRef>;
|
|
96
|
+
}
|
|
97
|
+
export interface IncomingConversation {
|
|
98
|
+
connectorId: string;
|
|
99
|
+
agentId?: string;
|
|
100
|
+
presetId?: string;
|
|
101
|
+
chatId: string;
|
|
102
|
+
channelSystem?: string;
|
|
103
|
+
turnContext?: string;
|
|
104
|
+
sender: {
|
|
105
|
+
id: string;
|
|
106
|
+
displayName?: string;
|
|
107
|
+
};
|
|
108
|
+
messages: ConvMessage[];
|
|
109
|
+
prepareAttachments?: (materializer: AttachmentMaterializer) => Promise<ConvMessage[]>;
|
|
110
|
+
supportsSuggestions?: boolean;
|
|
111
|
+
}
|
|
112
|
+
export interface ReplyContext {
|
|
113
|
+
parentMsgId: string | null;
|
|
114
|
+
}
|
|
115
|
+
export interface ReplyPayload {
|
|
116
|
+
text: string | null;
|
|
117
|
+
reasoning: string | null;
|
|
118
|
+
suggestions: string[] | null;
|
|
119
|
+
}
|
|
120
|
+
export interface ReplyChannel {
|
|
121
|
+
update(text: string): void;
|
|
122
|
+
updateReasoning?(acc: string): void;
|
|
123
|
+
segment(): void;
|
|
124
|
+
finish(r: ReplyPayload): Promise<string | null>;
|
|
125
|
+
}
|
|
126
|
+
export interface Connector {
|
|
127
|
+
id: string;
|
|
128
|
+
reply(chatId: string, ctx: ReplyContext): ReplyChannel;
|
|
129
|
+
recordAssistant(m: {
|
|
130
|
+
parentMsgId: string | null;
|
|
131
|
+
botMsgId: string | null;
|
|
132
|
+
text: string;
|
|
133
|
+
reasoning: string | null;
|
|
134
|
+
}): Promise<void>;
|
|
135
|
+
}
|
|
136
|
+
export interface GatePolicy {
|
|
137
|
+
agentId?: string;
|
|
138
|
+
accessPassword: string;
|
|
139
|
+
triggerPrefix: string;
|
|
140
|
+
replyWindow: number;
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/part-injection.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/core';
|
|
2
2
|
import { type LayoutEl } from '@coffer-org/sdk/fields';
|
|
3
|
-
import {
|
|
4
|
-
type Row
|
|
3
|
+
import type { RefState } from './part-ref.ts';
|
|
4
|
+
import { type Row } from './compute-unit.ts';
|
|
5
5
|
export type PartMemo = Map<string, unknown>;
|
|
6
6
|
export declare const newPartMemo: () => PartMemo;
|
|
7
7
|
export interface PartCtx {
|
|
@@ -14,8 +14,7 @@ export interface PartCtx {
|
|
|
14
14
|
em?: EntityManager;
|
|
15
15
|
memo?: PartMemo;
|
|
16
16
|
refs?: RefState;
|
|
17
|
+
now?: string;
|
|
17
18
|
}
|
|
18
|
-
export declare function injectParts(fields: LayoutEl[], row: Row, ctx: PartCtx): Promise<Row>;
|
|
19
19
|
export declare function hasStoredPartWork(fields: LayoutEl[]): boolean;
|
|
20
20
|
export declare function applyStoredParts(fields: LayoutEl[], row: Row, ctx: PartCtx): Promise<Row>;
|
|
21
|
-
export {};
|
package/dist/part-injection.js
CHANGED
|
@@ -1,358 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { PartContractError } from "./part-errors.js";
|
|
4
|
-
import { createRefLoader } from "./part-ref.js";
|
|
1
|
+
import { hasChildren, isNamedField, isCollectionGroup, isStorageGroup } from '@coffer-org/sdk/fields';
|
|
2
|
+
import { runFieldUnit, isComputeUnit, childScope, newScope, isPlainObject, } from "./compute-unit.js";
|
|
5
3
|
export const newPartMemo = () => new Map();
|
|
6
|
-
|
|
7
|
-
scope;
|
|
8
|
-
key;
|
|
9
|
-
constructor(scope, key) {
|
|
10
|
-
super(`needs completion: ${key}`);
|
|
11
|
-
this.scope = scope;
|
|
12
|
-
this.key = key;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
let scopeUid = 0;
|
|
16
|
-
async function partValue(p, ctx) {
|
|
17
|
-
return typeof p.value === 'function' ? await p.value(ctx) : p.value;
|
|
18
|
-
}
|
|
19
|
-
function rethrowLoud(e) {
|
|
20
|
-
if (e instanceof PartContractError || e instanceof NeedsCompletion)
|
|
21
|
-
throw e;
|
|
22
|
-
}
|
|
23
|
-
function isComputeUnit(fm) {
|
|
24
|
-
return Boolean(fm.parts?.length) || (fm.virtual === true && fm.compute !== undefined);
|
|
25
|
-
}
|
|
26
|
-
const unitsCache = new WeakMap();
|
|
27
|
-
function scopeUnits(fields) {
|
|
28
|
-
const cached = unitsCache.get(fields);
|
|
29
|
-
if (cached)
|
|
30
|
-
return cached;
|
|
31
|
-
const out = new Map();
|
|
32
|
-
const scan = (list) => {
|
|
33
|
-
for (const it of list) {
|
|
34
|
-
if (isField(it)) {
|
|
35
|
-
if (isComputeUnit(it.type))
|
|
36
|
-
out.set(it.key, it.type);
|
|
37
|
-
}
|
|
38
|
-
else if (isGroup(it) && !it.key)
|
|
39
|
-
scan(it.fields);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
scan(fields);
|
|
43
|
-
unitsCache.set(fields, out);
|
|
44
|
-
return out;
|
|
45
|
-
}
|
|
46
|
-
const embeddedCache = new WeakMap();
|
|
47
|
-
function scopeEmbedded(fields) {
|
|
48
|
-
const cached = embeddedCache.get(fields);
|
|
49
|
-
if (cached)
|
|
50
|
-
return cached;
|
|
51
|
-
const out = new Map();
|
|
52
|
-
const scan = (list) => {
|
|
53
|
-
for (const it of list) {
|
|
54
|
-
if (!isGroup(it))
|
|
55
|
-
continue;
|
|
56
|
-
if (!it.key)
|
|
57
|
-
scan(it.fields);
|
|
58
|
-
else if (!isCollectionGroup(it))
|
|
59
|
-
out.set(it.key, it.fields);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
scan(fields);
|
|
63
|
-
embeddedCache.set(fields, out);
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
const relationsCache = new WeakMap();
|
|
67
|
-
function levelRelations(fields) {
|
|
68
|
-
const cached = relationsCache.get(fields);
|
|
69
|
-
if (cached)
|
|
70
|
-
return cached;
|
|
71
|
-
const out = new Map();
|
|
72
|
-
const scan = (list) => {
|
|
73
|
-
for (const it of list) {
|
|
74
|
-
if (isField(it)) {
|
|
75
|
-
if (it.type.relation)
|
|
76
|
-
out.set(it.key, it.type);
|
|
77
|
-
}
|
|
78
|
-
else if (isGroup(it) && !it.key)
|
|
79
|
-
scan(it.fields);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
scan(fields);
|
|
83
|
-
relationsCache.set(fields, out);
|
|
84
|
-
return out;
|
|
85
|
-
}
|
|
86
|
-
function childScope(scope, key) {
|
|
87
|
-
const existing = scope.groups.get(key);
|
|
88
|
-
if (existing)
|
|
89
|
-
return existing;
|
|
90
|
-
const sub = scope.embedded.get(key);
|
|
91
|
-
if (!sub)
|
|
92
|
-
return null;
|
|
93
|
-
const cur = scope.obj[key];
|
|
94
|
-
if (!isPlainObject(cur))
|
|
95
|
-
return null;
|
|
96
|
-
const copy = { ...cur };
|
|
97
|
-
scope.obj[key] = copy;
|
|
98
|
-
const child = newScope(scope.st, copy, sub, `${scope.path}${key}.`, scope.level, scope.level.label);
|
|
99
|
-
scope.groups.set(key, child);
|
|
100
|
-
return child;
|
|
101
|
-
}
|
|
102
|
-
function makeProxy(scope) {
|
|
103
|
-
return new Proxy(scope.obj, {
|
|
104
|
-
get(target, prop, receiver) {
|
|
105
|
-
if (typeof prop === 'string') {
|
|
106
|
-
if (scope.units.has(prop) && scope.status.get(prop) !== 'done')
|
|
107
|
-
throw new NeedsCompletion(scope, prop);
|
|
108
|
-
const child = childScope(scope, prop);
|
|
109
|
-
if (child)
|
|
110
|
-
return child.proxy;
|
|
111
|
-
}
|
|
112
|
-
return Reflect.get(target, prop, receiver);
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
function newScope(st, obj, fields, path, level, label) {
|
|
117
|
-
const scope = {
|
|
118
|
-
st,
|
|
119
|
-
uid: ++scopeUid,
|
|
120
|
-
obj,
|
|
121
|
-
units: scopeUnits(fields),
|
|
122
|
-
embedded: scopeEmbedded(fields),
|
|
123
|
-
status: new Map(),
|
|
124
|
-
inflight: new Map(),
|
|
125
|
-
path,
|
|
126
|
-
level: level,
|
|
127
|
-
proxy: null,
|
|
128
|
-
groups: new Map(),
|
|
129
|
-
};
|
|
130
|
-
scope.proxy = makeProxy(scope);
|
|
131
|
-
if (!level)
|
|
132
|
-
scope.level = { fields, scope, proxy: scope.proxy, label };
|
|
133
|
-
return scope;
|
|
134
|
-
}
|
|
135
|
-
function refsOf(st) {
|
|
136
|
-
if (!st.refs) {
|
|
137
|
-
st.refs = {
|
|
138
|
-
chain: st.selfKey ? [st.selfKey] : [],
|
|
139
|
-
loader: createRefLoader((m, record, refs) => injectParts(m.fields, record, {
|
|
140
|
-
settings: {},
|
|
141
|
-
global: {},
|
|
142
|
-
meta: { library: m.library, shelf: m.shelf },
|
|
143
|
-
...(st.ctx.em ? { em: st.ctx.em } : {}),
|
|
144
|
-
refs,
|
|
145
|
-
}), st.ctx.em),
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
return st.refs;
|
|
149
|
-
}
|
|
150
|
-
function siteOf(field, role) {
|
|
151
|
-
return role ? `'${field}.${role}'` : `'${field}'`;
|
|
152
|
-
}
|
|
153
|
-
function refFor(scope, key, field, role) {
|
|
154
|
-
const level = scope.level;
|
|
155
|
-
const fm = levelRelations(level.fields).get(key);
|
|
156
|
-
if (!fm?.relation) {
|
|
157
|
-
const known = [...levelRelations(level.fields).keys()];
|
|
158
|
-
throw new PartContractError(`ctx.ref('${key}') in ${scope.st.ctx.meta.library}/${scope.st.ctx.meta.shelf} ${siteOf(field, role)}: ` +
|
|
159
|
-
`'${key}' is not a relation field of ${level.label} ` +
|
|
160
|
-
`(relation fields there: ${known.length ? known.map((k) => `'${k}'`).join(', ') : 'none'}). ` +
|
|
161
|
-
`ctx.ref takes the KEY of a relation declared next to this field, not a path.`);
|
|
162
|
-
}
|
|
163
|
-
if (fm.hints?.['multi'] === true || fm.hints?.['multiple'] === true) {
|
|
164
|
-
throw new PartContractError(`ctx.ref('${key}') in ${scope.st.ctx.meta.library}/${scope.st.ctx.meta.shelf} ${siteOf(field, role)}: ` +
|
|
165
|
-
`'${key}' is a MULTIPLE relation — ctx.ref resolves one record, not a list.`);
|
|
166
|
-
}
|
|
167
|
-
const raw = level.scope.obj[key];
|
|
168
|
-
if (raw == null || raw === '')
|
|
169
|
-
return Promise.resolve(null);
|
|
170
|
-
const id = typeof raw === 'number' ? raw : Number(raw);
|
|
171
|
-
if (!Number.isInteger(id))
|
|
172
|
-
return Promise.resolve(null);
|
|
173
|
-
const refs = refsOf(scope.st);
|
|
174
|
-
return refs.loader.load(fm.relation.library, fm.relation.shelf, id, refs.chain);
|
|
175
|
-
}
|
|
176
|
-
function storedRefError(scope, key, field, role) {
|
|
177
|
-
const { library, shelf } = scope.st.ctx.meta;
|
|
178
|
-
return new PartContractError(`ctx.ref('${key}') in ${library}/${shelf} ${siteOf(field, role)}: a computed-AND-STORED part must not read ` +
|
|
179
|
-
`another record. Its column is written once, at write time, and nothing recomputes it when the record it ` +
|
|
180
|
-
`read changes — the value would be stale and look authoritative. Drop the part's 'key' so the value is ` +
|
|
181
|
-
`computed on every read instead.`);
|
|
182
|
-
}
|
|
183
|
-
function computeCtxFor(scope, self, stored, field, role) {
|
|
184
|
-
const st = scope.st;
|
|
185
|
-
return {
|
|
186
|
-
record: st.recordLevel.proxy,
|
|
187
|
-
parent: scope.level.proxy,
|
|
188
|
-
settings: st.ctx.settings,
|
|
189
|
-
global: st.ctx.global,
|
|
190
|
-
meta: st.ctx.meta,
|
|
191
|
-
self,
|
|
192
|
-
ref: stored
|
|
193
|
-
? (key) => {
|
|
194
|
-
throw storedRefError(scope, key, field, role);
|
|
195
|
-
}
|
|
196
|
-
: (key) => refFor(scope, key, field, role),
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
async function completeValue(scope, key, fm, value, idx) {
|
|
200
|
-
if (value == null || typeof value !== 'object' || Array.isArray(value))
|
|
201
|
-
return value;
|
|
202
|
-
const st = scope.st;
|
|
203
|
-
const write = st.mode === 'write';
|
|
204
|
-
const self = { ...value };
|
|
205
|
-
const scopeId = `${st.ctx.meta.library}/${st.ctx.meta.shelf}`;
|
|
206
|
-
const path = idx === undefined ? `${scopeId}|${scope.path}${key}` : `${scopeId}|${scope.path}${key}.${idx}`;
|
|
207
|
-
const contextOnly = [];
|
|
208
|
-
for (const p of fm.parts) {
|
|
209
|
-
if (p.mode !== 'pinned' && p.mode !== 'computed')
|
|
210
|
-
continue;
|
|
211
|
-
if (write)
|
|
212
|
-
contextOnly.push(p.role);
|
|
213
|
-
const memoKey = `${path}|${p.role}`;
|
|
214
|
-
if (st.memo.has(memoKey)) {
|
|
215
|
-
self[p.role] = st.memo.get(memoKey);
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
try {
|
|
219
|
-
const v = await partValue(p, computeCtxFor(scope, self, false, key, p.role));
|
|
220
|
-
self[p.role] = v;
|
|
221
|
-
st.memo.set(memoKey, v);
|
|
222
|
-
}
|
|
223
|
-
catch (e) {
|
|
224
|
-
rethrowLoud(e);
|
|
225
|
-
console.warn(`[${write ? 'part-compute' : 'part-injection'}] '${key}.${p.role}' threw`, e);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (write) {
|
|
229
|
-
for (const p of fm.parts) {
|
|
230
|
-
if (p.mode !== 'computedStored')
|
|
231
|
-
continue;
|
|
232
|
-
try {
|
|
233
|
-
self[p.key] = await partValue(p, computeCtxFor(scope, self, true, key, p.role));
|
|
234
|
-
}
|
|
235
|
-
catch (e) {
|
|
236
|
-
rethrowLoud(e);
|
|
237
|
-
console.warn(`[part-compute] '${key}.${p.role}' threw`, e);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (contextOnly.length)
|
|
241
|
-
st.strip.push({ value: self, roles: contextOnly });
|
|
242
|
-
}
|
|
243
|
-
return self;
|
|
244
|
-
}
|
|
245
|
-
function materializable(fm) {
|
|
246
|
-
const parts = fm.parts ?? [];
|
|
247
|
-
if (!parts.some((p) => p.mode === 'computedStored'))
|
|
248
|
-
return false;
|
|
249
|
-
if (mandatedClientParts(fm.required, parts).length > 0)
|
|
250
|
-
return false;
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
function storedFilled(fm, value) {
|
|
254
|
-
if (value == null || typeof value !== 'object')
|
|
255
|
-
return false;
|
|
256
|
-
const v = value;
|
|
257
|
-
return (fm.parts ?? []).some((p) => p.key && v[p.key] != null && v[p.key] !== '');
|
|
258
|
-
}
|
|
259
|
-
function isPlainObject(v) {
|
|
260
|
-
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
261
|
-
}
|
|
262
|
-
async function runVirtualField(scope, key, fm) {
|
|
263
|
-
if (scope.st.mode === 'write') {
|
|
264
|
-
delete scope.obj[key];
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
try {
|
|
268
|
-
scope.obj[key] = await partValue({ value: fm.compute }, computeCtxFor(scope, {}, false, key, ''));
|
|
269
|
-
}
|
|
270
|
-
catch (e) {
|
|
271
|
-
rethrowLoud(e);
|
|
272
|
-
console.warn(`[part-injection] '${key}' threw`, e);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
async function runFieldUnit(scope, key, fm) {
|
|
276
|
-
if (fm.virtual === true && fm.compute !== undefined)
|
|
277
|
-
return runVirtualField(scope, key, fm);
|
|
278
|
-
if (!fm.parts?.length)
|
|
279
|
-
return;
|
|
280
|
-
const value = scope.obj[key];
|
|
281
|
-
if (value == null) {
|
|
282
|
-
if (scope.st.mode !== 'write' || fm.hints?.['multiple'] === true || !materializable(fm))
|
|
283
|
-
return;
|
|
284
|
-
const made = await completeValue(scope, key, fm, {}, undefined);
|
|
285
|
-
if (storedFilled(fm, made))
|
|
286
|
-
scope.obj[key] = made;
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (fm.hints?.['multiple'] === true) {
|
|
290
|
-
if (!Array.isArray(value))
|
|
291
|
-
return;
|
|
292
|
-
const out = [];
|
|
293
|
-
for (const [i, v] of value.entries())
|
|
294
|
-
out.push(await completeValue(scope, key, fm, v, i));
|
|
295
|
-
scope.obj[key] = out;
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
scope.obj[key] = await completeValue(scope, key, fm, value, undefined);
|
|
299
|
-
}
|
|
300
|
-
async function completeUnit(scope, key, chain) {
|
|
4
|
+
async function completeUnit(scope, key) {
|
|
301
5
|
if (scope.status.get(key) === 'done')
|
|
302
6
|
return;
|
|
303
7
|
const fm = scope.units.get(key);
|
|
304
8
|
if (!fm)
|
|
305
9
|
return;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (repeat !== -1) {
|
|
309
|
-
const names = [...chain.slice(repeat), hop].map((h) => `'${h.slice(h.indexOf(':') + 1)}'`);
|
|
310
|
-
throw new PartContractError(`[part-injection] ${scope.st.ctx.meta.library}/${scope.st.ctx.meta.shelf}: completion cycle ${names.join(' → ')}` +
|
|
311
|
-
` — these composite parts read each other, so neither can be completed first.`);
|
|
312
|
-
}
|
|
313
|
-
const inflight = scope.inflight.get(key);
|
|
314
|
-
if (inflight)
|
|
315
|
-
return inflight;
|
|
316
|
-
const next = [...chain, hop];
|
|
317
|
-
const run = (async () => {
|
|
318
|
-
const resolved = new Set();
|
|
319
|
-
for (;;) {
|
|
320
|
-
try {
|
|
321
|
-
await runFieldUnit(scope, key, fm);
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
catch (e) {
|
|
325
|
-
if (!(e instanceof NeedsCompletion))
|
|
326
|
-
throw e;
|
|
327
|
-
const dep = `${e.scope.uid}:${e.key}`;
|
|
328
|
-
if (resolved.has(dep))
|
|
329
|
-
throw new PartContractError(`[part-injection] ${scope.st.ctx.meta.library}/${scope.st.ctx.meta.shelf}: '${key}' demands '${e.key}' ` +
|
|
330
|
-
`again after it was completed — its compute function cannot make progress (a stale suspension held ` +
|
|
331
|
-
`across invocations, or a value object read outside the context it was handed).`);
|
|
332
|
-
resolved.add(dep);
|
|
333
|
-
await completeUnit(e.scope, e.key, next);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
scope.status.set(key, 'done');
|
|
337
|
-
})();
|
|
338
|
-
scope.inflight.set(key, run);
|
|
339
|
-
try {
|
|
340
|
-
await run;
|
|
341
|
-
}
|
|
342
|
-
finally {
|
|
343
|
-
scope.inflight.delete(key);
|
|
344
|
-
}
|
|
10
|
+
await runFieldUnit(scope, key, fm);
|
|
11
|
+
scope.status.set(key, 'done');
|
|
345
12
|
}
|
|
346
13
|
async function driveScope(scope, fields) {
|
|
347
14
|
for (const it of fields) {
|
|
348
|
-
if (
|
|
15
|
+
if (isNamedField(it)) {
|
|
349
16
|
if (!isComputeUnit(it.type))
|
|
350
17
|
continue;
|
|
351
|
-
await completeUnit(scope, it.key
|
|
18
|
+
await completeUnit(scope, it.key);
|
|
352
19
|
}
|
|
353
|
-
else if (
|
|
20
|
+
else if (hasChildren(it)) {
|
|
354
21
|
const sub = it.fields;
|
|
355
|
-
if (!it
|
|
22
|
+
if (!isStorageGroup(it)) {
|
|
356
23
|
await driveScope(scope, sub);
|
|
357
24
|
}
|
|
358
25
|
else if (isCollectionGroup(it)) {
|
|
@@ -362,7 +29,7 @@ async function driveScope(scope, fields) {
|
|
|
362
29
|
const copies = rows.map((row) => (isPlainObject(row) ? { ...row } : row));
|
|
363
30
|
scope.obj[it.key] = copies;
|
|
364
31
|
await Promise.all(copies.map((row, i) => isPlainObject(row)
|
|
365
|
-
? runScope(scope.st, row, sub,
|
|
32
|
+
? runScope(scope.st, row, sub, [...scope.path, it.key, i], null, `collection row '${it.key}'`)
|
|
366
33
|
: Promise.resolve(row)));
|
|
367
34
|
}
|
|
368
35
|
else {
|
|
@@ -372,7 +39,7 @@ async function driveScope(scope, fields) {
|
|
|
372
39
|
await driveScope(child, sub);
|
|
373
40
|
}
|
|
374
41
|
else if (cur == null && scope.st.mode === 'write' && partWork(sub).computedStored) {
|
|
375
|
-
const made = newScope(scope.st, {}, sub,
|
|
42
|
+
const made = newScope(scope.st, {}, sub, [...scope.path, it.key], scope.level, scope.level.label);
|
|
376
43
|
await driveScope(made, sub);
|
|
377
44
|
if (Object.keys(made.obj).length) {
|
|
378
45
|
scope.obj[it.key] = made.obj;
|
|
@@ -393,56 +60,46 @@ function partWork(fields) {
|
|
|
393
60
|
const cached = partWorkCache.get(fields);
|
|
394
61
|
if (cached !== undefined)
|
|
395
62
|
return cached;
|
|
396
|
-
const found = {
|
|
63
|
+
const found = { computedStored: false };
|
|
397
64
|
for (const it of fields) {
|
|
398
|
-
if (
|
|
399
|
-
if (it.type.
|
|
400
|
-
found.
|
|
401
|
-
for (const p of it.type.parts ?? [])
|
|
402
|
-
if (p.mode === '
|
|
403
|
-
found.unstored = true;
|
|
404
|
-
else if (p.mode === 'computedStored')
|
|
65
|
+
if (isNamedField(it)) {
|
|
66
|
+
if (it.type.compute !== undefined)
|
|
67
|
+
found.computedStored = true;
|
|
68
|
+
for (const p of it.type.parts ?? [])
|
|
69
|
+
if (p.mode === 'computedStored')
|
|
405
70
|
found.computedStored = true;
|
|
406
|
-
}
|
|
407
71
|
}
|
|
408
|
-
else if (
|
|
72
|
+
else if (hasChildren(it)) {
|
|
73
|
+
if (isStorageGroup(it) && it.compute !== undefined)
|
|
74
|
+
found.computedStored = true;
|
|
409
75
|
const sub = partWork(it.fields);
|
|
410
|
-
found.unstored ||= sub.unstored;
|
|
411
76
|
found.computedStored ||= sub.computedStored;
|
|
412
77
|
}
|
|
413
78
|
}
|
|
414
79
|
partWorkCache.set(fields, found);
|
|
415
80
|
return found;
|
|
416
81
|
}
|
|
417
|
-
async function runWalk(fields, row, ctx
|
|
82
|
+
async function runWalk(fields, row, ctx) {
|
|
418
83
|
const id = row['id'] ?? row['base_id'];
|
|
419
84
|
const st = {
|
|
420
85
|
ctx,
|
|
421
|
-
mode,
|
|
86
|
+
mode: 'write',
|
|
422
87
|
memo: ctx.memo ?? newPartMemo(),
|
|
423
88
|
refs: ctx.refs ?? null,
|
|
424
|
-
strip: [],
|
|
425
89
|
selfKey: typeof id === 'number' ? `${ctx.meta.library}/${ctx.meta.shelf}#${id}` : null,
|
|
426
90
|
recordLevel: null,
|
|
91
|
+
now: ctx.now ?? new Date().toISOString(),
|
|
427
92
|
};
|
|
428
|
-
const scope = newScope(st, { ...row }, fields,
|
|
93
|
+
const scope = newScope(st, { ...row }, fields, [], null, `${ctx.meta.library}/${ctx.meta.shelf}`);
|
|
429
94
|
st.recordLevel = scope.level;
|
|
430
95
|
await driveScope(scope, fields);
|
|
431
|
-
for (const s of st.strip)
|
|
432
|
-
for (const role of s.roles)
|
|
433
|
-
delete s.value[role];
|
|
434
96
|
return scope.obj;
|
|
435
97
|
}
|
|
436
|
-
export async function injectParts(fields, row, ctx) {
|
|
437
|
-
if (!partWork(fields).unstored)
|
|
438
|
-
return row;
|
|
439
|
-
return runWalk(fields, row, ctx, 'read');
|
|
440
|
-
}
|
|
441
98
|
export function hasStoredPartWork(fields) {
|
|
442
99
|
return partWork(fields).computedStored;
|
|
443
100
|
}
|
|
444
101
|
export async function applyStoredParts(fields, row, ctx) {
|
|
445
102
|
if (!partWork(fields).computedStored)
|
|
446
103
|
return row;
|
|
447
|
-
return runWalk(fields, row, ctx
|
|
104
|
+
return runWalk(fields, row, ctx);
|
|
448
105
|
}
|
package/dist/plugin-hooks.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface AgentTool {
|
|
|
42
42
|
export interface AgentContribution {
|
|
43
43
|
instructions?: string | ((ctx: PluginCtx) => string | Promise<string>);
|
|
44
44
|
tools?: AgentTool[];
|
|
45
|
+
starterHint?: string | ((ctx: PluginCtx) => string | Promise<string>);
|
|
45
46
|
}
|
|
46
47
|
export type PluginAction = (body: Record<string, unknown>) => Promise<unknown>;
|
|
47
48
|
export declare class HttpError extends Error {
|
package/dist/plugin-i18n.js
CHANGED
|
@@ -3,7 +3,8 @@ const cache = new Map();
|
|
|
3
3
|
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");
|
|
7
|
+
return (await getPluginSettings(SYSTEM_SETTINGS_ID))['language'] || fallback;
|
|
7
8
|
}
|
|
8
9
|
catch {
|
|
9
10
|
return fallback;
|
package/dist/plugin-runtime.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type Registry } from '@coffer-org/
|
|
1
|
+
import { type Registry } from '@coffer-org/sdk/compose';
|
|
2
2
|
import type { PluginManifest } from '@coffer-org/sdk/plugin';
|
|
3
3
|
export declare function getPlugins(): Promise<PluginManifest[]>;
|
|
4
4
|
export declare function readDisabled(): Promise<Set<string>>;
|
|
5
5
|
export declare function getDisabled(): Promise<Set<string>>;
|
|
6
|
-
export declare function getPluginSettings(
|
|
6
|
+
export declare function getPluginSettings(groupId: string): Promise<Record<string, unknown>>;
|
|
7
7
|
export declare function requireSettings<K extends string>(pluginId: string, keys: readonly K[]): Promise<Record<K, string>>;
|
|
8
8
|
export declare function initStorage(): Promise<Set<string>>;
|
|
9
9
|
export declare function initPlugins(): Promise<Registry>;
|