@mcp-b/do-runtime 0.6.0 → 0.7.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/CHANGELOG.md +24 -0
- package/README.md +257 -67
- package/dist/backends/sqlite-wasm.d.ts +6 -1
- package/dist/backends/sqlite-wasm.js +70 -5
- package/dist/backends/sqlite-wasm.js.map +1 -1
- package/dist/browser/alarm-coordinator.js +159 -0
- package/dist/browser/alarm-coordinator.js.map +1 -0
- package/dist/browser/message-port-websocket.js +243 -0
- package/dist/browser/message-port-websocket.js.map +1 -0
- package/dist/browser/offscreen-document.js +33 -0
- package/dist/browser/offscreen-document.js.map +1 -0
- package/dist/browser.js +55 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunks/web-socket-PWFZxlBg.js +2573 -0
- package/dist/chunks/web-socket-PWFZxlBg.js.map +1 -0
- package/dist/gate.js +11 -7
- package/dist/gate.js.map +1 -1
- package/dist/index.js +152 -2668
- package/dist/index.js.map +1 -1
- package/dist/src/api/web-socket.d.ts +6 -0
- package/dist/src/browser/alarm-coordinator.d.ts +46 -0
- package/dist/src/browser/message-port-websocket.d.ts +62 -0
- package/dist/src/browser/offscreen-document.d.ts +17 -0
- package/dist/src/browser.d.ts +15 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/server/hibernation-mirror.d.ts +18 -0
- package/docs/assets/browser-agent-runtime.png +0 -0
- package/docs/assets/extension-agent-runtime.svg +178 -0
- package/docs/migrations.md +235 -0
- package/package.json +27 -3
|
@@ -15,12 +15,18 @@ export interface RawWebSocket {
|
|
|
15
15
|
binaryType?: string;
|
|
16
16
|
}
|
|
17
17
|
export interface HibernationHost {
|
|
18
|
+
/** Retained auto-response configuration, restored before actor construction. */
|
|
19
|
+
readonly autoResponsePair?: {
|
|
20
|
+
request: string;
|
|
21
|
+
response: string;
|
|
22
|
+
} | null;
|
|
18
23
|
accepted(socket: RawWebSocket, tags: readonly string[]): void;
|
|
19
24
|
attachment(socket: RawWebSocket, bytes: Uint8Array | null): void;
|
|
20
25
|
autoResponse(pair: {
|
|
21
26
|
request: string;
|
|
22
27
|
response: string;
|
|
23
28
|
} | null): void;
|
|
29
|
+
autoResponseTimestamp?(socket: RawWebSocket, timestamp: number): void;
|
|
24
30
|
closed(socket: RawWebSocket): void;
|
|
25
31
|
}
|
|
26
32
|
export type RehydratedWebSocket = {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
type LooseRecord = Record<string, unknown>;
|
|
2
|
+
export type BrowserAlarmProjection = {
|
|
3
|
+
readonly generation: number;
|
|
4
|
+
readonly when: number | null;
|
|
5
|
+
} & LooseRecord;
|
|
6
|
+
export type BrowserAlarmDelivery = {
|
|
7
|
+
readonly generation: number;
|
|
8
|
+
readonly retryCount: number;
|
|
9
|
+
readonly wake: number;
|
|
10
|
+
} & LooseRecord;
|
|
11
|
+
export type BrowserAlarmTransportJournal = {
|
|
12
|
+
readonly delivery: BrowserAlarmDelivery | null;
|
|
13
|
+
readonly projection: BrowserAlarmProjection;
|
|
14
|
+
} & LooseRecord;
|
|
15
|
+
export interface BrowserAlarmTransportStore {
|
|
16
|
+
load(): Promise<BrowserAlarmTransportJournal | null>;
|
|
17
|
+
save(journal: BrowserAlarmTransportJournal): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export interface BrowserPhysicalAlarm {
|
|
20
|
+
clear(): Promise<void>;
|
|
21
|
+
create(when: number): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export type BrowserAlarmCoordinatorOptions = {
|
|
24
|
+
deliver(scheduledTime: number): Promise<BrowserAlarmProjection>;
|
|
25
|
+
now?: () => number;
|
|
26
|
+
physical: BrowserPhysicalAlarm;
|
|
27
|
+
store: BrowserAlarmTransportStore;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Projects a logical namespace alarm onto one crash-prone browser alarm.
|
|
31
|
+
*
|
|
32
|
+
* `AlarmScheduler` remains authoritative for actor delivery and retries. This
|
|
33
|
+
* coordinator journals the physical hop so a browser background worker can be
|
|
34
|
+
* stopped between any two awaited operations without losing the next wake.
|
|
35
|
+
*/
|
|
36
|
+
export declare class BrowserAlarmCoordinator {
|
|
37
|
+
#private;
|
|
38
|
+
private readonly options;
|
|
39
|
+
constructor(options: BrowserAlarmCoordinatorOptions);
|
|
40
|
+
project(projection: BrowserAlarmProjection): Promise<void>;
|
|
41
|
+
/** Repairs an acknowledged physical operation interrupted by browser suspension. */
|
|
42
|
+
reconcile(): Promise<void>;
|
|
43
|
+
fire(scheduledTime: number): Promise<BrowserAlarmProjection | null>;
|
|
44
|
+
}
|
|
45
|
+
export declare function parseBrowserAlarmTransportJournal(value: unknown): BrowserAlarmTransportJournal | null;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { RawWebSocket } from "../api/web-socket.js";
|
|
2
|
+
import type { UpgradeWebSocket } from "../browser.js";
|
|
3
|
+
export type MessagePortWebSocketData = string | ArrayBuffer | ArrayBufferView;
|
|
4
|
+
export type MessagePortWebSocketWireMessage = {
|
|
5
|
+
readonly type: "message";
|
|
6
|
+
readonly data: MessagePortWebSocketData;
|
|
7
|
+
} | {
|
|
8
|
+
readonly type: "close";
|
|
9
|
+
readonly code: number;
|
|
10
|
+
readonly reason: string;
|
|
11
|
+
};
|
|
12
|
+
/** One WebSocket-shaped endpoint backed by one dedicated MessagePort. */
|
|
13
|
+
export declare class MessagePortWebSocket extends EventTarget implements RawWebSocket {
|
|
14
|
+
#private;
|
|
15
|
+
readonly url: string;
|
|
16
|
+
private readonly port;
|
|
17
|
+
static readonly CONNECTING = 0;
|
|
18
|
+
static readonly OPEN = 1;
|
|
19
|
+
static readonly CLOSING = 2;
|
|
20
|
+
static readonly CLOSED = 3;
|
|
21
|
+
readonly CONNECTING = 0;
|
|
22
|
+
readonly OPEN = 1;
|
|
23
|
+
readonly CLOSING = 2;
|
|
24
|
+
readonly CLOSED = 3;
|
|
25
|
+
binaryType: BinaryType;
|
|
26
|
+
bufferedAmount: number;
|
|
27
|
+
extensions: string;
|
|
28
|
+
protocol: string;
|
|
29
|
+
readyState: number;
|
|
30
|
+
onopen: ((event: Event) => void) | null;
|
|
31
|
+
onmessage: ((event: MessageEvent<MessagePortWebSocketData>) => void) | null;
|
|
32
|
+
onclose: ((event: CloseEvent) => void) | null;
|
|
33
|
+
onerror: ((event: Event) => void) | null;
|
|
34
|
+
constructor(url: string, port: MessagePort, autoOpen?: boolean);
|
|
35
|
+
send(data: MessagePortWebSocketData): void;
|
|
36
|
+
close(code?: number, reason?: string): void;
|
|
37
|
+
accept(_options?: {
|
|
38
|
+
allowHalfOpen?: boolean;
|
|
39
|
+
}): void;
|
|
40
|
+
open(): void;
|
|
41
|
+
/** End a socket whose physical host disappeared without notifying that host. */
|
|
42
|
+
protected disconnect(code?: number, reason?: string): void;
|
|
43
|
+
}
|
|
44
|
+
export interface MessagePortWebSocket {
|
|
45
|
+
addEventListener(type: "message", listener: (event: MessageEvent<MessagePortWebSocketData>) => void, options?: boolean | AddEventListenerOptions): void;
|
|
46
|
+
addEventListener(type: "close", listener: (event: CloseEvent) => void, options?: boolean | AddEventListenerOptions): void;
|
|
47
|
+
addEventListener(type: "error" | "open", listener: (event: Event) => void, options?: boolean | AddEventListenerOptions): void;
|
|
48
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
|
|
49
|
+
}
|
|
50
|
+
/** Connect two accepted socket endpoints and open the MessagePort side. */
|
|
51
|
+
export declare function bridgeWebSocket(socket: UpgradeWebSocket, bridge: MessagePortWebSocket): void;
|
|
52
|
+
export type MessagePortWebSocketConstructor = {
|
|
53
|
+
new (url: string | URL, protocols?: string | string[]): MessagePortWebSocket;
|
|
54
|
+
readonly CONNECTING: 0;
|
|
55
|
+
readonly OPEN: 1;
|
|
56
|
+
readonly CLOSING: 2;
|
|
57
|
+
readonly CLOSED: 3;
|
|
58
|
+
};
|
|
59
|
+
/** A browser WebSocket constructor whose sockets cross one broker MessagePort. */
|
|
60
|
+
export declare function createMessagePortWebSocketConstructor(port: MessagePort): MessagePortWebSocketConstructor;
|
|
61
|
+
/** Serve brokered MessagePort sockets from real in-worker socket endpoints. */
|
|
62
|
+
export declare function serveMessagePortWebSockets(port: MessagePort, connect: (url: string) => Promise<UpgradeWebSocket>): () => void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type OffscreenDocumentAdapter = {
|
|
2
|
+
exists(): Promise<boolean>;
|
|
3
|
+
create(): Promise<void>;
|
|
4
|
+
close(): Promise<void>;
|
|
5
|
+
isOccupiedError(error: unknown): boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Keeps one browser offscreen document alive across concurrent callers and a
|
|
9
|
+
* stale, unlisted document slot. Readiness and application policy stay with
|
|
10
|
+
* the embedding host.
|
|
11
|
+
*/
|
|
12
|
+
export declare class OffscreenDocumentCoordinator {
|
|
13
|
+
#private;
|
|
14
|
+
private readonly adapter;
|
|
15
|
+
constructor(adapter: OffscreenDocumentAdapter);
|
|
16
|
+
ensure(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type RawWebSocket } from "./api/web-socket.js";
|
|
2
|
+
export type UpgradeWebSocket = EventTarget & RawWebSocket & {
|
|
3
|
+
accept(): void;
|
|
4
|
+
readonly readyState: number;
|
|
5
|
+
};
|
|
6
|
+
type CloneableRequest = {
|
|
7
|
+
readonly headers: Headers;
|
|
8
|
+
clone(): CloneableRequest;
|
|
9
|
+
};
|
|
10
|
+
/** Install the Request/Response half of browser-hosted WebSocket upgrades. */
|
|
11
|
+
export declare function installWebSocketUpgradeGlobals(): void;
|
|
12
|
+
export declare function upgradeWebSocket(response: Response): UpgradeWebSocket | undefined;
|
|
13
|
+
/** Preserve the upgrade signal across browser `Request.clone()` calls. */
|
|
14
|
+
export declare function withWebSocketUpgrade<T extends CloneableRequest>(request: T): T;
|
|
15
|
+
export {};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export type { Module as SourceModule, ModuleContent, ModulesSource, WorkerSource
|
|
|
53
53
|
*/
|
|
54
54
|
export type { ActorContainer, ActorContainerOptions, ActorEntry, ActorPorts, HibernationHost, FacetHandle, FacetHost, FacetId, FacetStartRequest, FacetTree, } from "./server/actor-container.js";
|
|
55
55
|
export { createActorContainer, FACET_ALARM_UNIMPLEMENTED_MESSAGE, noFacets, } from "./server/actor-container.js";
|
|
56
|
+
export type { HibernationAutoResponse } from "./server/hibernation-mirror.js";
|
|
57
|
+
export { HibernationMirror } from "./server/hibernation-mirror.js";
|
|
56
58
|
export type { ActorChannelFactory, GlobalActorRequest } from "./api/actor.js";
|
|
57
59
|
export { createDurableObjectNamespace } from "./server/actor-namespace.js";
|
|
58
60
|
export { ACTOR_CLASS_SERIALIZATION_UNIMPLEMENTED_MESSAGE } from "./api/actor.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HibernationHost, RawWebSocket, RehydratedWebSocket } from "../api/web-socket.js";
|
|
2
|
+
export type HibernationAutoResponse = {
|
|
3
|
+
request: string;
|
|
4
|
+
response: string;
|
|
5
|
+
};
|
|
6
|
+
/** In-memory socket state shared by embedders that replace live actor containers. */
|
|
7
|
+
export declare class HibernationMirror implements HibernationHost {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(rehydrated?: readonly RehydratedWebSocket[], autoResponsePair?: HibernationAutoResponse | null);
|
|
10
|
+
get autoResponsePair(): HibernationAutoResponse | null;
|
|
11
|
+
accepted(socket: RawWebSocket, tags: readonly string[]): void;
|
|
12
|
+
attachment(socket: RawWebSocket, bytes: Uint8Array | null): void;
|
|
13
|
+
autoResponse(pair: HibernationAutoResponse | null): void;
|
|
14
|
+
/** Also called by a host that answers a ping while no actor container is live. */
|
|
15
|
+
autoResponseTimestamp(socket: RawWebSocket, timestamp: number): void;
|
|
16
|
+
closed(socket: RawWebSocket): void;
|
|
17
|
+
snapshot(): RehydratedWebSocket[];
|
|
18
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="760" viewBox="0 0 1400 760" role="img" aria-labelledby="title description">
|
|
2
|
+
<title id="title">A local agent running inside a browser extension</title>
|
|
3
|
+
<desc id="description">A browser window with an extension popup open over an abstract, persistent agent workspace.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="backdrop" x1="0" y1="0" x2="1" y2="1">
|
|
6
|
+
<stop offset="0" stop-color="#080b16" />
|
|
7
|
+
<stop offset="0.52" stop-color="#11162a" />
|
|
8
|
+
<stop offset="1" stop-color="#0a101d" />
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<linearGradient id="viewport" x1="0" y1="0" x2="1" y2="1">
|
|
11
|
+
<stop offset="0" stop-color="#12182d" />
|
|
12
|
+
<stop offset="0.48" stop-color="#0e1830" />
|
|
13
|
+
<stop offset="1" stop-color="#071622" />
|
|
14
|
+
</linearGradient>
|
|
15
|
+
<radialGradient id="violet-haze" cx="0" cy="0" r="1" gradientTransform="translate(491 391) rotate(20) scale(365 290)" gradientUnits="userSpaceOnUse">
|
|
16
|
+
<stop stop-color="#7c3aed" stop-opacity="0.34" />
|
|
17
|
+
<stop offset="0.55" stop-color="#4f46e5" stop-opacity="0.11" />
|
|
18
|
+
<stop offset="1" stop-color="#4f46e5" stop-opacity="0" />
|
|
19
|
+
</radialGradient>
|
|
20
|
+
<radialGradient id="teal-haze" cx="0" cy="0" r="1" gradientTransform="translate(745 546) rotate(-14) scale(414 236)" gradientUnits="userSpaceOnUse">
|
|
21
|
+
<stop stop-color="#2dd4bf" stop-opacity="0.24" />
|
|
22
|
+
<stop offset="1" stop-color="#2dd4bf" stop-opacity="0" />
|
|
23
|
+
</radialGradient>
|
|
24
|
+
<linearGradient id="agent" x1="0" y1="0" x2="1" y2="1">
|
|
25
|
+
<stop stop-color="#a78bfa" />
|
|
26
|
+
<stop offset="0.5" stop-color="#6366f1" />
|
|
27
|
+
<stop offset="1" stop-color="#2563eb" />
|
|
28
|
+
</linearGradient>
|
|
29
|
+
<linearGradient id="thread" x1="0" y1="0" x2="1" y2="0">
|
|
30
|
+
<stop stop-color="#22d3ee" stop-opacity="0" />
|
|
31
|
+
<stop offset="0.28" stop-color="#2dd4bf" />
|
|
32
|
+
<stop offset="0.7" stop-color="#60a5fa" />
|
|
33
|
+
<stop offset="1" stop-color="#818cf8" stop-opacity="0" />
|
|
34
|
+
</linearGradient>
|
|
35
|
+
<linearGradient id="popup" x1="0" y1="0" x2="0" y2="1">
|
|
36
|
+
<stop stop-color="#ffffff" />
|
|
37
|
+
<stop offset="1" stop-color="#f5f7fb" />
|
|
38
|
+
</linearGradient>
|
|
39
|
+
<pattern id="grid" width="34" height="34" patternUnits="userSpaceOnUse">
|
|
40
|
+
<path d="M34 0H0V34" fill="none" stroke="#94a3b8" stroke-opacity="0.075" />
|
|
41
|
+
</pattern>
|
|
42
|
+
<clipPath id="window-clip">
|
|
43
|
+
<rect x="100" y="62" width="1200" height="636" rx="28" />
|
|
44
|
+
</clipPath>
|
|
45
|
+
<filter id="window-shadow" x="-20%" y="-20%" width="140%" height="155%">
|
|
46
|
+
<feDropShadow dx="0" dy="30" stdDeviation="34" flood-color="#000000" flood-opacity="0.42" />
|
|
47
|
+
</filter>
|
|
48
|
+
<filter id="popup-shadow" x="-35%" y="-25%" width="170%" height="170%">
|
|
49
|
+
<feDropShadow dx="0" dy="22" stdDeviation="24" flood-color="#020617" flood-opacity="0.38" />
|
|
50
|
+
</filter>
|
|
51
|
+
<filter id="orb-glow" x="-100%" y="-100%" width="300%" height="300%">
|
|
52
|
+
<feGaussianBlur stdDeviation="18" result="blur" />
|
|
53
|
+
<feMerge>
|
|
54
|
+
<feMergeNode in="blur" />
|
|
55
|
+
<feMergeNode in="SourceGraphic" />
|
|
56
|
+
</feMerge>
|
|
57
|
+
</filter>
|
|
58
|
+
<filter id="line-glow" x="-20%" y="-250%" width="140%" height="600%">
|
|
59
|
+
<feGaussianBlur stdDeviation="6" result="blur" />
|
|
60
|
+
<feMerge>
|
|
61
|
+
<feMergeNode in="blur" />
|
|
62
|
+
<feMergeNode in="SourceGraphic" />
|
|
63
|
+
</feMerge>
|
|
64
|
+
</filter>
|
|
65
|
+
</defs>
|
|
66
|
+
|
|
67
|
+
<rect width="1400" height="760" rx="32" fill="url(#backdrop)" />
|
|
68
|
+
<circle cx="170" cy="126" r="230" fill="#4f46e5" fill-opacity="0.1" />
|
|
69
|
+
<circle cx="1254" cy="654" r="280" fill="#0d9488" fill-opacity="0.08" />
|
|
70
|
+
|
|
71
|
+
<g filter="url(#window-shadow)">
|
|
72
|
+
<rect x="100" y="62" width="1200" height="636" rx="28" fill="#f8fafc" />
|
|
73
|
+
</g>
|
|
74
|
+
|
|
75
|
+
<g clip-path="url(#window-clip)">
|
|
76
|
+
<!-- Browser chrome -->
|
|
77
|
+
<rect x="100" y="62" width="1200" height="94" fill="#eef1f6" />
|
|
78
|
+
<circle cx="132" cy="92" r="6" fill="#ef6a67" />
|
|
79
|
+
<circle cx="154" cy="92" r="6" fill="#e6b84a" />
|
|
80
|
+
<circle cx="176" cy="92" r="6" fill="#58b96f" />
|
|
81
|
+
|
|
82
|
+
<path d="M226 75H456A15 15 0 0 1 471 90V113H211V90A15 15 0 0 1 226 75Z" fill="#ffffff" />
|
|
83
|
+
<circle cx="238" cy="94" r="7" fill="#6d5dfc" />
|
|
84
|
+
<rect x="255" y="89" width="108" height="10" rx="5" fill="#cbd2df" />
|
|
85
|
+
<path d="M443 89 451 97M451 89 443 97" stroke="#9ca7b9" stroke-width="1.6" stroke-linecap="round" />
|
|
86
|
+
|
|
87
|
+
<rect x="100" y="113" width="1200" height="43" fill="#ffffff" />
|
|
88
|
+
<path d="M132 134H148M132 134 139 127M132 134 139 141" fill="none" stroke="#68758b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
89
|
+
<path d="M175 134H191M191 134 184 127M191 134 184 141" fill="none" stroke="#c2c9d4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
90
|
+
<path d="M219 128A9 9 0 1 0 222 140M219 128 225 127 224 133" fill="none" stroke="#68758b" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
91
|
+
<rect x="248" y="120" width="770" height="28" rx="14" fill="#eef1f5" />
|
|
92
|
+
<path d="M270 131V128A4 4 0 0 1 278 128V131M268 131H280V140H268Z" fill="none" stroke="#8d98aa" stroke-width="1.25" stroke-linejoin="round" />
|
|
93
|
+
<rect x="292" y="130" width="155" height="8" rx="4" fill="#c9d0dc" />
|
|
94
|
+
|
|
95
|
+
<!-- The highlighted extension button anchors the popup to the browser. -->
|
|
96
|
+
<circle cx="1128" cy="134" r="18" fill="#ece9ff" />
|
|
97
|
+
<path d="M1122 125H1128V130A4 4 0 1 1 1134 134V143H1125V139A4 4 0 1 1 1122 133Z" fill="#6857e5" />
|
|
98
|
+
<circle cx="1180" cy="134" r="3" fill="#68758b" />
|
|
99
|
+
<circle cx="1191" cy="134" r="3" fill="#68758b" />
|
|
100
|
+
<circle cx="1202" cy="134" r="3" fill="#68758b" />
|
|
101
|
+
|
|
102
|
+
<!-- An ordinary web page, kept intentionally abstract. -->
|
|
103
|
+
<rect x="100" y="156" width="1200" height="542" fill="url(#viewport)" />
|
|
104
|
+
<rect x="100" y="156" width="1200" height="542" fill="url(#grid)" />
|
|
105
|
+
<rect x="100" y="156" width="1200" height="542" fill="url(#violet-haze)" />
|
|
106
|
+
<rect x="100" y="156" width="1200" height="542" fill="url(#teal-haze)" />
|
|
107
|
+
|
|
108
|
+
<rect x="136" y="193" width="52" height="468" rx="18" fill="#151d33" stroke="#ffffff" stroke-opacity="0.07" />
|
|
109
|
+
<circle cx="162" cy="222" r="10" fill="#6d5dfc" />
|
|
110
|
+
<circle cx="162" cy="282" r="6" fill="#68758b" />
|
|
111
|
+
<circle cx="162" cy="323" r="6" fill="#68758b" />
|
|
112
|
+
<circle cx="162" cy="364" r="6" fill="#68758b" />
|
|
113
|
+
<rect x="154" y="619" width="16" height="16" rx="5" fill="#29334c" />
|
|
114
|
+
|
|
115
|
+
<rect x="226" y="201" width="228" height="12" rx="6" fill="#dbe4f4" fill-opacity="0.64" />
|
|
116
|
+
<rect x="226" y="229" width="138" height="8" rx="4" fill="#9aa9c5" fill-opacity="0.28" />
|
|
117
|
+
|
|
118
|
+
<!-- One continuous thread remains while the surrounding surface fades. -->
|
|
119
|
+
<path d="M242 534C328 534 337 473 407 473S502 552 579 552 663 453 746 453 819 489 881 489" fill="none" stroke="#0b2235" stroke-width="18" stroke-linecap="round" />
|
|
120
|
+
<path d="M242 534C328 534 337 473 407 473S502 552 579 552 663 453 746 453 819 489 881 489" fill="none" stroke="url(#thread)" stroke-width="4" stroke-linecap="round" filter="url(#line-glow)" />
|
|
121
|
+
<circle cx="309" cy="513" r="4" fill="#5eead4" />
|
|
122
|
+
<circle cx="579" cy="552" r="5" fill="#67e8f9" />
|
|
123
|
+
<circle cx="800" cy="471" r="4" fill="#93c5fd" />
|
|
124
|
+
|
|
125
|
+
<!-- The agent is a presence, not another architecture box. -->
|
|
126
|
+
<circle cx="515" cy="385" r="112" fill="#5b4be8" fill-opacity="0.09" />
|
|
127
|
+
<circle cx="515" cy="385" r="82" fill="#5b4be8" fill-opacity="0.13" />
|
|
128
|
+
<circle cx="515" cy="385" r="53" fill="url(#agent)" filter="url(#orb-glow)" />
|
|
129
|
+
<path d="M492 386C502 374 528 374 538 386M498 403C510 413 522 413 534 403" fill="none" stroke="#ffffff" stroke-opacity="0.9" stroke-width="4" stroke-linecap="round" />
|
|
130
|
+
<circle cx="496" cy="370" r="5" fill="#ffffff" fill-opacity="0.92" />
|
|
131
|
+
<circle cx="534" cy="370" r="5" fill="#ffffff" fill-opacity="0.92" />
|
|
132
|
+
<circle cx="409" cy="335" r="8" fill="#2dd4bf" fill-opacity="0.9" />
|
|
133
|
+
<circle cx="648" cy="314" r="7" fill="#60a5fa" fill-opacity="0.82" />
|
|
134
|
+
<circle cx="680" cy="407" r="4" fill="#c4b5fd" fill-opacity="0.8" />
|
|
135
|
+
|
|
136
|
+
<!-- A quiet alarm pulse reads as wake-up without becoming a diagram. -->
|
|
137
|
+
<circle cx="746" cy="453" r="29" fill="#f97316" fill-opacity="0.07" stroke="#fb923c" stroke-opacity="0.28" />
|
|
138
|
+
<circle cx="746" cy="453" r="13" fill="#fb923c" fill-opacity="0.16" stroke="#fdba74" stroke-width="2" />
|
|
139
|
+
<path d="M746 445V453L752 457" fill="none" stroke="#fed7aa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
140
|
+
|
|
141
|
+
<rect x="226" y="608" width="512" height="10" rx="5" fill="#33405a" fill-opacity="0.75" />
|
|
142
|
+
<rect x="226" y="632" width="362" height="8" rx="4" fill="#29364f" fill-opacity="0.68" />
|
|
143
|
+
</g>
|
|
144
|
+
|
|
145
|
+
<!-- Extension popup -->
|
|
146
|
+
<g filter="url(#popup-shadow)">
|
|
147
|
+
<path d="M1128 151 1142 169H1114Z" fill="#ffffff" />
|
|
148
|
+
<rect x="892" y="165" width="332" height="438" rx="24" fill="url(#popup)" />
|
|
149
|
+
</g>
|
|
150
|
+
<rect x="893" y="166" width="330" height="436" rx="23" fill="none" stroke="#d9dfeb" />
|
|
151
|
+
|
|
152
|
+
<circle cx="935" cy="210" r="17" fill="url(#agent)" />
|
|
153
|
+
<path d="M928 211C932 206 938 206 942 211" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" />
|
|
154
|
+
<rect x="966" y="199" width="95" height="10" rx="5" fill="#182033" />
|
|
155
|
+
<rect x="966" y="218" width="56" height="7" rx="3.5" fill="#a8b1c1" />
|
|
156
|
+
<circle cx="1183" cy="210" r="6" fill="#34d399" />
|
|
157
|
+
<circle cx="1183" cy="210" r="11" fill="none" stroke="#34d399" stroke-opacity="0.2" />
|
|
158
|
+
<path d="M916 249H1200" stroke="#e4e8f0" />
|
|
159
|
+
|
|
160
|
+
<text x="1058" y="357" text-anchor="middle" fill="#121827" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="104" font-weight="690" letter-spacing="-6">21</text>
|
|
161
|
+
<circle cx="1058" cy="395" r="5" fill="#6366f1" />
|
|
162
|
+
<circle cx="1075" cy="395" r="5" fill="#d8dce6" />
|
|
163
|
+
<circle cx="1041" cy="395" r="5" fill="#d8dce6" />
|
|
164
|
+
|
|
165
|
+
<rect x="924" y="439" width="268" height="70" rx="20" fill="#111827" />
|
|
166
|
+
<circle cx="974" cy="474" r="18" fill="#262f43" />
|
|
167
|
+
<path d="M966 474H982M974 466V482" stroke="#ffffff" stroke-width="2.6" stroke-linecap="round" />
|
|
168
|
+
<path d="M1019 474H1117" stroke="#465269" stroke-width="4" stroke-linecap="round" />
|
|
169
|
+
<path d="M1019 474C1037 474 1040 459 1055 459S1076 488 1092 488 1103 474 1117 474" fill="none" stroke="url(#thread)" stroke-width="3" stroke-linecap="round" />
|
|
170
|
+
<circle cx="1154" cy="474" r="18" fill="#fff0e6" />
|
|
171
|
+
<circle cx="1154" cy="474" r="8" fill="none" stroke="#f97316" stroke-width="2" />
|
|
172
|
+
<path d="M1154 469V474L1158 477" fill="none" stroke="#f97316" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
173
|
+
|
|
174
|
+
<rect x="924" y="538" width="88" height="8" rx="4" fill="#d5dae5" />
|
|
175
|
+
<rect x="924" y="558" width="206" height="8" rx="4" fill="#e2e6ed" />
|
|
176
|
+
<circle cx="1173" cy="552" r="17" fill="#e9e7ff" />
|
|
177
|
+
<path d="M1166 553 1171 558 1181 546" fill="none" stroke="#6657de" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" />
|
|
178
|
+
</svg>
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# How to migrate Durable Object and Agent data
|
|
2
|
+
|
|
3
|
+
Use the same application migration mechanism on Cloudflare and on
|
|
4
|
+
`@mcp-b/do-runtime`. This package does not add an application migration registry.
|
|
5
|
+
|
|
6
|
+
Start by identifying what changed:
|
|
7
|
+
|
|
8
|
+
| Change | Migration owner | What to do |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| Add, rename, transfer, or delete a Durable Object class on Cloudflare | Cloudflare deployment configuration | Update Wrangler's `exports`, or the legacy `migrations` array if the Worker still uses it. |
|
|
11
|
+
| Change an application SQL table | The application | Generate and run a Drizzle migration in the actor constructor. |
|
|
12
|
+
| Change the JSON shape stored by `Agent.state` | The application | Version the state and override `migratePersistedState()` in the Rook Agents SDK fork. |
|
|
13
|
+
| Change an Agents SDK internal `cf_agents_*` table | The Agents SDK | Upgrade the SDK. Its `_ensureSchema()` runs automatically. |
|
|
14
|
+
| Change a `do-runtime` internal `_cf_*` table | `do-runtime` | Upgrade this package. Runtime storage migration runs before actor construction. |
|
|
15
|
+
|
|
16
|
+
## Understand the Wrangler declaration
|
|
17
|
+
|
|
18
|
+
Wrangler's Durable Object configuration manages class and namespace lifecycle. It
|
|
19
|
+
does not run application SQL or transform `Agent.state`.
|
|
20
|
+
|
|
21
|
+
For a new Worker, follow Cloudflare's current `exports` form:
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
|
+
{
|
|
25
|
+
"$schema": "./node_modules/wrangler/config-schema.json",
|
|
26
|
+
"durable_objects": {
|
|
27
|
+
"bindings": [
|
|
28
|
+
{
|
|
29
|
+
"name": "ROOK_AGENT",
|
|
30
|
+
"class_name": "RookAgent"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
"RookAgent": {
|
|
36
|
+
"type": "durable-object",
|
|
37
|
+
"storage": "sqlite"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
An existing Worker may still have the legacy form:
|
|
44
|
+
|
|
45
|
+
```jsonc
|
|
46
|
+
{
|
|
47
|
+
"durable_objects": {
|
|
48
|
+
"bindings": [
|
|
49
|
+
{
|
|
50
|
+
"name": "ROOK_AGENT",
|
|
51
|
+
"class_name": "RookAgent"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"migrations": [
|
|
56
|
+
{
|
|
57
|
+
"tag": "v1",
|
|
58
|
+
"new_sqlite_classes": ["RookAgent"]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Keep the legacy history unless you intentionally follow Cloudflare's conversion
|
|
65
|
+
guide. A Worker cannot use `exports` and `migrations` together.
|
|
66
|
+
|
|
67
|
+
The legacy `tag` orders class-lifecycle changes. It is not an application schema
|
|
68
|
+
version and does not point at Drizzle migrations. Adding a SQL column or changing
|
|
69
|
+
a TypeScript state type does not require another Wrangler entry.
|
|
70
|
+
|
|
71
|
+
A local `do-runtime` host has no Wrangler deployment. The host registers the
|
|
72
|
+
class in `ActorContainerOptions.exports` and constructs its namespace with a
|
|
73
|
+
stable `uniqueKey`. Never change that key for an existing namespace: it is part
|
|
74
|
+
of every actor ID and therefore determines which storage the actor opens.
|
|
75
|
+
|
|
76
|
+
Cloudflare reference:
|
|
77
|
+
|
|
78
|
+
- [Durable Object class exports](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/)
|
|
79
|
+
- [Legacy Durable Object class migrations](https://developers.cloudflare.com/durable-objects/reference/durable-object-class-migrations-legacy/)
|
|
80
|
+
|
|
81
|
+
## Migrate application SQL with Drizzle
|
|
82
|
+
|
|
83
|
+
Use Drizzle's Durable Object driver and migrator for tables owned by Rook. The
|
|
84
|
+
same constructor pattern runs on Cloudflare and on `do-runtime`.
|
|
85
|
+
|
|
86
|
+
1. Change the Drizzle schema.
|
|
87
|
+
2. Run the repository's `drizzle-kit generate` command.
|
|
88
|
+
3. Review and commit the generated SQL and migration journal.
|
|
89
|
+
4. Import that migration bundle into the actor.
|
|
90
|
+
5. Run `migrate()` inside `blockConcurrencyWhile()` so no event can observe a
|
|
91
|
+
partially initialized schema.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { Agent } from "agents";
|
|
95
|
+
import { drizzle, type DrizzleSqliteDODatabase } from "drizzle-orm/durable-sqlite";
|
|
96
|
+
import { migrate } from "drizzle-orm/durable-sqlite/migrator";
|
|
97
|
+
import migrations from "../drizzle/migrations";
|
|
98
|
+
|
|
99
|
+
export class RookAgent extends Agent<Env> {
|
|
100
|
+
readonly db: DrizzleSqliteDODatabase;
|
|
101
|
+
|
|
102
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
103
|
+
super(ctx, env);
|
|
104
|
+
this.db = drizzle(ctx.storage);
|
|
105
|
+
|
|
106
|
+
void ctx.blockConcurrencyWhile(async () => {
|
|
107
|
+
await migrate(this.db, migrations);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Drizzle records applied migrations in each actor's database. Every actor runs the
|
|
114
|
+
constructor on its next wake, applies only missing migrations, and then accepts
|
|
115
|
+
events. Do not use schema push as a production migration strategy, and do not
|
|
116
|
+
edit or reorder a migration after shipping it.
|
|
117
|
+
|
|
118
|
+
The executable proof for this runtime is
|
|
119
|
+
[`src/drizzle-migrations.test.ts`](../src/drizzle-migrations.test.ts). It starts
|
|
120
|
+
an actor with one schema generation, preserves its data across eviction, starts
|
|
121
|
+
the same actor with a second generation, and verifies the pending migration runs
|
|
122
|
+
once.
|
|
123
|
+
|
|
124
|
+
Upstream references:
|
|
125
|
+
|
|
126
|
+
- [Cloudflare: initialize storage and run migrations in the constructor](https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/#initialize-storage-and-run-migrations-in-the-constructor)
|
|
127
|
+
- [Drizzle: Cloudflare Durable Objects SQLite](https://orm.drizzle.team/docs/sqlite/connect-cloudflare-do)
|
|
128
|
+
|
|
129
|
+
## Migrate persisted `Agent.state`
|
|
130
|
+
|
|
131
|
+
`Agent.state` is one JSON value stored in the Agents SDK's `cf_agents_state`
|
|
132
|
+
table. Drizzle does not know its application shape, and `initialState` applies
|
|
133
|
+
only when no persisted state exists.
|
|
134
|
+
|
|
135
|
+
Give the state an integer version and handle every shipped shape. The
|
|
136
|
+
`migratePersistedState()` hook is currently supplied by the Rook Agents SDK fork;
|
|
137
|
+
it is not part of upstream Agents 0.22.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { Agent } from "agents";
|
|
141
|
+
|
|
142
|
+
type RookState = {
|
|
143
|
+
stateVersion: 1;
|
|
144
|
+
count: number;
|
|
145
|
+
label: string;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
149
|
+
return typeof value === "object" && value !== null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export class RookAgent extends Agent<Env, RookState> {
|
|
153
|
+
override initialState: RookState = {
|
|
154
|
+
stateVersion: 1,
|
|
155
|
+
count: 0,
|
|
156
|
+
label: "current"
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
protected override migratePersistedState(value: unknown): RookState {
|
|
160
|
+
if (!isRecord(value) || typeof value.count !== "number") {
|
|
161
|
+
throw new TypeError("invalid persisted Rook state");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const version = value.stateVersion ?? 0;
|
|
165
|
+
if (version === 0) {
|
|
166
|
+
return {
|
|
167
|
+
stateVersion: 1,
|
|
168
|
+
count: value.count,
|
|
169
|
+
label: typeof value.label === "string" ? value.label : "migrated"
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (version === 1 && typeof value.label === "string") {
|
|
174
|
+
return value as RookState;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
throw new TypeError(`unsupported persisted Rook state version: ${String(version)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The hook runs when existing state is first hydrated, before the value is exposed
|
|
183
|
+
to the Agent or sent to a client. Returning a changed object persists the new
|
|
184
|
+
value without treating boot repair as a user update. Returning the same object
|
|
185
|
+
by reference is the no-op path. If migration throws, the stored row remains
|
|
186
|
+
unchanged so a fixed release can retry it.
|
|
187
|
+
|
|
188
|
+
Keep these constraints:
|
|
189
|
+
|
|
190
|
+
- Treat the input as `unknown`; TypeScript types do not validate stored JSON.
|
|
191
|
+
- Retain every migration path for a state shape that reached users.
|
|
192
|
+
- Validate the current shape before returning it.
|
|
193
|
+
- Do not call `setState()` from the migration hook.
|
|
194
|
+
- Do not replace invalid persisted data with `initialState` automatically.
|
|
195
|
+
|
|
196
|
+
Cloudflare documents the persistence behavior and the fact that existing Agents
|
|
197
|
+
load stored state rather than merging `initialState`:
|
|
198
|
+
|
|
199
|
+
- [Store and sync Agent state](https://developers.cloudflare.com/agents/runtime/lifecycle/state/)
|
|
200
|
+
|
|
201
|
+
## Leave package-owned schemas alone
|
|
202
|
+
|
|
203
|
+
The Agents SDK calls its versioned `_ensureSchema()` during construction. Rook
|
|
204
|
+
must not create or alter `cf_agents_*` tables itself.
|
|
205
|
+
|
|
206
|
+
`do-runtime` opens and versions its own `_cf_*` storage before constructing the
|
|
207
|
+
actor. Application SQL cannot read or write the runtime's `PRAGMA user_version`,
|
|
208
|
+
matching workerd's pragma restrictions. Rook must not create or alter `_cf_*`
|
|
209
|
+
tables either.
|
|
210
|
+
|
|
211
|
+
## Verify an upgrade before release
|
|
212
|
+
|
|
213
|
+
For every persisted shape that has shipped:
|
|
214
|
+
|
|
215
|
+
1. Start the previous application version and write representative data.
|
|
216
|
+
2. Destroy the actor instance without deleting its storage.
|
|
217
|
+
3. Start the new version over the same storage.
|
|
218
|
+
4. Verify SQL rows and `Agent.state` were preserved and transformed.
|
|
219
|
+
5. Recreate the actor once more and verify every migration is now a no-op.
|
|
220
|
+
6. Inject an invalid or future state version and verify the original data remains
|
|
221
|
+
available for recovery.
|
|
222
|
+
|
|
223
|
+
Run the package's focused checks with:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
pnpm exec vitest run --config vitest.unit.config.ts \
|
|
227
|
+
src/drizzle-migrations.test.ts \
|
|
228
|
+
src/util/sqlite-migrations.test.ts
|
|
229
|
+
|
|
230
|
+
pnpm --filter do-runtime-example-extension e2e
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The extension end-to-end test seeds an old unversioned `Agent.state`, destroys
|
|
234
|
+
the browser host, and verifies the replacement persists the migrated value before
|
|
235
|
+
a newly connected client sees it.
|