@ixo/oracle-runtime-workers 0.2.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/README.md +110 -0
- package/package.json +77 -0
- package/src/attachments/classify.ts +114 -0
- package/src/attachments/content-blocks.ts +51 -0
- package/src/attachments/download.ts +235 -0
- package/src/attachments/extract.ts +241 -0
- package/src/attachments/index.ts +84 -0
- package/src/attachments/magic.ts +133 -0
- package/src/attachments/pipeline.ts +409 -0
- package/src/attachments/retention.ts +205 -0
- package/src/attachments/route.ts +41 -0
- package/src/attachments/sandbox-archive.ts +90 -0
- package/src/attachments/sanitize.ts +35 -0
- package/src/attachments/types.ts +86 -0
- package/src/attachments/view.ts +107 -0
- package/src/core/env.ts +200 -0
- package/src/core/index.ts +513 -0
- package/src/core/llm.ts +842 -0
- package/src/core/main-agent-types.ts +123 -0
- package/src/core/main-agent.ts +399 -0
- package/src/core/manifest.ts +365 -0
- package/src/core/meta-tools.ts +232 -0
- package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
- package/src/core/middlewares/capability-gate.ts +76 -0
- package/src/core/middlewares/index.ts +18 -0
- package/src/core/middlewares/page-context.ts +98 -0
- package/src/core/middlewares/safety-guardrail.ts +117 -0
- package/src/core/middlewares/summarization.ts +99 -0
- package/src/core/middlewares/tool-repetition-guard.ts +125 -0
- package/src/core/middlewares/tool-validation.ts +83 -0
- package/src/core/middlewares/work-status.ts +57 -0
- package/src/core/openrouter-pricing.ts +124 -0
- package/src/core/plugin-loader.ts +192 -0
- package/src/core/plugins/index.ts +6 -0
- package/src/core/plugins/skills/index.ts +8 -0
- package/src/core/plugins/skills/skills-tools.ts +356 -0
- package/src/core/plugins/skills/skills.plugin.ts +113 -0
- package/src/core/plugins/weather/index.ts +8 -0
- package/src/core/plugins/weather/weather-client.ts +153 -0
- package/src/core/plugins/weather/weather-middleware.ts +27 -0
- package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
- package/src/core/plugins/weather/weather-tools.ts +103 -0
- package/src/core/plugins/weather/weather.plugin.ts +227 -0
- package/src/core/prompt-composer.ts +469 -0
- package/src/core/registries.ts +642 -0
- package/src/core/runtime-context.ts +619 -0
- package/src/core/state.ts +108 -0
- package/src/core/sub-agent-fallback.ts +187 -0
- package/src/core/subagent-as-tool.ts +322 -0
- package/src/core/template.ts +245 -0
- package/src/core/utils.ts +246 -0
- package/src/core/wrap-plugin-tool.ts +100 -0
- package/src/do/ambient.ts +266 -0
- package/src/do/contracts.ts +562 -0
- package/src/do/debug-timers.ts +120 -0
- package/src/do/fresh-stub.ts +32 -0
- package/src/do/gateway-retry.ts +73 -0
- package/src/do/secrets-adapter.ts +32 -0
- package/src/do/size-alerts.ts +76 -0
- package/src/do/sse-stream.ts +472 -0
- package/src/do/transcript.ts +192 -0
- package/src/do/turn-body.ts +84 -0
- package/src/do/turn-metadata.ts +146 -0
- package/src/do/ucan-service.ts +339 -0
- package/src/do/user-oracle-do.ts +3035 -0
- package/src/gateway-worker.ts +82 -0
- package/src/index.ts +237 -0
- package/src/llm/byo-adapter.ts +59 -0
- package/src/llm/byo-catalog.ts +520 -0
- package/src/llm/byo-client.ts +280 -0
- package/src/llm/byo-routes.ts +255 -0
- package/src/llm/byo-service.ts +836 -0
- package/src/llm/chatgpt-oauth.ts +430 -0
- package/src/llm/provider-error.ts +283 -0
- package/src/matrix/gateway-do.ts +918 -0
- package/src/matrix/ingest.ts +209 -0
- package/src/matrix/replay-format.ts +34 -0
- package/src/matrix/reply-chain.ts +109 -0
- package/src/matrix/room-state-codec.ts +115 -0
- package/src/matrix/user-homeserver.ts +106 -0
- package/src/matrix/work-status.ts +294 -0
- package/src/memory/session-history-indexer.ts +328 -0
- package/src/owner-store/ixo-vfs-store.ts +570 -0
- package/src/owner-store/matrix-media-store.ts +136 -0
- package/src/owner-store/migrating-store.ts +185 -0
- package/src/owner-store/owner-copy-errors.ts +175 -0
- package/src/owner-store/retry.ts +45 -0
- package/src/owner-store/tus-upload.ts +333 -0
- package/src/owner-store/types.ts +0 -0
- package/src/plugin-api/define-plugin.ts +45 -0
- package/src/plugin-api/index.ts +56 -0
- package/src/plugin-api/oracle-plugin.ts +145 -0
- package/src/plugin-api/tool-helper.ts +82 -0
- package/src/plugin-api/types.ts +816 -0
- package/src/plugin-api/ucan-errors.ts +16 -0
- package/src/plugins/agui/agui-agent.ts +115 -0
- package/src/plugins/agui/agui.plugin.ts +148 -0
- package/src/plugins/agui/index.ts +2 -0
- package/src/plugins/attachments/attachments.plugin.ts +54 -0
- package/src/plugins/attachments/index.ts +6 -0
- package/src/plugins/attachments/view-attachment-tool.ts +91 -0
- package/src/plugins/base64.ts +26 -0
- package/src/plugins/composio/composio-tools.ts +365 -0
- package/src/plugins/composio/composio-ucan.ts +43 -0
- package/src/plugins/composio/composio.plugin.ts +214 -0
- package/src/plugins/composio/index.ts +15 -0
- package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
- package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
- package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
- package/src/plugins/domain-indexer/index.ts +3 -0
- package/src/plugins/editor/blocknote-bridge.ts +120 -0
- package/src/plugins/editor/content-session.ts +331 -0
- package/src/plugins/editor/content-tools.ts +686 -0
- package/src/plugins/editor/document-model.ts +0 -0
- package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
- package/src/plugins/editor/editor-agent.ts +34 -0
- package/src/plugins/editor/editor-config.ts +64 -0
- package/src/plugins/editor/editor-mx.ts +128 -0
- package/src/plugins/editor/editor.plugin.ts +143 -0
- package/src/plugins/editor/failures.ts +154 -0
- package/src/plugins/editor/index.ts +31 -0
- package/src/plugins/editor/jsdom-shim.ts +55 -0
- package/src/plugins/editor/prompts.ts +59 -0
- package/src/plugins/editor/prop-policy.ts +147 -0
- package/src/plugins/editor/provider.ts +386 -0
- package/src/plugins/editor/room-membership.ts +95 -0
- package/src/plugins/editor/standalone-editor-tool.ts +200 -0
- package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
- package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
- package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
- package/src/plugins/firecrawl/index.ts +15 -0
- package/src/plugins/flows/action-metadata.ts +3505 -0
- package/src/plugins/flows/actions.ts +190 -0
- package/src/plugins/flows/edit.ts +314 -0
- package/src/plugins/flows/errors.ts +38 -0
- package/src/plugins/flows/explain.ts +43 -0
- package/src/plugins/flows/flow-doc.ts +111 -0
- package/src/plugins/flows/flows.plugin.ts +129 -0
- package/src/plugins/flows/forms.ts +330 -0
- package/src/plugins/flows/index.ts +2 -0
- package/src/plugins/flows/linkage.ts +147 -0
- package/src/plugins/flows/port-types.ts +53 -0
- package/src/plugins/flows/prompts.ts +65 -0
- package/src/plugins/flows/read.ts +392 -0
- package/src/plugins/flows/references.ts +66 -0
- package/src/plugins/flows/test-support.ts +165 -0
- package/src/plugins/flows/tools/authoring.ts +516 -0
- package/src/plugins/flows/tools/discovery.ts +119 -0
- package/src/plugins/flows/tools/forms.ts +142 -0
- package/src/plugins/flows/tools/inspect.ts +165 -0
- package/src/plugins/flows/tools/linkage.ts +117 -0
- package/src/plugins/flows/tools/settings.ts +184 -0
- package/src/plugins/flows/translator.ts +328 -0
- package/src/plugins/flows/types.ts +210 -0
- package/src/plugins/flows/ydoc-helpers.ts +543 -0
- package/src/plugins/index.ts +70 -0
- package/src/plugins/mcp-call-timeout.ts +38 -0
- package/src/plugins/mcp-tool-adapter.ts +87 -0
- package/src/plugins/memory/index.ts +17 -0
- package/src/plugins/memory/memory-tools.ts +463 -0
- package/src/plugins/memory/memory-ucan.ts +51 -0
- package/src/plugins/memory/memory.plugin.ts +122 -0
- package/src/plugins/memory/types.ts +147 -0
- package/src/plugins/portal/action-log.ts +34 -0
- package/src/plugins/portal/index.ts +2 -0
- package/src/plugins/portal/portal.plugin.ts +203 -0
- package/src/plugins/sandbox/index.ts +34 -0
- package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
- package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
- package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
- package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
- package/src/plugins/tasks/index.ts +17 -0
- package/src/plugins/tasks/manifest.ts +81 -0
- package/src/plugins/tasks/middleware.ts +219 -0
- package/src/plugins/tasks/tasks-tools.ts +414 -0
- package/src/plugins/tasks/tasks.plugin.ts +74 -0
- package/src/plugins/ucan-failure.ts +133 -0
- package/src/plugins/user-preferences/index.ts +22 -0
- package/src/plugins/user-preferences/schema.ts +31 -0
- package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
- package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
- package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
- package/src/plugins/vfs/index.ts +40 -0
- package/src/plugins/vfs/vfs-auth.ts +56 -0
- package/src/plugins/vfs/vfs-client.ts +655 -0
- package/src/plugins/vfs/vfs-content.ts +181 -0
- package/src/plugins/vfs/vfs-errors.ts +163 -0
- package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
- package/src/plugins/vfs/vfs-tools.ts +635 -0
- package/src/plugins/vfs/vfs.plugin.ts +201 -0
- package/src/realtime/frontend-call-registry.ts +163 -0
- package/src/realtime/realtime-endpoint.ts +619 -0
- package/src/realtime/session-socket-hub.ts +249 -0
- package/src/realtime/socket-io-codec.ts +187 -0
- package/src/secrets/jwe.ts +74 -0
- package/src/secrets/pin-cipher.ts +43 -0
- package/src/secrets/secrets-service.ts +319 -0
- package/src/shell/app.ts +564 -0
- package/src/shell/auth.ts +290 -0
- package/src/sqlite/blob-codec.ts +100 -0
- package/src/sqlite/blob-compactor.ts +146 -0
- package/src/sqlite/cache-config.ts +45 -0
- package/src/sqlite/database.ts +643 -0
- package/src/sqlite/do-vfs.ts +1485 -0
- package/src/sqlite/index.ts +61 -0
- package/src/sqlite/serialization.ts +370 -0
- package/src/sqlite/sessions-store.ts +311 -0
- package/src/sqlite/sqlite-saver.ts +919 -0
- package/src/sqlite/vacuum-policy.ts +77 -0
- package/src/sqlite/wa-sqlite-loader.ts +123 -0
- package/src/sqlite/wasm.d.ts +10 -0
- package/src/tasks/schedule.ts +174 -0
- package/src/tasks/scheduler.ts +753 -0
- package/src/tasks/spec.ts +125 -0
- package/src/tasks/store.ts +351 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { PluginManifest } from '../../plugin-api/types';
|
|
2
|
+
|
|
3
|
+
export const tasksManifest: PluginManifest = {
|
|
4
|
+
title: 'Scheduled Tasks',
|
|
5
|
+
summary:
|
|
6
|
+
"Schedule the agent to run on time-based triggers and deliver the result to the user's oracle chat room — runs happen in the background on the user's own oracle, not inline in the current conversation.",
|
|
7
|
+
whenToUse: [
|
|
8
|
+
"User wants to set up a reminder or recurring report ('every morning at 7', 'tomorrow at 5pm', 'remind me to …')",
|
|
9
|
+
'User wants the agent to monitor or track something on a schedule',
|
|
10
|
+
'User wants the agent to run a piece of work later, without sitting in the chat',
|
|
11
|
+
"User replies to a pending task-approval request ('yes, send it', 'no, drop it')",
|
|
12
|
+
],
|
|
13
|
+
whenNotToUse: [
|
|
14
|
+
'One-shot action the user wants done right now — just do it inline',
|
|
15
|
+
'Real-time / streaming requirements — tasks run on a scheduled cadence, not on demand',
|
|
16
|
+
],
|
|
17
|
+
examples: [
|
|
18
|
+
{
|
|
19
|
+
user: 'Every morning at 7 give me a one-paragraph crypto brief.',
|
|
20
|
+
thought:
|
|
21
|
+
'New recurring task. ALWAYS preview first so the user sees the schedule and confirms before anything is created.',
|
|
22
|
+
tool: 'preview_task',
|
|
23
|
+
args: {
|
|
24
|
+
title: 'Morning Crypto Brief',
|
|
25
|
+
intent:
|
|
26
|
+
'Summarize BTC, ETH, SOL movement over the last 24h. Highlight any moves > 5%. Keep it under 300 words, no trade recommendations.',
|
|
27
|
+
schedule: { kind: 'cron', cron: '0 7 * * *', timezone: 'UTC' },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
user: 'Looks good, schedule it.',
|
|
32
|
+
thought:
|
|
33
|
+
'The user confirmed after seeing the preview. Create with the SAME title/intent/schedule.',
|
|
34
|
+
tool: 'create_task',
|
|
35
|
+
args: {
|
|
36
|
+
title: 'Morning Crypto Brief',
|
|
37
|
+
intent:
|
|
38
|
+
'Summarize BTC, ETH, SOL movement over the last 24h. Highlight any moves > 5%. Keep it under 300 words, no trade recommendations.',
|
|
39
|
+
schedule: { kind: 'cron', cron: '0 7 * * *', timezone: 'UTC' },
|
|
40
|
+
approval: 'never',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
user: 'Every weekday at 9am post a LinkedIn update about our latest blog, but let me approve it before it goes out.',
|
|
45
|
+
thought:
|
|
46
|
+
"An action task — set approval to 'before-action' so each run asks the user in their room before it executes.",
|
|
47
|
+
tool: 'create_task',
|
|
48
|
+
args: {
|
|
49
|
+
title: 'Daily LinkedIn Update',
|
|
50
|
+
intent:
|
|
51
|
+
"Draft and publish a short LinkedIn post (under 120 words) about the company's latest blog article, with the article link.",
|
|
52
|
+
schedule: { kind: 'cron', cron: '0 9 * * 1-5', timezone: 'UTC' },
|
|
53
|
+
approval: 'before-action',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
user: '(replying to an approval request) Yes — go ahead.',
|
|
58
|
+
thought:
|
|
59
|
+
'The user approved the pending run — record it; approval executes the run and delivers the result.',
|
|
60
|
+
tool: 'resolve_task_approval',
|
|
61
|
+
args: {
|
|
62
|
+
taskId: 'task_daily-linkedin-update_a1b2c3d4',
|
|
63
|
+
outcome: 'approved',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
user: 'What tasks do I have?',
|
|
68
|
+
tool: 'list_my_tasks',
|
|
69
|
+
args: {},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
user: 'Pause the crypto brief.',
|
|
73
|
+
tool: 'pause_task',
|
|
74
|
+
args: { taskId: 'task_morning-crypto-brief_a1b2c3d4' },
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
category: 'automation',
|
|
78
|
+
visibility: 'on-demand',
|
|
79
|
+
stability: 'beta',
|
|
80
|
+
tags: ['scheduler', 'cron', 'automation', 'approval', 'background'],
|
|
81
|
+
};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The task approval gate — the Workers port of the Node runtime's task-room
|
|
3
|
+
* middleware. On every model call it checks whether any of the current user's
|
|
4
|
+
* tasks is waiting on a `before-action` approval and, when one is, appends a
|
|
5
|
+
* system-prompt hint so the model treats the user's reply as the decision.
|
|
6
|
+
*
|
|
7
|
+
* Differences from Node, by design of the Workers approval flow:
|
|
8
|
+
* - There are no dedicated task rooms or room→session bindings — pending
|
|
9
|
+
* approvals live on the task records themselves (`pendingApprovalAt`), so
|
|
10
|
+
* the gate reads them straight off `OracleTasksSurface.list()`.
|
|
11
|
+
* - The gate never resolves an approval itself: on Workers, resolving an
|
|
12
|
+
* approval EXECUTES the run (a full agent turn), which does not belong
|
|
13
|
+
* inside a model-call wrapper. Plain yes/no replies are still classified
|
|
14
|
+
* deterministically (`classifyReplyFast`) — the classification shapes the
|
|
15
|
+
* hint, and the model records the decision through the
|
|
16
|
+
* `resolve_task_approval` tool.
|
|
17
|
+
*
|
|
18
|
+
* Never short-circuits the model and never posts to Matrix — system-prompt
|
|
19
|
+
* hints only. Any internal error degrades to a plain pass-through.
|
|
20
|
+
*/
|
|
21
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
22
|
+
import { createMiddleware, type AgentMiddleware } from 'langchain';
|
|
23
|
+
import type { Logger, OracleTasksSurface } from '../../plugin-api/types';
|
|
24
|
+
import { pendingApprovalOf } from '../../tasks/store';
|
|
25
|
+
|
|
26
|
+
const APPROVE = new Set([
|
|
27
|
+
'yes',
|
|
28
|
+
'y',
|
|
29
|
+
'yes please',
|
|
30
|
+
'approve',
|
|
31
|
+
'approved',
|
|
32
|
+
'approve it',
|
|
33
|
+
'i approve',
|
|
34
|
+
'ok',
|
|
35
|
+
'okay',
|
|
36
|
+
'ok do it',
|
|
37
|
+
'okay do it',
|
|
38
|
+
'do it',
|
|
39
|
+
'go',
|
|
40
|
+
'go ahead',
|
|
41
|
+
'send',
|
|
42
|
+
'send it',
|
|
43
|
+
'yes send it',
|
|
44
|
+
'ship',
|
|
45
|
+
'ship it',
|
|
46
|
+
'lgtm',
|
|
47
|
+
'looks good',
|
|
48
|
+
'sure',
|
|
49
|
+
'confirm',
|
|
50
|
+
'confirmed',
|
|
51
|
+
'proceed',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const REJECT = new Set([
|
|
55
|
+
'no',
|
|
56
|
+
'n',
|
|
57
|
+
'nope',
|
|
58
|
+
'no thanks',
|
|
59
|
+
'cancel',
|
|
60
|
+
'cancel it',
|
|
61
|
+
'reject',
|
|
62
|
+
'rejected',
|
|
63
|
+
'reject it',
|
|
64
|
+
'i reject',
|
|
65
|
+
'decline',
|
|
66
|
+
'declined',
|
|
67
|
+
'stop',
|
|
68
|
+
"don't",
|
|
69
|
+
'dont',
|
|
70
|
+
"don't send",
|
|
71
|
+
'dont send',
|
|
72
|
+
"don't send it",
|
|
73
|
+
'discard',
|
|
74
|
+
'discard it',
|
|
75
|
+
'abort',
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
export type FastReply = 'approved' | 'rejected' | 'other';
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* EXACT-match classification of a reply to a pending approval. Normalization
|
|
82
|
+
* is lowercase + punctuation-to-space + whitespace collapse; anything that is
|
|
83
|
+
* not a verbatim member of the approve/reject sets is `'other'` — no prefix
|
|
84
|
+
* or length heuristics, so "ok so what does this do" never reads as approval.
|
|
85
|
+
*/
|
|
86
|
+
export function classifyReplyFast(text: string): FastReply {
|
|
87
|
+
const normalized = text
|
|
88
|
+
.toLowerCase()
|
|
89
|
+
.replace(/[.!?,;:]+/g, ' ')
|
|
90
|
+
.replace(/\s+/g, ' ')
|
|
91
|
+
.trim();
|
|
92
|
+
if (APPROVE.has(normalized)) return 'approved';
|
|
93
|
+
if (REJECT.has(normalized)) return 'rejected';
|
|
94
|
+
return 'other';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The hint to append when tasks are awaiting approval, or undefined when
|
|
99
|
+
* nothing is pending. Exported for direct unit testing.
|
|
100
|
+
*/
|
|
101
|
+
export async function computeApprovalHint(
|
|
102
|
+
surface: OracleTasksSurface,
|
|
103
|
+
lastHumanReply: string,
|
|
104
|
+
): Promise<string | undefined> {
|
|
105
|
+
const records = await surface.list();
|
|
106
|
+
const pending = records.filter(
|
|
107
|
+
(record) =>
|
|
108
|
+
record.status === 'active' && pendingApprovalOf(record) !== undefined,
|
|
109
|
+
);
|
|
110
|
+
if (pending.length === 0) return undefined;
|
|
111
|
+
|
|
112
|
+
const decision = classifyReplyFast(lastHumanReply);
|
|
113
|
+
const only = pending.length === 1 ? pending[0] : undefined;
|
|
114
|
+
if (only && decision === 'approved') {
|
|
115
|
+
return (
|
|
116
|
+
`\n\n[Task approval gate] The user's reply APPROVES the pending run of task ${only.id} ("${only.title}"). ` +
|
|
117
|
+
'Call `resolve_task_approval` with outcome "approved" NOW — approval executes the run and delivers its result — then confirm to the user.'
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (only && decision === 'rejected') {
|
|
121
|
+
return (
|
|
122
|
+
`\n\n[Task approval gate] The user's reply DECLINES the pending run of task ${only.id} ("${only.title}"). ` +
|
|
123
|
+
'Call `resolve_task_approval` with outcome "declined" — nothing must be executed — then acknowledge briefly.'
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const listing = pending
|
|
127
|
+
.map((record) => `${record.id} ("${record.title}")`)
|
|
128
|
+
.join(', ');
|
|
129
|
+
return (
|
|
130
|
+
`\n\n[Task approval gate] ${pending.length} task run(s) are waiting for the user's approval: ${listing}. ` +
|
|
131
|
+
'If this reply decides one of them (possibly with tweaks), call `resolve_task_approval` with the outcome — "approved" executes the run (pass requested tweaks in `note`), "declined" drops it. ' +
|
|
132
|
+
'Otherwise answer normally and remind the user of the pending approval.'
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Defensive read of `{ user: { did } }` off the untyped invoke context. */
|
|
137
|
+
export function userDidFromContext(context: unknown): string | undefined {
|
|
138
|
+
if (!context || typeof context !== 'object' || !('user' in context)) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const { user } = context;
|
|
142
|
+
if (!user || typeof user !== 'object' || !('did' in user)) return undefined;
|
|
143
|
+
const { did } = user;
|
|
144
|
+
return typeof did === 'string' && did.length > 0 ? did : undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Text of the latest HumanMessage — string content or joined text parts. */
|
|
148
|
+
export function lastHumanText(messages: BaseMessage[]): string | null {
|
|
149
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
150
|
+
const message = messages[i];
|
|
151
|
+
if (!message || message.type !== 'human') continue;
|
|
152
|
+
return textOfContent(message.content);
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function textOfContent(content: unknown): string | null {
|
|
158
|
+
if (typeof content === 'string') {
|
|
159
|
+
const trimmed = content.trim();
|
|
160
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
161
|
+
}
|
|
162
|
+
if (!Array.isArray(content)) return null;
|
|
163
|
+
const parts: unknown[] = content;
|
|
164
|
+
const texts: string[] = [];
|
|
165
|
+
for (const part of parts) {
|
|
166
|
+
if (typeof part === 'string') {
|
|
167
|
+
texts.push(part);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (
|
|
171
|
+
part !== null &&
|
|
172
|
+
typeof part === 'object' &&
|
|
173
|
+
'text' in part &&
|
|
174
|
+
typeof part.text === 'string'
|
|
175
|
+
) {
|
|
176
|
+
texts.push(part.text);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const joined = texts.join(' ').trim();
|
|
180
|
+
return joined.length > 0 ? joined : null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface TaskApprovalGateOptions {
|
|
184
|
+
/**
|
|
185
|
+
* Per-user surface lookup. The plugin stashes each request's `ctx.tasks`
|
|
186
|
+
* keyed by user DID (see `tasks.plugin.ts`) because middlewares are built
|
|
187
|
+
* once at boot while the surface is per-user.
|
|
188
|
+
*/
|
|
189
|
+
surfaceFor: (userDid: string) => OracleTasksSurface | undefined;
|
|
190
|
+
logger?: Logger;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function createTaskApprovalGateMiddleware(
|
|
194
|
+
options: TaskApprovalGateOptions,
|
|
195
|
+
): AgentMiddleware {
|
|
196
|
+
return createMiddleware({
|
|
197
|
+
name: 'TaskApprovalGateMiddleware',
|
|
198
|
+
wrapModelCall: async (request, handler) => {
|
|
199
|
+
let hint: string | undefined;
|
|
200
|
+
try {
|
|
201
|
+
const did = userDidFromContext(request.runtime.context);
|
|
202
|
+
const surface = did ? options.surfaceFor(did) : undefined;
|
|
203
|
+
if (surface) {
|
|
204
|
+
const text = lastHumanText(request.messages);
|
|
205
|
+
if (text) hint = await computeApprovalHint(surface, text);
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
options.logger?.warn(
|
|
209
|
+
`[TaskApprovalGate] gate errored — passing through: ${err instanceof Error ? err.message : String(err)}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (!hint) return handler(request);
|
|
213
|
+
return handler({
|
|
214
|
+
...request,
|
|
215
|
+
systemMessage: request.systemMessage.concat(hint),
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|