@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.
Files changed (101) hide show
  1. package/dist/auth-api.js +4 -5
  2. package/dist/cache-clock.d.ts +8 -0
  3. package/dist/cache-clock.js +103 -0
  4. package/dist/cache-refresh.d.ts +7 -0
  5. package/dist/cache-refresh.js +322 -0
  6. package/dist/collection-io.d.ts +13 -0
  7. package/dist/collection-io.js +40 -29
  8. package/dist/compute-unit.d.ts +66 -0
  9. package/dist/compute-unit.js +369 -0
  10. package/dist/condition-where.js +1 -3
  11. package/dist/data-dir.d.ts +3 -0
  12. package/dist/data-dir.js +39 -0
  13. package/dist/entity-schema.js +5 -2
  14. package/dist/extend-table.d.ts +1 -1
  15. package/dist/extend-table.js +22 -8
  16. package/dist/file-fields.js +50 -23
  17. package/dist/frontend-agent.d.ts +1 -1
  18. package/dist/frontend-agent.js +2 -3
  19. package/dist/global-search.js +1 -1
  20. package/dist/index.js +20 -26
  21. package/dist/mcp-contract/client.d.ts +24 -0
  22. package/dist/mcp-contract/client.js +12 -0
  23. package/dist/mcp-contract/schema.d.ts +46 -0
  24. package/dist/mcp-contract/schema.js +90 -0
  25. package/dist/mcp-contract/tools.d.ts +19 -0
  26. package/dist/mcp-contract/tools.js +160 -0
  27. package/dist/mcp-http.js +4 -2
  28. package/dist/mcp-local.d.ts +1 -1
  29. package/dist/mcp-local.js +1 -1
  30. package/dist/mcp-tools.d.ts +13 -1
  31. package/dist/mcp-tools.js +46 -16
  32. package/dist/migrations.d.ts +5 -0
  33. package/dist/migrations.js +122 -0
  34. package/dist/mutate.js +77 -36
  35. package/dist/orchestrator/agent-capabilities.d.ts +2 -0
  36. package/dist/orchestrator/agent-capabilities.js +201 -0
  37. package/dist/orchestrator/allow.d.ts +16 -0
  38. package/dist/orchestrator/allow.js +65 -0
  39. package/dist/orchestrator/attachments.d.ts +2 -0
  40. package/dist/orchestrator/attachments.js +11 -0
  41. package/dist/orchestrator/config.d.ts +4 -0
  42. package/dist/orchestrator/config.js +19 -0
  43. package/dist/orchestrator/db.d.ts +17 -0
  44. package/dist/orchestrator/db.js +12 -0
  45. package/dist/orchestrator/diagnostics.d.ts +9 -0
  46. package/dist/orchestrator/diagnostics.js +13 -0
  47. package/dist/orchestrator/environment.d.ts +2 -0
  48. package/dist/orchestrator/environment.js +14 -0
  49. package/dist/orchestrator/file-inspection.d.ts +3 -0
  50. package/dist/orchestrator/file-inspection.js +61 -0
  51. package/dist/orchestrator/format.d.ts +1 -0
  52. package/dist/orchestrator/format.js +6 -0
  53. package/dist/orchestrator/index.d.ts +30 -0
  54. package/dist/orchestrator/index.js +56 -0
  55. package/dist/orchestrator/live-message.d.ts +14 -0
  56. package/dist/orchestrator/live-message.js +100 -0
  57. package/dist/orchestrator/pipeline.d.ts +11 -0
  58. package/dist/orchestrator/pipeline.js +198 -0
  59. package/dist/orchestrator/registry.d.ts +13 -0
  60. package/dist/orchestrator/registry.js +74 -0
  61. package/dist/orchestrator/starters.d.ts +19 -0
  62. package/dist/orchestrator/starters.js +81 -0
  63. package/dist/orchestrator/suggestion-capabilities.d.ts +5 -0
  64. package/dist/orchestrator/suggestion-capabilities.js +33 -0
  65. package/dist/orchestrator/system-assembly.d.ts +4 -0
  66. package/dist/orchestrator/system-assembly.js +3 -0
  67. package/dist/orchestrator/system-capabilities.d.ts +2 -0
  68. package/dist/orchestrator/system-capabilities.js +63 -0
  69. package/dist/orchestrator/types.d.ts +141 -0
  70. package/dist/orchestrator/types.js +1 -0
  71. package/dist/part-injection.d.ts +3 -4
  72. package/dist/part-injection.js +25 -368
  73. package/dist/plugin-hooks.d.ts +1 -0
  74. package/dist/plugin-i18n.js +2 -1
  75. package/dist/plugin-runtime.d.ts +2 -2
  76. package/dist/plugin-runtime.js +20 -11
  77. package/dist/plugin-updates.d.ts +2 -2
  78. package/dist/plugin-updates.js +6 -12
  79. package/dist/public-url.js +2 -1
  80. package/dist/recompute-derived.d.ts +2 -1
  81. package/dist/recompute-derived.js +111 -34
  82. package/dist/records-api.js +14 -16
  83. package/dist/registry-context.d.ts +1 -1
  84. package/dist/registry-context.js +3 -0
  85. package/dist/search-index.js +1 -1
  86. package/dist/search-indexer.d.ts +1 -1
  87. package/dist/search-indexer.js +3 -3
  88. package/dist/settings-groups.d.ts +14 -0
  89. package/dist/settings-groups.js +25 -0
  90. package/dist/settings-write.d.ts +4 -4
  91. package/dist/settings-write.js +23 -26
  92. package/dist/system-settings.d.ts +3 -0
  93. package/dist/system-settings.js +27 -0
  94. package/dist/thread-store.d.ts +3 -2
  95. package/dist/thread-store.js +6 -5
  96. package/dist/trigger-queue.d.ts +33 -0
  97. package/dist/trigger-queue.js +432 -0
  98. package/dist/uploads.js +6 -3
  99. package/dist/value-compare.d.ts +1 -0
  100. package/dist/value-compare.js +7 -0
  101. 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 {};
