@ixo/oracle-runtime-workers 0.37.2 → 0.39.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/package.json +4 -4
- package/src/attachments/channel-links.ts +74 -7
- package/src/attachments/channel.ts +15 -4
- package/src/attachments/content-blocks.ts +19 -16
- package/src/attachments/download.ts +173 -50
- package/src/attachments/index.ts +6 -5
- package/src/attachments/pipeline.ts +117 -49
- package/src/attachments/resize.ts +142 -0
- package/src/attachments/retention.ts +26 -70
- package/src/attachments/room-media.ts +643 -0
- package/src/attachments/route.ts +47 -4
- package/src/attachments/sandbox-archive.ts +15 -4
- package/src/attachments/sanitize.ts +24 -13
- package/src/attachments/view.ts +82 -38
- package/src/channels/replies.ts +8 -4
- package/src/core/capability-expiry.ts +62 -0
- package/src/core/capability-router.ts +126 -95
- package/src/core/context-budget.ts +116 -4
- package/src/core/domain-context/document.ts +74 -2
- package/src/core/domain-context/index.ts +1 -0
- package/src/core/domain-context/resolver.ts +9 -6
- package/src/core/domain-context/turn-context.ts +62 -13
- package/src/core/domain-context/types.ts +17 -0
- package/src/core/env.ts +21 -2
- package/src/core/index.ts +4 -0
- package/src/core/llm.ts +29 -0
- package/src/core/main-agent-types.ts +53 -4
- package/src/core/main-agent.ts +228 -125
- package/src/core/manifest.ts +109 -24
- package/src/core/mcp-result.ts +56 -0
- package/src/core/meta-tools.ts +258 -110
- package/src/core/middlewares/attachment-recall.ts +502 -0
- package/src/core/middlewares/capability-gate.ts +77 -28
- package/src/core/middlewares/context-guard.ts +154 -21
- package/src/core/middlewares/index.ts +5 -0
- package/src/core/middlewares/result-cap.ts +1 -1
- package/src/core/middlewares/summarization.ts +9 -0
- package/src/core/middlewares/tool-marks.ts +11 -2
- package/src/core/middlewares/turn-boundary.ts +22 -12
- package/src/core/operating-guides.ts +126 -0
- package/src/core/plugins/skills/skills-tools.ts +6 -31
- package/src/core/plugins/skills/skills.plugin.ts +4 -3
- package/src/core/plugins/weather/weather.plugin.ts +1 -0
- package/src/core/prompt-composer.ts +188 -111
- package/src/core/read-result-tool.ts +48 -14
- package/src/core/registries.ts +13 -5
- package/src/core/state.ts +4 -3
- package/src/core/sub-agent-fallback.ts +1 -0
- package/src/core/subagent-as-tool.ts +4 -1
- package/src/core/tool-access.ts +3 -32
- package/src/core/wrap-plugin-tool.ts +2 -1
- package/src/do/contracts.ts +64 -24
- package/src/do/room-mirror.ts +144 -20
- package/src/do/transcript.ts +41 -12
- package/src/do/turn-attachments.ts +45 -6
- package/src/do/turn-metadata.ts +16 -4
- package/src/do/user-oracle-do.ts +639 -152
- package/src/index.ts +1 -0
- package/src/llm/provider-error.ts +5 -2
- package/src/llm/stream-liveness.ts +3 -2
- package/src/matrix/gateway-do.ts +164 -102
- package/src/matrix/ingest.ts +12 -3
- package/src/matrix/media-deadline.ts +5 -47
- package/src/memory/session-history-indexer.ts +60 -14
- package/src/plugin-api/oracle-plugin.ts +4 -5
- package/src/plugin-api/types.ts +32 -3
- package/src/plugins/action-proposals/action-proposals.plugin.ts +7 -2
- package/src/plugins/agui/agui.plugin.ts +1 -0
- package/src/plugins/attachments/attachments.plugin.ts +3 -3
- package/src/plugins/attachments/view-attachment-tool.ts +6 -4
- package/src/plugins/compact-schema.ts +80 -0
- package/src/plugins/composio/composio.plugin.ts +1 -0
- package/src/plugins/domain-indexer/domain-indexer.plugin.ts +4 -3
- package/src/plugins/editor/editor.plugin.ts +4 -0
- package/src/plugins/firecrawl/firecrawl-agent.ts +2 -0
- package/src/plugins/firecrawl/firecrawl.plugin.ts +1 -0
- package/src/plugins/flows/flows.plugin.ts +37 -6
- package/src/plugins/flows/prompts.ts +1 -1
- package/src/plugins/flows/tools/authoring.ts +5 -7
- package/src/plugins/flows/tools/discovery.ts +1 -4
- package/src/plugins/flows/tools/forms.ts +2 -8
- package/src/plugins/flows/tools/linkage.ts +1 -4
- package/src/plugins/flows/tools/settings.ts +1 -4
- package/src/plugins/flows/translator.ts +16 -5
- package/src/plugins/flows/types.ts +27 -48
- package/src/plugins/hub-actions/hub-actions.plugin.ts +30 -4
- package/src/plugins/index.ts +2 -2
- package/src/plugins/ixo-chain/ixo-chain.plugin.ts +29 -5
- package/src/plugins/ixo-chain/kinds/core.ts +2 -2
- package/src/plugins/ixo-chain/kinds/types.ts +34 -5
- package/src/plugins/ixo-chain/queries.ts +10 -1
- package/src/plugins/ixo-transaction/ixo-transaction.plugin.ts +1 -0
- package/src/plugins/matrix-group-chats/index.ts +1 -0
- package/src/plugins/memory/memory-tools.ts +4 -4
- package/src/plugins/memory/memory.plugin.ts +27 -1
- package/src/plugins/memory/oracle-document-tools.ts +24 -49
- package/src/plugins/pod-creator/pod-creator.plugin.ts +1 -0
- package/src/plugins/portal/portal.plugin.ts +1 -0
- package/src/plugins/sandbox/sandbox-write-blob.ts +3 -15
- package/src/plugins/sandbox/sandbox.plugin.ts +19 -2
- package/src/plugins/tasks/manifest.ts +1 -0
- package/src/plugins/tasks/tasks-tools.ts +14 -19
- package/src/plugins/user-preferences/user-preferences-tool.ts +11 -37
- package/src/plugins/user-preferences/user-preferences.plugin.ts +4 -0
- package/src/plugins/vfs/vfs-tools.ts +6 -6
- package/src/plugins/vfs/vfs.plugin.ts +4 -5
- package/src/plugins/yoma/yoma.plugin.ts +1 -0
- package/src/sqlite/inline-payloads.ts +121 -0
- package/src/sqlite/serialization.ts +12 -0
- package/src/sqlite/sqlite-saver.ts +258 -42
- package/src/tasks/schedule-input.ts +5 -22
package/src/core/manifest.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ManifestExample,
|
|
4
|
+
PluginManifest,
|
|
5
|
+
PluginTool,
|
|
6
|
+
} from '../plugin-api/types';
|
|
3
7
|
|
|
4
8
|
// ── Schema ──────────────────────────────────────────────────────────────────
|
|
5
9
|
|
|
@@ -48,6 +52,9 @@ export const pluginManifestSchema: z.ZodType<PluginManifest> = z.object({
|
|
|
48
52
|
tags: z.array(z.string()).optional(),
|
|
49
53
|
category: manifestCategorySchema.optional(),
|
|
50
54
|
visibility: manifestVisibilitySchema.optional(),
|
|
55
|
+
brief: z.string().optional(),
|
|
56
|
+
onDemandTools: z.array(z.string().min(1)).optional(),
|
|
57
|
+
routable: z.boolean().optional(),
|
|
51
58
|
stability: manifestStabilitySchema.optional(),
|
|
52
59
|
requires: z
|
|
53
60
|
.array(z.object({ resource: z.string().min(1), action: z.string().min(1) }))
|
|
@@ -69,6 +76,19 @@ export function unmetRequirements(
|
|
|
69
76
|
);
|
|
70
77
|
}
|
|
71
78
|
|
|
79
|
+
/**
|
|
80
|
+
* A tool's effective visibility: its own `visibility`, else the manifest's
|
|
81
|
+
* `onDemandTools`, else the plugin's visibility (`on-demand` by default).
|
|
82
|
+
*/
|
|
83
|
+
export function effectiveToolVisibility(
|
|
84
|
+
tool: Pick<PluginTool, 'name' | 'visibility'>,
|
|
85
|
+
manifest: PluginManifest | undefined,
|
|
86
|
+
): NonNullable<PluginManifest['visibility']> {
|
|
87
|
+
if (tool.visibility) return tool.visibility;
|
|
88
|
+
if (manifest?.onDemandTools?.includes(tool.name)) return 'on-demand';
|
|
89
|
+
return manifest?.visibility ?? 'on-demand';
|
|
90
|
+
}
|
|
91
|
+
|
|
72
92
|
/** `can` on `with` pairs for a message: "`*` on `ixo:filesystem`, …". */
|
|
73
93
|
export function describeRequirements(
|
|
74
94
|
requirements: readonly CapabilityRequirement[],
|
|
@@ -90,6 +110,7 @@ export interface ManifestValidationResult {
|
|
|
90
110
|
/** Soft-cap thresholds — violations only emit warnings. */
|
|
91
111
|
const SOFT_LIMITS = {
|
|
92
112
|
summaryMaxChars: 120,
|
|
113
|
+
briefMaxChars: 60,
|
|
93
114
|
whenToUseMaxItems: 8,
|
|
94
115
|
whenToUseItemMaxChars: 100,
|
|
95
116
|
whenNotToUseMaxItems: 4,
|
|
@@ -161,6 +182,13 @@ export function validateManifest(
|
|
|
161
182
|
);
|
|
162
183
|
}
|
|
163
184
|
|
|
185
|
+
// Soft rule: brief length cap.
|
|
186
|
+
if (m.brief && m.brief.length > SOFT_LIMITS.briefMaxChars) {
|
|
187
|
+
warnings.push(
|
|
188
|
+
`${tag(pluginName, 'brief')} is ${m.brief.length} chars (recommended ≤ ${SOFT_LIMITS.briefMaxChars}).`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
164
192
|
// Soft rule: whenToUse caps.
|
|
165
193
|
if (m.whenToUse.length > SOFT_LIMITS.whenToUseMaxItems) {
|
|
166
194
|
warnings.push(
|
|
@@ -202,9 +230,9 @@ export function validateManifest(
|
|
|
202
230
|
}
|
|
203
231
|
|
|
204
232
|
/**
|
|
205
|
-
* Cross-check `examples[].tool` references against the
|
|
206
|
-
* actually registered by this plugin. Run after the
|
|
207
|
-
* has been invoked.
|
|
233
|
+
* Cross-check `examples[].tool` and `onDemandTools` references against the
|
|
234
|
+
* set of tool names actually registered by this plugin. Run after the
|
|
235
|
+
* plugin's `getTools()` has been invoked.
|
|
208
236
|
*
|
|
209
237
|
* **Lenient for request-time plugins**: when `registeredToolNames` is empty,
|
|
210
238
|
* the plugin's tools come from `getRequestTools` — boot-time validation
|
|
@@ -218,18 +246,23 @@ export function validateExamplesAgainstTools(
|
|
|
218
246
|
pluginName: string,
|
|
219
247
|
): { errors: string[] } {
|
|
220
248
|
const errors: string[] = [];
|
|
221
|
-
if (!manifest.examples) return { errors };
|
|
222
|
-
|
|
223
249
|
if (registeredToolNames.length === 0) return { errors };
|
|
224
250
|
|
|
225
251
|
const known = new Set(registeredToolNames);
|
|
226
|
-
manifest.examples
|
|
252
|
+
manifest.examples?.forEach((ex, i) => {
|
|
227
253
|
if (!known.has(ex.tool)) {
|
|
228
254
|
errors.push(
|
|
229
255
|
`${tag(pluginName, `examples[${i}].tool`)} references unknown tool "${ex.tool}".`,
|
|
230
256
|
);
|
|
231
257
|
}
|
|
232
258
|
});
|
|
259
|
+
manifest.onDemandTools?.forEach((name, i) => {
|
|
260
|
+
if (!known.has(name)) {
|
|
261
|
+
errors.push(
|
|
262
|
+
`${tag(pluginName, `onDemandTools[${i}]`)} references unknown tool "${name}".`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
233
266
|
|
|
234
267
|
return { errors };
|
|
235
268
|
}
|
|
@@ -273,8 +306,20 @@ export interface Tier1Entry {
|
|
|
273
306
|
manifest: PluginManifest;
|
|
274
307
|
}
|
|
275
308
|
|
|
309
|
+
/** One line of the on-demand catalogue. */
|
|
310
|
+
export interface CatalogueEntry extends Tier1Entry {
|
|
311
|
+
/** The user's authorization lacks what the plugin requires. */
|
|
312
|
+
unavailable?: boolean;
|
|
313
|
+
}
|
|
314
|
+
|
|
276
315
|
export interface Tier1Input {
|
|
277
316
|
manifests: Tier1Entry[];
|
|
317
|
+
/**
|
|
318
|
+
* Capabilities with something left to load this turn. Rendered whether or
|
|
319
|
+
* not the thread already loaded them, so the block does not change when a
|
|
320
|
+
* plugin is loaded and stays inside the provider's prompt cache.
|
|
321
|
+
*/
|
|
322
|
+
catalogue?: CatalogueEntry[];
|
|
278
323
|
/** Soft budget in tokens. Default 5000. Exceeding it triggers a warning. */
|
|
279
324
|
tokenBudget?: number;
|
|
280
325
|
/**
|
|
@@ -283,12 +328,6 @@ export interface Tier1Input {
|
|
|
283
328
|
* budget is a soft operator hint where ±20% is immaterial.
|
|
284
329
|
*/
|
|
285
330
|
estimateTokens?: (text: string) => number;
|
|
286
|
-
/**
|
|
287
|
-
* Whether the meta-tools are bound this turn. Default `true`. Pass `false`
|
|
288
|
-
* when they are not, so the block does not end by telling the model to
|
|
289
|
-
* call `list_capabilities` / `load_capability`.
|
|
290
|
-
*/
|
|
291
|
-
capabilityDiscovery?: boolean;
|
|
292
331
|
}
|
|
293
332
|
|
|
294
333
|
export interface Tier1Output {
|
|
@@ -303,8 +342,10 @@ export interface Tier1Output {
|
|
|
303
342
|
const DEFAULT_BUDGET = 5000;
|
|
304
343
|
|
|
305
344
|
const HEADER = '## Available Capabilities\n\n';
|
|
306
|
-
const
|
|
307
|
-
'\n\
|
|
345
|
+
const CATALOGUE_HEADER =
|
|
346
|
+
'## On-demand capabilities\n\nNot in your tool list until loaded. Call `load_capability({ names: [...] })` once with all you need, right from this list (no `list_capabilities` needed); the tools join your list on the next step. Anything already in your tool list needs no loading.\n\n';
|
|
347
|
+
/** Catalogue line of a plugin that declares no `brief`. */
|
|
348
|
+
const CLIPPED_SUMMARY_CHARS = 60;
|
|
308
349
|
|
|
309
350
|
/**
|
|
310
351
|
* Chars/4 token estimate. Replaces the Node runtime's `js-tiktoken`
|
|
@@ -331,6 +372,37 @@ function formatExample(ex: ManifestExample): string {
|
|
|
331
372
|
return `"${ex.user}" → ${argSummary}`;
|
|
332
373
|
}
|
|
333
374
|
|
|
375
|
+
/** The manifest's `brief`, or its summary clipped at a word boundary. */
|
|
376
|
+
function catalogueText(manifest: PluginManifest): string {
|
|
377
|
+
if (manifest.brief) return manifest.brief;
|
|
378
|
+
const summary = manifest.summary.trim();
|
|
379
|
+
if (summary.length <= CLIPPED_SUMMARY_CHARS) return summary;
|
|
380
|
+
const clipped = summary.slice(0, CLIPPED_SUMMARY_CHARS);
|
|
381
|
+
const boundary = clipped.lastIndexOf(' ');
|
|
382
|
+
return `${boundary > 0 ? clipped.slice(0, boundary) : clipped}…`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function formatCatalogueLine({
|
|
386
|
+
pluginName,
|
|
387
|
+
manifest,
|
|
388
|
+
unavailable,
|
|
389
|
+
}: CatalogueEntry): string {
|
|
390
|
+
const line = `- ${pluginName}: ${catalogueText(manifest)}`;
|
|
391
|
+
return unavailable ? `${line} (not authorized by this user)` : line;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The first example the prompt may teach: one naming a tool still behind
|
|
396
|
+
* `load_capability` would invite a call the capability gate refuses.
|
|
397
|
+
*/
|
|
398
|
+
function firstBoundExample(
|
|
399
|
+
manifest: PluginManifest,
|
|
400
|
+
): ManifestExample | undefined {
|
|
401
|
+
return manifest.examples?.find(
|
|
402
|
+
(example) => !manifest.onDemandTools?.includes(example.tool),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
334
406
|
function formatEntry(entry: Tier1Entry): string {
|
|
335
407
|
const { pluginName, manifest } = entry;
|
|
336
408
|
const lines: string[] = [`- **${pluginName}** — ${manifest.summary}`];
|
|
@@ -344,7 +416,7 @@ function formatEntry(entry: Tier1Entry): string {
|
|
|
344
416
|
lines.push(` - Avoid for: ${manifest.whenNotToUse[0]}`);
|
|
345
417
|
}
|
|
346
418
|
|
|
347
|
-
const firstExample = manifest
|
|
419
|
+
const firstExample = firstBoundExample(manifest);
|
|
348
420
|
if (firstExample) {
|
|
349
421
|
lines.push(` - Example: ${formatExample(firstExample)}`);
|
|
350
422
|
}
|
|
@@ -355,10 +427,11 @@ function formatEntry(entry: Tier1Entry): string {
|
|
|
355
427
|
/**
|
|
356
428
|
* Compose the Tier-1 capability block from a list of plugin manifests.
|
|
357
429
|
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
430
|
+
* Full entries for the `visibility: 'always'` manifests, then one line per
|
|
431
|
+
* `catalogue` entry, each list sorted alphabetically for prompt-caching
|
|
432
|
+
* determinism. Over-budget calls produce a warning naming the largest
|
|
433
|
+
* manifests so operators can mark them `'on-demand'` — degrading verbosity
|
|
434
|
+
* is an operator decision, not a runtime guess.
|
|
362
435
|
*/
|
|
363
436
|
export function renderTier1(input: Tier1Input): Tier1Output {
|
|
364
437
|
const budget = input.tokenBudget ?? DEFAULT_BUDGET;
|
|
@@ -369,12 +442,20 @@ export function renderTier1(input: Tier1Input): Tier1Output {
|
|
|
369
442
|
.slice()
|
|
370
443
|
.sort((a, b) => a.pluginName.localeCompare(b.pluginName));
|
|
371
444
|
|
|
372
|
-
|
|
445
|
+
const catalogue = (input.catalogue ?? [])
|
|
446
|
+
.slice()
|
|
447
|
+
.sort((a, b) => a.pluginName.localeCompare(b.pluginName))
|
|
448
|
+
.map(formatCatalogueLine);
|
|
449
|
+
|
|
450
|
+
if (entries.length === 0 && catalogue.length === 0) {
|
|
373
451
|
return { block: '', tokens: 0, warnings: [] };
|
|
374
452
|
}
|
|
375
453
|
|
|
376
454
|
const renderedLines = entries.map(formatEntry);
|
|
377
|
-
const tokens = renderedLines.reduce(
|
|
455
|
+
const tokens = [...renderedLines, ...catalogue].reduce(
|
|
456
|
+
(sum, line) => sum + estimate(line),
|
|
457
|
+
0,
|
|
458
|
+
);
|
|
378
459
|
const warnings: string[] = [];
|
|
379
460
|
|
|
380
461
|
if (tokens > budget) {
|
|
@@ -393,7 +474,11 @@ export function renderTier1(input: Tier1Input): Tier1Output {
|
|
|
393
474
|
);
|
|
394
475
|
}
|
|
395
476
|
|
|
396
|
-
const
|
|
397
|
-
|
|
477
|
+
const block = [
|
|
478
|
+
entries.length > 0 ? `${HEADER}${renderedLines.join('\n\n')}` : '',
|
|
479
|
+
catalogue.length > 0 ? `${CATALOGUE_HEADER}${catalogue.join('\n')}` : '',
|
|
480
|
+
]
|
|
481
|
+
.filter((section) => section !== '')
|
|
482
|
+
.join('\n\n');
|
|
398
483
|
return { block, tokens, warnings };
|
|
399
484
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@langchain/mcp-adapters` answers a tool that returns `structuredContent`
|
|
3
|
+
* with one block holding the text and the structured value side by side, and
|
|
4
|
+
* a tool's non-string result is stringified for the model. A server's text
|
|
5
|
+
* block is normally the JSON of its structured content (the MCP specification
|
|
6
|
+
* asks for that), so the model reads the whole result twice, the first copy
|
|
7
|
+
* with every quote escaped.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
11
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Whether two JSON values are equal, whatever the order of their keys. */
|
|
15
|
+
function sameJson(a: unknown, b: unknown): boolean {
|
|
16
|
+
if (Array.isArray(a))
|
|
17
|
+
return (
|
|
18
|
+
Array.isArray(b) &&
|
|
19
|
+
a.length === b.length &&
|
|
20
|
+
a.every((item, index) => sameJson(item, b[index]))
|
|
21
|
+
);
|
|
22
|
+
if (isRecord(a)) {
|
|
23
|
+
if (!isRecord(b)) return false;
|
|
24
|
+
const keys = Object.keys(a);
|
|
25
|
+
return (
|
|
26
|
+
keys.length === Object.keys(b).length &&
|
|
27
|
+
keys.every((key) => key in b && sameJson(a[key], b[key]))
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return a === b;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parsesTo(text: string, value: unknown): boolean {
|
|
34
|
+
try {
|
|
35
|
+
return sameJson(JSON.parse(text), value);
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A tool's output without a structured copy of its text. Only an exact copy
|
|
43
|
+
* goes: a server whose text says something else (a summary beside the data)
|
|
44
|
+
* keeps both, and a block with `meta` keeps it. Anything else passes through.
|
|
45
|
+
*/
|
|
46
|
+
export function withoutStructuredCopy(output: unknown): unknown {
|
|
47
|
+
if (
|
|
48
|
+
!isRecord(output) ||
|
|
49
|
+
output.type !== 'text' ||
|
|
50
|
+
typeof output.text !== 'string' ||
|
|
51
|
+
!parsesTo(output.text, output.structuredContent)
|
|
52
|
+
)
|
|
53
|
+
return output;
|
|
54
|
+
const { structuredContent: _copy, ...block } = output;
|
|
55
|
+
return 'meta' in block ? block : block.text;
|
|
56
|
+
}
|