@ixo/oracle-runtime-workers 0.13.0 → 0.15.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 +32 -10
- package/package.json +13 -8
- package/src/artifacts/config.ts +158 -0
- package/src/artifacts/crypto.ts +94 -0
- package/src/artifacts/routes.ts +73 -0
- package/src/artifacts/store.ts +238 -0
- package/src/artifacts/sweep.ts +200 -0
- package/src/artifacts/tool.ts +103 -0
- package/src/artifacts/viewer-page.ts +614 -0
- package/src/attachments/download.ts +168 -45
- package/src/attachments/index.ts +9 -0
- package/src/attachments/magic.ts +33 -0
- package/src/attachments/pipeline.ts +105 -49
- package/src/attachments/sandbox-archive.ts +9 -8
- package/src/attachments/view-cache.ts +142 -0
- package/src/attachments/view.ts +53 -12
- package/src/channels/auth.ts +267 -0
- package/src/channels/contract.ts +137 -0
- package/src/channels/turns.ts +270 -0
- package/src/core/budgeted-llm.ts +223 -0
- package/src/core/capability-router.ts +273 -0
- package/src/core/context-budget.ts +97 -10
- package/src/core/env.ts +111 -0
- package/src/core/execution-profile.ts +17 -0
- package/src/core/index.ts +267 -9
- package/src/core/llm.ts +30 -9
- package/src/core/main-agent-types.ts +41 -1
- package/src/core/main-agent.ts +367 -65
- package/src/core/manifest.ts +34 -0
- package/src/core/meta-tools.ts +128 -18
- package/src/core/middlewares/capability-gate.ts +130 -12
- package/src/core/middlewares/context-guard.ts +55 -24
- package/src/core/middlewares/index.ts +20 -2
- package/src/core/middlewares/page-context.ts +46 -2
- package/src/core/middlewares/return-direct-first.ts +47 -0
- package/src/core/middlewares/summarization.ts +152 -10
- package/src/core/middlewares/tool-execution.ts +323 -0
- package/src/core/middlewares/tool-repetition-guard.ts +202 -35
- package/src/core/middlewares/turn-boundary.ts +162 -0
- package/src/core/openrouter-pricing.ts +155 -22
- package/src/core/prompt-composer.ts +112 -7
- package/src/core/registries.ts +253 -48
- package/src/core/request-admission.ts +159 -0
- package/src/core/runtime-context.ts +107 -8
- package/src/core/state.ts +1 -1
- package/src/core/sub-agent-fallback.ts +21 -2
- package/src/core/subagent-as-tool.ts +78 -43
- package/src/core/tool-access.ts +189 -0
- package/src/core/tool-scheduler.ts +125 -0
- package/src/core/turn-budget.ts +205 -0
- package/src/core/turn-time-note.ts +43 -0
- package/src/core/user-kv.ts +446 -0
- package/src/core/wrap-plugin-tool.ts +20 -0
- package/src/delivery/plan.ts +278 -0
- package/src/delivery/profile.ts +161 -0
- package/src/delivery/prompt.ts +27 -0
- package/src/delivery/schema.ts +61 -0
- package/src/delivery/shaper.ts +389 -0
- package/src/delivery/types.ts +84 -0
- package/src/do/ambient.ts +51 -15
- package/src/do/contracts.ts +170 -6
- package/src/do/idle-eviction.ts +38 -0
- package/src/do/matrix-turn-ledger.ts +24 -5
- package/src/do/result-store-test-do.ts +137 -3
- package/src/do/result-store.ts +239 -58
- package/src/do/room-mirror.ts +29 -0
- package/src/do/run-buffer.ts +64 -9
- package/src/do/run-coordinator.ts +451 -76
- package/src/do/run-request.ts +2 -0
- package/src/do/run-store-test-do.ts +301 -1
- package/src/do/run-store.ts +385 -43
- package/src/do/secrets-adapter.ts +22 -3
- package/src/do/sse-stream.ts +60 -9
- package/src/do/transcript.ts +48 -5
- package/src/do/turn-body.ts +105 -5
- package/src/do/ucan-service.ts +204 -34
- package/src/do/user-oracle-do.ts +1883 -264
- package/src/feedback/config.ts +127 -0
- package/src/feedback/contract.ts +148 -0
- package/src/feedback/linear-sink.ts +285 -0
- package/src/feedback/privacy.ts +150 -0
- package/src/feedback/reservation.ts +298 -0
- package/src/feedback/submit.ts +247 -0
- package/src/index.ts +138 -6
- package/src/llm/byo-adapter.ts +27 -6
- package/src/llm/byo-catalog.ts +13 -0
- package/src/llm/byo-model-fallback.ts +248 -0
- package/src/llm/byo-service.ts +45 -8
- package/src/llm/provider-error.ts +139 -2
- package/src/matrix/gateway-do.ts +504 -108
- package/src/matrix/group-chat.ts +66 -23
- package/src/matrix/inbox-store.ts +16 -1
- package/src/matrix/ingest.ts +79 -14
- package/src/matrix/media-deadline.ts +130 -0
- package/src/matrix/replay-format.ts +5 -3
- package/src/matrix/reply-parts.ts +92 -0
- package/src/matrix/room-alias.ts +96 -0
- package/src/matrix/user-homeserver.ts +166 -7
- package/src/owner-store/ixo-vfs-store.ts +170 -43
- package/src/owner-store/migrating-store.ts +31 -15
- package/src/owner-store/types.ts +18 -3
- package/src/plugin-api/index.ts +43 -2
- package/src/plugin-api/oracle-plugin.ts +35 -0
- package/src/plugin-api/request-admission.ts +41 -0
- package/src/plugin-api/tool-helper.ts +25 -1
- package/src/plugin-api/tool-plane.ts +92 -0
- package/src/plugin-api/types.ts +194 -6
- package/src/plugins/agui/agui.plugin.ts +31 -16
- package/src/plugins/composio/composio-tools.ts +57 -6
- package/src/plugins/composio/composio-ucan.ts +3 -2
- package/src/plugins/composio/composio.plugin.ts +2 -0
- package/src/plugins/delegated-capabilities.ts +25 -0
- package/src/plugins/editor/content-session.ts +173 -83
- package/src/plugins/editor/content-tools.ts +76 -17
- package/src/plugins/editor/editor-mx.ts +28 -9
- package/src/plugins/editor/editor.plugin.ts +3 -1
- package/src/plugins/editor/failures.ts +28 -7
- package/src/plugins/editor/prompts.ts +2 -1
- package/src/plugins/editor/provider.ts +553 -40
- package/src/plugins/editor/standalone-editor-tool.ts +61 -18
- package/src/plugins/firecrawl/firecrawl-tools.ts +13 -5
- package/src/plugins/flows/action-metadata.ts +111 -11
- package/src/plugins/flows/actions.ts +18 -0
- package/src/plugins/flows/decision-context.ts +31 -0
- package/src/plugins/flows/edit.ts +223 -52
- package/src/plugins/flows/errors.ts +22 -1
- package/src/plugins/flows/flow-doc.ts +92 -9
- package/src/plugins/flows/flows.plugin.ts +11 -5
- package/src/plugins/flows/forms.ts +34 -0
- package/src/plugins/flows/index.ts +2 -0
- package/src/plugins/flows/input-policy.ts +140 -0
- package/src/plugins/flows/prompts.ts +14 -11
- package/src/plugins/flows/read.ts +49 -9
- package/src/plugins/flows/ref-syntax.ts +129 -0
- package/src/plugins/flows/test-homeserver.ts +245 -0
- package/src/plugins/flows/tools/authoring.ts +216 -112
- package/src/plugins/flows/tools/discovery.ts +4 -1
- package/src/plugins/flows/tools/forms.ts +21 -2
- package/src/plugins/flows/tools/settings.ts +34 -13
- package/src/plugins/flows/translator.ts +13 -10
- package/src/plugins/flows/types.ts +167 -42
- package/src/plugins/index.ts +11 -1
- package/src/plugins/ixo-transaction/chain-ids.ts +43 -0
- package/src/plugins/ixo-transaction/index.ts +2 -0
- package/src/plugins/ixo-transaction/ixo-transaction.plugin.ts +119 -0
- package/src/plugins/ixo-transaction/receipts.ts +99 -0
- package/src/plugins/ixo-transaction/tools.ts +329 -0
- package/src/plugins/ixo-transaction/wallet-signing.ts +202 -0
- package/src/plugins/mcp-tool-adapter.ts +23 -2
- package/src/plugins/memory/memory-tools.ts +7 -2
- package/src/plugins/memory/memory-ucan.ts +3 -2
- package/src/plugins/memory/memory.plugin.ts +2 -0
- package/src/plugins/pod-creator/blueprint-store.ts +163 -0
- package/src/plugins/pod-creator/blueprint-types.ts +61 -0
- package/src/plugins/pod-creator/bounded-map.ts +87 -0
- package/src/plugins/pod-creator/capsule-content-client.ts +285 -0
- package/src/plugins/pod-creator/chain-gateway.ts +110 -0
- package/src/plugins/pod-creator/config.ts +85 -0
- package/src/plugins/pod-creator/create-session-store.ts +180 -0
- package/src/plugins/pod-creator/create-tools.ts +538 -0
- package/src/plugins/pod-creator/design-pod-roles.ts +111 -0
- package/src/plugins/pod-creator/index.ts +65 -0
- package/src/plugins/pod-creator/orchestration-tools.ts +172 -0
- package/src/plugins/pod-creator/pod-creator.plugin.ts +190 -0
- package/src/plugins/pod-creator/stage.ts +201 -0
- package/src/plugins/pod-creator/sub-agents.ts +189 -0
- package/src/plugins/portal/action-log.ts +21 -1
- package/src/plugins/portal/declared-tools.ts +90 -0
- package/src/plugins/portal/portal.plugin.ts +42 -16
- package/src/plugins/sandbox/sandbox-bridge.ts +79 -10
- package/src/plugins/sandbox/sandbox-mcp.ts +3 -2
- package/src/plugins/sandbox/sandbox-write-blob.ts +11 -1
- package/src/plugins/sandbox/sandbox.plugin.ts +8 -16
- package/src/plugins/tasks/manifest.ts +1 -1
- package/src/plugins/tasks/middleware.ts +20 -7
- package/src/plugins/tasks/tasks-tools.ts +30 -3
- package/src/plugins/tasks/tasks.plugin.ts +64 -11
- package/src/plugins/user-preferences/schema.ts +12 -1
- package/src/plugins/user-preferences/user-preferences-store.ts +38 -13
- package/src/plugins/user-preferences/user-preferences-tool.ts +3 -2
- package/src/plugins/vfs/vfs-client.ts +250 -71
- package/src/plugins/vfs/vfs-content.ts +30 -11
- package/src/plugins/vfs/vfs-errors.ts +22 -0
- package/src/plugins/vfs/vfs-sandbox-tools.ts +12 -5
- package/src/plugins/vfs/vfs-tools.ts +60 -20
- package/src/realtime/frontend-call-registry.ts +228 -81
- package/src/realtime/realtime-endpoint.ts +215 -44
- package/src/realtime/session-socket-hub.ts +47 -2
- package/src/secrets/secrets-service.ts +9 -3
- package/src/shell/app.ts +529 -82
- package/src/shell/auth.ts +131 -37
- package/src/shell/turn-body-cap.ts +11 -13
- package/src/sqlite/blob-compactor.ts +61 -28
- package/src/sqlite/database.ts +156 -90
- package/src/sqlite/do-vfs.ts +416 -129
- package/src/sqlite/page-tier.ts +133 -40
- package/src/sqlite/serialization.ts +40 -0
- package/src/sqlite/sessions-store.ts +10 -2
- package/src/sqlite/sqlite-saver.ts +139 -19
- package/src/sqlite/user-kv-store.ts +317 -0
- package/src/tasks/schedule.ts +201 -12
- package/src/tasks/scheduler.ts +586 -98
- package/src/tasks/spec.ts +67 -3
- package/src/tasks/store.ts +292 -27
- package/src/tasks/topic-deliverables.ts +70 -0
package/src/do/contracts.ts
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TopicDeliverableCommand,
|
|
3
|
+
TopicDeliverableResult,
|
|
4
|
+
} from '../tasks/topic-deliverables';
|
|
5
|
+
import type {
|
|
6
|
+
ChannelIdentity,
|
|
7
|
+
ChannelTurnInput,
|
|
8
|
+
ChannelTurnOutcome,
|
|
9
|
+
} from '../channels/contract';
|
|
10
|
+
import type { TaskExecutionProfile } from '../core/execution-profile';
|
|
11
|
+
import type { CapabilityRouterMode } from '@ixo/common/ai/decisions';
|
|
1
12
|
import type { TranscriptPageOptions } from './transcript';
|
|
13
|
+
import type { TurnUsage } from '../core/turn-budget';
|
|
2
14
|
import type { BotStatus, EncryptedFileInfo } from '@ixo/matrix-bot-workers-sdk';
|
|
3
15
|
import type { AttachmentInput } from '../attachments/types';
|
|
4
16
|
import type { RealtimeStatus } from '../realtime/realtime-endpoint';
|
|
5
17
|
import type { TierFlushResult, TierStatus } from '../sqlite/do-vfs';
|
|
18
|
+
import type { OwnedArtifact } from '../artifacts/store';
|
|
19
|
+
import type {
|
|
20
|
+
FeedbackReservation,
|
|
21
|
+
FeedbackSettlement,
|
|
22
|
+
FeedbackTarget,
|
|
23
|
+
} from '../feedback/contract';
|
|
6
24
|
/**
|
|
7
25
|
* Cross-object contracts for the Workers runtime.
|
|
8
26
|
*
|
|
@@ -25,6 +43,7 @@ import type { TierFlushResult, TierStatus } from '../sqlite/do-vfs';
|
|
|
25
43
|
|
|
26
44
|
/** Bindings every oracle Worker must declare (see the example `wrangler.jsonc`). */
|
|
27
45
|
export interface OracleWorkerEnv {
|
|
46
|
+
TOPIC_DELIVERABLES_ENABLED?: string;
|
|
28
47
|
USER_ORACLE: DurableObjectNamespace<UserOracleObject>;
|
|
29
48
|
MATRIX_GATEWAY: DurableObjectNamespace<MatrixGatewayObject>;
|
|
30
49
|
|
|
@@ -40,6 +59,12 @@ export interface OracleWorkerEnv {
|
|
|
40
59
|
/** Blocksync GraphQL endpoint used to resolve `did:ixo` verification keys. */
|
|
41
60
|
BLOCKSYNC_GRAPHQL_URL: string;
|
|
42
61
|
UCAN_AUTH_MAX_TTL_SECONDS?: string;
|
|
62
|
+
/** `true` restores the legacy bare-delegation authentication (off by default). */
|
|
63
|
+
UCAN_ALLOW_BARE_DELEGATION_AUTH?: string;
|
|
64
|
+
CHANNEL_SERVICE_DID?: string;
|
|
65
|
+
AUTH_HUB?: Fetcher;
|
|
66
|
+
AUTH_HUB_URL?: string;
|
|
67
|
+
AUTH_HUB_CHANNEL_SERVICE_KEY?: string;
|
|
43
68
|
|
|
44
69
|
// --- matrix -------------------------------------------------------------
|
|
45
70
|
MATRIX_BASE_URL: string;
|
|
@@ -131,6 +156,22 @@ export interface OracleWorkerEnv {
|
|
|
131
156
|
TIER_EVICT_AFTER_PERIODS?: string;
|
|
132
157
|
/** Length of one access-tracking period in ms (default one day; tests shorten it). */
|
|
133
158
|
TIER_PERIOD_MS?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Artefacts (chat delivery): long replies on chat surfaces become documents
|
|
161
|
+
* behind a link. The R2 bucket holds only ciphertext share copies; absent
|
|
162
|
+
* bucket = artefacts off; a bucket without a public origin keeps stored
|
|
163
|
+
* artefacts readable and revocable but makes no new ones. Long replies are
|
|
164
|
+
* then split into messages instead.
|
|
165
|
+
*/
|
|
166
|
+
ARTIFACT_BUCKET?: R2Bucket;
|
|
167
|
+
/** The oracle Worker's public origin; artefact links point at `/a/:id` under it. */
|
|
168
|
+
ORACLE_PUBLIC_URL?: string;
|
|
169
|
+
/** A shared viewer page (Qi.Space). Unset = the oracle's own `/a/:id` page. */
|
|
170
|
+
ARTIFACT_VIEWER_URL?: string;
|
|
171
|
+
/** Days an artefact link stays valid (default 30). */
|
|
172
|
+
ARTIFACT_LINK_TTL_DAYS?: string;
|
|
173
|
+
/** Hours between the cron's sweeps of expired share copies (default 24, 1–168). */
|
|
174
|
+
ARTIFACT_SWEEP_INTERVAL_HOURS?: string;
|
|
134
175
|
/** IXO VFS worker base URL when `OWNER_STORE=vfs` (defaults per NETWORK). */
|
|
135
176
|
VFS_BASE_URL?: string;
|
|
136
177
|
/** UCAN store worker base URL when `OWNER_STORE=vfs` (defaults per NETWORK). */
|
|
@@ -149,6 +190,36 @@ export interface OracleWorkerEnv {
|
|
|
149
190
|
* local harness and vitest pool, where the shell degrades to no limiting.
|
|
150
191
|
*/
|
|
151
192
|
RATE_LIMIT?: RateLimit;
|
|
193
|
+
/**
|
|
194
|
+
* Optional rate-limit binding for anonymous feedback, keyed per client IP
|
|
195
|
+
* pseudonym (declare it with `simple: { limit: 3, period: 60 }`). Without
|
|
196
|
+
* it the per-IP check uses `RATE_LIMIT`'s limit under a feedback-only key.
|
|
197
|
+
*/
|
|
198
|
+
FEEDBACK_RATE_LIMIT?: RateLimit;
|
|
199
|
+
|
|
200
|
+
// --- anonymous response feedback (docs/configuration.md) ------------------
|
|
201
|
+
// Read by the shell only (`src/feedback/config.ts`); never part of the
|
|
202
|
+
// validated config plugins receive. Off unless the key and secret are set.
|
|
203
|
+
/** Linear API key allowed to create issues in the feedback team. */
|
|
204
|
+
FEEDBACK_LINEAR_API_KEY?: string;
|
|
205
|
+
/** At least 32 characters; derives the user/session/message pseudonyms. */
|
|
206
|
+
FEEDBACK_HMAC_SECRET?: string;
|
|
207
|
+
/** Linear team (default: Studio). */
|
|
208
|
+
FEEDBACK_LINEAR_TEAM_ID?: string;
|
|
209
|
+
/** Linear project (default: "User Feedback from Portal"). */
|
|
210
|
+
FEEDBACK_LINEAR_PROJECT_ID?: string;
|
|
211
|
+
/** Optional comma-separated Linear label ids. */
|
|
212
|
+
FEEDBACK_LINEAR_LABEL_IDS?: string;
|
|
213
|
+
/** Linear GraphQL endpoint override (default `https://api.linear.app/graphql`). */
|
|
214
|
+
FEEDBACK_LINEAR_API_URL?: string;
|
|
215
|
+
/** Release or commit identifier shown in feedback issues (default `unknown`). */
|
|
216
|
+
QIFORGE_BUILD_VERSION?: string;
|
|
217
|
+
/**
|
|
218
|
+
* Workers AI binding. Optional: `DECISION_PROVIDER=cloudflare-jev` runs the
|
|
219
|
+
* Jev model through it instead of the account credentials. Declare it with
|
|
220
|
+
* `"ai": { "binding": "AI" }` in the wrangler config.
|
|
221
|
+
*/
|
|
222
|
+
AI?: Ai;
|
|
152
223
|
|
|
153
224
|
// --- llm ----------------------------------------------------------------
|
|
154
225
|
OPEN_ROUTER_API_KEY: string;
|
|
@@ -158,6 +229,15 @@ export interface OracleWorkerEnv {
|
|
|
158
229
|
MAIN_REASONING_EFFORT?: string;
|
|
159
230
|
/** LangGraph steps one turn may take before `GraphRecursionError` (default 600; Node hard-codes 200). */
|
|
160
231
|
TURN_RECURSION_LIMIT?: string;
|
|
232
|
+
/** Turn budget: cumulative model tokens (default 500000), tool attempts (120), deadline in ms (600000). */
|
|
233
|
+
TURN_MAX_TOKENS?: string;
|
|
234
|
+
TURN_MAX_TOOL_CALLS?: string;
|
|
235
|
+
TURN_TIMEOUT_MS?: string;
|
|
236
|
+
/** Repetition guard: identical successful writes (default 1) and reads (5) per turn. */
|
|
237
|
+
TURN_MAX_IDENTICAL_WRITES?: string;
|
|
238
|
+
TURN_MAX_IDENTICAL_READS?: string;
|
|
239
|
+
/** Time limit per request-admission handler in ms (default 2000). */
|
|
240
|
+
REQUEST_ADMISSION_TIMEOUT_MS?: string;
|
|
161
241
|
/** Durable-run knobs (docs/plans/durable-runs.md); see `runDurabilityConfig`. */
|
|
162
242
|
RUN_KEEPALIVE_MS?: string;
|
|
163
243
|
RUN_SEGMENT_FLUSH_MS?: string;
|
|
@@ -183,6 +263,22 @@ export interface OracleWorkerEnv {
|
|
|
183
263
|
/** Nebius Token Factory API key — required when `LLM_PROVIDER=nebius`. */
|
|
184
264
|
NEBIUS_API_KEY?: string;
|
|
185
265
|
|
|
266
|
+
// --- decisions ----------------------------------------------------------
|
|
267
|
+
/** Bounded semantic Decision provider; unset leaves Decisions unconfigured. `openrouter-jev` reuses `OPEN_ROUTER_API_KEY`. */
|
|
268
|
+
DECISION_PROVIDER?: 'cloudflare-jev' | 'openrouter-jev';
|
|
269
|
+
/** Jev model id override (`typesafe/jev` on Workers AI, `typesafe/jev-1.13` on OpenRouter). */
|
|
270
|
+
DECISION_MODEL?: string;
|
|
271
|
+
/** Cloudflare account for `cloudflare-jev` over the REST API; not needed when the `AI` binding is declared. */
|
|
272
|
+
CLOUDFLARE_ACCOUNT_ID?: string;
|
|
273
|
+
/** Workers AI token for `cloudflare-jev` over the REST API; not needed when the `AI` binding is declared. */
|
|
274
|
+
CLOUDFLARE_API_TOKEN?: string;
|
|
275
|
+
/**
|
|
276
|
+
* Capability router (needs a `DECISION_PROVIDER`). `off` (default) never
|
|
277
|
+
* evaluates; `shadow` evaluates and logs the verdict without preloading;
|
|
278
|
+
* `on` preloads the predicted on-demand plugin's tools for the turn.
|
|
279
|
+
*/
|
|
280
|
+
CAPABILITY_ROUTER?: CapabilityRouterMode;
|
|
281
|
+
|
|
186
282
|
// --- langsmith tracing ---------------------------------------------------
|
|
187
283
|
/** `'true'` traces every turn (explicit tracer — Workers has no env auto-attach). */
|
|
188
284
|
LANGSMITH_TRACING?: string;
|
|
@@ -234,6 +330,7 @@ export type JsonString = string;
|
|
|
234
330
|
|
|
235
331
|
/** Who is talking and through which transport. */
|
|
236
332
|
export interface TurnIdentity {
|
|
333
|
+
channel?: ChannelIdentity;
|
|
237
334
|
/** Validated user DID (from the UCAN signer or the Matrix sender mapping). */
|
|
238
335
|
userDid: string;
|
|
239
336
|
/** `@did-ixo-…:server` when known. */
|
|
@@ -247,10 +344,16 @@ export interface TurnIdentity {
|
|
|
247
344
|
|
|
248
345
|
/** Request the shell / gateway sends to `UserOracleDO` to run one turn. */
|
|
249
346
|
export interface TurnRequest {
|
|
347
|
+
channel?: {
|
|
348
|
+
provider: 'whatsapp';
|
|
349
|
+
bindingId: string;
|
|
350
|
+
remoteMessageRef: string;
|
|
351
|
+
};
|
|
352
|
+
executionProfile?: TaskExecutionProfile;
|
|
250
353
|
identity: TurnIdentity;
|
|
251
354
|
sessionId: string;
|
|
252
355
|
message: string;
|
|
253
|
-
client: 'portal' | 'matrix';
|
|
356
|
+
client: 'portal' | 'matrix' | 'channel';
|
|
254
357
|
/** Matrix room the turn arrived in (Matrix turns) or the user's DM room. */
|
|
255
358
|
roomId?: string;
|
|
256
359
|
/** Thread root when the user replied inside a thread. */
|
|
@@ -342,7 +445,7 @@ export interface RunSummary {
|
|
|
342
445
|
runId: string;
|
|
343
446
|
sessionId: string;
|
|
344
447
|
requestId: string;
|
|
345
|
-
client: 'portal' | 'matrix';
|
|
448
|
+
client: 'portal' | 'matrix' | 'channel';
|
|
346
449
|
status:
|
|
347
450
|
| 'queued'
|
|
348
451
|
| 'running'
|
|
@@ -398,16 +501,29 @@ export interface RunsStatus {
|
|
|
398
501
|
nextAttemptAt: number | null;
|
|
399
502
|
error: string | null;
|
|
400
503
|
taskRunId: string | null;
|
|
504
|
+
/** The turn's budget usage, once the run ended (`TurnUsage`). */
|
|
505
|
+
usage: TurnUsage | null;
|
|
401
506
|
}
|
|
402
507
|
>;
|
|
508
|
+
/** Writes whose outcome is still unknown (tool-execution.ts); never their arguments. */
|
|
509
|
+
claims: Array<{
|
|
510
|
+
fingerprint: string;
|
|
511
|
+
toolName: string;
|
|
512
|
+
runId: string;
|
|
513
|
+
sessionId: string;
|
|
514
|
+
startedAt: string;
|
|
515
|
+
state: 'pending' | 'warned';
|
|
516
|
+
}>;
|
|
403
517
|
}
|
|
404
518
|
|
|
405
519
|
/** Non-streaming turn result. */
|
|
406
520
|
export interface TurnResult {
|
|
407
521
|
sessionId: string;
|
|
408
522
|
requestId: string;
|
|
409
|
-
/**
|
|
523
|
+
/** The model's final text, on every surface (never a chat rendering). */
|
|
410
524
|
text: string;
|
|
525
|
+
/** Chat-surface turns: the Reply Plan (JSON) to deliver part by part. */
|
|
526
|
+
plan?: JsonString;
|
|
411
527
|
/** Id of the final assistant message, when one was produced. */
|
|
412
528
|
messageId?: string;
|
|
413
529
|
/** Tool calls made during the turn (name + summarized status), for logging. */
|
|
@@ -513,6 +629,16 @@ export interface StorageStatus {
|
|
|
513
629
|
* piped straight to the client.
|
|
514
630
|
*/
|
|
515
631
|
export interface UserOracleObject extends Rpc.DurableObjectBranded {
|
|
632
|
+
topicDeliverable(
|
|
633
|
+
identity: TurnIdentity,
|
|
634
|
+
operationId: string,
|
|
635
|
+
command: TopicDeliverableCommand,
|
|
636
|
+
): Promise<TopicDeliverableResult>;
|
|
637
|
+
channelTurn(
|
|
638
|
+
identity: TurnIdentity,
|
|
639
|
+
input: ChannelTurnInput,
|
|
640
|
+
requestHash: string,
|
|
641
|
+
): Promise<ChannelTurnOutcome>;
|
|
516
642
|
/** Run a turn and return the final text (used by the Matrix gateway). */
|
|
517
643
|
runTurn(req: TurnRequest): Promise<TurnResult>;
|
|
518
644
|
/**
|
|
@@ -531,6 +657,13 @@ export interface UserOracleObject extends Rpc.DurableObjectBranded {
|
|
|
531
657
|
opts?: { limit?: number; offset?: number },
|
|
532
658
|
): Promise<{ sessions: SessionSummary[]; total: number }>;
|
|
533
659
|
deleteSession(identity: TurnIdentity, sessionId: string): Promise<boolean>;
|
|
660
|
+
/** `GET /artifacts/:id` — the owner's canonical copy, or null. */
|
|
661
|
+
artifact(
|
|
662
|
+
identity: TurnIdentity,
|
|
663
|
+
artifactId: string,
|
|
664
|
+
): Promise<OwnedArtifact | null>;
|
|
665
|
+
/** `DELETE /artifacts/:id` — false when there is no such artefact. */
|
|
666
|
+
revokeArtifact(identity: TurnIdentity, artifactId: string): Promise<boolean>;
|
|
534
667
|
/** JSON-encoded `MessageDto[]` for `GET /messages/:sessionId`. */
|
|
535
668
|
listMessages(identity: TurnIdentity, sessionId: string): Promise<JsonString>;
|
|
536
669
|
/**
|
|
@@ -545,6 +678,20 @@ export interface UserOracleObject extends Rpc.DurableObjectBranded {
|
|
|
545
678
|
): Promise<
|
|
546
679
|
{ ok: true; json: JsonString } | { ok: false; status: 400; message: string }
|
|
547
680
|
>;
|
|
681
|
+
/**
|
|
682
|
+
* Anonymous feedback (src/feedback/): validate the target message in this
|
|
683
|
+
* user's database and reserve its idempotency marker. Never sees the text.
|
|
684
|
+
*/
|
|
685
|
+
reserveMessageFeedback(
|
|
686
|
+
identity: TurnIdentity,
|
|
687
|
+
target: FeedbackTarget,
|
|
688
|
+
): Promise<FeedbackReservation>;
|
|
689
|
+
/** Record the delivery, keep the earlier submission's, or release the reservation. */
|
|
690
|
+
settleMessageFeedback(
|
|
691
|
+
identity: TurnIdentity,
|
|
692
|
+
target: FeedbackTarget,
|
|
693
|
+
outcome: FeedbackSettlement,
|
|
694
|
+
): Promise<void>;
|
|
548
695
|
abortTurn(sessionId: string): Promise<boolean>;
|
|
549
696
|
/** The session's active (queued/running/recovering) run, if any. */
|
|
550
697
|
sessionRun(
|
|
@@ -595,9 +742,12 @@ export interface UserOracleObject extends Rpc.DurableObjectBranded {
|
|
|
595
742
|
/**
|
|
596
743
|
* Drop the working copy (pages + caches) so the next access re-imports from
|
|
597
744
|
* the owner store. Simulates eviction/loss of the DO cache in tests and is
|
|
598
|
-
* the "user deleted their file upstream" path.
|
|
745
|
+
* the "user deleted their file upstream" path. `userDid` is the caller the
|
|
746
|
+
* shell authenticated, so an object that never booted boots for it.
|
|
599
747
|
*/
|
|
600
|
-
resetWorkingCopy(
|
|
748
|
+
resetWorkingCopy(
|
|
749
|
+
identity?: TurnIdentity,
|
|
750
|
+
): Promise<{ reloadedFromOwnerStore: boolean }>;
|
|
601
751
|
/** Run one R2 page-tier eviction pass now (debug routes; `force` ignores recency). */
|
|
602
752
|
tierFlush(opts?: {
|
|
603
753
|
force?: boolean;
|
|
@@ -658,6 +808,16 @@ export interface CreateRoomOptions {
|
|
|
658
808
|
userDid: string;
|
|
659
809
|
}
|
|
660
810
|
|
|
811
|
+
/**
|
|
812
|
+
* Deadline for one gateway media download, from opening it to the last byte
|
|
813
|
+
* (the gateway's default applies when omitted). Only the timeout crosses the
|
|
814
|
+
* RPC: an `AbortSignal` cannot, so the caller stops a transfer early by
|
|
815
|
+
* cancelling the returned stream.
|
|
816
|
+
*/
|
|
817
|
+
export interface MediaDownloadDeadline {
|
|
818
|
+
timeoutMs?: number;
|
|
819
|
+
}
|
|
820
|
+
|
|
661
821
|
/**
|
|
662
822
|
* RPC surface of `MatrixGatewayDO`. User objects call it for anything that
|
|
663
823
|
* needs the bot identity (sending, media, room lookups); the shell calls
|
|
@@ -676,9 +836,13 @@ export interface MatrixGatewayObject extends Rpc.DurableObjectBranded {
|
|
|
676
836
|
downloadEventMediaStream(
|
|
677
837
|
roomId: string,
|
|
678
838
|
eventId: string,
|
|
839
|
+
opts?: MediaDownloadDeadline,
|
|
679
840
|
): Promise<MediaStream | null>;
|
|
680
841
|
/** Authenticated media download of an `mxc://` URI, as a stream. */
|
|
681
|
-
downloadMxcMediaStream(
|
|
842
|
+
downloadMxcMediaStream(
|
|
843
|
+
mxc: string,
|
|
844
|
+
opts?: MediaDownloadDeadline,
|
|
845
|
+
): Promise<ReadableStream<Uint8Array>>;
|
|
682
846
|
/** Forcibly reset the gateway object like a platform host drain (debug routes only). The call itself rejects. */
|
|
683
847
|
debugAbortObject(): Promise<void>;
|
|
684
848
|
/** Stop the bot (operator/debug; `ensureStarted`/cron brings it back). */
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dropping an idle user's working copy. The object only drops it when the
|
|
3
|
+
* owner copy upstream is verified current, and that check awaits (the
|
|
4
|
+
* checksum reads the whole file, from R2 for tiered pages), and while an
|
|
5
|
+
* object awaits I/O other than its own storage a request can arrive. So
|
|
6
|
+
* whatever the tick decided before is re-read after the check, and the
|
|
7
|
+
* in-memory handles are dropped before the first await of the wipe: from
|
|
8
|
+
* then on a request boots afresh from the owner copy instead of reaching a
|
|
9
|
+
* database being deleted under it.
|
|
10
|
+
*/
|
|
11
|
+
export interface IdleEvictionSteps {
|
|
12
|
+
/** The owner copy matches the working copy (may await network I/O). */
|
|
13
|
+
ownerCopyIsCurrent(): Promise<boolean>;
|
|
14
|
+
/** Everything that decides idleness, read again: still no activity. */
|
|
15
|
+
stillIdle(): Promise<boolean>;
|
|
16
|
+
/** Forget the in-memory handles. Synchronous, so nothing interleaves. */
|
|
17
|
+
dropHandles(): void;
|
|
18
|
+
/** Delete the working copy. */
|
|
19
|
+
wipe(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type IdleEvictionOutcome =
|
|
23
|
+
/** The working copy was deleted. */
|
|
24
|
+
| 'evicted'
|
|
25
|
+
/** The owner copy is not verified current: keep the working copy. */
|
|
26
|
+
| 'not-current'
|
|
27
|
+
/** Activity arrived while the owner copy was checked: keep it. */
|
|
28
|
+
| 'active';
|
|
29
|
+
|
|
30
|
+
export async function evictIdleWorkingCopy(
|
|
31
|
+
steps: IdleEvictionSteps,
|
|
32
|
+
): Promise<IdleEvictionOutcome> {
|
|
33
|
+
if (!(await steps.ownerCopyIsCurrent())) return 'not-current';
|
|
34
|
+
if (!(await steps.stillIdle())) return 'active';
|
|
35
|
+
steps.dropHandles();
|
|
36
|
+
await steps.wipe();
|
|
37
|
+
return 'evicted';
|
|
38
|
+
}
|
|
@@ -29,6 +29,8 @@ export interface MatrixTurnRecord {
|
|
|
29
29
|
startedAt: string;
|
|
30
30
|
answeredAt: string | null;
|
|
31
31
|
replyText: string | null;
|
|
32
|
+
/** The Reply Plan (JSON) a chat-style reply was delivered as. */
|
|
33
|
+
replyPlan?: string;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export type MatrixTurnDecision = 'run' | 'answered' | 'interrupted';
|
|
@@ -50,6 +52,7 @@ type LedgerRow = {
|
|
|
50
52
|
started_at: string;
|
|
51
53
|
answered_at: string | null;
|
|
52
54
|
reply_text: string | null;
|
|
55
|
+
reply_plan: string | null;
|
|
53
56
|
} & Record<string, string | null>;
|
|
54
57
|
|
|
55
58
|
export class MatrixTurnLedger {
|
|
@@ -76,8 +79,14 @@ export class MatrixTurnLedger {
|
|
|
76
79
|
request_id TEXT NOT NULL,
|
|
77
80
|
started_at TEXT NOT NULL,
|
|
78
81
|
answered_at TEXT,
|
|
79
|
-
reply_text TEXT
|
|
82
|
+
reply_text TEXT,
|
|
83
|
+
reply_plan TEXT
|
|
80
84
|
)`);
|
|
85
|
+
const columns = await this.db.exec<{ name: string }>(
|
|
86
|
+
`PRAGMA table_info(matrix_turns)`,
|
|
87
|
+
);
|
|
88
|
+
if (!columns.some((c) => c.name === 'reply_plan'))
|
|
89
|
+
await this.db.run(`ALTER TABLE matrix_turns ADD COLUMN reply_plan TEXT`);
|
|
81
90
|
await this.db.run(`DELETE FROM matrix_turns WHERE started_at < ?`, [
|
|
82
91
|
new Date(this.now() - RETENTION_MS).toISOString(),
|
|
83
92
|
]);
|
|
@@ -86,7 +95,7 @@ export class MatrixTurnLedger {
|
|
|
86
95
|
async get(eventId: string): Promise<MatrixTurnRecord | undefined> {
|
|
87
96
|
await this.setup();
|
|
88
97
|
const row = await this.db.get<LedgerRow>(
|
|
89
|
-
`SELECT event_id, session_id, request_id, started_at, answered_at, reply_text
|
|
98
|
+
`SELECT event_id, session_id, request_id, started_at, answered_at, reply_text, reply_plan
|
|
90
99
|
FROM matrix_turns WHERE event_id = ?`,
|
|
91
100
|
[eventId],
|
|
92
101
|
);
|
|
@@ -98,6 +107,7 @@ export class MatrixTurnLedger {
|
|
|
98
107
|
startedAt: row.started_at,
|
|
99
108
|
answeredAt: row.answered_at,
|
|
100
109
|
replyText: row.reply_text,
|
|
110
|
+
...(row.reply_plan !== null ? { replyPlan: row.reply_plan } : {}),
|
|
101
111
|
};
|
|
102
112
|
}
|
|
103
113
|
|
|
@@ -116,11 +126,20 @@ export class MatrixTurnLedger {
|
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
/** Record the reply; from now on the event is answered, never re-run. */
|
|
119
|
-
async answer(
|
|
129
|
+
async answer(
|
|
130
|
+
eventId: string,
|
|
131
|
+
replyText: string,
|
|
132
|
+
replyPlan?: string,
|
|
133
|
+
): Promise<void> {
|
|
120
134
|
await this.setup();
|
|
121
135
|
await this.db.run(
|
|
122
|
-
`UPDATE matrix_turns SET reply_text = ?, answered_at = ? WHERE event_id = ?`,
|
|
123
|
-
[
|
|
136
|
+
`UPDATE matrix_turns SET reply_text = ?, reply_plan = ?, answered_at = ? WHERE event_id = ?`,
|
|
137
|
+
[
|
|
138
|
+
replyText,
|
|
139
|
+
replyPlan ?? null,
|
|
140
|
+
new Date(this.now()).toISOString(),
|
|
141
|
+
eventId,
|
|
142
|
+
],
|
|
124
143
|
);
|
|
125
144
|
}
|
|
126
145
|
}
|
|
@@ -22,13 +22,58 @@ export class ResultStoreTestDO extends DurableObject<{ TIER_TEST: R2Bucket }> {
|
|
|
22
22
|
|
|
23
23
|
private withBucket = true;
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* How R2 deletes behave: `fail` throws (an outage during a sweep),
|
|
27
|
+
* `delete-then-fail` deletes and then throws (a lost response),
|
|
28
|
+
* `fail-first` throws on the first call only.
|
|
29
|
+
*/
|
|
30
|
+
private deletes: 'ok' | 'fail' | 'delete-then-fail' | 'fail-first' = 'ok';
|
|
31
|
+
|
|
32
|
+
private deleteCalls = 0;
|
|
33
|
+
|
|
34
|
+
/** While set, an R2 delete waits for `release` (after signalling `reached`). */
|
|
35
|
+
private deleteHold: { reached: () => void; release: Promise<void> } | null =
|
|
36
|
+
null;
|
|
37
|
+
|
|
38
|
+
private async database(): Promise<DoSqliteDatabase> {
|
|
26
39
|
if (!this.db || !this.db.isOpen) {
|
|
27
40
|
this.db = await DoSqliteDatabase.open(this.ctx, 'results-test.db');
|
|
28
41
|
this.store = undefined;
|
|
29
42
|
}
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
return this.db;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private bucket(): R2Bucket {
|
|
47
|
+
const remove = async (keys: string | string[]): Promise<void> => {
|
|
48
|
+
this.deleteCalls += 1;
|
|
49
|
+
const hold = this.deleteHold;
|
|
50
|
+
if (hold) {
|
|
51
|
+
hold.reached();
|
|
52
|
+
await hold.release;
|
|
53
|
+
}
|
|
54
|
+
if (
|
|
55
|
+
this.deletes === 'fail' ||
|
|
56
|
+
(this.deletes === 'fail-first' && this.deleteCalls === 1)
|
|
57
|
+
)
|
|
58
|
+
throw new Error('R2 unavailable');
|
|
59
|
+
await this.env.TIER_TEST.delete(keys);
|
|
60
|
+
if (this.deletes === 'delete-then-fail')
|
|
61
|
+
throw new Error('R2 response lost');
|
|
62
|
+
};
|
|
63
|
+
return new Proxy(this.env.TIER_TEST, {
|
|
64
|
+
get: (target, property) => {
|
|
65
|
+
if (property === 'delete') return remove;
|
|
66
|
+
// The binding's methods only run on the binding itself.
|
|
67
|
+
const value: unknown = Reflect.get(target, property);
|
|
68
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async resultStore(): Promise<ResultStore> {
|
|
74
|
+
const db = await this.database();
|
|
75
|
+
this.store ??= new ResultStore(db, {
|
|
76
|
+
...(this.withBucket ? { bucket: this.bucket() } : {}),
|
|
32
77
|
prefix: `test-${this.ctx.id.toString()}`,
|
|
33
78
|
ttlMs: 60 * 60 * 1000,
|
|
34
79
|
r2MinBytes: this.r2MinBytes,
|
|
@@ -40,12 +85,101 @@ export class ResultStoreTestDO extends DurableObject<{ TIER_TEST: R2Bucket }> {
|
|
|
40
85
|
async configure(opts: {
|
|
41
86
|
r2MinBytes?: number;
|
|
42
87
|
withBucket?: boolean;
|
|
88
|
+
failingDeletes?: boolean;
|
|
89
|
+
deletes?: 'ok' | 'fail' | 'delete-then-fail' | 'fail-first';
|
|
43
90
|
}): Promise<void> {
|
|
44
91
|
if (opts.r2MinBytes !== undefined) this.r2MinBytes = opts.r2MinBytes;
|
|
45
92
|
if (opts.withBucket !== undefined) this.withBucket = opts.withBucket;
|
|
93
|
+
if (opts.failingDeletes !== undefined)
|
|
94
|
+
this.deletes = opts.failingDeletes ? 'fail' : 'ok';
|
|
95
|
+
if (opts.deletes !== undefined) this.deletes = opts.deletes;
|
|
96
|
+
this.deleteCalls = 0;
|
|
46
97
|
this.store = undefined;
|
|
47
98
|
}
|
|
48
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Delete one session's results while another session stores the same
|
|
102
|
+
* content: the put starts while the delete waits on R2, and R2 answers
|
|
103
|
+
* after the put had time to finish. Returns how the result reads then.
|
|
104
|
+
*/
|
|
105
|
+
async deleteWhilePutting(input: {
|
|
106
|
+
deleting: string;
|
|
107
|
+
putting: string;
|
|
108
|
+
content: string;
|
|
109
|
+
}): Promise<ReadResultOutcome> {
|
|
110
|
+
const store = await this.resultStore();
|
|
111
|
+
await store.setup();
|
|
112
|
+
let reached!: () => void;
|
|
113
|
+
const atDelete = new Promise<void>((resolve) => {
|
|
114
|
+
reached = resolve;
|
|
115
|
+
});
|
|
116
|
+
let release!: () => void;
|
|
117
|
+
this.deleteHold = {
|
|
118
|
+
reached,
|
|
119
|
+
release: new Promise<void>((resolve) => {
|
|
120
|
+
release = resolve;
|
|
121
|
+
}),
|
|
122
|
+
};
|
|
123
|
+
const deleting = store.deleteForSession(input.deleting);
|
|
124
|
+
await atDelete;
|
|
125
|
+
const putting = store.put({
|
|
126
|
+
sessionId: input.putting,
|
|
127
|
+
toolName: 't',
|
|
128
|
+
content: input.content,
|
|
129
|
+
});
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
131
|
+
release();
|
|
132
|
+
const [, ref] = await Promise.all([deleting, putting]);
|
|
133
|
+
this.deleteHold = null;
|
|
134
|
+
if (!ref) throw new Error('the result was not stored');
|
|
135
|
+
return store.read(ref.id, 0, 5);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Rows as a store written before results were shared across sessions
|
|
140
|
+
* left them: the `tool_results` table alone. Call before any other method.
|
|
141
|
+
*/
|
|
142
|
+
async seedLegacy(
|
|
143
|
+
rows: Array<{ id: string; sessionId: string; content: string }>,
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
const db = await this.database();
|
|
146
|
+
await db.run(`
|
|
147
|
+
CREATE TABLE IF NOT EXISTS tool_results (
|
|
148
|
+
id TEXT PRIMARY KEY,
|
|
149
|
+
session_id TEXT NOT NULL,
|
|
150
|
+
tool_name TEXT NOT NULL,
|
|
151
|
+
size INTEGER NOT NULL,
|
|
152
|
+
tier TEXT NOT NULL,
|
|
153
|
+
content TEXT,
|
|
154
|
+
created_at TEXT NOT NULL,
|
|
155
|
+
expires_at INTEGER NOT NULL
|
|
156
|
+
)`);
|
|
157
|
+
for (const row of rows)
|
|
158
|
+
await db.run(
|
|
159
|
+
`INSERT INTO tool_results VALUES (?, ?, 't', ?, 'sqlite', ?, ?, ?)`,
|
|
160
|
+
[
|
|
161
|
+
row.id,
|
|
162
|
+
row.sessionId,
|
|
163
|
+
new TextEncoder().encode(row.content).byteLength,
|
|
164
|
+
row.content,
|
|
165
|
+
new Date(this.nowMs).toISOString(),
|
|
166
|
+
this.nowMs + 60 * 60 * 1000,
|
|
167
|
+
],
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Re-open the store over the same database (the next boot). */
|
|
172
|
+
async reboot(): Promise<void> {
|
|
173
|
+
this.store = undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async references(): Promise<Array<{ id: string; session_id: string }>> {
|
|
177
|
+
const db = await this.database();
|
|
178
|
+
return db.exec<{ id: string; session_id: string }>(
|
|
179
|
+
`SELECT id, session_id FROM tool_result_sessions ORDER BY id, session_id`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
49
183
|
async setNow(ms: number): Promise<void> {
|
|
50
184
|
this.nowMs = ms;
|
|
51
185
|
}
|