@@ -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 { type RefState } from './part-ref.ts';
4
- type Row = Record<string, unknown>;
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 {};
@@ -1,358 +1,25 @@
1
- import { isField, isGroup, isCollectionGroup } from '@coffer-org/sdk/fields';
2
- import { mandatedClientParts } from '@coffer-org/sdk/parts';
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
- class NeedsCompletion extends Error {
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
- const hop = `${scope.uid}:${key}`;
307
- const repeat = chain.indexOf(hop);
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 (isField(it)) {
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 (isGroup(it)) {
20
+ else if (hasChildren(it)) {
354
21
  const sub = it.fields;
355
- if (!it.key) {
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, `${scope.path}${it.key}.${i}.`, null, `collection row '${it.key}'`)
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, `${scope.path}${it.key}.`, scope.level, scope.level.label);
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 = { unstored: false, computedStored: false };
63
+ const found = { computedStored: false };
397
64
  for (const it of fields) {
398
- if (isField(it)) {
399
- if (it.type.virtual === true && it.type.compute !== undefined)
400
- found.unstored = true;
401
- for (const p of it.type.parts ?? []) {
402
- if (p.mode === 'pinned' || p.mode === 'computed')
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 (isGroup(it)) {
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, mode) {
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, '', null, `${ctx.meta.library}/${ctx.meta.shelf}`);
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, 'write');
104
+ return runWalk(fields, row, ctx);
448
105
  }
@@ -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 {
@@ -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
- return (await getPluginSettings('core'))['language'] || fallback;
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;
@@ -1,9 +1,9 @@
1
- import { type Registry } from '@coffer-org/core/compose';
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(pluginId: string): Promise<Record<string, unknown>>;
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>;