@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
|
@@ -32,70 +32,59 @@ const DOCUMENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,119}$/;
|
|
|
32
32
|
const spaceSchema = z
|
|
33
33
|
.enum(['public', 'private'])
|
|
34
34
|
.describe(
|
|
35
|
-
"
|
|
35
|
+
"'public': every user of this oracle. 'private': the domain's members only (controllers, owner, admins, DAO members).",
|
|
36
36
|
);
|
|
37
37
|
|
|
38
38
|
const sourceSchema = z
|
|
39
39
|
.object({
|
|
40
40
|
kind: z
|
|
41
41
|
.enum(['attachment', 'vfs'])
|
|
42
|
-
.describe(
|
|
43
|
-
"'attachment' for a file the person attached in this conversation, 'vfs' for a file in their Library.",
|
|
44
|
-
),
|
|
42
|
+
.describe("'attachment': attached here. 'vfs': in their Library."),
|
|
45
43
|
ref: z
|
|
46
44
|
.string()
|
|
47
45
|
.min(1)
|
|
48
46
|
.describe(
|
|
49
|
-
'
|
|
47
|
+
'attachment: the value in the `[source: eventId="…"]` or `[source: url="…"]` line beside the file, exactly as given ("$abc…" or an mxc:// URL). vfs: the Library path, e.g. /private/policies/travel.pdf.',
|
|
50
48
|
),
|
|
51
49
|
})
|
|
52
|
-
.strict()
|
|
53
|
-
.describe('Where the file is.');
|
|
50
|
+
.strict();
|
|
54
51
|
|
|
55
52
|
export const addOracleDocumentSchema = z
|
|
56
53
|
.object({
|
|
57
54
|
source: sourceSchema,
|
|
58
|
-
title: z
|
|
59
|
-
.string()
|
|
60
|
-
.min(1)
|
|
61
|
-
.max(200)
|
|
62
|
-
.describe('A short human title for the document, 1 to 200 characters.'),
|
|
55
|
+
title: z.string().min(1).max(200),
|
|
63
56
|
knowledge_space_type: spaceSchema,
|
|
64
|
-
group_as_document: z
|
|
65
|
-
.boolean()
|
|
66
|
-
.describe(
|
|
67
|
-
'Required, no default. true: the file is ONE coherent record and is kept together under a document id (it can be listed and removed as one). false: the file is a collection of unrelated facts and each part is stored as ordinary loose knowledge with no document grouping. Decide with the person, see the tool description.',
|
|
68
|
-
),
|
|
57
|
+
group_as_document: z.boolean(),
|
|
69
58
|
document_id: z
|
|
70
59
|
.string()
|
|
71
60
|
.regex(DOCUMENT_ID_PATTERN)
|
|
72
61
|
.optional()
|
|
73
62
|
.describe(
|
|
74
|
-
"
|
|
63
|
+
"Grouped only: a stable id such as 'board-resolution-2026-10'. Default: derived from title and content, so adding the same file again replaces it.",
|
|
75
64
|
),
|
|
76
65
|
source_description: z
|
|
77
66
|
.string()
|
|
78
67
|
.max(500)
|
|
79
68
|
.optional()
|
|
80
69
|
.describe(
|
|
81
|
-
"
|
|
70
|
+
"Grouped only: where it comes from, e.g. 'Signed PDF from the board secretary, 12 Oct 2026'.",
|
|
82
71
|
),
|
|
83
72
|
if_exists: z
|
|
84
73
|
.enum(['replace', 'fail'])
|
|
85
74
|
.optional()
|
|
86
75
|
.describe(
|
|
87
|
-
"
|
|
76
|
+
"Grouped only: for a document_id that exists, 'replace' (default) or 'fail'.",
|
|
88
77
|
),
|
|
89
78
|
allow_truncation: z
|
|
90
79
|
.boolean()
|
|
91
80
|
.optional()
|
|
92
81
|
.describe(
|
|
93
|
-
'
|
|
82
|
+
'true keeps only the first part of a file over the limits. Only after the person agreed.',
|
|
94
83
|
),
|
|
95
84
|
confirmed_insertion_from_user: z
|
|
96
85
|
.boolean()
|
|
97
86
|
.describe(
|
|
98
|
-
'true only
|
|
87
|
+
'true only after the person asked to store this file and you told them who will see it.',
|
|
99
88
|
),
|
|
100
89
|
})
|
|
101
90
|
.strict();
|
|
@@ -108,57 +97,43 @@ export const listOracleDocumentsSchema = z
|
|
|
108
97
|
knowledge_space_type: spaceSchema,
|
|
109
98
|
status: z
|
|
110
99
|
.enum(['ingesting', 'ready', 'partial', 'deleting', 'delete_failed'])
|
|
111
|
-
.optional()
|
|
112
|
-
.describe('Only list documents in this status. Leave out for all.'),
|
|
100
|
+
.optional(),
|
|
113
101
|
limit: z
|
|
114
102
|
.number()
|
|
115
103
|
.int()
|
|
116
104
|
.min(1)
|
|
117
105
|
.max(MAX_LIST_LIMIT)
|
|
118
106
|
.optional()
|
|
119
|
-
.describe(
|
|
120
|
-
`Most documents to list, 1 to ${MAX_LIST_LIMIT} (default ${DEFAULT_LIST_LIMIT}).`,
|
|
121
|
-
),
|
|
107
|
+
.describe(`Default ${DEFAULT_LIST_LIMIT}.`),
|
|
122
108
|
})
|
|
123
109
|
.strict();
|
|
124
110
|
|
|
125
111
|
export const deleteOracleDocumentSchema = z
|
|
126
112
|
.object({
|
|
127
|
-
document_id: z
|
|
128
|
-
.string()
|
|
129
|
-
.regex(DOCUMENT_ID_PATTERN)
|
|
130
|
-
.describe(
|
|
131
|
-
'The id of a grouped document, as shown by list_oracle_documents.',
|
|
132
|
-
),
|
|
113
|
+
document_id: z.string().regex(DOCUMENT_ID_PATTERN),
|
|
133
114
|
knowledge_space_type: spaceSchema,
|
|
134
|
-
confirmed_deletion_from_user: z
|
|
135
|
-
.boolean()
|
|
136
|
-
.describe(
|
|
137
|
-
'true only when the person has explicitly asked you to remove this document after you told them it will be deleted for everyone who can see that space.',
|
|
138
|
-
),
|
|
115
|
+
confirmed_deletion_from_user: z.boolean(),
|
|
139
116
|
})
|
|
140
117
|
.strict();
|
|
141
118
|
|
|
142
119
|
export const ADD_ORACLE_DOCUMENT_DESCRIPTION = [
|
|
143
|
-
"Use this, not memory-engine__add_oracle_knowledge, whenever the knowledge comes from a file
|
|
144
|
-
'',
|
|
145
|
-
'Decide with group_as_document, which is required and has no default:',
|
|
146
|
-
'- true: the file is ONE coherent record that belongs together, such as a board resolution, a contract or a report. It is stored under one document id, so it can be listed with list_oracle_documents, replaced by adding it again with the same document_id, and removed as one with delete_oracle_document.',
|
|
147
|
-
'- false: the file is a place where someone dumped many unrelated company facts to upload them easily (for example a notes file with the office address, the refund rule, the CEO name and the opening hours). Each part is stored as an ordinary knowledge fact with no document grouping. Loose facts cannot be listed as a document or removed as a group; they are found by search and removed one by one with delete_episode.',
|
|
120
|
+
"Use this, not memory-engine__add_oracle_knowledge, whenever the knowledge comes from a file the person attached or one in their Library, even when its text is visible in the conversation. The file's text is split into parts and stored in this oracle's public or private knowledge space. The memory engine refuses anyone but a controller, owner, admin or DAO member of the domain.",
|
|
148
121
|
'',
|
|
149
|
-
'
|
|
122
|
+
'group_as_document is required and has no default:',
|
|
123
|
+
'- true: ONE coherent record, such as a board resolution, a contract or a report. Kept under one document id: listed with list_oracle_documents, replaced by adding it again, removed as one with delete_oracle_document.',
|
|
124
|
+
'- false: a dump of unrelated company facts (a notes file with the office address, the refund rule, the opening hours). Each part becomes a loose fact: found by search, removed one by one with delete_episode.',
|
|
125
|
+
'If the file could be either, ask the person first: say what you found in one sentence and offer the two options in plain words. Never pick silently.',
|
|
150
126
|
'',
|
|
151
|
-
'Always confirm first: tell the person
|
|
127
|
+
'Always confirm first: tell the person which space will hold the content and only then set confirmed_insertion_from_user. Never store a file they did not ask you to store.',
|
|
152
128
|
'',
|
|
153
|
-
'Limits:
|
|
154
|
-
'The result says how many parts were queued. Indexing continues in the background; for a grouped document list_oracle_documents shows the status (ingesting, ready, partial, deleting or delete_failed).',
|
|
129
|
+
'Limits: text or a PDF with a text layer up to 25 MB; a scanned PDF, image or Office file up to 10 MB; at most 64 parts of about 12 KB. A larger file is refused with its size unless allow_truncation is set. The result gives the parts queued and the document id; indexing continues in the background (list_oracle_documents shows its status).',
|
|
155
130
|
].join('\n');
|
|
156
131
|
|
|
157
132
|
export const LIST_ORACLE_DOCUMENTS_DESCRIPTION =
|
|
158
|
-
"List the grouped documents in one of this oracle's knowledge spaces
|
|
133
|
+
"List the grouped documents in one of this oracle's knowledge spaces: id, title, status, parts. This is the oracle's knowledge base, not the domain documents of the verified domain context. Loose facts (group_as_document false) are not listed; they are found by search.";
|
|
159
134
|
|
|
160
135
|
export const DELETE_ORACLE_DOCUMENT_DESCRIPTION =
|
|
161
|
-
'Remove a grouped document
|
|
136
|
+
'Remove a grouped document, with all its parts, for everyone who can see that space. Cannot be undone: tell the person which document will go and set confirmed_deletion_from_user only after they say yes. Loose facts: use delete_episode. Runs in the background; if list_oracle_documents later shows delete_failed, call this again to retry.';
|
|
162
137
|
|
|
163
138
|
const NO_ENGINE_MESSAGE =
|
|
164
139
|
'Memory engine is not reachable from this oracle (missing MEMORY_ENGINE_URL or signing key).';
|
|
@@ -7,25 +7,13 @@ import type { SandboxMcpTool } from './sandbox.plugin';
|
|
|
7
7
|
const sandboxWriteBlobSchema = z.object({
|
|
8
8
|
blobId: z
|
|
9
9
|
.string()
|
|
10
|
-
.describe(
|
|
11
|
-
'The blobId returned by the tool that produced the value (e.g. mint_invocation). Format: `blob_<16 hex chars>`.',
|
|
12
|
-
),
|
|
10
|
+
.describe('`blob_<16 hex chars>`, from the tool that made the value.'),
|
|
13
11
|
path: z
|
|
14
12
|
.string()
|
|
15
|
-
.describe(
|
|
16
|
-
'Destination path in the sandbox, e.g. `/workspace/data/<skill>/ucan_token`. Must be under /workspace/data/.',
|
|
17
|
-
),
|
|
13
|
+
.describe('Under /workspace/data/, e.g. `/workspace/data/<skill>/token`.'),
|
|
18
14
|
});
|
|
19
15
|
|
|
20
|
-
const SANDBOX_WRITE_BLOB_DESCRIPTION = `Write a server-stored
|
|
21
|
-
|
|
22
|
-
Use this whenever a tool returns a \`blobId\` (e.g. \`mint_invocation\`'s response). Pass the blobId + the destination path; the runtime looks the value up server-side and calls sandbox_write_file with its content. The long opaque value (base64 CARs, JWTs, etc.) never enters the LLM context — eliminates string-corruption-in-relay as a failure mode.
|
|
23
|
-
|
|
24
|
-
Inputs:
|
|
25
|
-
- blobId: short hex identifier returned by the producing tool (format \`blob_<16 hex chars>\`).
|
|
26
|
-
- path: destination path in the sandbox. Must be under \`/workspace/data/\` (the sandbox enforces this; writes elsewhere are rejected).
|
|
27
|
-
|
|
28
|
-
Returns: \`{ success: true, path, bytesWritten }\` on success, \`{ success: false, error }\` on failure. Common errors: blob not found (re-mint and retry — for invocations this can happen on a multi-replica deployment if the lookup lands on a different process), invalid blobId format, path outside /workspace/data/.`;
|
|
16
|
+
const SANDBOX_WRITE_BLOB_DESCRIPTION = `Write a server-stored value to a sandbox file without passing it through you, so a long opaque value (base64 CAR, JWT) cannot be corrupted. Use it whenever a tool returns a \`blobId\` (e.g. \`mint_invocation\`). Returns \`{ success, path, bytesWritten }\` or \`{ success: false, error }\`; on "blob not found", mint it again and retry.`;
|
|
29
17
|
|
|
30
18
|
export interface CreateSandboxWriteBlobToolParams {
|
|
31
19
|
/** Upstream MCP `sandbox_write_file` tool — looked up once per request from
|
|
@@ -51,10 +51,20 @@ const siblingEnvSchema = z.object({
|
|
|
51
51
|
SKILLS_CAPSULES_BASE_URL: z.string().url().optional(),
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
// In the guide and not in the summary: an `always` host would pay for it in
|
|
55
|
+
// the capability block on top of the tool descriptions, and a preload delivers
|
|
56
|
+
// a guide but not the manifest.
|
|
57
|
+
const OPERATING_GUIDE = [
|
|
58
|
+
'### Sandbox',
|
|
59
|
+
'- `sandbox_run` writes anywhere through the shell, `/tmp` included. `sandbox_write_file` writes raw bytes under `/workspace/data/` only: paths like `/tmp/...` or `/workspace/tmp/...` are rejected.',
|
|
60
|
+
"- A file at a path the user names belongs in their library (`vfs`), not here. Report the path you really wrote: `/workspace/data/...` is the box, not the user's `/...`.",
|
|
61
|
+
'- The box is a scratch disk, not your file store. Files under `/workspace/data/` are pushed up to durable storage after each call, but nothing comes back down automatically and the box is wiped after a few idle minutes. Any file not created in the current call must be fetched with `load_artifact` before you can read it.',
|
|
62
|
+
].join('\n');
|
|
63
|
+
|
|
54
64
|
const manifest: PluginManifest = {
|
|
55
65
|
title: 'Sandbox',
|
|
56
66
|
summary:
|
|
57
|
-
'Per-user Linux box
|
|
67
|
+
'Per-user Linux box: `sandbox_run` runs shell or python, `sandbox_write_file` writes under `/workspace/data/`.',
|
|
58
68
|
whenToUse: [
|
|
59
69
|
"Execute a skill — call `sandbox_run` with `cid` so user + oracle secrets are injected; the skill folder mounts read-only at `/workspace/skills/<skill-name>/` (use the absolute paths from `load_skill`'s `skillFiles`).",
|
|
60
70
|
'Read a skill file (SKILL.md, scripts, configs) — call `sandbox_run` with `cid` and shell: `cat /workspace/skills/<skill-name>/SKILL.md`, `ls /workspace/skills/<skill-name>/`, `grep -r "<pattern>" /workspace/skills/<skill-name>/`, or `sed -n "1,80p" <file>` for a line range. There is no dedicated `read_skill` tool.',
|
|
@@ -67,6 +77,7 @@ const manifest: PluginManifest = {
|
|
|
67
77
|
'Always check the result envelope: `success === true` AND `exitCode === 0` before trusting `output`. On failure, READ the `error` text and change your approach — do not retry the same call with the same args.',
|
|
68
78
|
],
|
|
69
79
|
whenNotToUse: [
|
|
80
|
+
'Saving a file at a path the user names: that is their library, use `vfs`.',
|
|
70
81
|
'The value is already inline in chat — just use it; opening the sandbox to echo it back wastes a turn.',
|
|
71
82
|
'Fetching a URL the user just mentioned — prefer `process_file`, which archives it as an artifact (pull it into the box with `load_artifact` when you need to read it).',
|
|
72
83
|
'A long human-readable page (blog, article, news) — use the Firecrawl agent.',
|
|
@@ -139,7 +150,12 @@ const manifest: PluginManifest = {
|
|
|
139
150
|
},
|
|
140
151
|
},
|
|
141
152
|
],
|
|
142
|
-
|
|
153
|
+
// Every tool loads together: running code, its files and artifacts and the
|
|
154
|
+
// skill lifecycle (`load_skill` included) depend on one another. A host
|
|
155
|
+
// that wants it on every turn sets `visibility: 'always'` through
|
|
156
|
+
// `manifestOverrides`.
|
|
157
|
+
visibility: 'on-demand',
|
|
158
|
+
brief: 'run code; run or publish skills; generate charts and reports',
|
|
143
159
|
stability: 'stable',
|
|
144
160
|
category: 'core',
|
|
145
161
|
tags: ['sandbox', 'execution', 'workspace', 'artifacts'],
|
|
@@ -249,6 +265,7 @@ export class SandboxPlugin extends OraclePlugin {
|
|
|
249
265
|
readonly name = SandboxPlugin.NAME;
|
|
250
266
|
readonly version = '1.0.0';
|
|
251
267
|
readonly manifest = manifest;
|
|
268
|
+
override readonly operatingGuide = OPERATING_GUIDE;
|
|
252
269
|
override readonly configSchema = configSchema;
|
|
253
270
|
override readonly autoDetectHint = 'SANDBOX_MCP_URL';
|
|
254
271
|
|
|
@@ -76,6 +76,7 @@ export const tasksManifest: PluginManifest = {
|
|
|
76
76
|
],
|
|
77
77
|
category: 'automation',
|
|
78
78
|
visibility: 'on-demand',
|
|
79
|
+
brief: 'reminders, scheduled and recurring background jobs',
|
|
79
80
|
stability: 'beta',
|
|
80
81
|
tags: ['scheduler', 'cron', 'automation', 'approval', 'background'],
|
|
81
82
|
};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* model always gets an actionable message.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
+
import { modelFacingSchema } from '../compact-schema';
|
|
12
13
|
import { tool } from '../../plugin-api/tool-helper';
|
|
13
14
|
import type {
|
|
14
15
|
OracleTaskRecord,
|
|
@@ -32,14 +33,11 @@ const taskIdSchema = z.object({
|
|
|
32
33
|
});
|
|
33
34
|
|
|
34
35
|
const APPROVAL_FIELD_DESCRIPTION =
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
|
|
40
|
-
'there; record the decision with `resolve_task_approval` — approval ' +
|
|
41
|
-
'starts the run in the background (its result is posted to the room when ' +
|
|
42
|
-
'it finishes), a decline drops it.';
|
|
36
|
+
"'never' (default): do the work and deliver the result. 'before-action': for " +
|
|
37
|
+
'runs the user should sign off on first (a tweet, a message, a ticket, an ' +
|
|
38
|
+
'email, a publish): each fire posts an approval request into their oracle ' +
|
|
39
|
+
'room and waits; they reply there and you record it with ' +
|
|
40
|
+
'`resolve_task_approval`.';
|
|
43
41
|
|
|
44
42
|
const taskInputShape = {
|
|
45
43
|
title: z.string().min(1).max(120),
|
|
@@ -47,7 +45,7 @@ const taskInputShape = {
|
|
|
47
45
|
.string()
|
|
48
46
|
.min(1)
|
|
49
47
|
.describe(
|
|
50
|
-
'Markdown instructions
|
|
48
|
+
'Markdown instructions for a background run with NO memory of this conversation: every ID, URL and name it needs goes here.',
|
|
51
49
|
),
|
|
52
50
|
// Flat at the tool boundary (a `kind` enum + optional per-kind fields),
|
|
53
51
|
// converted to the store's discriminated union on parse — see
|
|
@@ -61,11 +59,8 @@ const taskInputShape = {
|
|
|
61
59
|
.enum(['auto', 'yes', 'no'])
|
|
62
60
|
.default('auto')
|
|
63
61
|
.describe(
|
|
64
|
-
"Whether
|
|
65
|
-
"
|
|
66
|
-
'creates one for frequent (sub-daily) cron tasks, long intents and ongoing ' +
|
|
67
|
-
'monitor/watch/track tasks; otherwise results go to the main oracle room. ' +
|
|
68
|
-
'Pass yes/no only when the user asked for it.',
|
|
62
|
+
"Whether results and approval requests get their own '[Task] <title>' room " +
|
|
63
|
+
"instead of the main oracle room. 'auto' decides; yes/no only when the user asked.",
|
|
69
64
|
),
|
|
70
65
|
};
|
|
71
66
|
|
|
@@ -160,7 +155,7 @@ function previewTask(): PluginTool {
|
|
|
160
155
|
name: 'preview_task',
|
|
161
156
|
effect: 'read',
|
|
162
157
|
description:
|
|
163
|
-
'Validate a candidate task spec and return its next run times WITHOUT creating anything. ALWAYS call this before create_task and show the user the result.
|
|
158
|
+
'Validate a candidate task spec and return its next run times WITHOUT creating anything. ALWAYS call this before create_task and show the user the result.',
|
|
164
159
|
schema: previewInput,
|
|
165
160
|
},
|
|
166
161
|
);
|
|
@@ -190,8 +185,8 @@ function createTask(): PluginTool {
|
|
|
190
185
|
{
|
|
191
186
|
name: 'create_task',
|
|
192
187
|
description:
|
|
193
|
-
'Schedule a previewed task. Call this ONLY after the user has seen the preview_task output and confirmed in a NEW message
|
|
194
|
-
schema: createInput,
|
|
188
|
+
'Schedule a previewed task, with the same fields as preview_task. Call this ONLY after the user has seen the preview_task output and confirmed in a NEW message, never in the same turn as preview_task. Set `approval` to "before-action" when a run would send, post, publish or create anything on the user\'s behalf.',
|
|
189
|
+
schema: modelFacingSchema(createInput, { descriptions: false }),
|
|
195
190
|
},
|
|
196
191
|
);
|
|
197
192
|
}
|
|
@@ -311,8 +306,8 @@ function updateTask(): PluginTool {
|
|
|
311
306
|
{
|
|
312
307
|
name: 'update_task',
|
|
313
308
|
description:
|
|
314
|
-
'Patch a task
|
|
315
|
-
schema: updateInput,
|
|
309
|
+
'Patch a task: title, schedule, approval or intent, as in preview_task. A new intent changes what every future run executes: show it to the user and get their confirmation first. A new schedule reschedules the next run.',
|
|
310
|
+
schema: modelFacingSchema(updateInput, { descriptions: false }),
|
|
316
311
|
},
|
|
317
312
|
);
|
|
318
313
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `set_user_preferences
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* writes through the host's `ctx.preferences` surface for the active room.
|
|
2
|
+
* `set_user_preferences`: the Node runtime's tool, the same schema with
|
|
3
|
+
* shorter text (the model reads it on every call). The handler writes
|
|
4
|
+
* through the host's `ctx.preferences` surface for the active room.
|
|
6
5
|
*/
|
|
7
6
|
import { z } from 'zod';
|
|
8
7
|
import { tool } from '../../plugin-api/tool-helper';
|
|
@@ -26,67 +25,42 @@ export const setUserPreferencesSchema = z.object({
|
|
|
26
25
|
.string()
|
|
27
26
|
.max(80)
|
|
28
27
|
.optional()
|
|
29
|
-
.describe(
|
|
30
|
-
"What the user wants you (the agent) to be called (e.g. 'Companion'). Use exactly the form the user asked for. Max 80 characters.",
|
|
31
|
-
),
|
|
28
|
+
.describe('What the user wants to call you, in exactly their form.'),
|
|
32
29
|
userName: z
|
|
33
30
|
.string()
|
|
34
31
|
.max(80)
|
|
35
32
|
.optional()
|
|
36
|
-
.describe(
|
|
37
|
-
"What the user wants you to call them (e.g. 'Yousef'). Use exactly the form the user asked for. Max 80 characters.",
|
|
38
|
-
),
|
|
33
|
+
.describe('What the user wants to be called, in exactly their form.'),
|
|
39
34
|
language: z
|
|
40
35
|
.string()
|
|
41
36
|
.max(20)
|
|
42
37
|
.optional()
|
|
43
38
|
.describe(
|
|
44
|
-
"
|
|
45
|
-
'Be precise — if the user specified a dialect or variant, capture that. Max 20 characters.',
|
|
39
|
+
"Reply language, code or name, with any dialect (e.g. 'ar', 'Egyptian Arabic').",
|
|
46
40
|
),
|
|
47
41
|
tone: z
|
|
48
42
|
.string()
|
|
49
43
|
.max(MAX_TONE_LENGTH)
|
|
50
44
|
.optional()
|
|
51
45
|
.describe(
|
|
52
|
-
"Short tone label
|
|
53
|
-
`'concise and dry', 'patient teacher'). Pick descriptive words, not vague ones. Max ${MAX_TONE_LENGTH} characters.`,
|
|
46
|
+
"Short descriptive tone label (e.g. 'playful and warm', 'concise and dry').",
|
|
54
47
|
),
|
|
55
48
|
formality: z
|
|
56
49
|
.enum(FORMALITY_LEVELS)
|
|
57
50
|
.optional()
|
|
58
|
-
.describe(
|
|
59
|
-
'How formal the replies should be. Pick the value closest to what the user actually wants, ' +
|
|
60
|
-
'not what the request literally said.',
|
|
61
|
-
),
|
|
51
|
+
.describe('The value closest to what the user actually wants.'),
|
|
62
52
|
customInstructions: z
|
|
63
53
|
.string()
|
|
64
54
|
.max(2000)
|
|
65
55
|
.optional()
|
|
66
56
|
.describe(
|
|
67
|
-
'
|
|
68
|
-
'a system prompt: be specific, complete, and unambiguous. Include the WHY when it matters ' +
|
|
69
|
-
"(e.g. 'User has dyslexia — keep sentences short and avoid long lists'). " +
|
|
70
|
-
'When updating, MERGE with existing instructions rather than replacing — read what is already ' +
|
|
71
|
-
'there (visible in your current system prompt under "User Preferences") and write the full new ' +
|
|
72
|
-
'instruction set, preserving anything still relevant. Max 2000 characters.',
|
|
57
|
+
'Instructions you will read on every turn, like a system prompt: specific, complete, with the why when it matters (e.g. \'User has dyslexia: short sentences, no long lists\'). This field is replaced whole: write the full new set, keeping what is still relevant from "User Preferences" in your system prompt.',
|
|
73
58
|
),
|
|
74
59
|
});
|
|
75
60
|
|
|
76
61
|
export const SET_USER_PREFERENCES_DESCRIPTION =
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
'IMPORTANT — you are writing instructions for your FUTURE SELF. ' +
|
|
80
|
-
'On every later turn this exact text will be injected back into your system prompt and you will read it cold, ' +
|
|
81
|
-
'with no memory of this conversation or why the user asked. So:\n' +
|
|
82
|
-
" • Be specific and self-contained. Don't write 'be more casual' — write 'Use casual, conversational " +
|
|
83
|
-
"tone. Avoid corporate phrasing. Use contractions. Drop filler words.'\n" +
|
|
84
|
-
" • Capture intent, not just the literal request. If the user says 'stop being so formal', infer the " +
|
|
85
|
-
'positive form: what should you do instead?\n' +
|
|
86
|
-
' • Preserve existing instructions when adding new ones — only overwrite the fields you mean to change. ' +
|
|
87
|
-
'Other fields are kept as-is.\n' +
|
|
88
|
-
" • If unsure between two interpretations, pick the more specific one — your future self can't ask back.\n\n" +
|
|
89
|
-
"Note: changes apply from the user's NEXT message (the current turn's prompt is already built).";
|
|
62
|
+
"Save how the user wants you to behave (e.g. 'call me Yousef', 'reply in Arabic', 'be more casual'). Takes effect from their NEXT message. Only the fields you pass change.\n\n" +
|
|
63
|
+
"You are writing for your FUTURE SELF, who reads this text cold on every later turn with no memory of why. Be specific and self-contained: not 'be more casual' but 'Use a casual, conversational tone. Use contractions. Avoid corporate phrasing.' Capture the intent as what to do ('stop being so formal' becomes what to do instead). Between two readings pick the more specific one.";
|
|
90
64
|
|
|
91
65
|
export interface CreateSetUserPreferencesToolOptions {
|
|
92
66
|
logger?: Logger;
|
|
@@ -79,7 +79,11 @@ const manifest: PluginManifest = {
|
|
|
79
79
|
args: { tone: 'concise' },
|
|
80
80
|
},
|
|
81
81
|
],
|
|
82
|
+
// The Tier-1 entry stays, so the prompt says when a preference must be
|
|
83
|
+
// saved; only the tool waits for a load.
|
|
82
84
|
visibility: 'always',
|
|
85
|
+
brief: 'save lasting reply preferences: name, language, tone',
|
|
86
|
+
onDemandTools: [SET_USER_PREFERENCES_TOOL_NAME],
|
|
83
87
|
stability: 'stable',
|
|
84
88
|
category: 'core',
|
|
85
89
|
};
|
|
@@ -172,6 +172,11 @@ async function guard(
|
|
|
172
172
|
try {
|
|
173
173
|
return await fn();
|
|
174
174
|
} catch (err) {
|
|
175
|
+
if (err instanceof VfsAuthError && err.kind !== 'no-delegation') {
|
|
176
|
+
// The user granted access but no bearer could be made (the UCAN store
|
|
177
|
+
// or the mint failed): the model only reads a generic sentence.
|
|
178
|
+
ctx.logger.warn(`[vfs] ${err.kind}: ${err.message}`);
|
|
179
|
+
}
|
|
175
180
|
if (err instanceof VfsHttpError || err instanceof VfsAuthError) {
|
|
176
181
|
return mapVfsError(err, { ...info, oracleDid: readOracleDid(ctx) });
|
|
177
182
|
}
|
|
@@ -384,11 +389,8 @@ export function createVfsTools(deps: CreateVfsToolsDeps): PluginTool[] {
|
|
|
384
389
|
{
|
|
385
390
|
name: 'vfs_search',
|
|
386
391
|
description:
|
|
387
|
-
"Find files by meaning across the user's filesystem. Use for questions and paraphrases ('the doc about pricing'). Returns file paths + cited line ranges. Follow with `vfs_read` to see the actual lines before answering or editing.
|
|
392
|
+
"Find files by meaning across the user's filesystem. Use for questions and paraphrases ('the doc about pricing'). Returns file paths + cited line ranges. Follow with `vfs_read` to see the actual lines before answering or editing.",
|
|
388
393
|
schema: searchSchema,
|
|
389
|
-
// Recall must work with zero load steps — this tool and `vfs_read`
|
|
390
|
-
// stay visible while the rest of the plugin is on-demand.
|
|
391
|
-
visibility: 'always',
|
|
392
394
|
},
|
|
393
395
|
);
|
|
394
396
|
|
|
@@ -471,8 +473,6 @@ export function createVfsTools(deps: CreateVfsToolsDeps): PluginTool[] {
|
|
|
471
473
|
description:
|
|
472
474
|
"Read a file's contents by path. Text files come back a window of numbered lines at a time (default first 2000; page with `offset` when `hasMore`). Images and PDFs are read too — described/transcribed for you. Always read before editing or answering from a file; do not guess its contents. Reading an image costs an extra step, so read it only when you need to see it.",
|
|
473
475
|
schema: readSchema,
|
|
474
|
-
// See vfs_search — the read half of the always-available recall pair.
|
|
475
|
-
visibility: 'always',
|
|
476
476
|
},
|
|
477
477
|
);
|
|
478
478
|
|
|
@@ -74,12 +74,10 @@ const manifest: PluginManifest = {
|
|
|
74
74
|
summary:
|
|
75
75
|
"The user's Virtual Filesystem (VFS) — their persistent, secure, governed filesystem: the canonical home for their real documents, notes, datasets, and artifacts, in folders, searchable and versioned linked to their domain. Reusable context that lives across sessions — not scratch files, not chat attachments, not the web. Securely stored and access-controlled (managed encryption); authorized IXO services can read content to power search, previews, and these tools, so it is NOT end-to-end encrypted — never describe it as zero-knowledge or unreadable by IXO.",
|
|
76
76
|
category: 'data',
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
// load step. The write/organise/share surface (and the sandbox bridges)
|
|
80
|
-
// loads via `load_capability(['vfs'])` — keeping ~11 tool schemas out of
|
|
81
|
-
// every unrelated chat turn.
|
|
77
|
+
// Every tool, recall included, loads via `load_capability(['vfs'])`: the
|
|
78
|
+
// catalogue line names the files, so a question about them costs one load.
|
|
82
79
|
visibility: 'on-demand',
|
|
80
|
+
brief: "the user's library: save, read, search, organise files at any path",
|
|
83
81
|
whenToUse: [
|
|
84
82
|
"The user refers to a document, note, file, folder, or something they 'saved', 'uploaded', or 'shared with you'.",
|
|
85
83
|
'You need to create, update, or organise a file for the user (draft, report, notes, export) that should persist.',
|
|
@@ -95,6 +93,7 @@ const manifest: PluginManifest = {
|
|
|
95
93
|
"Content the user pasted directly into chat (act on that inline; don't write it to a file unless asked).",
|
|
96
94
|
"Temporary/scratch or intermediate files produced during a code or compute run — those belong to the sandbox, not the user's filesystem.",
|
|
97
95
|
'Another plugin owns the data (flows, skills, memory) — use that plugin.',
|
|
96
|
+
"The oracle's own material (its handbook, policies, procedures, reference documents): that is oracle knowledge, found with the memory search, not in the user's files.",
|
|
98
97
|
],
|
|
99
98
|
examples: [
|
|
100
99
|
{
|
|
@@ -661,6 +661,7 @@ const manifest: PluginManifest = {
|
|
|
661
661
|
category: 'data',
|
|
662
662
|
// Yoma's assistant sets `visibility: 'always'` through `manifestOverrides`.
|
|
663
663
|
visibility: 'on-demand',
|
|
664
|
+
brief: 'Yoma programmes and jobs; apply, log work, check payment',
|
|
664
665
|
stability: 'beta',
|
|
665
666
|
};
|
|
666
667
|
|