@ixo/oracle-runtime-workers 0.11.1 → 0.13.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 +2 -2
- package/src/core/main-agent.ts +28 -1
- package/src/core/prompt-composer.ts +42 -3
- package/src/do/contracts.ts +28 -0
- package/src/do/run-request.ts +47 -11
- package/src/do/user-oracle-do.ts +15 -25
- package/src/matrix/gateway-do.ts +22 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixo/oracle-runtime-workers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "QiForge oracle runtime for Cloudflare Workers — Durable Object per user (SQLite in WASM over DO storage), Matrix E2EE gateway Durable Object, Hono HTTP/SSE shell.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@composio/core": "0.10.0",
|
|
34
34
|
"@composio/langchain": "0.9.2",
|
|
35
35
|
"@ixo/editor": "6.0.1",
|
|
36
|
-
"@ixo/matrix-bot-workers-sdk": "0.
|
|
36
|
+
"@ixo/matrix-bot-workers-sdk": "0.7.0",
|
|
37
37
|
"@ixo/matrix-crdt": "^1.2.5",
|
|
38
38
|
"@langchain/core": "1.1.48",
|
|
39
39
|
"@langchain/langgraph": "1.3.2",
|
package/src/core/main-agent.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
composePrompt,
|
|
47
47
|
formatTimeContext,
|
|
48
48
|
formatUserPreferences,
|
|
49
|
+
PORTAL_CAPABILITY,
|
|
49
50
|
} from './prompt-composer';
|
|
50
51
|
import { formatByPlugin, type ManifestRegistry } from './registries';
|
|
51
52
|
import {
|
|
@@ -54,7 +55,7 @@ import {
|
|
|
54
55
|
type RunConfig,
|
|
55
56
|
type RuntimeStateInput,
|
|
56
57
|
} from './runtime-context';
|
|
57
|
-
import { MainAgentGraphState } from './state';
|
|
58
|
+
import { MainAgentGraphState, type BrowserToolCall } from './state';
|
|
58
59
|
import { toolEffectOf } from './middlewares/tool-marks';
|
|
59
60
|
import { collectSubAgentsWithFallback } from './sub-agent-fallback';
|
|
60
61
|
import { computeSubAgentToolName } from './subagent-as-tool';
|
|
@@ -97,6 +98,21 @@ function selectByVisibility(
|
|
|
97
98
|
});
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Names of the browser tools the client declared on this request. The array
|
|
103
|
+
* comes straight off the wire, so an entry without a usable name is skipped
|
|
104
|
+
* rather than rendered as "undefined".
|
|
105
|
+
*/
|
|
106
|
+
function browserToolNames(
|
|
107
|
+
browserTools: BrowserToolCall[] | undefined,
|
|
108
|
+
): string[] {
|
|
109
|
+
return (browserTools ?? []).flatMap((entry) =>
|
|
110
|
+
typeof entry?.name === 'string' && entry.name.length > 0
|
|
111
|
+
? [entry.name]
|
|
112
|
+
: [],
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
100
116
|
/**
|
|
101
117
|
* Build a compiled main agent from the runtime's registries plus per-request
|
|
102
118
|
* context. Same pipeline as the Node runtime — tools from the registries
|
|
@@ -472,9 +488,20 @@ export async function createMainAgent(
|
|
|
472
488
|
|
|
473
489
|
const customInstructions = identity.prompt?.customInstructions?.trim() ?? '';
|
|
474
490
|
|
|
491
|
+
// Browser tools never surface through `search_skills` / `list_capabilities`,
|
|
492
|
+
// so the prompt names them per turn and says whether the capability gate
|
|
493
|
+
// still hides them (portal is on-demand by default).
|
|
494
|
+
const browserTools = {
|
|
495
|
+
names: browserToolNames(state.browserTools),
|
|
496
|
+
bound:
|
|
497
|
+
manifestViz.get(PORTAL_CAPABILITY) === 'always' ||
|
|
498
|
+
loadedSet.has(PORTAL_CAPABILITY),
|
|
499
|
+
};
|
|
500
|
+
|
|
475
501
|
const systemPrompt = await composePrompt({
|
|
476
502
|
identity,
|
|
477
503
|
capabilityBlock: tier1.block,
|
|
504
|
+
browserTools,
|
|
478
505
|
customInstructions,
|
|
479
506
|
operationalMode: hooks?.operationalMode ?? DEFAULT_OPERATIONAL_MODE,
|
|
480
507
|
userPreferencesContext: formatUserPreferences(state.userPreferences),
|
|
@@ -76,6 +76,13 @@ export interface ComposePromptInput {
|
|
|
76
76
|
* mandate a discovery flow the model cannot run.
|
|
77
77
|
*/
|
|
78
78
|
capabilityDiscovery?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Browser tools the client declared for this turn (`state.browserTools`),
|
|
81
|
+
* by name, plus whether they are already callable: `bound` is true when the
|
|
82
|
+
* portal plugin is `always` visible or has been loaded on this thread.
|
|
83
|
+
* Omit (or pass empty `names`) on non-Portal turns — nothing renders.
|
|
84
|
+
*/
|
|
85
|
+
browserTools?: { names: string[]; bound: boolean };
|
|
79
86
|
/** User preferences rendered as bullets. Empty when not set. */
|
|
80
87
|
userPreferencesContext: string;
|
|
81
88
|
/** Memory-engine context blocks. */
|
|
@@ -331,13 +338,40 @@ export function buildOracleSection(input: {
|
|
|
331
338
|
return `You are ${oracleName}, an AI agent built on QiForge.`;
|
|
332
339
|
}
|
|
333
340
|
|
|
341
|
+
/** The plugin that binds the client-declared browser tools. */
|
|
342
|
+
export const PORTAL_CAPABILITY = 'portal';
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Render the per-turn "Browser tools this turn" section. Empty string when the
|
|
346
|
+
* client declared no browser tools, so non-Portal turns pay nothing for it.
|
|
347
|
+
* The load line appears only while the tools sit behind the capability gate
|
|
348
|
+
* (portal is `on-demand` and not yet in `loadedPlugins`).
|
|
349
|
+
*/
|
|
350
|
+
function buildBrowserToolsBlock(
|
|
351
|
+
browserTools: ComposePromptInput['browserTools'],
|
|
352
|
+
): string {
|
|
353
|
+
if (!browserTools || browserTools.names.length === 0) return '';
|
|
354
|
+
const lines = [
|
|
355
|
+
'## Browser tools this turn',
|
|
356
|
+
'',
|
|
357
|
+
`The Portal exposed these browser-side tools for this turn (they act on the user's screen): ${browserTools.names.join(', ')}`,
|
|
358
|
+
];
|
|
359
|
+
if (!browserTools.bound) {
|
|
360
|
+
lines.push(
|
|
361
|
+
`They are bound behind the \`${PORTAL_CAPABILITY}\` capability — call \`load_capability({ names: ['${PORTAL_CAPABILITY}'] })\` once before using them.`,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
return lines.join('\n');
|
|
365
|
+
}
|
|
366
|
+
|
|
334
367
|
/**
|
|
335
368
|
* The discovery mandate. Rendered on every turn whose surface can actually
|
|
336
369
|
* grow — i.e. wherever the meta-tools are bound.
|
|
337
370
|
*/
|
|
338
|
-
const DISCOVERY_PRINCIPLE = `- **Search first, build second.** Before doing any non-trivial task, you MUST run discovery before producing the answer yourself.
|
|
339
|
-
1.
|
|
340
|
-
2.
|
|
371
|
+
const DISCOVERY_PRINCIPLE = `- **Search first, build second.** Before doing any non-trivial task, you MUST run discovery before producing the answer yourself. Capabilities live in three lanes, each found differently:
|
|
372
|
+
1. **Packaged skills** — call \`search_skills\` against the user's request whenever a skill could plausibly do the job (generating a file, document, report, or page; calculations; lookups; multi-step workflows).
|
|
373
|
+
2. **Server-side plugins and third-party integrations** (memory, sandbox, email, calendar, web, files, external services) — call \`list_capabilities\` to scan loaded + on-demand plugins, then call \`load_capability({ names: [...] })\` ONCE with everything you need — it accepts an array, so never make multiple \`load_capability\` calls in the same turn.
|
|
374
|
+
3. **Browser tools** — the \`[Portal]\`-prefixed tools already in your tool list. They run on the user's screen inside the Portal: navigate, open a URL or page, create pages/rooms, fill forms, grant access, change what the user sees. Use them for anything that happens on the user's side. They NEVER appear in \`search_skills\` or \`list_capabilities\` results — check your tool list (and the "Browser tools this turn" section when present) directly.
|
|
341
375
|
Do this proactively — even when the task wording doesn't literally match a capability's \`whenToUse\`. Tell the user in ONE short sentence what you're checking ("Checking the skills registry for an invoice generator…") before the search call, not after. If something fits, use it (load + run). If nothing fits after the search, say so in one short sentence and only THEN build from scratch. Reusing a vetted capability is almost always better than reinventing it; silently skipping discovery is the worst failure mode.`;
|
|
342
376
|
|
|
343
377
|
/**
|
|
@@ -357,6 +391,10 @@ const TEMPLATE = `{{{ORACLE_SECTION}}}
|
|
|
357
391
|
{{{CAPABILITY_BLOCK}}}
|
|
358
392
|
|
|
359
393
|
{{/CAPABILITY_BLOCK}}
|
|
394
|
+
{{#BROWSER_TOOLS_BLOCK}}
|
|
395
|
+
{{{BROWSER_TOOLS_BLOCK}}}
|
|
396
|
+
|
|
397
|
+
{{/BROWSER_TOOLS_BLOCK}}
|
|
360
398
|
## Operating principles
|
|
361
399
|
|
|
362
400
|
- The user's current message is your primary instruction. Background context (what you already know about them) is for adapting tone and suggestions, not for overriding their intent.
|
|
@@ -436,6 +474,7 @@ export async function composePrompt(
|
|
|
436
474
|
ORACLE_SECTION: oracleSection,
|
|
437
475
|
CAPABILITIES_NOTE: capabilitiesNote,
|
|
438
476
|
CAPABILITY_BLOCK: input.capabilityBlock,
|
|
477
|
+
BROWSER_TOOLS_BLOCK: buildBrowserToolsBlock(input.browserTools),
|
|
439
478
|
DISCOVERY_PRINCIPLE:
|
|
440
479
|
input.capabilityDiscovery === false ? '' : DISCOVERY_PRINCIPLE,
|
|
441
480
|
CUSTOM_INSTRUCTIONS: input.customInstructions,
|
package/src/do/contracts.ts
CHANGED
|
@@ -73,6 +73,34 @@ export interface OracleWorkerEnv {
|
|
|
73
73
|
MATRIX_HOT_ROOMS?: string;
|
|
74
74
|
/** Cap on events replayed per room after a restart (SDK default 0 = unbounded). */
|
|
75
75
|
MATRIX_BACKFILL_MAX_EVENTS?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Gateway memory guards (`@ixo/matrix-bot-workers-sdk` 0.5+): events per
|
|
78
|
+
* room per `/sync` batch (default 30), the largest `/sync` response held
|
|
79
|
+
* in memory (default 16 MiB; over it the batch is re-requested at timeline
|
|
80
|
+
* limit 1 and the rooms catch up through `/messages`), events per catch-up
|
|
81
|
+
* page (default 100), the largest catch-up page (default 10 MiB; a bigger
|
|
82
|
+
* one is fetched again smaller) and the largest ciphertext the bot will
|
|
83
|
+
* decrypt (default 0 = no cap). A cap of `0` switches that guard off.
|
|
84
|
+
*/
|
|
85
|
+
MATRIX_SYNC_TIMELINE_LIMIT?: string;
|
|
86
|
+
MATRIX_SYNC_BYTES_CAP?: string;
|
|
87
|
+
MATRIX_CATCHUP_PAGE_SIZE?: string;
|
|
88
|
+
MATRIX_PAGE_BYTES_CAP?: string;
|
|
89
|
+
MATRIX_MAX_DECRYPT_BYTES?: string;
|
|
90
|
+
/**
|
|
91
|
+
* `true` restores every room key the account's key backup holds on a
|
|
92
|
+
* device's first start (SDK 0.6+), capped at `..._MAX_KEYS` (default
|
|
93
|
+
* 5,000; a larger backup is skipped and keys stay on demand). Off by
|
|
94
|
+
* default: the gateway reads keys from the backup one session at a time.
|
|
95
|
+
*/
|
|
96
|
+
MATRIX_BACKUP_BULK_RESTORE?: string;
|
|
97
|
+
MATRIX_BACKUP_BULK_RESTORE_MAX_KEYS?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Keep-alive fuse (SDK 0.7+): while the gateway object works, its alarm is
|
|
100
|
+
* re-armed this far ahead so a host drain brings it back within the fuse
|
|
101
|
+
* (default 10 s, floor 2 s, `0` = off).
|
|
102
|
+
*/
|
|
103
|
+
MATRIX_KEEPALIVE_FUSE_MS?: string;
|
|
76
104
|
|
|
77
105
|
// --- storage ------------------------------------------------------------
|
|
78
106
|
/**
|
package/src/do/run-request.ts
CHANGED
|
@@ -1,39 +1,75 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a run row stores so a later incarnation of the user object can
|
|
3
3
|
* rebuild the attempt: the `TurnRequest` (minus attachments — they are in
|
|
4
|
-
* the checkpointed first message)
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* the checkpointed first message), the client-declared tool surface of the
|
|
5
|
+
* turn (browser tools + AG-UI actions: the portal and agui plugins bind
|
|
6
|
+
* them at build time, so an attempt cannot recover them from anywhere
|
|
7
|
+
* else), and the stream options the frame producer needs.
|
|
8
8
|
*/
|
|
9
9
|
import type { RunSummary, TurnRequest } from './contracts';
|
|
10
10
|
import type { RunRecord } from './run-store';
|
|
11
|
+
import type { TurnBody } from './turn-body';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
/** The client-declared surface of a turn, as the body carries it. */
|
|
14
|
+
export type ClientSurfaceBody = Pick<TurnBody, 'tools' | 'agActions'>;
|
|
15
|
+
|
|
16
|
+
export interface StoredRunRequest extends ClientSurfaceBody {
|
|
13
17
|
turn: Omit<TurnRequest, 'attachments'> & {
|
|
14
18
|
attachments?: TurnRequest['attachments'];
|
|
15
19
|
};
|
|
16
20
|
timezone?: string;
|
|
17
|
-
/** Names the client declared as AG-UI actions (rendered as `action_call`). */
|
|
18
|
-
agActionNames?: string[];
|
|
19
21
|
stream?: boolean;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export function storedRunRequest(
|
|
23
25
|
req: TurnRequest,
|
|
24
|
-
extra: Pick<
|
|
26
|
+
extra: Pick<
|
|
27
|
+
StoredRunRequest,
|
|
28
|
+
'timezone' | 'tools' | 'agActions' | 'stream'
|
|
29
|
+
> = {},
|
|
25
30
|
): StoredRunRequest {
|
|
26
31
|
const { attachments, ...turn } = req;
|
|
27
32
|
return {
|
|
28
33
|
turn: { ...turn, ...(attachments?.length ? { attachments } : {}) },
|
|
29
34
|
...(extra.timezone ? { timezone: extra.timezone } : {}),
|
|
30
|
-
...(extra.
|
|
31
|
-
|
|
32
|
-
: {}),
|
|
35
|
+
...(extra.tools?.length ? { tools: extra.tools } : {}),
|
|
36
|
+
...(extra.agActions?.length ? { agActions: extra.agActions } : {}),
|
|
33
37
|
...(extra.stream !== undefined ? { stream: extra.stream } : {}),
|
|
34
38
|
};
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
/** The surface as the graph state stores it (`browserTools` / `agActions`). */
|
|
42
|
+
export interface ClientSurfaceState {
|
|
43
|
+
browserTools?: TurnBody['tools'];
|
|
44
|
+
agActions?: TurnBody['agActions'];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The client-declared surface of one attempt, by the Node agent-builder's
|
|
49
|
+
* rule: what the body carries, else what the thread's checkpoint holds.
|
|
50
|
+
*
|
|
51
|
+
* `state` feeds the agent build (the portal / agui plugins read it from
|
|
52
|
+
* `history.state`, so it must be complete). `input` is what the graph input
|
|
53
|
+
* writes back to the checkpoint: only what THIS body declared, so a turn
|
|
54
|
+
* without a catalogue (a resumed attempt, a Matrix turn, a task run) never
|
|
55
|
+
* erases the one the client sent earlier.
|
|
56
|
+
*/
|
|
57
|
+
export function clientSurfaceFor(
|
|
58
|
+
body: ClientSurfaceBody,
|
|
59
|
+
prior: ClientSurfaceState,
|
|
60
|
+
): { state: Required<ClientSurfaceState>; input: ClientSurfaceState } {
|
|
61
|
+
return {
|
|
62
|
+
state: {
|
|
63
|
+
browserTools: body.tools ?? prior.browserTools ?? [],
|
|
64
|
+
agActions: body.agActions ?? prior.agActions ?? [],
|
|
65
|
+
},
|
|
66
|
+
input: {
|
|
67
|
+
...(body.tools !== undefined ? { browserTools: body.tools } : {}),
|
|
68
|
+
...(body.agActions !== undefined ? { agActions: body.agActions } : {}),
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
37
73
|
export function runSummaryOf(record: RunRecord): RunSummary {
|
|
38
74
|
return {
|
|
39
75
|
runId: record.runId,
|
package/src/do/user-oracle-do.ts
CHANGED
|
@@ -200,6 +200,7 @@ import {
|
|
|
200
200
|
} from './run-store';
|
|
201
201
|
import {
|
|
202
202
|
runSummaryOf,
|
|
203
|
+
clientSurfaceFor,
|
|
203
204
|
storedRunRequest,
|
|
204
205
|
type StoredRunRequest,
|
|
205
206
|
} from './run-request';
|
|
@@ -3011,7 +3012,6 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3011
3012
|
// (docs/plans/durable-runs.md). The response is one subscriber of the
|
|
3012
3013
|
// run's buffer; the run itself outlives it.
|
|
3013
3014
|
this.replayToRoom(req, body.message, 'user');
|
|
3014
|
-
const agActionNames = (body.agActions ?? []).map((a) => a.name);
|
|
3015
3015
|
const { live, queued } = await this.runs!.begin({
|
|
3016
3016
|
runId: crypto.randomUUID(),
|
|
3017
3017
|
sessionId,
|
|
@@ -3020,7 +3020,8 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3020
3020
|
request: JSON.stringify(
|
|
3021
3021
|
storedRunRequest(req, {
|
|
3022
3022
|
timezone: body.timezone,
|
|
3023
|
-
|
|
3023
|
+
tools: body.tools,
|
|
3024
|
+
agActions: body.agActions,
|
|
3024
3025
|
stream: true,
|
|
3025
3026
|
}),
|
|
3026
3027
|
),
|
|
@@ -3138,6 +3139,8 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3138
3139
|
message: req.message,
|
|
3139
3140
|
timezone: stored.timezone ?? req.identity.timezone,
|
|
3140
3141
|
model: req.model,
|
|
3142
|
+
tools: stored.tools,
|
|
3143
|
+
agActions: stored.agActions,
|
|
3141
3144
|
...(resumed ? {} : { attachments: req.attachments }),
|
|
3142
3145
|
},
|
|
3143
3146
|
{
|
|
@@ -3178,7 +3181,7 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3178
3181
|
abortController: live.abort,
|
|
3179
3182
|
mirror: (eventName, payload) =>
|
|
3180
3183
|
this.events.emitToTaps(eventName, payload),
|
|
3181
|
-
agActionNames: new Set(stored.
|
|
3184
|
+
agActionNames: new Set((stored.agActions ?? []).map((a) => a.name)),
|
|
3182
3185
|
byoProvider,
|
|
3183
3186
|
toolOutputCapChars,
|
|
3184
3187
|
messageIdOf: () => lastAiMessageId(capture),
|
|
@@ -3858,20 +3861,9 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3858
3861
|
const hostRoomTitle = opts.hooks?.getRoomTitle;
|
|
3859
3862
|
const hostSafetyModel = opts.hooks?.safetyModel;
|
|
3860
3863
|
|
|
3861
|
-
//
|
|
3862
|
-
// checkpointed
|
|
3863
|
-
const
|
|
3864
|
-
browserTools?: TurnBody['tools'];
|
|
3865
|
-
agActions?: TurnBody['agActions'];
|
|
3866
|
-
};
|
|
3867
|
-
const browserTools =
|
|
3868
|
-
body.tools ??
|
|
3869
|
-
(run.resumed ? priorSurface.browserTools : undefined) ??
|
|
3870
|
-
[];
|
|
3871
|
-
const agActions =
|
|
3872
|
-
body.agActions ??
|
|
3873
|
-
(run.resumed ? priorSurface.agActions : undefined) ??
|
|
3874
|
-
[];
|
|
3864
|
+
// The client-declared tool surface: this body's, else the thread's
|
|
3865
|
+
// checkpointed one (run-request.ts).
|
|
3866
|
+
const surface = clientSurfaceFor(body, priorState);
|
|
3875
3867
|
|
|
3876
3868
|
// Write-ahead tool marks + the resume policy (tool-marks.ts). The
|
|
3877
3869
|
// effect map is filled from the build below; the closure reads it at
|
|
@@ -3965,12 +3957,11 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
3965
3957
|
...priorState,
|
|
3966
3958
|
userPreferences,
|
|
3967
3959
|
...metadataBuildState(meta, priorMeta),
|
|
3968
|
-
// The client-declared surface
|
|
3969
|
-
//
|
|
3970
|
-
//
|
|
3971
|
-
//
|
|
3972
|
-
|
|
3973
|
-
agActions,
|
|
3960
|
+
// The client-declared surface: the portal and AG-UI plugins turn
|
|
3961
|
+
// these into tools at build time (`getRequestTools` reads
|
|
3962
|
+
// `history.state`), so they must be here and not only in the
|
|
3963
|
+
// graph input.
|
|
3964
|
+
...surface.state,
|
|
3974
3965
|
},
|
|
3975
3966
|
checkpointer: saver,
|
|
3976
3967
|
abortSignal: abortController.signal,
|
|
@@ -4036,8 +4027,7 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
4036
4027
|
config: { did: req.identity.userDid },
|
|
4037
4028
|
client: req.client,
|
|
4038
4029
|
...metadataGraphInput(meta, priorMeta),
|
|
4039
|
-
|
|
4040
|
-
agActions,
|
|
4030
|
+
...surface.input,
|
|
4041
4031
|
};
|
|
4042
4032
|
// LangSmith: metadata is attached unconditionally (inert without a
|
|
4043
4033
|
// tracer); the explicit tracer only when this turn is traced (global
|
package/src/matrix/gateway-do.ts
CHANGED
|
@@ -310,6 +310,23 @@ export class MatrixGatewayDO
|
|
|
310
310
|
env.MATRIX_RECYCLE_AFTER_SENDS ?? String(DEFAULT_RECYCLE_AFTER_SENDS),
|
|
311
311
|
MATRIX_HOT_ROOMS: env.MATRIX_HOT_ROOMS,
|
|
312
312
|
MATRIX_BACKFILL_MAX_EVENTS: env.MATRIX_BACKFILL_MAX_EVENTS,
|
|
313
|
+
// Memory guards (SDK 0.5+): byte-capped /sync and catch-up pages and
|
|
314
|
+
// an optional ciphertext cap on decryption. Unset = the SDK defaults.
|
|
315
|
+
MATRIX_SYNC_TIMELINE_LIMIT: env.MATRIX_SYNC_TIMELINE_LIMIT,
|
|
316
|
+
MATRIX_SYNC_BYTES_CAP: env.MATRIX_SYNC_BYTES_CAP,
|
|
317
|
+
MATRIX_CATCHUP_PAGE_SIZE: env.MATRIX_CATCHUP_PAGE_SIZE,
|
|
318
|
+
MATRIX_PAGE_BYTES_CAP: env.MATRIX_PAGE_BYTES_CAP,
|
|
319
|
+
MATRIX_MAX_DECRYPT_BYTES: env.MATRIX_MAX_DECRYPT_BYTES,
|
|
320
|
+
// Opt-in bulk restore of the key backup on a device's first start
|
|
321
|
+
// (SDK 0.6+); off by default — the gateway fetches room keys from the
|
|
322
|
+
// backup on demand, one session at a time.
|
|
323
|
+
MATRIX_BACKUP_BULK_RESTORE: env.MATRIX_BACKUP_BULK_RESTORE,
|
|
324
|
+
MATRIX_BACKUP_BULK_RESTORE_MAX_KEYS:
|
|
325
|
+
env.MATRIX_BACKUP_BULK_RESTORE_MAX_KEYS,
|
|
326
|
+
// Keep-alive fuse (SDK 0.7+): the alarm is re-armed a few seconds
|
|
327
|
+
// ahead while the object works, so a host drain brings it back within
|
|
328
|
+
// the fuse instead of at the next cycle.
|
|
329
|
+
MATRIX_KEEPALIVE_FUSE_MS: env.MATRIX_KEEPALIVE_FUSE_MS,
|
|
313
330
|
LOG_LEVEL: env.LOG_LEVEL,
|
|
314
331
|
});
|
|
315
332
|
return {
|
|
@@ -685,14 +702,13 @@ export class MatrixGatewayDO
|
|
|
685
702
|
}
|
|
686
703
|
|
|
687
704
|
/**
|
|
688
|
-
*
|
|
689
|
-
*
|
|
705
|
+
* After every successful start of this instance — a request, the
|
|
706
|
+
* keep-alive alarm bringing it back after a reset, a device rotation
|
|
707
|
+
* (the SDK's `onStarted` hook, detached from the caller). That is the
|
|
690
708
|
* moment to re-dispatch the turns a previous incarnation left unfinished.
|
|
691
709
|
*/
|
|
692
|
-
override async
|
|
693
|
-
|
|
694
|
-
if (result.started) this.scheduleInboxReplay();
|
|
695
|
-
return result;
|
|
710
|
+
protected override async onStarted(_result: StartResult): Promise<void> {
|
|
711
|
+
this.scheduleInboxReplay();
|
|
696
712
|
}
|
|
697
713
|
|
|
698
714
|
private scheduleInboxReplay(): void {
|