@abraca/orchestrator 2.3.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 +237 -0
- package/dist/abracadabra-orchestrator.cjs +2709 -0
- package/dist/abracadabra-orchestrator.cjs.map +1 -0
- package/dist/abracadabra-orchestrator.esm.js +2665 -0
- package/dist/abracadabra-orchestrator.esm.js.map +1 -0
- package/dist/index.d.ts +261 -0
- package/package.json +39 -0
- package/src/actions/awareness.ts +101 -0
- package/src/actions/chat.ts +30 -0
- package/src/actions/connect.ts +16 -0
- package/src/actions/content.ts +46 -0
- package/src/actions/cursor.ts +30 -0
- package/src/actions/document.ts +120 -0
- package/src/actions/flow.ts +46 -0
- package/src/actions/index.ts +197 -0
- package/src/actions/navigate.ts +15 -0
- package/src/actions/type.ts +113 -0
- package/src/actor-connection.ts +246 -0
- package/src/converters/markdownToYjs.ts +932 -0
- package/src/converters/types.ts +179 -0
- package/src/crypto.ts +71 -0
- package/src/define.ts +147 -0
- package/src/easing.ts +15 -0
- package/src/index.ts +105 -0
- package/src/orchestrator.ts +153 -0
- package/src/timeline-runner.ts +84 -0
- package/src/types.ts +245 -0
- package/src/utils.ts +50 -0
- package/src/yjs-utils.ts +238 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
//#region packages/orchestrator/src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Type definitions for the orchestrator scene format.
|
|
4
|
+
*/
|
|
5
|
+
interface ServerConfig {
|
|
6
|
+
url: string;
|
|
7
|
+
inviteCode?: string;
|
|
8
|
+
}
|
|
9
|
+
interface ActorDef {
|
|
10
|
+
name: string;
|
|
11
|
+
color: string;
|
|
12
|
+
keyFile?: string;
|
|
13
|
+
avatar?: string;
|
|
14
|
+
}
|
|
15
|
+
type EasingName = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut';
|
|
16
|
+
interface ConnectAction {
|
|
17
|
+
type: 'connect';
|
|
18
|
+
}
|
|
19
|
+
interface DisconnectAction {
|
|
20
|
+
type: 'disconnect';
|
|
21
|
+
}
|
|
22
|
+
interface NavigateAction {
|
|
23
|
+
type: 'navigate';
|
|
24
|
+
docId: string;
|
|
25
|
+
}
|
|
26
|
+
interface TypeAction {
|
|
27
|
+
type: 'type';
|
|
28
|
+
docId: string;
|
|
29
|
+
text: string;
|
|
30
|
+
/** ms per character (default 80) */
|
|
31
|
+
speed?: number;
|
|
32
|
+
/** random variance +/- ms (default 30) */
|
|
33
|
+
variance?: number;
|
|
34
|
+
/** character index to start typing at (default: end of doc) */
|
|
35
|
+
position?: number;
|
|
36
|
+
}
|
|
37
|
+
interface SelectAction {
|
|
38
|
+
type: 'select';
|
|
39
|
+
docId: string;
|
|
40
|
+
anchor: number;
|
|
41
|
+
head: number;
|
|
42
|
+
}
|
|
43
|
+
interface MoveCursorAction {
|
|
44
|
+
type: 'moveCursor';
|
|
45
|
+
docId: string;
|
|
46
|
+
from: number;
|
|
47
|
+
to: number;
|
|
48
|
+
duration: number;
|
|
49
|
+
easing?: EasingName;
|
|
50
|
+
}
|
|
51
|
+
interface SetStatusAction {
|
|
52
|
+
type: 'setStatus';
|
|
53
|
+
status: string | null;
|
|
54
|
+
}
|
|
55
|
+
interface SetAwarenessAction {
|
|
56
|
+
type: 'setAwareness';
|
|
57
|
+
/** If set, applies to child provider; otherwise root */
|
|
58
|
+
docId?: string;
|
|
59
|
+
fields: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
interface ClearAwarenessAction {
|
|
62
|
+
type: 'clearAwareness';
|
|
63
|
+
docId?: string;
|
|
64
|
+
fields: string[];
|
|
65
|
+
}
|
|
66
|
+
interface CreateDocumentAction {
|
|
67
|
+
type: 'createDocument';
|
|
68
|
+
parentId: string;
|
|
69
|
+
label: string;
|
|
70
|
+
docType?: string;
|
|
71
|
+
meta?: Record<string, unknown>;
|
|
72
|
+
/** Store created doc ID under this key in vars for later reference */
|
|
73
|
+
assignId?: string;
|
|
74
|
+
}
|
|
75
|
+
interface MoveDocumentAction {
|
|
76
|
+
type: 'moveDocument';
|
|
77
|
+
docId: string;
|
|
78
|
+
newParentId: string;
|
|
79
|
+
order?: number;
|
|
80
|
+
}
|
|
81
|
+
interface WriteContentAction {
|
|
82
|
+
type: 'writeContent';
|
|
83
|
+
docId: string;
|
|
84
|
+
markdown: string;
|
|
85
|
+
}
|
|
86
|
+
interface DeleteContentAction {
|
|
87
|
+
type: 'deleteContent';
|
|
88
|
+
docId: string;
|
|
89
|
+
from: number;
|
|
90
|
+
length: number;
|
|
91
|
+
}
|
|
92
|
+
interface SetMetaAction {
|
|
93
|
+
type: 'setMeta';
|
|
94
|
+
docId: string;
|
|
95
|
+
meta: Record<string, unknown>;
|
|
96
|
+
}
|
|
97
|
+
interface PointerMoveAction {
|
|
98
|
+
type: 'pointerMove';
|
|
99
|
+
docId: string;
|
|
100
|
+
from: {
|
|
101
|
+
x: number;
|
|
102
|
+
y: number;
|
|
103
|
+
};
|
|
104
|
+
to: {
|
|
105
|
+
x: number;
|
|
106
|
+
y: number;
|
|
107
|
+
};
|
|
108
|
+
duration: number;
|
|
109
|
+
easing?: EasingName;
|
|
110
|
+
}
|
|
111
|
+
interface ScrollToAction {
|
|
112
|
+
type: 'scrollTo';
|
|
113
|
+
docId: string;
|
|
114
|
+
/** 0-1 normalized scroll position */
|
|
115
|
+
position: number;
|
|
116
|
+
}
|
|
117
|
+
interface KanbanHoverAction {
|
|
118
|
+
type: 'kanbanHover';
|
|
119
|
+
docId: string;
|
|
120
|
+
cardId: string | null;
|
|
121
|
+
}
|
|
122
|
+
interface KanbanDragAction {
|
|
123
|
+
type: 'kanbanDrag';
|
|
124
|
+
docId: string;
|
|
125
|
+
cardId: string;
|
|
126
|
+
toColumnId: string;
|
|
127
|
+
duration: number;
|
|
128
|
+
}
|
|
129
|
+
interface RenameDocumentAction {
|
|
130
|
+
type: 'renameDocument';
|
|
131
|
+
docId: string;
|
|
132
|
+
label: string;
|
|
133
|
+
}
|
|
134
|
+
interface SendChatAction {
|
|
135
|
+
type: 'sendChat';
|
|
136
|
+
/** Channel key (e.g. 'group:docId' or 'dm:key1:key2') */
|
|
137
|
+
channel: string;
|
|
138
|
+
message: string;
|
|
139
|
+
}
|
|
140
|
+
interface TypeDeleteAction {
|
|
141
|
+
type: 'typeDelete';
|
|
142
|
+
docId: string;
|
|
143
|
+
/** Number of characters to delete (backspace) */
|
|
144
|
+
count: number;
|
|
145
|
+
/** ms per deletion (default 60) */
|
|
146
|
+
speed?: number;
|
|
147
|
+
/** random variance +/- ms (default 20) */
|
|
148
|
+
variance?: number;
|
|
149
|
+
/** character index to start deleting from (default: end of doc) */
|
|
150
|
+
position?: number;
|
|
151
|
+
}
|
|
152
|
+
interface RepeatAction {
|
|
153
|
+
type: 'repeat';
|
|
154
|
+
/** Number of times to repeat */
|
|
155
|
+
times: number;
|
|
156
|
+
actions: TimelineEntry[];
|
|
157
|
+
}
|
|
158
|
+
interface WaitAction {
|
|
159
|
+
type: 'wait';
|
|
160
|
+
duration: number;
|
|
161
|
+
}
|
|
162
|
+
interface ParallelAction {
|
|
163
|
+
type: 'parallel';
|
|
164
|
+
actions: TimelineEntry[];
|
|
165
|
+
}
|
|
166
|
+
interface SequenceAction {
|
|
167
|
+
type: 'sequence';
|
|
168
|
+
actions: TimelineEntry[];
|
|
169
|
+
}
|
|
170
|
+
type Action = ConnectAction | DisconnectAction | NavigateAction | TypeAction | TypeDeleteAction | SelectAction | MoveCursorAction | SetStatusAction | SetAwarenessAction | ClearAwarenessAction | CreateDocumentAction | MoveDocumentAction | RenameDocumentAction | WriteContentAction | DeleteContentAction | SetMetaAction | PointerMoveAction | ScrollToAction | KanbanHoverAction | KanbanDragAction | SendChatAction | WaitAction | ParallelAction | SequenceAction | RepeatAction;
|
|
171
|
+
interface TimelineEntry {
|
|
172
|
+
/** ms from scene start (or parent start for nested entries) */
|
|
173
|
+
at?: number;
|
|
174
|
+
/** Which actor performs this action */
|
|
175
|
+
actor?: string;
|
|
176
|
+
action: Action;
|
|
177
|
+
}
|
|
178
|
+
interface Scene {
|
|
179
|
+
server: ServerConfig;
|
|
180
|
+
actors: ActorDef[];
|
|
181
|
+
timeline: TimelineEntry[];
|
|
182
|
+
/** Pre-defined variables (e.g. known doc IDs) */
|
|
183
|
+
vars?: Record<string, string>;
|
|
184
|
+
/** Max scene duration in ms. Auto-cleanup after this time. */
|
|
185
|
+
duration?: number;
|
|
186
|
+
/** Called after all actors are prepared but before timeline runs. */
|
|
187
|
+
onStart?: () => Promise<void> | void;
|
|
188
|
+
/** Called after timeline completes (before cleanup). */
|
|
189
|
+
onEnd?: () => Promise<void> | void;
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region packages/orchestrator/src/define.d.ts
|
|
193
|
+
/** Define a complete scene. */
|
|
194
|
+
declare function defineScene(scene: Scene): Scene;
|
|
195
|
+
/** Define an actor. */
|
|
196
|
+
declare function actor(name: string, opts: Omit<ActorDef, 'name'>): ActorDef;
|
|
197
|
+
/** Factory functions for all action types. */
|
|
198
|
+
declare const actions: {
|
|
199
|
+
connect(): ConnectAction;
|
|
200
|
+
disconnect(): DisconnectAction;
|
|
201
|
+
navigate(docId: string): NavigateAction;
|
|
202
|
+
type(docId: string, text: string, opts?: {
|
|
203
|
+
speed?: number;
|
|
204
|
+
variance?: number;
|
|
205
|
+
position?: number;
|
|
206
|
+
}): TypeAction;
|
|
207
|
+
typeDelete(docId: string, count: number, opts?: {
|
|
208
|
+
speed?: number;
|
|
209
|
+
variance?: number;
|
|
210
|
+
position?: number;
|
|
211
|
+
}): TypeDeleteAction;
|
|
212
|
+
select(docId: string, anchor: number, head: number): SelectAction;
|
|
213
|
+
moveCursor(docId: string, from: number, to: number, duration: number, easing?: EasingName): MoveCursorAction;
|
|
214
|
+
setStatus(status: string | null): SetStatusAction;
|
|
215
|
+
setAwareness(fields: Record<string, unknown>, docId?: string): SetAwarenessAction;
|
|
216
|
+
clearAwareness(fields: string[], docId?: string): ClearAwarenessAction;
|
|
217
|
+
createDocument(parentId: string, label: string, opts?: {
|
|
218
|
+
docType?: string;
|
|
219
|
+
meta?: Record<string, unknown>;
|
|
220
|
+
assignId?: string;
|
|
221
|
+
}): CreateDocumentAction;
|
|
222
|
+
moveDocument(docId: string, newParentId: string, order?: number): MoveDocumentAction;
|
|
223
|
+
renameDocument(docId: string, label: string): RenameDocumentAction;
|
|
224
|
+
writeContent(docId: string, markdown: string): WriteContentAction;
|
|
225
|
+
deleteContent(docId: string, from: number, length: number): DeleteContentAction;
|
|
226
|
+
setMeta(docId: string, meta: Record<string, unknown>): SetMetaAction;
|
|
227
|
+
pointerMove(docId: string, from: {
|
|
228
|
+
x: number;
|
|
229
|
+
y: number;
|
|
230
|
+
}, to: {
|
|
231
|
+
x: number;
|
|
232
|
+
y: number;
|
|
233
|
+
}, duration: number, easing?: EasingName): PointerMoveAction;
|
|
234
|
+
scrollTo(docId: string, position: number): ScrollToAction;
|
|
235
|
+
kanbanHover(docId: string, cardId: string | null): KanbanHoverAction;
|
|
236
|
+
kanbanDrag(docId: string, cardId: string, toColumnId: string, duration: number): KanbanDragAction;
|
|
237
|
+
sendChat(channel: string, message: string): SendChatAction;
|
|
238
|
+
wait(duration: number): WaitAction;
|
|
239
|
+
parallel(entries: TimelineEntry[]): ParallelAction;
|
|
240
|
+
sequence(entries: TimelineEntry[]): SequenceAction;
|
|
241
|
+
repeat(times: number, entries: TimelineEntry[]): RepeatAction;
|
|
242
|
+
};
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region packages/orchestrator/src/orchestrator.d.ts
|
|
245
|
+
declare class Orchestrator {
|
|
246
|
+
private scene;
|
|
247
|
+
private actors;
|
|
248
|
+
private vars;
|
|
249
|
+
/** Load a scene from a TypeScript file. */
|
|
250
|
+
load(scriptPath: string): Promise<void>;
|
|
251
|
+
/** Prepare actor connections (does not connect yet — that's a timeline action). */
|
|
252
|
+
prepare(): void;
|
|
253
|
+
/** Validate the scene without connecting. Logs the timeline structure. */
|
|
254
|
+
dryRun(): void;
|
|
255
|
+
/** Run the timeline. */
|
|
256
|
+
run(): Promise<void>;
|
|
257
|
+
/** Disconnect all actors gracefully. */
|
|
258
|
+
cleanup(): Promise<void>;
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
export { type Action, type ActorDef, type ClearAwarenessAction, type ConnectAction, type CreateDocumentAction, type DeleteContentAction, type DisconnectAction, type EasingName, type KanbanDragAction, type KanbanHoverAction, type MoveCursorAction, type MoveDocumentAction, type NavigateAction, Orchestrator, type ParallelAction, type PointerMoveAction, type RenameDocumentAction, type RepeatAction, type Scene, type ScrollToAction, type SelectAction, type SendChatAction, type SequenceAction, type ServerConfig, type SetAwarenessAction, type SetMetaAction, type SetStatusAction, type TimelineEntry, type TypeAction, type TypeDeleteAction, type WaitAction, type WriteContentAction, actions, actor, defineScene };
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@abraca/orchestrator",
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "CouShell commercial director — orchestrate simulated actors on Abracadabra for screen recording",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/abracadabra-orchestrator.cjs",
|
|
8
|
+
"module": "dist/abracadabra-orchestrator.esm.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"abracadabra-orchestrator": "./dist/abracadabra-orchestrator.esm.js"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"source": {
|
|
18
|
+
"import": "./src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"default": {
|
|
21
|
+
"import": "./dist/abracadabra-orchestrator.esm.js",
|
|
22
|
+
"require": "./dist/abracadabra-orchestrator.cjs",
|
|
23
|
+
"types": "./dist/index.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"src",
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@noble/ed25519": "^3.1.0",
|
|
32
|
+
"@noble/hashes": "^2.2.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@abraca/dabra": ">=1.0.0",
|
|
36
|
+
"y-protocols": "^1.0.6",
|
|
37
|
+
"yjs": "^13.6.8"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Awareness actions: setStatus, setAwareness, clearAwareness, pointerMove, scrollTo,
|
|
3
|
+
* kanbanHover, kanbanDrag.
|
|
4
|
+
*/
|
|
5
|
+
import type { ActorConnection } from '../actor-connection.ts'
|
|
6
|
+
import type {
|
|
7
|
+
SetStatusAction,
|
|
8
|
+
SetAwarenessAction,
|
|
9
|
+
ClearAwarenessAction,
|
|
10
|
+
PointerMoveAction,
|
|
11
|
+
ScrollToAction,
|
|
12
|
+
KanbanHoverAction,
|
|
13
|
+
KanbanDragAction,
|
|
14
|
+
} from '../types.ts'
|
|
15
|
+
import { sleep } from '../utils.ts'
|
|
16
|
+
import { getEasing } from '../easing.ts'
|
|
17
|
+
|
|
18
|
+
export async function executeSetStatus(
|
|
19
|
+
actor: ActorConnection,
|
|
20
|
+
action: SetStatusAction
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
actor.setRootAwareness('status', action.status)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function executeSetAwareness(
|
|
26
|
+
actor: ActorConnection,
|
|
27
|
+
action: SetAwarenessAction
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
if (action.docId) {
|
|
30
|
+
// Ensure child provider is loaded
|
|
31
|
+
await actor.getChildProvider(action.docId)
|
|
32
|
+
for (const [key, value] of Object.entries(action.fields)) {
|
|
33
|
+
actor.setChildAwareness(action.docId, key, value)
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
for (const [key, value] of Object.entries(action.fields)) {
|
|
37
|
+
actor.setRootAwareness(key, value)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function executeClearAwareness(
|
|
43
|
+
actor: ActorConnection,
|
|
44
|
+
action: ClearAwarenessAction
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
if (action.docId) {
|
|
47
|
+
for (const field of action.fields) {
|
|
48
|
+
actor.setChildAwareness(action.docId, field, null)
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
for (const field of action.fields) {
|
|
52
|
+
actor.setRootAwareness(field, null)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function executePointerMove(
|
|
58
|
+
actor: ActorConnection,
|
|
59
|
+
action: PointerMoveAction
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const easing = getEasing(action.easing)
|
|
62
|
+
const steps = Math.max(1, Math.round(action.duration / 16)) // ~60fps
|
|
63
|
+
const stepDuration = action.duration / steps
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i <= steps; i++) {
|
|
66
|
+
const t = easing(i / steps)
|
|
67
|
+
const x = action.from.x + (action.to.x - action.from.x) * t
|
|
68
|
+
const y = action.from.y + (action.to.y - action.from.y) * t
|
|
69
|
+
actor.setChildAwareness(action.docId, 'pos', { x, y })
|
|
70
|
+
if (i < steps) await sleep(stepDuration)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function executeScrollTo(
|
|
75
|
+
actor: ActorConnection,
|
|
76
|
+
action: ScrollToAction
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
actor.setChildAwareness(action.docId, 'doc:scroll', action.position)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function executeKanbanHover(
|
|
82
|
+
actor: ActorConnection,
|
|
83
|
+
action: KanbanHoverAction
|
|
84
|
+
): Promise<void> {
|
|
85
|
+
actor.setChildAwareness(action.docId, 'kanban:hovering', action.cardId)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function executeKanbanDrag(
|
|
89
|
+
actor: ActorConnection,
|
|
90
|
+
action: KanbanDragAction
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
// Set drag state
|
|
93
|
+
actor.setChildAwareness(action.docId, 'kanban:dragging', {
|
|
94
|
+
cardId: action.cardId,
|
|
95
|
+
toColumnId: action.toColumnId,
|
|
96
|
+
})
|
|
97
|
+
// Hold for duration
|
|
98
|
+
await sleep(action.duration)
|
|
99
|
+
// Clear drag state
|
|
100
|
+
actor.setChildAwareness(action.docId, 'kanban:dragging', null)
|
|
101
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat action — send a stateless message via the provider.
|
|
3
|
+
*
|
|
4
|
+
* Routes through the unified `messages:send` protocol. `action.channel` is
|
|
5
|
+
* interpreted as a doc id (the channel/dm doc UUID); legacy `group:<docId>`
|
|
6
|
+
* strings are accepted with the prefix stripped.
|
|
7
|
+
*/
|
|
8
|
+
import type { ActorConnection } from '../actor-connection.ts'
|
|
9
|
+
import type { SendChatAction } from '../types.ts'
|
|
10
|
+
|
|
11
|
+
export async function executeSendChat(
|
|
12
|
+
actor: ActorConnection,
|
|
13
|
+
action: SendChatAction
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const rootProvider = actor.rootProvider
|
|
16
|
+
if (!rootProvider) throw new Error(`${actor.actor.name}: not connected`)
|
|
17
|
+
|
|
18
|
+
const channel_doc_id = action.channel.startsWith('group:')
|
|
19
|
+
? action.channel.slice(6)
|
|
20
|
+
: action.channel
|
|
21
|
+
|
|
22
|
+
const payload = JSON.stringify({
|
|
23
|
+
type: 'messages:send',
|
|
24
|
+
channel_doc_id,
|
|
25
|
+
content: action.message,
|
|
26
|
+
mentions: [],
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
rootProvider.sendStateless(payload)
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connect / disconnect actions.
|
|
3
|
+
*/
|
|
4
|
+
import type { ActorConnection } from '../actor-connection.ts'
|
|
5
|
+
import type { ServerConfig } from '../types.ts'
|
|
6
|
+
|
|
7
|
+
export async function executeConnect(
|
|
8
|
+
actor: ActorConnection,
|
|
9
|
+
serverConfig: ServerConfig
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
await actor.connect(serverConfig)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function executeDisconnect(actor: ActorConnection): Promise<void> {
|
|
15
|
+
await actor.disconnect()
|
|
16
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content actions: writeContent (markdown → Y.js), deleteContent.
|
|
3
|
+
*
|
|
4
|
+
* Preserves TipTap schema nodes (documentHeader, documentMeta) —
|
|
5
|
+
* only body content is cleared/replaced.
|
|
6
|
+
*/
|
|
7
|
+
import type { ActorConnection } from '../actor-connection.ts'
|
|
8
|
+
import type { WriteContentAction, DeleteContentAction } from '../types.ts'
|
|
9
|
+
import { populateYDocFromMarkdown } from '../converters/markdownToYjs.ts'
|
|
10
|
+
import { bodyStartIndex } from '../yjs-utils.ts'
|
|
11
|
+
|
|
12
|
+
export async function executeWriteContent(
|
|
13
|
+
actor: ActorConnection,
|
|
14
|
+
action: WriteContentAction
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const provider = await actor.getChildProvider(action.docId)
|
|
17
|
+
const fragment = provider.document.getXmlFragment('default')
|
|
18
|
+
|
|
19
|
+
// Clear entire fragment (schema nodes + body) — populateYDocFromMarkdown rebuilds them
|
|
20
|
+
provider.document.transact(() => {
|
|
21
|
+
while (fragment.length > 0) {
|
|
22
|
+
fragment.delete(0, 1)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Populate from markdown (creates fresh documentHeader + documentMeta + body)
|
|
27
|
+
populateYDocFromMarkdown(fragment, action.markdown)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function executeDeleteContent(
|
|
31
|
+
actor: ActorConnection,
|
|
32
|
+
action: DeleteContentAction
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
const provider = await actor.getChildProvider(action.docId)
|
|
35
|
+
const fragment = provider.document.getXmlFragment('default')
|
|
36
|
+
|
|
37
|
+
provider.document.transact(() => {
|
|
38
|
+
// Offset from by the schema nodes to protect them
|
|
39
|
+
const start = bodyStartIndex(fragment)
|
|
40
|
+
const adjustedFrom = start + action.from
|
|
41
|
+
const count = Math.min(action.length, fragment.length - adjustedFrom)
|
|
42
|
+
if (count > 0) {
|
|
43
|
+
fragment.delete(adjustedFrom, count)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor actions: moveCursor (animated), select.
|
|
3
|
+
*/
|
|
4
|
+
import type { ActorConnection } from '../actor-connection.ts'
|
|
5
|
+
import type { MoveCursorAction, SelectAction } from '../types.ts'
|
|
6
|
+
import { sleep } from '../utils.ts'
|
|
7
|
+
import { getEasing } from '../easing.ts'
|
|
8
|
+
|
|
9
|
+
export async function executeMoveCursor(
|
|
10
|
+
actor: ActorConnection,
|
|
11
|
+
action: MoveCursorAction
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
const easing = getEasing(action.easing)
|
|
14
|
+
const steps = Math.max(1, Math.round(action.duration / 16))
|
|
15
|
+
const stepDuration = action.duration / steps
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i <= steps; i++) {
|
|
18
|
+
const t = easing(i / steps)
|
|
19
|
+
const pos = Math.round(action.from + (action.to - action.from) * t)
|
|
20
|
+
actor.setCursor(action.docId, pos)
|
|
21
|
+
if (i < steps) await sleep(stepDuration)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function executeSelect(
|
|
26
|
+
actor: ActorConnection,
|
|
27
|
+
action: SelectAction
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
actor.setSelection(action.docId, action.anchor, action.head)
|
|
30
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document management actions: createDocument, moveDocument, setMeta.
|
|
3
|
+
* These operate on the Y.js doc-tree map (same as the dashboard and MCP do).
|
|
4
|
+
*/
|
|
5
|
+
import type { ActorConnection } from '../actor-connection.ts'
|
|
6
|
+
import type { CreateDocumentAction, MoveDocumentAction, SetMetaAction, RenameDocumentAction } from '../types.ts'
|
|
7
|
+
import { log } from '../utils.ts'
|
|
8
|
+
|
|
9
|
+
export async function executeCreateDocument(
|
|
10
|
+
actor: ActorConnection,
|
|
11
|
+
action: CreateDocumentAction,
|
|
12
|
+
vars: Map<string, string>
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
const rootProvider = actor.rootProvider
|
|
15
|
+
if (!rootProvider) throw new Error(`${actor.actor.name}: not connected`)
|
|
16
|
+
|
|
17
|
+
const treeMap = rootProvider.document.getMap('doc-tree')
|
|
18
|
+
const id = crypto.randomUUID()
|
|
19
|
+
const now = Date.now()
|
|
20
|
+
|
|
21
|
+
// Normalize parentId: if it matches the root doc, use null (top-level)
|
|
22
|
+
const normalizedParent = action.parentId === actor.rootDocId ? null : action.parentId
|
|
23
|
+
|
|
24
|
+
rootProvider.document.transact(() => {
|
|
25
|
+
treeMap.set(id, {
|
|
26
|
+
label: action.label,
|
|
27
|
+
parentId: normalizedParent,
|
|
28
|
+
order: now,
|
|
29
|
+
type: action.docType,
|
|
30
|
+
meta: action.meta,
|
|
31
|
+
createdAt: now,
|
|
32
|
+
updatedAt: now,
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
log(`${actor.actor.name}: created document "${action.label}" (${id})`)
|
|
37
|
+
|
|
38
|
+
// Store the created ID for later reference in the script
|
|
39
|
+
if (action.assignId) {
|
|
40
|
+
vars.set(action.assignId, id)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function executeMoveDocument(
|
|
45
|
+
actor: ActorConnection,
|
|
46
|
+
action: MoveDocumentAction
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
const rootProvider = actor.rootProvider
|
|
49
|
+
if (!rootProvider) throw new Error(`${actor.actor.name}: not connected`)
|
|
50
|
+
|
|
51
|
+
const treeMap = rootProvider.document.getMap('doc-tree')
|
|
52
|
+
const entry = treeMap.get(action.docId) as Record<string, unknown> | undefined
|
|
53
|
+
if (!entry) {
|
|
54
|
+
log(`${actor.actor.name}: document ${action.docId} not found in tree`)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Normalize parentId
|
|
59
|
+
const normalizedParent = action.newParentId === actor.rootDocId ? null : action.newParentId
|
|
60
|
+
|
|
61
|
+
rootProvider.document.transact(() => {
|
|
62
|
+
treeMap.set(action.docId, {
|
|
63
|
+
...entry,
|
|
64
|
+
parentId: normalizedParent,
|
|
65
|
+
order: action.order ?? Date.now(),
|
|
66
|
+
updatedAt: Date.now(),
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
log(`${actor.actor.name}: moved document ${action.docId} to ${action.newParentId}`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function executeSetMeta(
|
|
74
|
+
actor: ActorConnection,
|
|
75
|
+
action: SetMetaAction
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const rootProvider = actor.rootProvider
|
|
78
|
+
if (!rootProvider) throw new Error(`${actor.actor.name}: not connected`)
|
|
79
|
+
|
|
80
|
+
const treeMap = rootProvider.document.getMap('doc-tree')
|
|
81
|
+
const entry = treeMap.get(action.docId) as Record<string, unknown> | undefined
|
|
82
|
+
if (!entry) {
|
|
83
|
+
log(`${actor.actor.name}: document ${action.docId} not found in tree`)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
rootProvider.document.transact(() => {
|
|
88
|
+
const currentMeta = (entry.meta as Record<string, unknown>) ?? {}
|
|
89
|
+
treeMap.set(action.docId, {
|
|
90
|
+
...entry,
|
|
91
|
+
meta: { ...currentMeta, ...action.meta },
|
|
92
|
+
updatedAt: Date.now(),
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function executeRenameDocument(
|
|
98
|
+
actor: ActorConnection,
|
|
99
|
+
action: RenameDocumentAction
|
|
100
|
+
): Promise<void> {
|
|
101
|
+
const rootProvider = actor.rootProvider
|
|
102
|
+
if (!rootProvider) throw new Error(`${actor.actor.name}: not connected`)
|
|
103
|
+
|
|
104
|
+
const treeMap = rootProvider.document.getMap('doc-tree')
|
|
105
|
+
const entry = treeMap.get(action.docId) as Record<string, unknown> | undefined
|
|
106
|
+
if (!entry) {
|
|
107
|
+
log(`${actor.actor.name}: document ${action.docId} not found in tree`)
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
rootProvider.document.transact(() => {
|
|
112
|
+
treeMap.set(action.docId, {
|
|
113
|
+
...entry,
|
|
114
|
+
label: action.label,
|
|
115
|
+
updatedAt: Date.now(),
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
log(`${actor.actor.name}: renamed ${action.docId} to "${action.label}"`)
|
|
120
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow control actions: wait, parallel, sequence.
|
|
3
|
+
*/
|
|
4
|
+
import type { WaitAction, ParallelAction, SequenceAction, RepeatAction, TimelineEntry } from '../types.ts'
|
|
5
|
+
import { sleep } from '../utils.ts'
|
|
6
|
+
import type { ActionExecutor } from './index.ts'
|
|
7
|
+
|
|
8
|
+
export async function executeWait(action: WaitAction): Promise<void> {
|
|
9
|
+
await sleep(action.duration)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function executeParallel(
|
|
13
|
+
action: ParallelAction,
|
|
14
|
+
execute: ActionExecutor
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const promises = action.actions.map(async (entry) => {
|
|
17
|
+
const delay = entry.at ?? 0
|
|
18
|
+
if (delay > 0) await sleep(delay)
|
|
19
|
+
await execute(entry)
|
|
20
|
+
})
|
|
21
|
+
await Promise.all(promises)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function executeSequence(
|
|
25
|
+
action: SequenceAction,
|
|
26
|
+
execute: ActionExecutor
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
for (const entry of action.actions) {
|
|
29
|
+
const delay = entry.at ?? 0
|
|
30
|
+
if (delay > 0) await sleep(delay)
|
|
31
|
+
await execute(entry)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function executeRepeat(
|
|
36
|
+
action: RepeatAction,
|
|
37
|
+
execute: ActionExecutor
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
for (let n = 0; n < action.times; n++) {
|
|
40
|
+
for (const entry of action.actions) {
|
|
41
|
+
const delay = entry.at ?? 0
|
|
42
|
+
if (delay > 0) await sleep(delay)
|
|
43
|
+
await execute(entry)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|