@composer-app/mcp 0.0.1-beta.5 → 0.0.2
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 +145 -0
- package/README.md +160 -0
- package/dist/{chunk-A5KBJAJW.js → chunk-GPFWLOYB.js} +1402 -237
- package/dist/cli.js +1 -1
- package/dist/mcp.js +21 -1
- package/package.json +3 -2
- package/skill/commenting/SKILL.md +67 -0
- package/skill/create/SKILL.md +131 -0
- package/skill/export/SKILL.md +67 -0
- package/skill/join/SKILL.md +117 -0
- package/skill/monitor/SKILL.md +159 -0
- package/skill/suggesting/SKILL.md +177 -0
- package/skill/.claude/settings.local.json +0 -8
- package/skill/SKILL.md +0 -417
|
@@ -6,16 +6,373 @@ import {
|
|
|
6
6
|
CallToolRequestSchema,
|
|
7
7
|
ListToolsRequestSchema
|
|
8
8
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import { nanoid as
|
|
9
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
10
|
+
import * as Y6 from "yjs";
|
|
10
11
|
import fs3 from "fs/promises";
|
|
11
12
|
import path3 from "path";
|
|
12
13
|
import os2 from "os";
|
|
13
14
|
import http from "http";
|
|
14
15
|
import { randomUUID } from "crypto";
|
|
15
16
|
|
|
17
|
+
// ../shared/src/activity.ts
|
|
18
|
+
import { nanoid } from "nanoid";
|
|
19
|
+
var ACTIVITY_CAP = 200;
|
|
20
|
+
var NOTIFIABLE_TYPES = /* @__PURE__ */ new Set([
|
|
21
|
+
"comment",
|
|
22
|
+
"suggestion",
|
|
23
|
+
"reply",
|
|
24
|
+
"accept",
|
|
25
|
+
"reject"
|
|
26
|
+
]);
|
|
27
|
+
function isActivityNotifiable(event) {
|
|
28
|
+
return NOTIFIABLE_TYPES.has(event.type);
|
|
29
|
+
}
|
|
30
|
+
function getActivityMap(doc) {
|
|
31
|
+
return doc.getMap("activity");
|
|
32
|
+
}
|
|
33
|
+
function getActivityStateMap(doc) {
|
|
34
|
+
return doc.getMap("activityState");
|
|
35
|
+
}
|
|
36
|
+
function emitActivity(doc, event, opts) {
|
|
37
|
+
if (opts?.silent) return;
|
|
38
|
+
const activity = getActivityMap(doc);
|
|
39
|
+
const id = nanoid();
|
|
40
|
+
activity.set(id, { ...event, id });
|
|
41
|
+
pruneIfOverCap(doc);
|
|
42
|
+
}
|
|
43
|
+
function pruneIfOverCap(doc) {
|
|
44
|
+
const activity = getActivityMap(doc);
|
|
45
|
+
const overBy = activity.size - ACTIVITY_CAP;
|
|
46
|
+
if (overBy <= 0) return;
|
|
47
|
+
const nonNotifiable = [];
|
|
48
|
+
const notifiable = [];
|
|
49
|
+
activity.forEach((value, key) => {
|
|
50
|
+
const event = value;
|
|
51
|
+
const entry = { key, createdAt: event.createdAt };
|
|
52
|
+
if (isActivityNotifiable(event)) notifiable.push(entry);
|
|
53
|
+
else nonNotifiable.push(entry);
|
|
54
|
+
});
|
|
55
|
+
nonNotifiable.sort((a, b) => a.createdAt - b.createdAt);
|
|
56
|
+
notifiable.sort((a, b) => a.createdAt - b.createdAt);
|
|
57
|
+
const toRemove = [...nonNotifiable, ...notifiable].slice(0, overBy);
|
|
58
|
+
const stateMap = getActivityStateMap(doc);
|
|
59
|
+
for (const entry of toRemove) {
|
|
60
|
+
activity.delete(entry.key);
|
|
61
|
+
stateMap.forEach((_value, stateKeyStr) => {
|
|
62
|
+
if (stateKeyStr.startsWith(`${entry.key}:`)) {
|
|
63
|
+
stateMap.delete(stateKeyStr);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function textPreview(text, maxLen = 80) {
|
|
69
|
+
if (!text) return void 0;
|
|
70
|
+
return text.length > maxLen ? text.slice(0, maxLen) + "\u2026" : text;
|
|
71
|
+
}
|
|
72
|
+
function getParticipantUserIds(thread) {
|
|
73
|
+
const ids = /* @__PURE__ */ new Set();
|
|
74
|
+
if (thread.authorUserId) ids.add(thread.authorUserId);
|
|
75
|
+
for (const r of thread.replies ?? []) {
|
|
76
|
+
if (r.authorUserId) ids.add(r.authorUserId);
|
|
77
|
+
}
|
|
78
|
+
return [...ids];
|
|
79
|
+
}
|
|
80
|
+
|
|
16
81
|
// src/roomState.ts
|
|
17
|
-
import * as
|
|
82
|
+
import * as Y5 from "yjs";
|
|
18
83
|
import YProvider from "y-partyserver/provider";
|
|
84
|
+
|
|
85
|
+
// ../node_modules/lib0/math.js
|
|
86
|
+
var floor = Math.floor;
|
|
87
|
+
var isNaN = Number.isNaN;
|
|
88
|
+
|
|
89
|
+
// ../node_modules/lib0/set.js
|
|
90
|
+
var create = () => /* @__PURE__ */ new Set();
|
|
91
|
+
|
|
92
|
+
// ../node_modules/lib0/array.js
|
|
93
|
+
var from = Array.from;
|
|
94
|
+
|
|
95
|
+
// ../node_modules/lib0/time.js
|
|
96
|
+
var getUnixTime = Date.now;
|
|
97
|
+
|
|
98
|
+
// ../node_modules/lib0/map.js
|
|
99
|
+
var create2 = () => /* @__PURE__ */ new Map();
|
|
100
|
+
var setIfUndefined = (map, key, createT) => {
|
|
101
|
+
let set = map.get(key);
|
|
102
|
+
if (set === void 0) {
|
|
103
|
+
map.set(key, set = createT());
|
|
104
|
+
}
|
|
105
|
+
return set;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// ../node_modules/lib0/observable.js
|
|
109
|
+
var Observable = class {
|
|
110
|
+
constructor() {
|
|
111
|
+
this._observers = create2();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* @param {N} name
|
|
115
|
+
* @param {function} f
|
|
116
|
+
*/
|
|
117
|
+
on(name, f) {
|
|
118
|
+
setIfUndefined(this._observers, name, create).add(f);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* @param {N} name
|
|
122
|
+
* @param {function} f
|
|
123
|
+
*/
|
|
124
|
+
once(name, f) {
|
|
125
|
+
const _f = (...args) => {
|
|
126
|
+
this.off(name, _f);
|
|
127
|
+
f(...args);
|
|
128
|
+
};
|
|
129
|
+
this.on(name, _f);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* @param {N} name
|
|
133
|
+
* @param {function} f
|
|
134
|
+
*/
|
|
135
|
+
off(name, f) {
|
|
136
|
+
const observers = this._observers.get(name);
|
|
137
|
+
if (observers !== void 0) {
|
|
138
|
+
observers.delete(f);
|
|
139
|
+
if (observers.size === 0) {
|
|
140
|
+
this._observers.delete(name);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Emit a named event. All registered event listeners that listen to the
|
|
146
|
+
* specified name will receive the event.
|
|
147
|
+
*
|
|
148
|
+
* @todo This should catch exceptions
|
|
149
|
+
*
|
|
150
|
+
* @param {N} name The event name.
|
|
151
|
+
* @param {Array<any>} args The arguments that are applied to the event listener.
|
|
152
|
+
*/
|
|
153
|
+
emit(name, args) {
|
|
154
|
+
return from((this._observers.get(name) || create2()).values()).forEach((f) => f(...args));
|
|
155
|
+
}
|
|
156
|
+
destroy() {
|
|
157
|
+
this._observers = create2();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// ../node_modules/lib0/trait/equality.js
|
|
162
|
+
var EqualityTraitSymbol = /* @__PURE__ */ Symbol("Equality");
|
|
163
|
+
|
|
164
|
+
// ../node_modules/lib0/object.js
|
|
165
|
+
var keys = Object.keys;
|
|
166
|
+
var size = (obj) => keys(obj).length;
|
|
167
|
+
var hasProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
168
|
+
|
|
169
|
+
// ../node_modules/lib0/function.js
|
|
170
|
+
var equalityDeep = (a, b) => {
|
|
171
|
+
if (a === b) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (a == null || b == null || a.constructor !== b.constructor && (a.constructor || Object) !== (b.constructor || Object)) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
if (a[EqualityTraitSymbol] != null) {
|
|
178
|
+
return a[EqualityTraitSymbol](b);
|
|
179
|
+
}
|
|
180
|
+
switch (a.constructor) {
|
|
181
|
+
case ArrayBuffer:
|
|
182
|
+
a = new Uint8Array(a);
|
|
183
|
+
b = new Uint8Array(b);
|
|
184
|
+
// eslint-disable-next-line no-fallthrough
|
|
185
|
+
case Uint8Array: {
|
|
186
|
+
if (a.byteLength !== b.byteLength) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
for (let i = 0; i < a.length; i++) {
|
|
190
|
+
if (a[i] !== b[i]) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
case Set: {
|
|
197
|
+
if (a.size !== b.size) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
for (const value of a) {
|
|
201
|
+
if (!b.has(value)) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case Map: {
|
|
208
|
+
if (a.size !== b.size) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
for (const key of a.keys()) {
|
|
212
|
+
if (!b.has(key) || !equalityDeep(a.get(key), b.get(key))) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case void 0:
|
|
219
|
+
case Object:
|
|
220
|
+
if (size(a) !== size(b)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
for (const key in a) {
|
|
224
|
+
if (!hasProperty(a, key) || !equalityDeep(a[key], b[key])) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
case Array:
|
|
230
|
+
if (a.length !== b.length) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
for (let i = 0; i < a.length; i++) {
|
|
234
|
+
if (!equalityDeep(a[i], b[i])) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
default:
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// ../node_modules/y-protocols/awareness.js
|
|
246
|
+
import * as Y from "yjs";
|
|
247
|
+
var outdatedTimeout = 3e4;
|
|
248
|
+
var Awareness = class extends Observable {
|
|
249
|
+
/**
|
|
250
|
+
* @param {Y.Doc} doc
|
|
251
|
+
*/
|
|
252
|
+
constructor(doc) {
|
|
253
|
+
super();
|
|
254
|
+
this.doc = doc;
|
|
255
|
+
this.clientID = doc.clientID;
|
|
256
|
+
this.states = /* @__PURE__ */ new Map();
|
|
257
|
+
this.meta = /* @__PURE__ */ new Map();
|
|
258
|
+
this._checkInterval = /** @type {any} */
|
|
259
|
+
setInterval(() => {
|
|
260
|
+
const now = getUnixTime();
|
|
261
|
+
if (this.getLocalState() !== null && outdatedTimeout / 2 <= now - /** @type {{lastUpdated:number}} */
|
|
262
|
+
this.meta.get(this.clientID).lastUpdated) {
|
|
263
|
+
this.setLocalState(this.getLocalState());
|
|
264
|
+
}
|
|
265
|
+
const remove = [];
|
|
266
|
+
this.meta.forEach((meta, clientid) => {
|
|
267
|
+
if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) {
|
|
268
|
+
remove.push(clientid);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
if (remove.length > 0) {
|
|
272
|
+
removeAwarenessStates(this, remove, "timeout");
|
|
273
|
+
}
|
|
274
|
+
}, floor(outdatedTimeout / 10));
|
|
275
|
+
doc.on("destroy", () => {
|
|
276
|
+
this.destroy();
|
|
277
|
+
});
|
|
278
|
+
this.setLocalState({});
|
|
279
|
+
}
|
|
280
|
+
destroy() {
|
|
281
|
+
this.emit("destroy", [this]);
|
|
282
|
+
this.setLocalState(null);
|
|
283
|
+
super.destroy();
|
|
284
|
+
clearInterval(this._checkInterval);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* @return {Object<string,any>|null}
|
|
288
|
+
*/
|
|
289
|
+
getLocalState() {
|
|
290
|
+
return this.states.get(this.clientID) || null;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* @param {Object<string,any>|null} state
|
|
294
|
+
*/
|
|
295
|
+
setLocalState(state) {
|
|
296
|
+
const clientID = this.clientID;
|
|
297
|
+
const currLocalMeta = this.meta.get(clientID);
|
|
298
|
+
const clock = currLocalMeta === void 0 ? 0 : currLocalMeta.clock + 1;
|
|
299
|
+
const prevState = this.states.get(clientID);
|
|
300
|
+
if (state === null) {
|
|
301
|
+
this.states.delete(clientID);
|
|
302
|
+
} else {
|
|
303
|
+
this.states.set(clientID, state);
|
|
304
|
+
}
|
|
305
|
+
this.meta.set(clientID, {
|
|
306
|
+
clock,
|
|
307
|
+
lastUpdated: getUnixTime()
|
|
308
|
+
});
|
|
309
|
+
const added = [];
|
|
310
|
+
const updated = [];
|
|
311
|
+
const filteredUpdated = [];
|
|
312
|
+
const removed = [];
|
|
313
|
+
if (state === null) {
|
|
314
|
+
removed.push(clientID);
|
|
315
|
+
} else if (prevState == null) {
|
|
316
|
+
if (state != null) {
|
|
317
|
+
added.push(clientID);
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
updated.push(clientID);
|
|
321
|
+
if (!equalityDeep(prevState, state)) {
|
|
322
|
+
filteredUpdated.push(clientID);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
|
|
326
|
+
this.emit("change", [{ added, updated: filteredUpdated, removed }, "local"]);
|
|
327
|
+
}
|
|
328
|
+
this.emit("update", [{ added, updated, removed }, "local"]);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* @param {string} field
|
|
332
|
+
* @param {any} value
|
|
333
|
+
*/
|
|
334
|
+
setLocalStateField(field, value) {
|
|
335
|
+
const state = this.getLocalState();
|
|
336
|
+
if (state !== null) {
|
|
337
|
+
this.setLocalState({
|
|
338
|
+
...state,
|
|
339
|
+
[field]: value
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* @return {Map<number,Object<string,any>>}
|
|
345
|
+
*/
|
|
346
|
+
getStates() {
|
|
347
|
+
return this.states;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var removeAwarenessStates = (awareness, clients, origin) => {
|
|
351
|
+
const removed = [];
|
|
352
|
+
for (let i = 0; i < clients.length; i++) {
|
|
353
|
+
const clientID = clients[i];
|
|
354
|
+
if (awareness.states.has(clientID)) {
|
|
355
|
+
awareness.states.delete(clientID);
|
|
356
|
+
if (clientID === awareness.clientID) {
|
|
357
|
+
const curMeta = (
|
|
358
|
+
/** @type {MetaClientState} */
|
|
359
|
+
awareness.meta.get(clientID)
|
|
360
|
+
);
|
|
361
|
+
awareness.meta.set(clientID, {
|
|
362
|
+
clock: curMeta.clock + 1,
|
|
363
|
+
lastUpdated: getUnixTime()
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
removed.push(clientID);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (removed.length > 0) {
|
|
370
|
+
awareness.emit("change", [{ added: [], updated: [], removed }, origin]);
|
|
371
|
+
awareness.emit("update", [{ added: [], updated: [], removed }, origin]);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// src/roomState.ts
|
|
19
376
|
import WebSocket from "ws";
|
|
20
377
|
|
|
21
378
|
// ../shared/src/editor-extensions.ts
|
|
@@ -445,10 +802,10 @@ function findDiffEnd(a, b, posA, posB) {
|
|
|
445
802
|
for (let iA = a.childCount, iB = b.childCount; ; ) {
|
|
446
803
|
if (iA == 0 || iB == 0)
|
|
447
804
|
return iA == iB ? null : { a: posA, b: posB };
|
|
448
|
-
let childA = a.child(--iA), childB = b.child(--iB),
|
|
805
|
+
let childA = a.child(--iA), childB = b.child(--iB), size2 = childA.nodeSize;
|
|
449
806
|
if (childA == childB) {
|
|
450
|
-
posA -=
|
|
451
|
-
posB -=
|
|
807
|
+
posA -= size2;
|
|
808
|
+
posB -= size2;
|
|
452
809
|
continue;
|
|
453
810
|
}
|
|
454
811
|
if (!childA.sameMarkup(childB))
|
|
@@ -467,18 +824,18 @@ function findDiffEnd(a, b, posA, posB) {
|
|
|
467
824
|
if (inner)
|
|
468
825
|
return inner;
|
|
469
826
|
}
|
|
470
|
-
posA -=
|
|
471
|
-
posB -=
|
|
827
|
+
posA -= size2;
|
|
828
|
+
posB -= size2;
|
|
472
829
|
}
|
|
473
830
|
}
|
|
474
831
|
var Fragment = class _Fragment {
|
|
475
832
|
/**
|
|
476
833
|
@internal
|
|
477
834
|
*/
|
|
478
|
-
constructor(content,
|
|
835
|
+
constructor(content, size2) {
|
|
479
836
|
this.content = content;
|
|
480
|
-
this.size =
|
|
481
|
-
if (
|
|
837
|
+
this.size = size2 || 0;
|
|
838
|
+
if (size2 == null)
|
|
482
839
|
for (let i = 0; i < content.length; i++)
|
|
483
840
|
this.size += content[i].nodeSize;
|
|
484
841
|
}
|
|
@@ -487,12 +844,12 @@ var Fragment = class _Fragment {
|
|
|
487
844
|
positions (relative to start of this fragment). Doesn't descend
|
|
488
845
|
into a node when the callback returns `false`.
|
|
489
846
|
*/
|
|
490
|
-
nodesBetween(
|
|
847
|
+
nodesBetween(from2, to, f, nodeStart = 0, parent) {
|
|
491
848
|
for (let i = 0, pos = 0; pos < to; i++) {
|
|
492
849
|
let child = this.content[i], end = pos + child.nodeSize;
|
|
493
|
-
if (end >
|
|
850
|
+
if (end > from2 && f(child, nodeStart + pos, parent || null, i) !== false && child.content.size) {
|
|
494
851
|
let start = pos + 1;
|
|
495
|
-
child.nodesBetween(Math.max(0,
|
|
852
|
+
child.nodesBetween(Math.max(0, from2 - start), Math.min(child.content.size, to - start), f, nodeStart + start);
|
|
496
853
|
}
|
|
497
854
|
pos = end;
|
|
498
855
|
}
|
|
@@ -509,10 +866,10 @@ var Fragment = class _Fragment {
|
|
|
509
866
|
Extract the text between `from` and `to`. See the same method on
|
|
510
867
|
[`Node`](https://prosemirror.net/docs/ref/#model.Node.textBetween).
|
|
511
868
|
*/
|
|
512
|
-
textBetween(
|
|
869
|
+
textBetween(from2, to, blockSeparator, leafText) {
|
|
513
870
|
let text = "", first = true;
|
|
514
|
-
this.nodesBetween(
|
|
515
|
-
let nodeText = node.isText ? node.text.slice(Math.max(
|
|
871
|
+
this.nodesBetween(from2, to, (node, pos) => {
|
|
872
|
+
let nodeText = node.isText ? node.text.slice(Math.max(from2, pos) - pos, to - pos) : !node.isLeaf ? "" : leafText ? typeof leafText === "function" ? leafText(node) : leafText : node.type.spec.leafText ? node.type.spec.leafText(node) : "";
|
|
516
873
|
if (node.isBlock && (node.isLeaf && nodeText || node.isTextblock) && blockSeparator) {
|
|
517
874
|
if (first)
|
|
518
875
|
first = false;
|
|
@@ -544,36 +901,36 @@ var Fragment = class _Fragment {
|
|
|
544
901
|
/**
|
|
545
902
|
Cut out the sub-fragment between the two given positions.
|
|
546
903
|
*/
|
|
547
|
-
cut(
|
|
548
|
-
if (
|
|
904
|
+
cut(from2, to = this.size) {
|
|
905
|
+
if (from2 == 0 && to == this.size)
|
|
549
906
|
return this;
|
|
550
|
-
let result = [],
|
|
551
|
-
if (to >
|
|
907
|
+
let result = [], size2 = 0;
|
|
908
|
+
if (to > from2)
|
|
552
909
|
for (let i = 0, pos = 0; pos < to; i++) {
|
|
553
910
|
let child = this.content[i], end = pos + child.nodeSize;
|
|
554
|
-
if (end >
|
|
555
|
-
if (pos <
|
|
911
|
+
if (end > from2) {
|
|
912
|
+
if (pos < from2 || end > to) {
|
|
556
913
|
if (child.isText)
|
|
557
|
-
child = child.cut(Math.max(0,
|
|
914
|
+
child = child.cut(Math.max(0, from2 - pos), Math.min(child.text.length, to - pos));
|
|
558
915
|
else
|
|
559
|
-
child = child.cut(Math.max(0,
|
|
916
|
+
child = child.cut(Math.max(0, from2 - pos - 1), Math.min(child.content.size, to - pos - 1));
|
|
560
917
|
}
|
|
561
918
|
result.push(child);
|
|
562
|
-
|
|
919
|
+
size2 += child.nodeSize;
|
|
563
920
|
}
|
|
564
921
|
pos = end;
|
|
565
922
|
}
|
|
566
|
-
return new _Fragment(result,
|
|
923
|
+
return new _Fragment(result, size2);
|
|
567
924
|
}
|
|
568
925
|
/**
|
|
569
926
|
@internal
|
|
570
927
|
*/
|
|
571
|
-
cutByIndex(
|
|
572
|
-
if (
|
|
928
|
+
cutByIndex(from2, to) {
|
|
929
|
+
if (from2 == to)
|
|
573
930
|
return _Fragment.empty;
|
|
574
|
-
if (
|
|
931
|
+
if (from2 == 0 && to == this.content.length)
|
|
575
932
|
return this;
|
|
576
|
-
return new _Fragment(this.content.slice(
|
|
933
|
+
return new _Fragment(this.content.slice(from2, to));
|
|
577
934
|
}
|
|
578
935
|
/**
|
|
579
936
|
Create a new fragment in which the node at the given index is
|
|
@@ -584,9 +941,9 @@ var Fragment = class _Fragment {
|
|
|
584
941
|
if (current == node)
|
|
585
942
|
return this;
|
|
586
943
|
let copy = this.content.slice();
|
|
587
|
-
let
|
|
944
|
+
let size2 = this.size + node.nodeSize - current.nodeSize;
|
|
588
945
|
copy[index] = node;
|
|
589
|
-
return new _Fragment(copy,
|
|
946
|
+
return new _Fragment(copy, size2);
|
|
590
947
|
}
|
|
591
948
|
/**
|
|
592
949
|
Create a new fragment by prepending the given node to this
|
|
@@ -731,10 +1088,10 @@ var Fragment = class _Fragment {
|
|
|
731
1088
|
static fromArray(array) {
|
|
732
1089
|
if (!array.length)
|
|
733
1090
|
return _Fragment.empty;
|
|
734
|
-
let joined,
|
|
1091
|
+
let joined, size2 = 0;
|
|
735
1092
|
for (let i = 0; i < array.length; i++) {
|
|
736
1093
|
let node = array[i];
|
|
737
|
-
|
|
1094
|
+
size2 += node.nodeSize;
|
|
738
1095
|
if (i && node.isText && array[i - 1].sameMarkup(node)) {
|
|
739
1096
|
if (!joined)
|
|
740
1097
|
joined = array.slice(0, i);
|
|
@@ -743,7 +1100,7 @@ var Fragment = class _Fragment {
|
|
|
743
1100
|
joined.push(node);
|
|
744
1101
|
}
|
|
745
1102
|
}
|
|
746
|
-
return new _Fragment(joined || array,
|
|
1103
|
+
return new _Fragment(joined || array, size2);
|
|
747
1104
|
}
|
|
748
1105
|
/**
|
|
749
1106
|
Create a fragment from something that can be interpreted as a
|
|
@@ -951,8 +1308,8 @@ var Slice = class _Slice {
|
|
|
951
1308
|
/**
|
|
952
1309
|
@internal
|
|
953
1310
|
*/
|
|
954
|
-
removeBetween(
|
|
955
|
-
return new _Slice(removeRange(this.content,
|
|
1311
|
+
removeBetween(from2, to) {
|
|
1312
|
+
return new _Slice(removeRange(this.content, from2 + this.openStart, to + this.openStart), this.openStart, this.openEnd);
|
|
956
1313
|
}
|
|
957
1314
|
/**
|
|
958
1315
|
Tests whether this slice is equal to another slice.
|
|
@@ -1004,17 +1361,17 @@ var Slice = class _Slice {
|
|
|
1004
1361
|
}
|
|
1005
1362
|
};
|
|
1006
1363
|
Slice.empty = new Slice(Fragment.empty, 0, 0);
|
|
1007
|
-
function removeRange(content,
|
|
1008
|
-
let { index, offset } = content.findIndex(
|
|
1364
|
+
function removeRange(content, from2, to) {
|
|
1365
|
+
let { index, offset } = content.findIndex(from2), child = content.maybeChild(index);
|
|
1009
1366
|
let { index: indexTo, offset: offsetTo } = content.findIndex(to);
|
|
1010
|
-
if (offset ==
|
|
1367
|
+
if (offset == from2 || child.isText) {
|
|
1011
1368
|
if (offsetTo != to && !content.child(indexTo).isText)
|
|
1012
1369
|
throw new RangeError("Removing non-flat range");
|
|
1013
|
-
return content.cut(0,
|
|
1370
|
+
return content.cut(0, from2).append(content.cut(to));
|
|
1014
1371
|
}
|
|
1015
1372
|
if (index != indexTo)
|
|
1016
1373
|
throw new RangeError("Removing non-flat range");
|
|
1017
|
-
return content.replaceChild(index, child.copy(removeRange(child.content,
|
|
1374
|
+
return content.replaceChild(index, child.copy(removeRange(child.content, from2 - offset - 1, to - offset - 1)));
|
|
1018
1375
|
}
|
|
1019
1376
|
function insertInto(content, dist, insert, parent) {
|
|
1020
1377
|
let { index, offset } = content.findIndex(dist), child = content.maybeChild(index);
|
|
@@ -1510,8 +1867,8 @@ var Node2 = class _Node {
|
|
|
1510
1867
|
recursed over. The last parameter can be used to specify a
|
|
1511
1868
|
starting position to count from.
|
|
1512
1869
|
*/
|
|
1513
|
-
nodesBetween(
|
|
1514
|
-
this.content.nodesBetween(
|
|
1870
|
+
nodesBetween(from2, to, f, startPos = 0) {
|
|
1871
|
+
this.content.nodesBetween(from2, to, f, startPos, this);
|
|
1515
1872
|
}
|
|
1516
1873
|
/**
|
|
1517
1874
|
Call the given callback for every descendant node. Doesn't
|
|
@@ -1534,8 +1891,8 @@ var Node2 = class _Node {
|
|
|
1534
1891
|
inserted for every non-text leaf node encountered, otherwise
|
|
1535
1892
|
[`leafText`](https://prosemirror.net/docs/ref/#model.NodeSpec.leafText) will be used.
|
|
1536
1893
|
*/
|
|
1537
|
-
textBetween(
|
|
1538
|
-
return this.content.textBetween(
|
|
1894
|
+
textBetween(from2, to, blockSeparator, leafText) {
|
|
1895
|
+
return this.content.textBetween(from2, to, blockSeparator, leafText);
|
|
1539
1896
|
}
|
|
1540
1897
|
/**
|
|
1541
1898
|
Returns this node's first child, or `null` if there are no
|
|
@@ -1592,19 +1949,19 @@ var Node2 = class _Node {
|
|
|
1592
1949
|
given positions. If `to` is not given, it defaults to the end of
|
|
1593
1950
|
the node.
|
|
1594
1951
|
*/
|
|
1595
|
-
cut(
|
|
1596
|
-
if (
|
|
1952
|
+
cut(from2, to = this.content.size) {
|
|
1953
|
+
if (from2 == 0 && to == this.content.size)
|
|
1597
1954
|
return this;
|
|
1598
|
-
return this.copy(this.content.cut(
|
|
1955
|
+
return this.copy(this.content.cut(from2, to));
|
|
1599
1956
|
}
|
|
1600
1957
|
/**
|
|
1601
1958
|
Cut out the part of the document between the given positions, and
|
|
1602
1959
|
return it as a `Slice` object.
|
|
1603
1960
|
*/
|
|
1604
|
-
slice(
|
|
1605
|
-
if (
|
|
1961
|
+
slice(from2, to = this.content.size, includeParents = false) {
|
|
1962
|
+
if (from2 == to)
|
|
1606
1963
|
return Slice.empty;
|
|
1607
|
-
let $from = this.resolve(
|
|
1964
|
+
let $from = this.resolve(from2), $to = this.resolve(to);
|
|
1608
1965
|
let depth = includeParents ? 0 : $from.sharedDepth(to);
|
|
1609
1966
|
let start = $from.start(depth), node = $from.node(depth);
|
|
1610
1967
|
let content = node.content.cut($from.pos - start, $to.pos - start);
|
|
@@ -1618,8 +1975,8 @@ var Node2 = class _Node {
|
|
|
1618
1975
|
into. If any of this is violated, an error of type
|
|
1619
1976
|
[`ReplaceError`](https://prosemirror.net/docs/ref/#model.ReplaceError) is thrown.
|
|
1620
1977
|
*/
|
|
1621
|
-
replace(
|
|
1622
|
-
return replace(this.resolve(
|
|
1978
|
+
replace(from2, to, slice) {
|
|
1979
|
+
return replace(this.resolve(from2), this.resolve(to), slice);
|
|
1623
1980
|
}
|
|
1624
1981
|
/**
|
|
1625
1982
|
Find the node directly after the given position.
|
|
@@ -1675,10 +2032,10 @@ var Node2 = class _Node {
|
|
|
1675
2032
|
Test whether a given mark or mark type occurs in this document
|
|
1676
2033
|
between the two given positions.
|
|
1677
2034
|
*/
|
|
1678
|
-
rangeHasMark(
|
|
2035
|
+
rangeHasMark(from2, to, type) {
|
|
1679
2036
|
let found2 = false;
|
|
1680
|
-
if (to >
|
|
1681
|
-
this.nodesBetween(
|
|
2037
|
+
if (to > from2)
|
|
2038
|
+
this.nodesBetween(from2, to, (node) => {
|
|
1682
2039
|
if (type.isInSet(node.marks))
|
|
1683
2040
|
found2 = true;
|
|
1684
2041
|
return !found2;
|
|
@@ -1761,8 +2118,8 @@ var Node2 = class _Node {
|
|
|
1761
2118
|
can optionally pass `start` and `end` indices into the
|
|
1762
2119
|
replacement fragment.
|
|
1763
2120
|
*/
|
|
1764
|
-
canReplace(
|
|
1765
|
-
let one = this.contentMatchAt(
|
|
2121
|
+
canReplace(from2, to, replacement = Fragment.empty, start = 0, end = replacement.childCount) {
|
|
2122
|
+
let one = this.contentMatchAt(from2).matchFragment(replacement, start, end);
|
|
1766
2123
|
let two = one && one.matchFragment(this.content, to);
|
|
1767
2124
|
if (!two || !two.validEnd)
|
|
1768
2125
|
return false;
|
|
@@ -1775,10 +2132,10 @@ var Node2 = class _Node {
|
|
|
1775
2132
|
Test whether replacing the range `from` to `to` (by index) with
|
|
1776
2133
|
a node of the given type would leave the node's content valid.
|
|
1777
2134
|
*/
|
|
1778
|
-
canReplaceWith(
|
|
2135
|
+
canReplaceWith(from2, to, type, marks) {
|
|
1779
2136
|
if (marks && !this.type.allowsMarks(marks))
|
|
1780
2137
|
return false;
|
|
1781
|
-
let start = this.contentMatchAt(
|
|
2138
|
+
let start = this.contentMatchAt(from2).matchType(type);
|
|
1782
2139
|
let end = start && start.matchFragment(this.content, to);
|
|
1783
2140
|
return end ? end.validEnd : false;
|
|
1784
2141
|
}
|
|
@@ -2139,38 +2496,38 @@ function nfa(expr) {
|
|
|
2139
2496
|
function node() {
|
|
2140
2497
|
return nfa2.push([]) - 1;
|
|
2141
2498
|
}
|
|
2142
|
-
function edge(
|
|
2499
|
+
function edge(from2, to, term) {
|
|
2143
2500
|
let edge2 = { term, to };
|
|
2144
|
-
nfa2[
|
|
2501
|
+
nfa2[from2].push(edge2);
|
|
2145
2502
|
return edge2;
|
|
2146
2503
|
}
|
|
2147
2504
|
function connect(edges, to) {
|
|
2148
2505
|
edges.forEach((edge2) => edge2.to = to);
|
|
2149
2506
|
}
|
|
2150
|
-
function compile(expr2,
|
|
2507
|
+
function compile(expr2, from2) {
|
|
2151
2508
|
if (expr2.type == "choice") {
|
|
2152
|
-
return expr2.exprs.reduce((out, expr3) => out.concat(compile(expr3,
|
|
2509
|
+
return expr2.exprs.reduce((out, expr3) => out.concat(compile(expr3, from2)), []);
|
|
2153
2510
|
} else if (expr2.type == "seq") {
|
|
2154
2511
|
for (let i = 0; ; i++) {
|
|
2155
|
-
let next = compile(expr2.exprs[i],
|
|
2512
|
+
let next = compile(expr2.exprs[i], from2);
|
|
2156
2513
|
if (i == expr2.exprs.length - 1)
|
|
2157
2514
|
return next;
|
|
2158
|
-
connect(next,
|
|
2515
|
+
connect(next, from2 = node());
|
|
2159
2516
|
}
|
|
2160
2517
|
} else if (expr2.type == "star") {
|
|
2161
2518
|
let loop = node();
|
|
2162
|
-
edge(
|
|
2519
|
+
edge(from2, loop);
|
|
2163
2520
|
connect(compile(expr2.expr, loop), loop);
|
|
2164
2521
|
return [edge(loop)];
|
|
2165
2522
|
} else if (expr2.type == "plus") {
|
|
2166
2523
|
let loop = node();
|
|
2167
|
-
connect(compile(expr2.expr,
|
|
2524
|
+
connect(compile(expr2.expr, from2), loop);
|
|
2168
2525
|
connect(compile(expr2.expr, loop), loop);
|
|
2169
2526
|
return [edge(loop)];
|
|
2170
2527
|
} else if (expr2.type == "opt") {
|
|
2171
|
-
return [edge(
|
|
2528
|
+
return [edge(from2)].concat(compile(expr2.expr, from2));
|
|
2172
2529
|
} else if (expr2.type == "range") {
|
|
2173
|
-
let cur =
|
|
2530
|
+
let cur = from2;
|
|
2174
2531
|
for (let i = 0; i < expr2.min; i++) {
|
|
2175
2532
|
let next = node();
|
|
2176
2533
|
connect(compile(expr2.expr, cur), next);
|
|
@@ -2188,7 +2545,7 @@ function nfa(expr) {
|
|
|
2188
2545
|
}
|
|
2189
2546
|
return [edge(cur)];
|
|
2190
2547
|
} else if (expr2.type == "name") {
|
|
2191
|
-
return [edge(
|
|
2548
|
+
return [edge(from2, void 0, expr2.value)];
|
|
2192
2549
|
} else {
|
|
2193
2550
|
throw new Error("Unknown expr type");
|
|
2194
2551
|
}
|
|
@@ -2487,9 +2844,9 @@ var StepResult = class _StepResult {
|
|
|
2487
2844
|
arguments. Create a successful result if it succeeds, and a
|
|
2488
2845
|
failed one if it throws a `ReplaceError`.
|
|
2489
2846
|
*/
|
|
2490
|
-
static fromReplace(doc,
|
|
2847
|
+
static fromReplace(doc, from2, to, slice) {
|
|
2491
2848
|
try {
|
|
2492
|
-
return _StepResult.ok(doc.replace(
|
|
2849
|
+
return _StepResult.ok(doc.replace(from2, to, slice));
|
|
2493
2850
|
} catch (e) {
|
|
2494
2851
|
if (e instanceof ReplaceError)
|
|
2495
2852
|
return _StepResult.fail(e.message);
|
|
@@ -2513,9 +2870,9 @@ var AddMarkStep = class _AddMarkStep extends Step {
|
|
|
2513
2870
|
/**
|
|
2514
2871
|
Create a mark step.
|
|
2515
2872
|
*/
|
|
2516
|
-
constructor(
|
|
2873
|
+
constructor(from2, to, mark) {
|
|
2517
2874
|
super();
|
|
2518
|
-
this.from =
|
|
2875
|
+
this.from = from2;
|
|
2519
2876
|
this.to = to;
|
|
2520
2877
|
this.mark = mark;
|
|
2521
2878
|
}
|
|
@@ -2533,10 +2890,10 @@ var AddMarkStep = class _AddMarkStep extends Step {
|
|
|
2533
2890
|
return new RemoveMarkStep(this.from, this.to, this.mark);
|
|
2534
2891
|
}
|
|
2535
2892
|
map(mapping) {
|
|
2536
|
-
let
|
|
2537
|
-
if (
|
|
2893
|
+
let from2 = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
|
|
2894
|
+
if (from2.deleted && to.deleted || from2.pos >= to.pos)
|
|
2538
2895
|
return null;
|
|
2539
|
-
return new _AddMarkStep(
|
|
2896
|
+
return new _AddMarkStep(from2.pos, to.pos, this.mark);
|
|
2540
2897
|
}
|
|
2541
2898
|
merge(other) {
|
|
2542
2899
|
if (other instanceof _AddMarkStep && other.mark.eq(this.mark) && this.from <= other.to && this.to >= other.from)
|
|
@@ -2565,9 +2922,9 @@ var RemoveMarkStep = class _RemoveMarkStep extends Step {
|
|
|
2565
2922
|
/**
|
|
2566
2923
|
Create a mark-removing step.
|
|
2567
2924
|
*/
|
|
2568
|
-
constructor(
|
|
2925
|
+
constructor(from2, to, mark) {
|
|
2569
2926
|
super();
|
|
2570
|
-
this.from =
|
|
2927
|
+
this.from = from2;
|
|
2571
2928
|
this.to = to;
|
|
2572
2929
|
this.mark = mark;
|
|
2573
2930
|
}
|
|
@@ -2582,10 +2939,10 @@ var RemoveMarkStep = class _RemoveMarkStep extends Step {
|
|
|
2582
2939
|
return new AddMarkStep(this.from, this.to, this.mark);
|
|
2583
2940
|
}
|
|
2584
2941
|
map(mapping) {
|
|
2585
|
-
let
|
|
2586
|
-
if (
|
|
2942
|
+
let from2 = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
|
|
2943
|
+
if (from2.deleted && to.deleted || from2.pos >= to.pos)
|
|
2587
2944
|
return null;
|
|
2588
|
-
return new _RemoveMarkStep(
|
|
2945
|
+
return new _RemoveMarkStep(from2.pos, to.pos, this.mark);
|
|
2589
2946
|
}
|
|
2590
2947
|
merge(other) {
|
|
2591
2948
|
if (other instanceof _RemoveMarkStep && other.mark.eq(this.mark) && this.from <= other.to && this.to >= other.from)
|
|
@@ -2705,9 +3062,9 @@ var ReplaceStep = class _ReplaceStep extends Step {
|
|
|
2705
3062
|
tokens (this is to guard against rebased replace steps
|
|
2706
3063
|
overwriting something they weren't supposed to).
|
|
2707
3064
|
*/
|
|
2708
|
-
constructor(
|
|
3065
|
+
constructor(from2, to, slice, structure = false) {
|
|
2709
3066
|
super();
|
|
2710
|
-
this.from =
|
|
3067
|
+
this.from = from2;
|
|
2711
3068
|
this.to = to;
|
|
2712
3069
|
this.slice = slice;
|
|
2713
3070
|
this.structure = structure;
|
|
@@ -2725,10 +3082,10 @@ var ReplaceStep = class _ReplaceStep extends Step {
|
|
|
2725
3082
|
}
|
|
2726
3083
|
map(mapping) {
|
|
2727
3084
|
let to = mapping.mapResult(this.to, -1);
|
|
2728
|
-
let
|
|
2729
|
-
if (
|
|
3085
|
+
let from2 = this.from == this.to && _ReplaceStep.MAP_BIAS < 0 ? to : mapping.mapResult(this.from, 1);
|
|
3086
|
+
if (from2.deletedAcross && to.deletedAcross)
|
|
2730
3087
|
return null;
|
|
2731
|
-
return new _ReplaceStep(
|
|
3088
|
+
return new _ReplaceStep(from2.pos, Math.max(from2.pos, to.pos), this.slice, this.structure);
|
|
2732
3089
|
}
|
|
2733
3090
|
merge(other) {
|
|
2734
3091
|
if (!(other instanceof _ReplaceStep) || other.structure || this.structure)
|
|
@@ -2769,9 +3126,9 @@ var ReplaceAroundStep = class _ReplaceAroundStep extends Step {
|
|
|
2769
3126
|
of the gap should be moved. `structure` has the same meaning as
|
|
2770
3127
|
it has in the [`ReplaceStep`](https://prosemirror.net/docs/ref/#transform.ReplaceStep) class.
|
|
2771
3128
|
*/
|
|
2772
|
-
constructor(
|
|
3129
|
+
constructor(from2, to, gapFrom, gapTo, slice, insert, structure = false) {
|
|
2773
3130
|
super();
|
|
2774
|
-
this.from =
|
|
3131
|
+
this.from = from2;
|
|
2775
3132
|
this.to = to;
|
|
2776
3133
|
this.gapFrom = gapFrom;
|
|
2777
3134
|
this.gapTo = gapTo;
|
|
@@ -2805,12 +3162,12 @@ var ReplaceAroundStep = class _ReplaceAroundStep extends Step {
|
|
|
2805
3162
|
return new _ReplaceAroundStep(this.from, this.from + this.slice.size + gap, this.from + this.insert, this.from + this.insert + gap, doc.slice(this.from, this.to).removeBetween(this.gapFrom - this.from, this.gapTo - this.from), this.gapFrom - this.from, this.structure);
|
|
2806
3163
|
}
|
|
2807
3164
|
map(mapping) {
|
|
2808
|
-
let
|
|
2809
|
-
let gapFrom = this.from == this.gapFrom ?
|
|
3165
|
+
let from2 = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
|
|
3166
|
+
let gapFrom = this.from == this.gapFrom ? from2.pos : mapping.map(this.gapFrom, -1);
|
|
2810
3167
|
let gapTo = this.to == this.gapTo ? to.pos : mapping.map(this.gapTo, 1);
|
|
2811
|
-
if (
|
|
3168
|
+
if (from2.deletedAcross && to.deletedAcross || gapFrom < from2.pos || gapTo > to.pos)
|
|
2812
3169
|
return null;
|
|
2813
|
-
return new _ReplaceAroundStep(
|
|
3170
|
+
return new _ReplaceAroundStep(from2.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure);
|
|
2814
3171
|
}
|
|
2815
3172
|
toJSON() {
|
|
2816
3173
|
let json = {
|
|
@@ -2837,8 +3194,8 @@ var ReplaceAroundStep = class _ReplaceAroundStep extends Step {
|
|
|
2837
3194
|
}
|
|
2838
3195
|
};
|
|
2839
3196
|
Step.jsonID("replaceAround", ReplaceAroundStep);
|
|
2840
|
-
function contentBetween(doc,
|
|
2841
|
-
let $from = doc.resolve(
|
|
3197
|
+
function contentBetween(doc, from2, to) {
|
|
3198
|
+
let $from = doc.resolve(from2), dist = to - from2, depth = $from.depth;
|
|
2842
3199
|
while (dist > 0 && depth > 0 && $from.indexAfter(depth) == $from.node(depth).childCount) {
|
|
2843
3200
|
depth--;
|
|
2844
3201
|
dist--;
|
|
@@ -3033,11 +3390,11 @@ var Selection = class {
|
|
|
3033
3390
|
let mapFrom = tr.steps.length, ranges = this.ranges;
|
|
3034
3391
|
for (let i = 0; i < ranges.length; i++) {
|
|
3035
3392
|
let { $from, $to } = ranges[i], mapping = tr.mapping.slice(mapFrom);
|
|
3036
|
-
let
|
|
3393
|
+
let from2 = mapping.map($from.pos), to = mapping.map($to.pos);
|
|
3037
3394
|
if (i) {
|
|
3038
|
-
tr.deleteRange(
|
|
3395
|
+
tr.deleteRange(from2, to);
|
|
3039
3396
|
} else {
|
|
3040
|
-
tr.replaceRangeWith(
|
|
3397
|
+
tr.replaceRangeWith(from2, to, node);
|
|
3041
3398
|
selectionToInsertionEnd(tr, mapFrom, node.isInline ? -1 : 1);
|
|
3042
3399
|
}
|
|
3043
3400
|
}
|
|
@@ -3279,8 +3636,8 @@ var NodeSelection = class _NodeSelection extends Selection {
|
|
|
3279
3636
|
/**
|
|
3280
3637
|
Create a node selection from non-resolved positions.
|
|
3281
3638
|
*/
|
|
3282
|
-
static create(doc,
|
|
3283
|
-
return new _NodeSelection(doc.resolve(
|
|
3639
|
+
static create(doc, from2) {
|
|
3640
|
+
return new _NodeSelection(doc.resolve(from2));
|
|
3284
3641
|
}
|
|
3285
3642
|
/**
|
|
3286
3643
|
Determines whether the given node may be selected as a node
|
|
@@ -3455,11 +3812,11 @@ var Plugin = class {
|
|
|
3455
3812
|
return state[this.key];
|
|
3456
3813
|
}
|
|
3457
3814
|
};
|
|
3458
|
-
var
|
|
3815
|
+
var keys2 = /* @__PURE__ */ Object.create(null);
|
|
3459
3816
|
function createKey(name) {
|
|
3460
|
-
if (name in
|
|
3461
|
-
return name + "$" + ++
|
|
3462
|
-
|
|
3817
|
+
if (name in keys2)
|
|
3818
|
+
return name + "$" + ++keys2[name];
|
|
3819
|
+
keys2[name] = 0;
|
|
3463
3820
|
return name + "$";
|
|
3464
3821
|
}
|
|
3465
3822
|
var PluginKey = class {
|
|
@@ -3535,6 +3892,18 @@ var mediaKeyboardNav_default = MediaKeyboardNav;
|
|
|
3535
3892
|
|
|
3536
3893
|
// ../shared/src/editor-extensions.ts
|
|
3537
3894
|
var CodeWithCombinableMarks = Code.extend({ excludes: "" });
|
|
3895
|
+
var TableWithId = Table.extend({
|
|
3896
|
+
addAttributes() {
|
|
3897
|
+
return {
|
|
3898
|
+
...this.parent?.() ?? {},
|
|
3899
|
+
tableId: {
|
|
3900
|
+
default: null,
|
|
3901
|
+
parseHTML: (el) => el.getAttribute("data-table-id"),
|
|
3902
|
+
renderHTML: (attrs) => attrs.tableId ? { "data-table-id": attrs.tableId } : {}
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
});
|
|
3538
3907
|
var FrontmatterSchema = CodeBlock.extend({
|
|
3539
3908
|
name: "frontmatter",
|
|
3540
3909
|
addInputRules() {
|
|
@@ -3549,7 +3918,7 @@ ${text}
|
|
|
3549
3918
|
}
|
|
3550
3919
|
});
|
|
3551
3920
|
function buildEditorExtensions(opts = {}) {
|
|
3552
|
-
const table = opts.table ??
|
|
3921
|
+
const table = opts.table ?? TableWithId;
|
|
3553
3922
|
const frontmatter = opts.frontmatter ?? FrontmatterSchema;
|
|
3554
3923
|
return [
|
|
3555
3924
|
StarterKit.configure({
|
|
@@ -3570,7 +3939,13 @@ function buildEditorExtensions(opts = {}) {
|
|
|
3570
3939
|
Highlight,
|
|
3571
3940
|
Subscript,
|
|
3572
3941
|
Superscript,
|
|
3573
|
-
table.configure({
|
|
3942
|
+
table.configure({
|
|
3943
|
+
resizable: true,
|
|
3944
|
+
handleWidth: 5,
|
|
3945
|
+
lastColumnResizable: true,
|
|
3946
|
+
cellMinWidth: 80,
|
|
3947
|
+
renderWrapper: true
|
|
3948
|
+
}),
|
|
3574
3949
|
TableRow,
|
|
3575
3950
|
TableCell,
|
|
3576
3951
|
TableHeader,
|
|
@@ -3628,7 +4003,7 @@ function markdownFromYFragment(fragment, extensions = editorExtensions) {
|
|
|
3628
4003
|
}
|
|
3629
4004
|
|
|
3630
4005
|
// ../shared/src/insertMedia.ts
|
|
3631
|
-
import { nanoid } from "nanoid";
|
|
4006
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
3632
4007
|
|
|
3633
4008
|
// ../shared/src/uploadRegistry.ts
|
|
3634
4009
|
var UploadRegistryImpl = class {
|
|
@@ -3812,11 +4187,16 @@ var UploadRegistryImpl = class {
|
|
|
3812
4187
|
var uploadRegistry = new UploadRegistryImpl();
|
|
3813
4188
|
|
|
3814
4189
|
// ../shared/src/uploadPlaceholderPlugin.ts
|
|
3815
|
-
import * as
|
|
4190
|
+
import * as Y2 from "yjs";
|
|
3816
4191
|
var uploadPlaceholderPluginKey = new PluginKey(
|
|
3817
4192
|
"uploadPlaceholder"
|
|
3818
4193
|
);
|
|
3819
4194
|
|
|
4195
|
+
// ../shared/src/awareness.ts
|
|
4196
|
+
function setLocalAwareness(awareness, key, value) {
|
|
4197
|
+
awareness.setLocalStateField(key, value);
|
|
4198
|
+
}
|
|
4199
|
+
|
|
3820
4200
|
// ../shared/src/insertMedia.ts
|
|
3821
4201
|
var IMAGE_MAX_BYTES = 25 * 1024 * 1024;
|
|
3822
4202
|
var VIDEO_MAX_BYTES = 50 * 1024 * 1024;
|
|
@@ -3825,14 +4205,14 @@ var VIDEO_MAX_BYTES = 50 * 1024 * 1024;
|
|
|
3825
4205
|
var mediaDeleteObserverKey = new PluginKey("mediaDeleteObserver");
|
|
3826
4206
|
|
|
3827
4207
|
// src/docReaders.ts
|
|
3828
|
-
import * as
|
|
4208
|
+
import * as Y3 from "yjs";
|
|
3829
4209
|
function isXmlElement(node) {
|
|
3830
|
-
return node instanceof
|
|
4210
|
+
return node instanceof Y3.XmlElement;
|
|
3831
4211
|
}
|
|
3832
4212
|
function getElementText(el) {
|
|
3833
4213
|
let out = "";
|
|
3834
4214
|
for (const child of el.toArray()) {
|
|
3835
|
-
if (child instanceof
|
|
4215
|
+
if (child instanceof Y3.XmlText) {
|
|
3836
4216
|
for (const op of child.toDelta()) {
|
|
3837
4217
|
if (typeof op.insert === "string") out += op.insert;
|
|
3838
4218
|
}
|
|
@@ -3945,13 +4325,13 @@ function serializeDocAsMarkdown(doc) {
|
|
|
3945
4325
|
}
|
|
3946
4326
|
|
|
3947
4327
|
// src/anchors.ts
|
|
3948
|
-
import * as
|
|
4328
|
+
import * as Y4 from "yjs";
|
|
3949
4329
|
function buildFlatMap(fragment) {
|
|
3950
4330
|
let flat = "";
|
|
3951
4331
|
const map = [];
|
|
3952
4332
|
const blockFlatStarts = [];
|
|
3953
4333
|
const walk = (node) => {
|
|
3954
|
-
if (node instanceof
|
|
4334
|
+
if (node instanceof Y4.XmlText) {
|
|
3955
4335
|
let localOffset = 0;
|
|
3956
4336
|
for (const op of node.toDelta()) {
|
|
3957
4337
|
const value = op.insert;
|
|
@@ -3979,7 +4359,7 @@ function buildFlatMap(fragment) {
|
|
|
3979
4359
|
return;
|
|
3980
4360
|
}
|
|
3981
4361
|
for (const child of node.toArray()) {
|
|
3982
|
-
if (child instanceof
|
|
4362
|
+
if (child instanceof Y4.XmlText || child instanceof Y4.XmlElement) {
|
|
3983
4363
|
walk(child);
|
|
3984
4364
|
}
|
|
3985
4365
|
}
|
|
@@ -3987,7 +4367,7 @@ function buildFlatMap(fragment) {
|
|
|
3987
4367
|
const topLevel = fragment.toArray();
|
|
3988
4368
|
topLevel.forEach((node, idx) => {
|
|
3989
4369
|
blockFlatStarts.push(flat.length);
|
|
3990
|
-
if (node instanceof
|
|
4370
|
+
if (node instanceof Y4.XmlText || node instanceof Y4.XmlElement) {
|
|
3991
4371
|
walk(node);
|
|
3992
4372
|
}
|
|
3993
4373
|
if (idx < topLevel.length - 1) {
|
|
@@ -4044,7 +4424,7 @@ function resolveAnchoredContext(doc, anchorFrom, anchorTo) {
|
|
|
4044
4424
|
let containingBlockIdx = -1;
|
|
4045
4425
|
for (let i = 0; i < topLevel.length; i++) {
|
|
4046
4426
|
const block = topLevel[i];
|
|
4047
|
-
if (block instanceof
|
|
4427
|
+
if (block instanceof Y4.XmlElement && containsType(block, fromAbs.type)) {
|
|
4048
4428
|
containingBlockIdx = i;
|
|
4049
4429
|
break;
|
|
4050
4430
|
}
|
|
@@ -4066,8 +4446,8 @@ function resolveAnchoredContext(doc, anchorFrom, anchorTo) {
|
|
|
4066
4446
|
function decodeAbs(doc, encoded) {
|
|
4067
4447
|
if (!encoded) return null;
|
|
4068
4448
|
try {
|
|
4069
|
-
const rel =
|
|
4070
|
-
const abs =
|
|
4449
|
+
const rel = Y4.decodeRelativePosition(encoded);
|
|
4450
|
+
const abs = Y4.createAbsolutePositionFromRelativePosition(rel, doc);
|
|
4071
4451
|
if (!abs) return null;
|
|
4072
4452
|
return { type: abs.type, index: abs.index };
|
|
4073
4453
|
} catch {
|
|
@@ -4090,7 +4470,7 @@ function flatIndexFor(map, abs) {
|
|
|
4090
4470
|
function containsType(el, target) {
|
|
4091
4471
|
for (const child of el.toArray()) {
|
|
4092
4472
|
if (child === target) return true;
|
|
4093
|
-
if (child instanceof
|
|
4473
|
+
if (child instanceof Y4.XmlElement && containsType(child, target)) {
|
|
4094
4474
|
return true;
|
|
4095
4475
|
}
|
|
4096
4476
|
}
|
|
@@ -4138,24 +4518,42 @@ function resolveServerAnchor(doc, spec) {
|
|
|
4138
4518
|
if (!lastCharEntry) {
|
|
4139
4519
|
return { ok: false, error: "text_not_found", currentSectionText };
|
|
4140
4520
|
}
|
|
4141
|
-
const fromRelPos =
|
|
4521
|
+
const fromRelPos = Y4.createRelativePositionFromTypeIndex(
|
|
4142
4522
|
startEntry.xmlText,
|
|
4143
4523
|
startEntry.offsetInText
|
|
4144
4524
|
);
|
|
4145
|
-
const toRelPos =
|
|
4525
|
+
const toRelPos = Y4.createRelativePositionFromTypeIndex(
|
|
4146
4526
|
lastCharEntry.xmlText,
|
|
4147
4527
|
lastCharEntry.offsetInText + 1
|
|
4148
4528
|
);
|
|
4149
4529
|
return {
|
|
4150
4530
|
ok: true,
|
|
4151
|
-
from:
|
|
4152
|
-
to:
|
|
4531
|
+
from: Y4.encodeRelativePosition(fromRelPos),
|
|
4532
|
+
to: Y4.encodeRelativePosition(toRelPos)
|
|
4153
4533
|
};
|
|
4154
4534
|
}
|
|
4155
4535
|
|
|
4156
4536
|
// src/roomState.ts
|
|
4157
|
-
var
|
|
4158
|
-
|
|
4537
|
+
var HAD_UPGRADE_403 = /* @__PURE__ */ Symbol("composer.upgrade403");
|
|
4538
|
+
var TerminalDetectingWS = class extends WebSocket {
|
|
4539
|
+
constructor(...args) {
|
|
4540
|
+
super(...args);
|
|
4541
|
+
this.on("unexpected-response", (_req, res) => {
|
|
4542
|
+
if (res.statusCode === 403) {
|
|
4543
|
+
this[HAD_UPGRADE_403] = true;
|
|
4544
|
+
}
|
|
4545
|
+
});
|
|
4546
|
+
}
|
|
4547
|
+
};
|
|
4548
|
+
var TERMINAL_CLOSE_CODES = /* @__PURE__ */ new Set([4403, 4410]);
|
|
4549
|
+
var MAX_CONSECUTIVE_FAILURES = 15;
|
|
4550
|
+
function reconnectDelayMs(failuresSoFar) {
|
|
4551
|
+
if (failuresSoFar <= 5) return 1e4;
|
|
4552
|
+
if (failuresSoFar <= 10) return 3e4;
|
|
4553
|
+
return 6e4;
|
|
4554
|
+
}
|
|
4555
|
+
var RoomState = class _RoomState {
|
|
4556
|
+
doc;
|
|
4159
4557
|
actingAs;
|
|
4160
4558
|
identity;
|
|
4161
4559
|
roomId;
|
|
@@ -4189,26 +4587,180 @@ var RoomState = class {
|
|
|
4189
4587
|
/**
|
|
4190
4588
|
* Set true when `composer_next_event`'s goodbye branch has fired. Tells
|
|
4191
4589
|
* `handleNextEvent` to refuse subsequent calls until the user explicitly
|
|
4192
|
-
* re-engages
|
|
4193
|
-
*
|
|
4590
|
+
* re-engages via `composer_create_room` / `composer_join_room` — both of
|
|
4591
|
+
* which clear the latch (create builds a fresh RoomState; rejoin reuses
|
|
4592
|
+
* the cached one and calls `clearGoodbye()`).
|
|
4194
4593
|
*/
|
|
4195
4594
|
goodbyeIssued = false;
|
|
4595
|
+
/**
|
|
4596
|
+
* Set when the connection enters a terminal state — either the server
|
|
4597
|
+
* explicitly rejected (HTTP 403 / close code 4403 / 4410) or the client
|
|
4598
|
+
* circuit breaker tripped after too many consecutive failures. Once set,
|
|
4599
|
+
* `ensureConnected` / `waitForInitialSync` reject immediately and tool
|
|
4600
|
+
* calls surface a clear error rather than the host silently hanging.
|
|
4601
|
+
*/
|
|
4602
|
+
terminalReject = null;
|
|
4603
|
+
/**
|
|
4604
|
+
* Listeners that fire when terminal state is signalled. Used to unblock
|
|
4605
|
+
* pending `waitForInitialSync` promises so the host doesn't hang for the
|
|
4606
|
+
* full 15s timeout after a kick.
|
|
4607
|
+
*/
|
|
4608
|
+
terminalRejectListeners = /* @__PURE__ */ new Set();
|
|
4609
|
+
/**
|
|
4610
|
+
* Counter for consecutive failed reconnects. Reset to 0 on a successful
|
|
4611
|
+
* `connected` status event. Per the design, does NOT reset on idle
|
|
4612
|
+
* disconnects — only on a real successful open.
|
|
4613
|
+
*/
|
|
4614
|
+
consecutiveFailures = 0;
|
|
4615
|
+
/**
|
|
4616
|
+
* Pending manually-scheduled reconnect timer (we replace y-partyserver's
|
|
4617
|
+
* default exponential backoff with the tiered schedule from the design).
|
|
4618
|
+
* Cleared on destroy and on each new close event.
|
|
4619
|
+
*/
|
|
4620
|
+
scheduledReconnect = null;
|
|
4196
4621
|
constructor(opts) {
|
|
4197
4622
|
this.roomId = opts.roomId;
|
|
4198
4623
|
this.actingAs = opts.actingAs;
|
|
4199
4624
|
this.identity = opts.identity;
|
|
4625
|
+
this.doc = opts._docForTest ?? new Y5.Doc();
|
|
4200
4626
|
this.watchMentions();
|
|
4201
4627
|
attachRemoteActivityTracker(this.doc, {
|
|
4202
4628
|
onActivity: (at) => {
|
|
4203
4629
|
this._lastRemoteActivityAt = at;
|
|
4204
4630
|
}
|
|
4205
4631
|
});
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4632
|
+
if (opts._providerForTest) {
|
|
4633
|
+
this.provider = opts._providerForTest;
|
|
4634
|
+
} else {
|
|
4635
|
+
this.provider = new YProvider(opts.serverHost, opts.roomId, this.doc, {
|
|
4636
|
+
party: "composer-room",
|
|
4637
|
+
connect: false,
|
|
4638
|
+
WebSocketPolyfill: TerminalDetectingWS
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
if (!opts._providerForTest) {
|
|
4642
|
+
this.provider.on(
|
|
4643
|
+
"connection-close",
|
|
4644
|
+
(...args) => {
|
|
4645
|
+
const event = args[0];
|
|
4646
|
+
if (this.terminalReject) return;
|
|
4647
|
+
if (!this.provider.shouldConnect) {
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
4650
|
+
const ws = this.provider.ws;
|
|
4651
|
+
const had403 = ws ? !!ws[HAD_UPGRADE_403] : false;
|
|
4652
|
+
if (had403 || TERMINAL_CLOSE_CODES.has(event.code)) {
|
|
4653
|
+
this.signalTerminalReject({
|
|
4654
|
+
kind: "rejected",
|
|
4655
|
+
code: had403 ? 403 : event.code,
|
|
4656
|
+
reason: had403 ? "server refused upgrade (HTTP 403). composer-mcp may be out of date or this client is blocked. Update composer-mcp or contact the room owner." : event.code === 4410 ? `server has killed this client version. ${event.reason || "Please update composer-mcp."}` : `server kicked this client (close code ${event.code}). ${event.reason || "Please update composer-mcp."}`
|
|
4657
|
+
});
|
|
4658
|
+
return;
|
|
4659
|
+
}
|
|
4660
|
+
this.consecutiveFailures++;
|
|
4661
|
+
if (this.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
4662
|
+
this.signalTerminalReject({
|
|
4663
|
+
kind: "aborted",
|
|
4664
|
+
failures: this.consecutiveFailures
|
|
4665
|
+
});
|
|
4666
|
+
return;
|
|
4667
|
+
}
|
|
4668
|
+
this.provider.shouldConnect = false;
|
|
4669
|
+
if (this.scheduledReconnect) clearTimeout(this.scheduledReconnect);
|
|
4670
|
+
const delay = reconnectDelayMs(this.consecutiveFailures);
|
|
4671
|
+
this.scheduledReconnect = setTimeout(() => {
|
|
4672
|
+
this.scheduledReconnect = null;
|
|
4673
|
+
if (this.terminalReject) return;
|
|
4674
|
+
this.provider.connect?.()?.catch?.((err) => {
|
|
4675
|
+
console.error(
|
|
4676
|
+
`[composer-mcp] reconnect attempt ${this.consecutiveFailures} failed: ${err}`
|
|
4677
|
+
);
|
|
4678
|
+
});
|
|
4679
|
+
}, delay);
|
|
4680
|
+
this.scheduledReconnect.unref?.();
|
|
4681
|
+
}
|
|
4682
|
+
);
|
|
4683
|
+
this.provider.on("status", (...args) => {
|
|
4684
|
+
const status = args[0];
|
|
4685
|
+
if (status.status === "connected") {
|
|
4686
|
+
this.consecutiveFailures = 0;
|
|
4687
|
+
}
|
|
4688
|
+
});
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
/**
|
|
4692
|
+
* Test-only factory: build a fully-functional `RoomState` without opening
|
|
4693
|
+
* a WebSocket. Uses a standalone `Awareness` instance so local awareness
|
|
4694
|
+
* writes still work and can be observed via `awarenessForTest`.
|
|
4695
|
+
*/
|
|
4696
|
+
static createForTest(opts) {
|
|
4697
|
+
const doc = new Y5.Doc();
|
|
4698
|
+
const awareness = new Awareness(doc);
|
|
4699
|
+
const fake = {
|
|
4700
|
+
awareness,
|
|
4701
|
+
synced: true,
|
|
4702
|
+
destroy() {
|
|
4703
|
+
awareness.destroy();
|
|
4704
|
+
}
|
|
4705
|
+
};
|
|
4706
|
+
return new _RoomState({
|
|
4707
|
+
roomId: opts.roomId,
|
|
4708
|
+
serverHost: "test://noop",
|
|
4709
|
+
actingAs: opts.actingAs,
|
|
4710
|
+
identity: opts.identity,
|
|
4711
|
+
_providerForTest: fake,
|
|
4712
|
+
_docForTest: doc
|
|
4210
4713
|
});
|
|
4211
4714
|
}
|
|
4715
|
+
signalTerminalReject(rej) {
|
|
4716
|
+
if (this.terminalReject) return;
|
|
4717
|
+
this.terminalReject = rej;
|
|
4718
|
+
if (this.scheduledReconnect) {
|
|
4719
|
+
clearTimeout(this.scheduledReconnect);
|
|
4720
|
+
this.scheduledReconnect = null;
|
|
4721
|
+
}
|
|
4722
|
+
const listeners = [...this.terminalRejectListeners];
|
|
4723
|
+
this.terminalRejectListeners.clear();
|
|
4724
|
+
for (const fn of listeners) {
|
|
4725
|
+
try {
|
|
4726
|
+
fn();
|
|
4727
|
+
} catch {
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
this.provider.destroy();
|
|
4731
|
+
}
|
|
4732
|
+
terminalRejectError() {
|
|
4733
|
+
const rej = this.terminalReject;
|
|
4734
|
+
if (!rej) {
|
|
4735
|
+
return new Error(
|
|
4736
|
+
`[composer-mcp] room "${this.roomId}" entered terminal state (unknown).`
|
|
4737
|
+
);
|
|
4738
|
+
}
|
|
4739
|
+
if (rej.kind === "aborted") {
|
|
4740
|
+
return new Error(
|
|
4741
|
+
`[composer-mcp] connection to room "${this.roomId}" aborted after ${rej.failures} failed reconnect attempts. Run composer_join_room to retry.`
|
|
4742
|
+
);
|
|
4743
|
+
}
|
|
4744
|
+
return new Error(
|
|
4745
|
+
`[composer-mcp] room "${this.roomId}" rejected by server: ${rej.reason}`
|
|
4746
|
+
);
|
|
4747
|
+
}
|
|
4748
|
+
/** Test/diagnostic accessor — not part of the stable surface. */
|
|
4749
|
+
get isTerminallyRejected() {
|
|
4750
|
+
return this.terminalReject !== null;
|
|
4751
|
+
}
|
|
4752
|
+
/** Test/diagnostic accessor — not part of the stable surface. */
|
|
4753
|
+
get terminalRejectKind() {
|
|
4754
|
+
return this.terminalReject?.kind ?? null;
|
|
4755
|
+
}
|
|
4756
|
+
/** Test/diagnostic accessor — not part of the stable surface. */
|
|
4757
|
+
get failureCount() {
|
|
4758
|
+
return this.consecutiveFailures;
|
|
4759
|
+
}
|
|
4760
|
+
/** Test-only accessor for the underlying awareness instance. */
|
|
4761
|
+
get awarenessForTest() {
|
|
4762
|
+
return this.provider.awareness;
|
|
4763
|
+
}
|
|
4212
4764
|
/**
|
|
4213
4765
|
* Resolves when the provider has completed its first sync handshake.
|
|
4214
4766
|
* Rejects if the handshake does not complete within `timeoutMs` (default
|
|
@@ -4216,23 +4768,38 @@ var RoomState = class {
|
|
|
4216
4768
|
* of hanging the MCP tool call indefinitely.
|
|
4217
4769
|
*/
|
|
4218
4770
|
async waitForInitialSync(timeoutMs = 15e3) {
|
|
4771
|
+
if (this.terminalReject) throw this.terminalRejectError();
|
|
4219
4772
|
if (this.provider.synced) return;
|
|
4773
|
+
if (!this.provider.on || !this.provider.off) return;
|
|
4774
|
+
const provider = this.provider;
|
|
4775
|
+
const onSub = provider.on.bind(provider);
|
|
4776
|
+
const offSub = provider.off.bind(provider);
|
|
4220
4777
|
await new Promise((resolve, reject) => {
|
|
4221
|
-
const onSync = (
|
|
4778
|
+
const onSync = (...args) => {
|
|
4779
|
+
const synced = args[0];
|
|
4222
4780
|
if (!synced) return;
|
|
4223
|
-
|
|
4224
|
-
this.provider.off("sync", onSync);
|
|
4781
|
+
cleanup();
|
|
4225
4782
|
resolve();
|
|
4226
4783
|
};
|
|
4784
|
+
const onTerminal = () => {
|
|
4785
|
+
cleanup();
|
|
4786
|
+
reject(this.terminalRejectError());
|
|
4787
|
+
};
|
|
4788
|
+
const cleanup = () => {
|
|
4789
|
+
clearTimeout(timer);
|
|
4790
|
+
offSub("sync", onSync);
|
|
4791
|
+
this.terminalRejectListeners.delete(onTerminal);
|
|
4792
|
+
};
|
|
4227
4793
|
const timer = setTimeout(() => {
|
|
4228
|
-
|
|
4794
|
+
cleanup();
|
|
4229
4795
|
reject(
|
|
4230
4796
|
new Error(
|
|
4231
4797
|
`timed out after ${timeoutMs}ms waiting for sync handshake on room "${this.roomId}"`
|
|
4232
4798
|
)
|
|
4233
4799
|
);
|
|
4234
4800
|
}, timeoutMs);
|
|
4235
|
-
|
|
4801
|
+
onSub("sync", onSync);
|
|
4802
|
+
this.terminalRejectListeners.add(onTerminal);
|
|
4236
4803
|
});
|
|
4237
4804
|
}
|
|
4238
4805
|
idleDisconnectTimer = null;
|
|
@@ -4243,10 +4810,12 @@ var RoomState = class {
|
|
|
4243
4810
|
* disconnect because we're back in active use.
|
|
4244
4811
|
*/
|
|
4245
4812
|
async ensureConnected(timeoutMs = 15e3) {
|
|
4813
|
+
if (this.terminalReject) throw this.terminalRejectError();
|
|
4246
4814
|
if (this.idleDisconnectTimer) {
|
|
4247
4815
|
clearTimeout(this.idleDisconnectTimer);
|
|
4248
4816
|
this.idleDisconnectTimer = null;
|
|
4249
4817
|
}
|
|
4818
|
+
if (!this.provider.connect) return;
|
|
4250
4819
|
if (this.provider.wsconnected && this.provider.synced) return;
|
|
4251
4820
|
await this.provider.connect();
|
|
4252
4821
|
await this.waitForInitialSync(timeoutMs);
|
|
@@ -4270,11 +4839,12 @@ var RoomState = class {
|
|
|
4270
4839
|
clearTimeout(this.idleDisconnectTimer);
|
|
4271
4840
|
this.idleDisconnectTimer = null;
|
|
4272
4841
|
}
|
|
4842
|
+
if (!this.provider.disconnect) return;
|
|
4273
4843
|
if (!this.provider.wsconnected) return;
|
|
4274
4844
|
this.idleDisconnectTimer = setTimeout(() => {
|
|
4275
4845
|
this.idleDisconnectTimer = null;
|
|
4276
4846
|
this.setMonitoring(false);
|
|
4277
|
-
this.
|
|
4847
|
+
this.disconnect();
|
|
4278
4848
|
}, ms);
|
|
4279
4849
|
this.idleDisconnectTimer.unref?.();
|
|
4280
4850
|
}
|
|
@@ -4282,14 +4852,19 @@ var RoomState = class {
|
|
|
4282
4852
|
* Close the socket immediately. Available for explicit teardown
|
|
4283
4853
|
* (e.g., destroy()). Does not touch monitoring state — the socket close
|
|
4284
4854
|
* causes y-partyserver to broadcast the awareness removal, which is the
|
|
4285
|
-
* visual signal peers care about.
|
|
4855
|
+
* visual signal peers care about. Clears any pending circuit-breaker
|
|
4856
|
+
* reconnect so the user's intent to disconnect isn't silently undone.
|
|
4286
4857
|
*/
|
|
4287
4858
|
disconnect() {
|
|
4288
4859
|
if (this.idleDisconnectTimer) {
|
|
4289
4860
|
clearTimeout(this.idleDisconnectTimer);
|
|
4290
4861
|
this.idleDisconnectTimer = null;
|
|
4291
4862
|
}
|
|
4292
|
-
this.
|
|
4863
|
+
if (this.scheduledReconnect) {
|
|
4864
|
+
clearTimeout(this.scheduledReconnect);
|
|
4865
|
+
this.scheduledReconnect = null;
|
|
4866
|
+
}
|
|
4867
|
+
this.provider.disconnect?.();
|
|
4293
4868
|
}
|
|
4294
4869
|
/**
|
|
4295
4870
|
* Toggle whether the agent is visibly monitoring the room. Broadcast via
|
|
@@ -4297,9 +4872,9 @@ var RoomState = class {
|
|
|
4297
4872
|
* socket must already be open (caller should have ensured this).
|
|
4298
4873
|
*
|
|
4299
4874
|
* setMonitoring(true) — called once on first composer_next_event entry.
|
|
4300
|
-
* setMonitoring(false) — called on the
|
|
4301
|
-
* immediately so the avatar disappears
|
|
4302
|
-
*
|
|
4875
|
+
* setMonitoring(false) — called on the silent-exit branch; clears
|
|
4876
|
+
* awareness immediately so the avatar disappears
|
|
4877
|
+
* as the agent leaves.
|
|
4303
4878
|
*
|
|
4304
4879
|
* Subsequent calls with the same value are no-ops.
|
|
4305
4880
|
*/
|
|
@@ -4334,6 +4909,15 @@ var RoomState = class {
|
|
|
4334
4909
|
markGoodbyeIssued() {
|
|
4335
4910
|
this.goodbyeIssued = true;
|
|
4336
4911
|
}
|
|
4912
|
+
/**
|
|
4913
|
+
* Reset the goodbye latch on rejoin. `composer_join_room` reuses the
|
|
4914
|
+
* cached RoomState when one is still in the rooms map, so without this
|
|
4915
|
+
* the latch would remain set and the next `composer_next_event` would
|
|
4916
|
+
* be refused — even though the user explicitly asked to re-engage.
|
|
4917
|
+
*/
|
|
4918
|
+
clearGoodbye() {
|
|
4919
|
+
this.goodbyeIssued = false;
|
|
4920
|
+
}
|
|
4337
4921
|
get hasIssuedGoodbye() {
|
|
4338
4922
|
return this.goodbyeIssued;
|
|
4339
4923
|
}
|
|
@@ -4356,7 +4940,10 @@ var RoomState = class {
|
|
|
4356
4940
|
}
|
|
4357
4941
|
async nextEvent(timeoutMs, signal) {
|
|
4358
4942
|
const pending = this.queue.shift();
|
|
4359
|
-
if (pending)
|
|
4943
|
+
if (pending) {
|
|
4944
|
+
this.onEventDelivered(pending);
|
|
4945
|
+
return pending;
|
|
4946
|
+
}
|
|
4360
4947
|
if (signal?.aborted) return { kind: "timeout" };
|
|
4361
4948
|
return new Promise((resolve) => {
|
|
4362
4949
|
const cleanup = () => {
|
|
@@ -4376,16 +4963,34 @@ var RoomState = class {
|
|
|
4376
4963
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4377
4964
|
const waiter = (ev) => {
|
|
4378
4965
|
cleanup();
|
|
4966
|
+
this.onEventDelivered(ev);
|
|
4379
4967
|
resolve(ev);
|
|
4380
4968
|
};
|
|
4381
4969
|
this.waiters.push(waiter);
|
|
4382
4970
|
});
|
|
4383
4971
|
}
|
|
4972
|
+
/**
|
|
4973
|
+
* Hook that runs at the moment an event is actually handed to the caller
|
|
4974
|
+
* of `composer_next_event`. For mentions this is where we publish the
|
|
4975
|
+
* `"thinking"` heartbeat — *not* at enqueue time — so that queued mentions
|
|
4976
|
+
* behind an in-flight one don't prematurely light up the indicator on the
|
|
4977
|
+
* user's client. The indicator only appears once the model has picked the
|
|
4978
|
+
* event up to process it.
|
|
4979
|
+
*/
|
|
4980
|
+
onEventDelivered(ev) {
|
|
4981
|
+
if (ev.kind !== "mention") return;
|
|
4982
|
+
this.publishAgentWork(ev.threadId, ev.replyId, "thinking");
|
|
4983
|
+
}
|
|
4384
4984
|
destroy() {
|
|
4385
4985
|
if (this.idleDisconnectTimer) {
|
|
4386
4986
|
clearTimeout(this.idleDisconnectTimer);
|
|
4387
4987
|
this.idleDisconnectTimer = null;
|
|
4388
4988
|
}
|
|
4989
|
+
if (this.scheduledReconnect) {
|
|
4990
|
+
clearTimeout(this.scheduledReconnect);
|
|
4991
|
+
this.scheduledReconnect = null;
|
|
4992
|
+
}
|
|
4993
|
+
this.terminalRejectListeners.clear();
|
|
4389
4994
|
this.provider.destroy();
|
|
4390
4995
|
this.doc.destroy();
|
|
4391
4996
|
}
|
|
@@ -4397,14 +5002,84 @@ var RoomState = class {
|
|
|
4397
5002
|
markThreadActive(threadId) {
|
|
4398
5003
|
this.activeThreads.add(threadId);
|
|
4399
5004
|
}
|
|
5005
|
+
/**
|
|
5006
|
+
* Publish or update an `agentWork` entry on the local awareness state for an
|
|
5007
|
+
* in-flight agent record. Keyed on `{threadId, replyId}` — when `replyId` is
|
|
5008
|
+
* absent the entry targets the thread-head record (an agent-authored Comment
|
|
5009
|
+
* or Suggestion). Upserts: calling twice with the same key refreshes state /
|
|
5010
|
+
* note / updatedAt rather than appending a duplicate. Idempotent.
|
|
5011
|
+
*
|
|
5012
|
+
* Entries are transient — `clearAgentWork` prunes a specific entry on a
|
|
5013
|
+
* `ready` transition; the full array is auto-cleaned when the provider
|
|
5014
|
+
* disconnects (server-side `onClose` → `removeAwarenessStates`). The MCP
|
|
5015
|
+
* also self-heals on `composer_agent_status` re-entry by scanning the array
|
|
5016
|
+
* and dropping entries whose target record is already `ready`.
|
|
5017
|
+
*/
|
|
5018
|
+
publishAgentWork(threadId, replyId, state, note) {
|
|
5019
|
+
const current = this.readAgentWork();
|
|
5020
|
+
const now = Date.now();
|
|
5021
|
+
const nextEntry = {
|
|
5022
|
+
threadId,
|
|
5023
|
+
...replyId !== void 0 ? { replyId } : {},
|
|
5024
|
+
state,
|
|
5025
|
+
...note !== void 0 ? { note } : {},
|
|
5026
|
+
startedAt: now,
|
|
5027
|
+
updatedAt: now
|
|
5028
|
+
};
|
|
5029
|
+
const existingIdx = current.findIndex(
|
|
5030
|
+
(e) => e.threadId === threadId && e.replyId === replyId
|
|
5031
|
+
);
|
|
5032
|
+
let nextArray;
|
|
5033
|
+
if (existingIdx >= 0) {
|
|
5034
|
+
const existing = current[existingIdx];
|
|
5035
|
+
nextArray = current.slice();
|
|
5036
|
+
nextArray[existingIdx] = {
|
|
5037
|
+
...existing,
|
|
5038
|
+
state,
|
|
5039
|
+
...note !== void 0 ? { note } : {},
|
|
5040
|
+
updatedAt: now
|
|
5041
|
+
};
|
|
5042
|
+
} else {
|
|
5043
|
+
nextArray = [...current, nextEntry];
|
|
5044
|
+
}
|
|
5045
|
+
setLocalAwareness(this.provider.awareness, "agentWork", nextArray);
|
|
5046
|
+
}
|
|
5047
|
+
/**
|
|
5048
|
+
* Remove the `agentWork` entry matching `{threadId, replyId}`. No-op if no
|
|
5049
|
+
* match exists (idempotent). When the result is empty, the field is cleared
|
|
5050
|
+
* entirely so remote peers see `agentWork` absent.
|
|
5051
|
+
*/
|
|
5052
|
+
clearAgentWork(threadId, replyId) {
|
|
5053
|
+
const current = this.readAgentWork();
|
|
5054
|
+
const nextArray = current.filter(
|
|
5055
|
+
(e) => !(e.threadId === threadId && e.replyId === replyId)
|
|
5056
|
+
);
|
|
5057
|
+
if (nextArray.length === current.length) return;
|
|
5058
|
+
setLocalAwareness(
|
|
5059
|
+
this.provider.awareness,
|
|
5060
|
+
"agentWork",
|
|
5061
|
+
nextArray.length === 0 ? void 0 : nextArray
|
|
5062
|
+
);
|
|
5063
|
+
}
|
|
5064
|
+
/** Current local `agentWork` array, or `[]` if absent / malformed. */
|
|
5065
|
+
readAgentWork() {
|
|
5066
|
+
const local = this.provider.awareness.getLocalState();
|
|
5067
|
+
const work = local?.agentWork;
|
|
5068
|
+
if (!Array.isArray(work)) return [];
|
|
5069
|
+
return work;
|
|
5070
|
+
}
|
|
4400
5071
|
/** Timestamp (ms) of the most recent non-local transaction on this doc. */
|
|
4401
5072
|
get lastRemoteActivityAt() {
|
|
4402
5073
|
return this._lastRemoteActivityAt;
|
|
4403
5074
|
}
|
|
4404
5075
|
enqueue(ev) {
|
|
4405
5076
|
const waiter = this.waiters.shift();
|
|
4406
|
-
if (waiter)
|
|
4407
|
-
|
|
5077
|
+
if (waiter) {
|
|
5078
|
+
this.onEventDelivered(ev);
|
|
5079
|
+
waiter(ev);
|
|
5080
|
+
} else {
|
|
5081
|
+
this.queue.push(ev);
|
|
5082
|
+
}
|
|
4408
5083
|
}
|
|
4409
5084
|
watchMentions() {
|
|
4410
5085
|
attachMentionObserver(this.doc, {
|
|
@@ -4413,9 +5088,26 @@ var RoomState = class {
|
|
|
4413
5088
|
activeThreads: this.activeThreads,
|
|
4414
5089
|
identityUserId: this.identity.userId,
|
|
4415
5090
|
actingAs: this.actingAs,
|
|
4416
|
-
getSoloHumanAuthorId: () => this.computeSoloHumanAuthorId()
|
|
5091
|
+
getSoloHumanAuthorId: () => this.computeSoloHumanAuthorId(),
|
|
5092
|
+
resolveUserName: (userId) => this.resolveUserName(userId)
|
|
4417
5093
|
});
|
|
4418
5094
|
}
|
|
5095
|
+
/**
|
|
5096
|
+
* Look up a display name for `userId` from the awareness roster. Returns
|
|
5097
|
+
* `undefined` if nobody is currently advertising that userId — in which case
|
|
5098
|
+
* the observer falls back to whatever `authorName` the triggering record
|
|
5099
|
+
* carries (still a usable fallback).
|
|
5100
|
+
*/
|
|
5101
|
+
resolveUserName(userId) {
|
|
5102
|
+
const states = this.provider.awareness.getStates();
|
|
5103
|
+
for (const state of states.values()) {
|
|
5104
|
+
const user = state?.user;
|
|
5105
|
+
if (!user || typeof user.userId !== "string") continue;
|
|
5106
|
+
if (user.userId !== userId) continue;
|
|
5107
|
+
if (typeof user.name === "string" && user.name.length > 0) return user.name;
|
|
5108
|
+
}
|
|
5109
|
+
return void 0;
|
|
5110
|
+
}
|
|
4419
5111
|
/**
|
|
4420
5112
|
* Read the provider's awareness map and decide whether the room is "solo"
|
|
4421
5113
|
* right now — exactly one agent (us) and exactly one human. Returns the
|
|
@@ -4461,10 +5153,24 @@ function attachMentionObserver(doc, opts) {
|
|
|
4461
5153
|
const identityUserId = opts.identityUserId;
|
|
4462
5154
|
const hasActingAsMention = buildActingAsMatcher(opts.actingAs);
|
|
4463
5155
|
const getSoloHumanAuthorId = opts.getSoloHumanAuthorId ?? (() => void 0);
|
|
5156
|
+
const resolveUserName = opts.resolveUserName ?? (() => void 0);
|
|
5157
|
+
const makeInvokerFields = (authorUserId, authorName) => {
|
|
5158
|
+
const fields = {};
|
|
5159
|
+
if (authorUserId) {
|
|
5160
|
+
fields.invokerUserId = authorUserId;
|
|
5161
|
+
const resolved = resolveUserName(authorUserId);
|
|
5162
|
+
if (resolved) fields.invokerName = resolved;
|
|
5163
|
+
else if (authorName) fields.invokerName = authorName;
|
|
5164
|
+
} else if (authorName) {
|
|
5165
|
+
fields.invokerName = authorName;
|
|
5166
|
+
}
|
|
5167
|
+
return fields;
|
|
5168
|
+
};
|
|
4464
5169
|
const scan = (kind, threadId, entry, isLocal) => {
|
|
4465
5170
|
if (!entry || typeof entry !== "object") return;
|
|
4466
5171
|
const record = entry;
|
|
4467
5172
|
const bodyAuthorUserId = typeof record.authorUserId === "string" ? record.authorUserId : void 0;
|
|
5173
|
+
const bodyAuthorName = typeof record.authorName === "string" ? record.authorName : void 0;
|
|
4468
5174
|
const replies = Array.isArray(record.replies) ? record.replies : [];
|
|
4469
5175
|
let lastAgentIdx = -1;
|
|
4470
5176
|
if (identityUserId !== void 0) {
|
|
@@ -4489,7 +5195,8 @@ function attachMentionObserver(doc, opts) {
|
|
|
4489
5195
|
threadKind: kind,
|
|
4490
5196
|
threadText: body,
|
|
4491
5197
|
reason: "direct_mention",
|
|
4492
|
-
...resolveAnchoredContext(doc, record.anchorFrom, record.anchorTo)
|
|
5198
|
+
...resolveAnchoredContext(doc, record.anchorFrom, record.anchorTo),
|
|
5199
|
+
...makeInvokerFields(bodyAuthorUserId, bodyAuthorName)
|
|
4493
5200
|
});
|
|
4494
5201
|
}
|
|
4495
5202
|
} else if (!seen.has(threadId) && !isLocal && !bodyAnswered && bodySidecar !== "miss" && !ANY_AT_MENTION_RE.test(body)) {
|
|
@@ -4502,7 +5209,8 @@ function attachMentionObserver(doc, opts) {
|
|
|
4502
5209
|
threadKind: kind,
|
|
4503
5210
|
threadText: body,
|
|
4504
5211
|
reason: "solo_room",
|
|
4505
|
-
...resolveAnchoredContext(doc, record.anchorFrom, record.anchorTo)
|
|
5212
|
+
...resolveAnchoredContext(doc, record.anchorFrom, record.anchorTo),
|
|
5213
|
+
...makeInvokerFields(bodyAuthorUserId, bodyAuthorName)
|
|
4506
5214
|
});
|
|
4507
5215
|
}
|
|
4508
5216
|
}
|
|
@@ -4517,18 +5225,21 @@ function attachMentionObserver(doc, opts) {
|
|
|
4517
5225
|
seen.add(key);
|
|
4518
5226
|
if (isLocal) continue;
|
|
4519
5227
|
if (i <= lastAgentIdx) continue;
|
|
5228
|
+
const replyAuthorUserId = typeof reply.authorUserId === "string" ? reply.authorUserId : void 0;
|
|
5229
|
+
const replyAuthorName = typeof reply.authorName === "string" ? reply.authorName : void 0;
|
|
5230
|
+
const replyAuthorIsAgent = reply.authorIsAgent === true;
|
|
4520
5231
|
const replySidecar = checkMentionsSidecar(reply.mentions, identityUserId);
|
|
4521
5232
|
const isDirect = replySidecar === "hit" || replySidecar === "absent" && hasActingAsMention(reply.text);
|
|
4522
5233
|
const inActiveThread = activeThreads.has(threadId);
|
|
4523
5234
|
let reason = isDirect ? "direct_mention" : inActiveThread ? "active_thread" : null;
|
|
4524
5235
|
if (!reason) {
|
|
4525
|
-
const replyAuthor = typeof reply.authorUserId === "string" ? reply.authorUserId : void 0;
|
|
4526
5236
|
const soloHuman = getSoloHumanAuthorId();
|
|
4527
|
-
if (replySidecar !== "miss" && !ANY_AT_MENTION_RE.test(reply.text) && soloHuman &&
|
|
5237
|
+
if (replySidecar !== "miss" && !ANY_AT_MENTION_RE.test(reply.text) && soloHuman && replyAuthorUserId === soloHuman) {
|
|
4528
5238
|
reason = "solo_room";
|
|
4529
5239
|
}
|
|
4530
5240
|
}
|
|
4531
5241
|
if (!reason) continue;
|
|
5242
|
+
if (replyAuthorIsAgent && reason !== "direct_mention") continue;
|
|
4532
5243
|
enqueue({
|
|
4533
5244
|
kind: "mention",
|
|
4534
5245
|
threadId,
|
|
@@ -4536,7 +5247,8 @@ function attachMentionObserver(doc, opts) {
|
|
|
4536
5247
|
threadText: reply.text,
|
|
4537
5248
|
replyId: reply.id,
|
|
4538
5249
|
reason,
|
|
4539
|
-
...resolveAnchoredContext(doc, record.anchorFrom, record.anchorTo)
|
|
5250
|
+
...resolveAnchoredContext(doc, record.anchorFrom, record.anchorTo),
|
|
5251
|
+
...makeInvokerFields(replyAuthorUserId, replyAuthorName)
|
|
4540
5252
|
});
|
|
4541
5253
|
}
|
|
4542
5254
|
};
|
|
@@ -4552,7 +5264,7 @@ function attachMentionObserver(doc, opts) {
|
|
|
4552
5264
|
doc.getMap("suggestions").observe(onChange("suggestion"));
|
|
4553
5265
|
}
|
|
4554
5266
|
function hashState(doc) {
|
|
4555
|
-
return Buffer.from(
|
|
5267
|
+
return Buffer.from(Y5.encodeStateVector(doc)).toString("base64");
|
|
4556
5268
|
}
|
|
4557
5269
|
function attachRemoteActivityTracker(doc, opts) {
|
|
4558
5270
|
const now = opts.now ?? (() => Date.now());
|
|
@@ -4564,7 +5276,7 @@ function attachRemoteActivityTracker(doc, opts) {
|
|
|
4564
5276
|
// src/identity.ts
|
|
4565
5277
|
import * as fs from "fs/promises";
|
|
4566
5278
|
import * as path from "path";
|
|
4567
|
-
import { nanoid as
|
|
5279
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
4568
5280
|
var PALETTE = [
|
|
4569
5281
|
"#9333ea",
|
|
4570
5282
|
// purple-600
|
|
@@ -4619,7 +5331,7 @@ async function loadOrCreateIdentity(dir) {
|
|
|
4619
5331
|
}
|
|
4620
5332
|
}
|
|
4621
5333
|
const identity = {
|
|
4622
|
-
userId:
|
|
5334
|
+
userId: nanoid3(),
|
|
4623
5335
|
color: pickColor()
|
|
4624
5336
|
};
|
|
4625
5337
|
await saveIdentity(dir, identity);
|
|
@@ -4726,15 +5438,18 @@ async function resolveActingAs(actingAsArg) {
|
|
|
4726
5438
|
if (!actingAsArg) {
|
|
4727
5439
|
throw new Error(
|
|
4728
5440
|
[
|
|
4729
|
-
"First Composer room on this machine \u2014
|
|
5441
|
+
"First Composer room on this machine \u2014 orient the user, then ask what to call you. STOP and output ONE short message before retrying.",
|
|
4730
5442
|
"",
|
|
4731
|
-
"
|
|
4732
|
-
|
|
4733
|
-
|
|
5443
|
+
"The message should cover four things, in order:",
|
|
5444
|
+
' 1. GREET \u2014 open with "Welcome to Composer!"',
|
|
5445
|
+
' 2. WHAT COMPOSER IS \u2014 "Composer is a real-time markdown editor for humans and agents."',
|
|
5446
|
+
' 3. WHAT YOU CAN DO IN IT \u2014 "I can join your docs with you, respond to comments, and post inline suggestions."',
|
|
5447
|
+
" 4. ASK FOR A NAME \u2014 suggest ONE default they can accept in a single word.",
|
|
5448
|
+
` - If you know the user's first name from conversation context, suggest "<FirstName>'s Agent" (e.g. "Josh's Agent").`,
|
|
5449
|
+
" - Otherwise pick something playful from outside the model-family space (e.g. Monty, Gerty, Rosie, Otto, Pip). Do NOT suggest Claude / Gemini / GPT / Sonnet / Opus / Haiku / other model names.",
|
|
5450
|
+
` - Phrase it as a check, e.g. "I'll go by <suggested name> in the doc if that's okay with you?"`,
|
|
4734
5451
|
"",
|
|
4735
|
-
|
|
4736
|
-
"",
|
|
4737
|
-
"Retry with their answer as `actingAs`. It persists to ~/.composer/user.json and is reused for every future room on this machine, so only ask once."
|
|
5452
|
+
"Retry the call with their answer as `actingAs`. The name persists to ~/.composer/user.json and is reused for every future room on this machine, so this orientation only happens once per machine."
|
|
4738
5453
|
].join("\n")
|
|
4739
5454
|
);
|
|
4740
5455
|
}
|
|
@@ -4808,7 +5523,7 @@ var TOOL_DEFS = [
|
|
|
4808
5523
|
},
|
|
4809
5524
|
{
|
|
4810
5525
|
name: "composer_next_event",
|
|
4811
|
-
description: "Block for up to `timeoutSec` (default 30s) waiting for a remote event. Returns `{ kind: 'mention' | 'timeout', ... }`. **The monitor loop is always-on** \u2014 every return carries a directive you must follow without waiting for user input. On `mention`, the `reason` is one of: `direct_mention` (sidecar or text named you \u2014 always reply), `active_thread` (plain reply on a thread you're already in \u2014 reply if the content invites one), or `solo_room` (you're alone with one human who didn't explicitly tag anyone \u2014 default to a helpful reply, but skip if the text reads like a note-to-self, acknowledgement, or aside). Handle the event, then execute the return's `requiredNextToolCall`. On `timeout
|
|
5526
|
+
description: "Block for up to `timeoutSec` (default 30s) waiting for a remote event. Returns `{ kind: 'mention' | 'timeout', ... }`. **The monitor loop is always-on** \u2014 every return carries a directive you must follow without waiting for user input. On `mention`, the `reason` is one of: `direct_mention` (sidecar or text named you \u2014 always reply), `active_thread` (plain reply on a thread you're already in \u2014 reply if the content invites one), or `solo_room` (you're alone with one human who didn't explicitly tag anyone \u2014 default to a helpful reply, but skip if the text reads like a note-to-self, acknowledgement, or aside). Handle the event, then execute the return's `requiredNextToolCall`. On a final `timeout` (no `requiredNextToolCall`), the return carries `userMessage` \u2014 the agent's goodbye line in its own voice. Print `userMessage` verbatim as your final output before exiting; do NOT post anything in the doc.",
|
|
4812
5527
|
inputSchema: {
|
|
4813
5528
|
type: "object",
|
|
4814
5529
|
properties: {
|
|
@@ -4853,7 +5568,7 @@ var TOOL_DEFS = [
|
|
|
4853
5568
|
},
|
|
4854
5569
|
{
|
|
4855
5570
|
name: "composer_add_comment",
|
|
4856
|
-
description: "Post a new top-level comment anchored to a text span anywhere in the doc. Anchor is { headingId, textToFind, occurrence? }. Use this to flag something the user didn't ask about \u2014 cross-referencing related sections, raising a concern elsewhere in the doc, or seeding a thread on a new span. Use `composer_reply_comment` instead when continuing an existing thread. Returns { id } on success or an isError result if the anchor cannot be resolved.",
|
|
5571
|
+
description: "Post a new top-level comment anchored to a text span anywhere in the doc. Anchor is { headingId, textToFind, occurrence? }. Use this to flag something the user didn't ask about \u2014 cross-referencing related sections, raising a concern elsewhere in the doc, or seeding a thread on a new span. Use `composer_reply_comment` instead when continuing an existing thread. Accepts optional `state` (ack-first flow: post with `state: \"thinking\"` to start the live indicator immediately) and `mentions` (array of target userIds \u2014 the invoker's userId from the mention event payload, for the `@invoker` backlink). Returns { id } on success or an isError result if the anchor cannot be resolved.",
|
|
4857
5572
|
inputSchema: {
|
|
4858
5573
|
type: "object",
|
|
4859
5574
|
properties: {
|
|
@@ -4867,20 +5582,40 @@ var TOOL_DEFS = [
|
|
|
4867
5582
|
},
|
|
4868
5583
|
required: ["headingId", "textToFind"]
|
|
4869
5584
|
},
|
|
4870
|
-
text: { type: "string" }
|
|
5585
|
+
text: { type: "string" },
|
|
5586
|
+
state: {
|
|
5587
|
+
type: "string",
|
|
5588
|
+
enum: ["thinking", "working", "replying", "ready"],
|
|
5589
|
+
description: 'Initial lifecycle state for the new comment. Set to "thinking" when posting an ack-first placeholder so the live indicator renders from the moment the record lands \u2014 no stateless flicker while a separate composer_agent_status call catches up.'
|
|
5590
|
+
},
|
|
5591
|
+
mentions: {
|
|
5592
|
+
type: "array",
|
|
5593
|
+
items: { type: "string" },
|
|
5594
|
+
description: "Target userIds of @-mentions in `text`, in order of occurrence. Typically a single entry: the invoker's userId from the triggering mention event. Rendered as bolded mention spans by the client."
|
|
5595
|
+
}
|
|
4871
5596
|
},
|
|
4872
5597
|
required: ["roomId", "anchor", "text"]
|
|
4873
5598
|
}
|
|
4874
5599
|
},
|
|
4875
5600
|
{
|
|
4876
5601
|
name: "composer_reply_comment",
|
|
4877
|
-
description:
|
|
5602
|
+
description: 'Append a reply to an existing comment thread. Accepts optional `state` (ack-first flow: post with `state: "thinking"` to start the live indicator immediately \u2014 avoids the stateless flicker that occurs if state is added in a separate composer_agent_status call after the reply lands) and `mentions` (array of target userIds \u2014 typically the invoker\'s userId from the triggering mention event).',
|
|
4878
5603
|
inputSchema: {
|
|
4879
5604
|
type: "object",
|
|
4880
5605
|
properties: {
|
|
4881
5606
|
roomId: { type: "string" },
|
|
4882
5607
|
threadId: { type: "string" },
|
|
4883
|
-
text: { type: "string" }
|
|
5608
|
+
text: { type: "string" },
|
|
5609
|
+
state: {
|
|
5610
|
+
type: "string",
|
|
5611
|
+
enum: ["thinking", "working", "replying", "ready"],
|
|
5612
|
+
description: 'Initial lifecycle state for the new reply. Set to "thinking" when posting an ack-first placeholder.'
|
|
5613
|
+
},
|
|
5614
|
+
mentions: {
|
|
5615
|
+
type: "array",
|
|
5616
|
+
items: { type: "string" },
|
|
5617
|
+
description: "Target userIds of @-mentions in `text`. Typically the invoker's userId."
|
|
5618
|
+
}
|
|
4884
5619
|
},
|
|
4885
5620
|
required: ["roomId", "threadId", "text"]
|
|
4886
5621
|
}
|
|
@@ -4905,20 +5640,40 @@ var TOOL_DEFS = [
|
|
|
4905
5640
|
},
|
|
4906
5641
|
required: ["headingId", "textToFind"]
|
|
4907
5642
|
},
|
|
4908
|
-
replacementText: { type: "string" }
|
|
5643
|
+
replacementText: { type: "string" },
|
|
5644
|
+
state: {
|
|
5645
|
+
type: "string",
|
|
5646
|
+
enum: ["thinking", "working", "replying", "ready"],
|
|
5647
|
+
description: `Initial lifecycle state for the new suggestion. Set to "working" when drafting a placeholder replacementText users shouldn't accept yet.`
|
|
5648
|
+
},
|
|
5649
|
+
mentions: {
|
|
5650
|
+
type: "array",
|
|
5651
|
+
items: { type: "string" },
|
|
5652
|
+
description: "Target userIds of @-mentions in `replacementText`."
|
|
5653
|
+
}
|
|
4909
5654
|
},
|
|
4910
5655
|
required: ["roomId", "replacementText"]
|
|
4911
5656
|
}
|
|
4912
5657
|
},
|
|
4913
5658
|
{
|
|
4914
5659
|
name: "composer_reply_suggestion",
|
|
4915
|
-
description: "Append a reply to an existing suggestion thread.",
|
|
5660
|
+
description: "Append a reply to an existing suggestion thread. Accepts optional `state` and `mentions` with the same semantics as `composer_reply_comment`.",
|
|
4916
5661
|
inputSchema: {
|
|
4917
5662
|
type: "object",
|
|
4918
5663
|
properties: {
|
|
4919
5664
|
roomId: { type: "string" },
|
|
4920
5665
|
threadId: { type: "string" },
|
|
4921
|
-
text: { type: "string" }
|
|
5666
|
+
text: { type: "string" },
|
|
5667
|
+
state: {
|
|
5668
|
+
type: "string",
|
|
5669
|
+
enum: ["thinking", "working", "replying", "ready"],
|
|
5670
|
+
description: "Initial lifecycle state for the new reply."
|
|
5671
|
+
},
|
|
5672
|
+
mentions: {
|
|
5673
|
+
type: "array",
|
|
5674
|
+
items: { type: "string" },
|
|
5675
|
+
description: "Target userIds of @-mentions in `text`."
|
|
5676
|
+
}
|
|
4922
5677
|
},
|
|
4923
5678
|
required: ["roomId", "threadId", "text"]
|
|
4924
5679
|
}
|
|
@@ -4934,6 +5689,71 @@ var TOOL_DEFS = [
|
|
|
4934
5689
|
},
|
|
4935
5690
|
required: ["roomId", "threadId"]
|
|
4936
5691
|
}
|
|
5692
|
+
},
|
|
5693
|
+
{
|
|
5694
|
+
name: "composer_export_to_file",
|
|
5695
|
+
description: "Dump the current Composer doc to a local markdown file. ONE-WAY ONLY: Composer is the source of truth, the file is OVERWRITTEN on every call. The path must be absolute. Use when the user asks to 'save this to a file', 'export to disk', or runs /composer:export. Returns { roomId, path, bytesWritten }.",
|
|
5696
|
+
inputSchema: {
|
|
5697
|
+
type: "object",
|
|
5698
|
+
properties: {
|
|
5699
|
+
roomId: { type: "string" },
|
|
5700
|
+
path: {
|
|
5701
|
+
type: "string",
|
|
5702
|
+
description: "Absolute filesystem path to write to. Existing files are overwritten. Parent directory must already exist."
|
|
5703
|
+
}
|
|
5704
|
+
},
|
|
5705
|
+
required: ["roomId", "path"]
|
|
5706
|
+
}
|
|
5707
|
+
},
|
|
5708
|
+
{
|
|
5709
|
+
name: "composer_agent_status",
|
|
5710
|
+
description: 'Drive the live state indicator on an in-flight agent-authored record (a reply, or the thread-head Comment / Suggestion). Use AFTER posting an ack with `state: "thinking"` via the reply/add tools \u2014 this tool advances state mid-work without creating a new reply, and optionally rewrites text in place at completion. Transitions: thinking \u2192 working (tool use started) \u2192 replying (assembling final answer, optional) \u2192 ready (terminal; text now holds the final content OR a thin pointer to a standalone artifact). When `replyId` is set, updates that reply inside `threadId`; when absent, updates the thread-head record (use `kind` to disambiguate). Returns { replyId } for reply updates or { id } for thread-head updates.',
|
|
5711
|
+
inputSchema: {
|
|
5712
|
+
type: "object",
|
|
5713
|
+
properties: {
|
|
5714
|
+
roomId: { type: "string" },
|
|
5715
|
+
threadId: { type: "string" },
|
|
5716
|
+
replyId: {
|
|
5717
|
+
type: "string",
|
|
5718
|
+
description: "ID of the reply to update. Omit to update the thread-head record itself (an agent-authored Comment or Suggestion)."
|
|
5719
|
+
},
|
|
5720
|
+
state: {
|
|
5721
|
+
type: "string",
|
|
5722
|
+
enum: ["thinking", "working", "replying", "ready"],
|
|
5723
|
+
description: "New lifecycle state. `ready` is terminal \u2014 pair with `text` for the final content. Non-ready states update the heartbeat; `text` is optional."
|
|
5724
|
+
},
|
|
5725
|
+
text: {
|
|
5726
|
+
type: "string",
|
|
5727
|
+
description: "Optional new body text. On `ready` transitions this typically carries the final answer or a pointer to a standalone artifact. For suggestion thread-head updates this writes to `replacementText`."
|
|
5728
|
+
},
|
|
5729
|
+
note: {
|
|
5730
|
+
type: "string",
|
|
5731
|
+
description: 'Optional short label describing what the agent is currently doing, e.g. "reading section 3\u2026". Rides on the awareness heartbeat only; not persisted on the record.'
|
|
5732
|
+
},
|
|
5733
|
+
kind: {
|
|
5734
|
+
type: "string",
|
|
5735
|
+
enum: ["comment", "suggestion"],
|
|
5736
|
+
description: 'Disambiguates which map holds the thread-head record. Defaults to "comment"; optional when `replyId` is set.'
|
|
5737
|
+
}
|
|
5738
|
+
},
|
|
5739
|
+
required: ["roomId", "threadId", "state"]
|
|
5740
|
+
}
|
|
5741
|
+
},
|
|
5742
|
+
{
|
|
5743
|
+
name: "composer_done",
|
|
5744
|
+
description: 'Signal that the agent is finished with a thread WITHOUT posting a reply \u2014 clears the live indicator left over from `onEventDelivered` (which publishes a thread-head `agentWork(threadId, undefined, "thinking")` placeholder when a mention is dequeued). Call this when you decide to skip a mention (the skill\'s heuristic filters out chatter, your own mentions, etc.) or otherwise close out a thread without a `state: "ready"` reply landing. The normal reply-with-`ready` path already clears its own placeholder; this tool exists for the skip case where no reply is ever written. Idempotent \u2014 clearing a non-existent entry is a no-op. When `replyId` is set, also clears that per-reply entry (use if you abandoned a thinking-state ack mid-flight).',
|
|
5745
|
+
inputSchema: {
|
|
5746
|
+
type: "object",
|
|
5747
|
+
properties: {
|
|
5748
|
+
roomId: { type: "string" },
|
|
5749
|
+
threadId: { type: "string" },
|
|
5750
|
+
replyId: {
|
|
5751
|
+
type: "string",
|
|
5752
|
+
description: 'Optional reply ID. When set, also clears the per-reply `agentWork` entry \u2014 useful if you posted an ack with `state: "thinking"` and then abandoned the work without reaching `ready`.'
|
|
5753
|
+
}
|
|
5754
|
+
},
|
|
5755
|
+
required: ["roomId", "threadId"]
|
|
5756
|
+
}
|
|
4937
5757
|
}
|
|
4938
5758
|
];
|
|
4939
5759
|
function okResult(data) {
|
|
@@ -4966,6 +5786,33 @@ function asOptionalString(value, field) {
|
|
|
4966
5786
|
}
|
|
4967
5787
|
return value;
|
|
4968
5788
|
}
|
|
5789
|
+
var AGENT_REPLY_STATES = [
|
|
5790
|
+
"thinking",
|
|
5791
|
+
"working",
|
|
5792
|
+
"replying",
|
|
5793
|
+
"ready"
|
|
5794
|
+
];
|
|
5795
|
+
function asOptionalAgentState(value, field) {
|
|
5796
|
+
if (value === void 0) return void 0;
|
|
5797
|
+
if (typeof value !== "string") {
|
|
5798
|
+
throw new Error(`${field} must be a string`);
|
|
5799
|
+
}
|
|
5800
|
+
if (!AGENT_REPLY_STATES.includes(value)) {
|
|
5801
|
+
throw new Error(
|
|
5802
|
+
`${field} must be one of: ${AGENT_REPLY_STATES.join(", ")}`
|
|
5803
|
+
);
|
|
5804
|
+
}
|
|
5805
|
+
return value;
|
|
5806
|
+
}
|
|
5807
|
+
function asAgentState(value, field) {
|
|
5808
|
+
const s = asOptionalAgentState(value, field);
|
|
5809
|
+
if (s === void 0) {
|
|
5810
|
+
throw new Error(
|
|
5811
|
+
`${field} must be one of: ${AGENT_REPLY_STATES.join(", ")}`
|
|
5812
|
+
);
|
|
5813
|
+
}
|
|
5814
|
+
return s;
|
|
5815
|
+
}
|
|
4969
5816
|
function asAnchor(value) {
|
|
4970
5817
|
if (!value || typeof value !== "object") {
|
|
4971
5818
|
throw new Error("anchor must be an object");
|
|
@@ -4984,11 +5831,12 @@ function asAnchor(value) {
|
|
|
4984
5831
|
}
|
|
4985
5832
|
function buildMonitorHandoff(roomId, browserUrl, actingAs, opts) {
|
|
4986
5833
|
const mention = `@${actingAs}`;
|
|
4987
|
-
const
|
|
5834
|
+
const opener = opts.mode === "create" ? "I've created a new doc for you" : "I've joined the doc";
|
|
5835
|
+
const step1_sayToUser = opts.isFirstRun ? `${opener} and am launching another agent to watch for comments. Tag me (${mention}) in any Composer comment to bring me in \u2014 and share the link to invite other people (or their agents) to collaborate with us.
|
|
4988
5836
|
|
|
4989
|
-
|
|
5837
|
+
${browserUrl}` : `${opener} and am launching another agent to watch for comments. Tag me (${mention}) in any Composer comment to bring me in.
|
|
4990
5838
|
|
|
4991
|
-
|
|
5839
|
+
${browserUrl}`;
|
|
4992
5840
|
return {
|
|
4993
5841
|
step1_sayToUser,
|
|
4994
5842
|
step2_callTool: {
|
|
@@ -5028,7 +5876,7 @@ async function handleCreateRoom(args) {
|
|
|
5028
5876
|
}
|
|
5029
5877
|
const { actingAs, isFirstRun } = await resolveActingAs(actingAsArg);
|
|
5030
5878
|
const identity = await getIdentity();
|
|
5031
|
-
const roomId =
|
|
5879
|
+
const roomId = nanoid4(10);
|
|
5032
5880
|
const browserUrl = browserUrlFor(roomId);
|
|
5033
5881
|
const state = new RoomState({
|
|
5034
5882
|
roomId,
|
|
@@ -5037,6 +5885,7 @@ async function handleCreateRoom(args) {
|
|
|
5037
5885
|
identity
|
|
5038
5886
|
});
|
|
5039
5887
|
await state.ensureConnected();
|
|
5888
|
+
state.setMonitoring(true);
|
|
5040
5889
|
if (seedMarkdown) {
|
|
5041
5890
|
writeMarkdownToFragment(state.doc.getXmlFragment("default"), seedMarkdown);
|
|
5042
5891
|
}
|
|
@@ -5048,7 +5897,10 @@ async function handleCreateRoom(args) {
|
|
|
5048
5897
|
browserUrl,
|
|
5049
5898
|
actingAs,
|
|
5050
5899
|
snapshot: state.snapshot(),
|
|
5051
|
-
...buildMonitorHandoff(roomId, browserUrl, actingAs, {
|
|
5900
|
+
...buildMonitorHandoff(roomId, browserUrl, actingAs, {
|
|
5901
|
+
isFirstRun,
|
|
5902
|
+
mode: "create"
|
|
5903
|
+
})
|
|
5052
5904
|
});
|
|
5053
5905
|
}
|
|
5054
5906
|
async function handleJoinRoom(args) {
|
|
@@ -5063,6 +5915,9 @@ async function handleJoinRoom(args) {
|
|
|
5063
5915
|
roomId,
|
|
5064
5916
|
actingAs: existing.actingAs
|
|
5065
5917
|
});
|
|
5918
|
+
existing.clearGoodbye();
|
|
5919
|
+
await existing.ensureConnected();
|
|
5920
|
+
existing.setMonitoring(true);
|
|
5066
5921
|
existing.scheduleIdleDisconnect(3e4);
|
|
5067
5922
|
return okResult({
|
|
5068
5923
|
roomId,
|
|
@@ -5073,7 +5928,8 @@ async function handleJoinRoom(args) {
|
|
|
5073
5928
|
// first-run situation — we already wrote a name to disk at least
|
|
5074
5929
|
// once in this process lifetime to have created `existing`.
|
|
5075
5930
|
...buildMonitorHandoff(roomId, browserUrl, existing.actingAs, {
|
|
5076
|
-
isFirstRun: false
|
|
5931
|
+
isFirstRun: false,
|
|
5932
|
+
mode: "join"
|
|
5077
5933
|
})
|
|
5078
5934
|
});
|
|
5079
5935
|
}
|
|
@@ -5086,6 +5942,7 @@ async function handleJoinRoom(args) {
|
|
|
5086
5942
|
identity
|
|
5087
5943
|
});
|
|
5088
5944
|
await state.ensureConnected();
|
|
5945
|
+
state.setMonitoring(true);
|
|
5089
5946
|
rooms.set(roomId, state);
|
|
5090
5947
|
log(`composer room joined \u2192 ${browserUrl}`, { roomId, actingAs });
|
|
5091
5948
|
state.scheduleIdleDisconnect(3e4);
|
|
@@ -5094,7 +5951,10 @@ async function handleJoinRoom(args) {
|
|
|
5094
5951
|
browserUrl,
|
|
5095
5952
|
actingAs,
|
|
5096
5953
|
snapshot: state.snapshot(),
|
|
5097
|
-
...buildMonitorHandoff(roomId, browserUrl, actingAs, {
|
|
5954
|
+
...buildMonitorHandoff(roomId, browserUrl, actingAs, {
|
|
5955
|
+
isFirstRun,
|
|
5956
|
+
mode: "join"
|
|
5957
|
+
})
|
|
5098
5958
|
});
|
|
5099
5959
|
}
|
|
5100
5960
|
function handleAttachRoom(args) {
|
|
@@ -5109,7 +5969,14 @@ var ACTIVITY_WINDOW_MS = (() => {
|
|
|
5109
5969
|
const n = Number(raw);
|
|
5110
5970
|
return Number.isFinite(n) && n > 0 ? n : 15 * 60 * 1e3;
|
|
5111
5971
|
})();
|
|
5112
|
-
var
|
|
5972
|
+
var goodbyeIdleTimeout = (roomId) => `Run \`/composer:join\` when you want me back \u2014 or open the doc again at ${browserUrlFor(roomId)}.`;
|
|
5973
|
+
var GOODBYE_SERVER_KICKED = `I'm having trouble reaching the Composer doc. Try restarting your Claude session, or try again in a bit.`;
|
|
5974
|
+
var GOODBYE_RECONNECT_ABORTED = `I'm having trouble reaching the Composer doc. Make sure you're connected to the internet or try running \`/composer:join\` in a bit to have me join again.`;
|
|
5975
|
+
function terminalRejectGoodbye(state) {
|
|
5976
|
+
if (state.terminalRejectKind === "rejected") return GOODBYE_SERVER_KICKED;
|
|
5977
|
+
if (state.terminalRejectKind === "aborted") return GOODBYE_RECONNECT_ABORTED;
|
|
5978
|
+
return null;
|
|
5979
|
+
}
|
|
5113
5980
|
async function handleNextEvent(args, signal) {
|
|
5114
5981
|
const a = asObject(args);
|
|
5115
5982
|
const roomId = asString(a.roomId, "roomId");
|
|
@@ -5120,6 +5987,16 @@ async function handleNextEvent(args, signal) {
|
|
|
5120
5987
|
"Already said goodbye for this room. The user must explicitly ask you to rejoin (which will trigger a fresh `composer_join_room`). Do not call `composer_next_event` again on this roomId."
|
|
5121
5988
|
);
|
|
5122
5989
|
}
|
|
5990
|
+
const earlyGoodbye = terminalRejectGoodbye(state);
|
|
5991
|
+
if (earlyGoodbye) {
|
|
5992
|
+
state.markGoodbyeIssued();
|
|
5993
|
+
return okResult({
|
|
5994
|
+
kind: "timeout",
|
|
5995
|
+
recentActivity: false,
|
|
5996
|
+
userMessage: earlyGoodbye,
|
|
5997
|
+
instruction: `Print \`userMessage\` verbatim as your final output, then exit. Do NOT post anything in the doc \u2014 the connection is gone, doc-side awareness is already cleared. Stop calling composer_next_event on this room until the user explicitly rejoins.`
|
|
5998
|
+
});
|
|
5999
|
+
}
|
|
5123
6000
|
state.setMonitoring(true);
|
|
5124
6001
|
const onAbort = () => {
|
|
5125
6002
|
state.setMonitoring(false);
|
|
@@ -5148,6 +6025,16 @@ async function handleNextEvent(args, signal) {
|
|
|
5148
6025
|
}
|
|
5149
6026
|
});
|
|
5150
6027
|
}
|
|
6028
|
+
const lateGoodbye = terminalRejectGoodbye(state);
|
|
6029
|
+
if (lateGoodbye) {
|
|
6030
|
+
state.markGoodbyeIssued();
|
|
6031
|
+
return okResult({
|
|
6032
|
+
kind: "timeout",
|
|
6033
|
+
recentActivity: false,
|
|
6034
|
+
userMessage: lateGoodbye,
|
|
6035
|
+
instruction: `Print \`userMessage\` verbatim as your final output, then exit. Do NOT post anything in the doc \u2014 the connection is gone. Stop calling composer_next_event on this room until the user explicitly rejoins.`
|
|
6036
|
+
});
|
|
6037
|
+
}
|
|
5151
6038
|
const msSinceActivity = Date.now() - state.lastRemoteActivityAt;
|
|
5152
6039
|
const recentActivity = msSinceActivity < ACTIVITY_WINDOW_MS;
|
|
5153
6040
|
const minutesSince = Math.max(1, Math.round(msSinceActivity / 6e4));
|
|
@@ -5170,8 +6057,8 @@ async function handleNextEvent(args, signal) {
|
|
|
5170
6057
|
kind: "timeout",
|
|
5171
6058
|
recentActivity: false,
|
|
5172
6059
|
secondsSinceActivity: Math.round(msSinceActivity / 1e3),
|
|
5173
|
-
userMessage:
|
|
5174
|
-
instruction: `
|
|
6060
|
+
userMessage: goodbyeIdleTimeout(roomId),
|
|
6061
|
+
instruction: `Print \`userMessage\` verbatim as your final output, then exit. Do NOT post anything in the doc \u2014 the avatar has already cleared. Stop calling composer_next_event on this room until the user explicitly rejoins.`
|
|
5175
6062
|
});
|
|
5176
6063
|
}
|
|
5177
6064
|
function handleGetSection(args) {
|
|
@@ -5237,12 +6124,10 @@ function handleGetThread(args) {
|
|
|
5237
6124
|
replies: shapedReplies
|
|
5238
6125
|
});
|
|
5239
6126
|
}
|
|
5240
|
-
function
|
|
5241
|
-
const a = asObject(args);
|
|
5242
|
-
const roomId = asString(a.roomId, "roomId");
|
|
6127
|
+
function performAddComment(state, a) {
|
|
5243
6128
|
const anchor = asAnchor(a.anchor);
|
|
5244
6129
|
const text = asString(a.text, "text");
|
|
5245
|
-
const
|
|
6130
|
+
const agentState = asOptionalAgentState(a.state, "state");
|
|
5246
6131
|
const resolved = resolveServerAnchor(state.doc, anchor);
|
|
5247
6132
|
if (!resolved.ok) {
|
|
5248
6133
|
return errorResult(
|
|
@@ -5250,36 +6135,59 @@ function handleAddComment(args) {
|
|
|
5250
6135
|
${resolved.currentSectionText}`
|
|
5251
6136
|
);
|
|
5252
6137
|
}
|
|
5253
|
-
const id =
|
|
5254
|
-
const
|
|
5255
|
-
|
|
6138
|
+
const id = nanoid4();
|
|
6139
|
+
const createdAt = Date.now();
|
|
6140
|
+
const comment = {
|
|
5256
6141
|
id,
|
|
5257
6142
|
authorName: state.actingAs,
|
|
5258
6143
|
authorColor: state.identity.color,
|
|
5259
6144
|
authorUserId: state.identity.userId,
|
|
5260
6145
|
authorIsAgent: true,
|
|
5261
6146
|
text,
|
|
5262
|
-
createdAt
|
|
6147
|
+
createdAt,
|
|
5263
6148
|
resolved: false,
|
|
5264
6149
|
anchorFrom: resolved.from,
|
|
5265
6150
|
anchorTo: resolved.to,
|
|
5266
|
-
replies: []
|
|
6151
|
+
replies: [],
|
|
6152
|
+
...agentState !== void 0 ? { state: agentState } : {}
|
|
6153
|
+
};
|
|
6154
|
+
Y6.transact(state.doc, () => {
|
|
6155
|
+
state.doc.getMap("comments").set(id, comment);
|
|
6156
|
+
emitActivity(state.doc, {
|
|
6157
|
+
type: "comment",
|
|
6158
|
+
threadId: id,
|
|
6159
|
+
authorName: state.actingAs,
|
|
6160
|
+
authorColor: state.identity.color,
|
|
6161
|
+
authorUserId: state.identity.userId,
|
|
6162
|
+
authorIsAgent: true,
|
|
6163
|
+
text: textPreview(text),
|
|
6164
|
+
participantUserIds: getParticipantUserIds(comment),
|
|
6165
|
+
createdAt
|
|
6166
|
+
});
|
|
5267
6167
|
});
|
|
5268
6168
|
state.markThreadActive(id);
|
|
6169
|
+
if (agentState !== void 0 && agentState !== "ready") {
|
|
6170
|
+
state.publishAgentWork(id, void 0, agentState);
|
|
6171
|
+
}
|
|
5269
6172
|
return okResult({ id });
|
|
5270
6173
|
}
|
|
5271
|
-
function
|
|
6174
|
+
function handleAddComment(args) {
|
|
5272
6175
|
const a = asObject(args);
|
|
5273
6176
|
const roomId = asString(a.roomId, "roomId");
|
|
6177
|
+
const state = getOrError(roomId);
|
|
6178
|
+
return performAddComment(state, a);
|
|
6179
|
+
}
|
|
6180
|
+
function performReplyComment(state, a) {
|
|
5274
6181
|
const threadId = asString(a.threadId, "threadId");
|
|
5275
6182
|
const text = asString(a.text, "text");
|
|
5276
|
-
const
|
|
6183
|
+
const agentState = asOptionalAgentState(a.state, "state");
|
|
5277
6184
|
const comments = state.doc.getMap("comments");
|
|
5278
6185
|
const existing = comments.get(threadId);
|
|
5279
6186
|
if (!existing) {
|
|
5280
6187
|
return errorResult(`comment not found: ${threadId}`);
|
|
5281
6188
|
}
|
|
5282
|
-
const replyId =
|
|
6189
|
+
const replyId = nanoid4();
|
|
6190
|
+
const createdAt = Date.now();
|
|
5283
6191
|
const reply = {
|
|
5284
6192
|
id: replyId,
|
|
5285
6193
|
authorName: state.actingAs,
|
|
@@ -5287,21 +6195,49 @@ function handleReplyComment(args) {
|
|
|
5287
6195
|
authorUserId: state.identity.userId,
|
|
5288
6196
|
authorIsAgent: true,
|
|
5289
6197
|
text,
|
|
5290
|
-
createdAt
|
|
6198
|
+
createdAt,
|
|
6199
|
+
...agentState !== void 0 ? { state: agentState } : {}
|
|
5291
6200
|
};
|
|
5292
|
-
|
|
6201
|
+
const updated = {
|
|
5293
6202
|
...existing,
|
|
5294
6203
|
replies: [...existing.replies ?? [], reply]
|
|
6204
|
+
};
|
|
6205
|
+
Y6.transact(state.doc, () => {
|
|
6206
|
+
comments.set(threadId, updated);
|
|
6207
|
+
emitActivity(state.doc, {
|
|
6208
|
+
type: "reply",
|
|
6209
|
+
threadId,
|
|
6210
|
+
replyId,
|
|
6211
|
+
threadKind: "comment",
|
|
6212
|
+
authorName: state.actingAs,
|
|
6213
|
+
authorColor: state.identity.color,
|
|
6214
|
+
authorUserId: state.identity.userId,
|
|
6215
|
+
authorIsAgent: true,
|
|
6216
|
+
text: textPreview(text),
|
|
6217
|
+
participantUserIds: getParticipantUserIds(
|
|
6218
|
+
updated
|
|
6219
|
+
),
|
|
6220
|
+
createdAt
|
|
6221
|
+
});
|
|
5295
6222
|
});
|
|
5296
6223
|
state.markThreadActive(threadId);
|
|
6224
|
+
if (agentState !== void 0 && agentState !== "ready") {
|
|
6225
|
+
state.publishAgentWork(threadId, replyId, agentState);
|
|
6226
|
+
} else if (agentState === "ready") {
|
|
6227
|
+
state.clearAgentWork(threadId, void 0);
|
|
6228
|
+
}
|
|
5297
6229
|
return okResult({ replyId });
|
|
5298
6230
|
}
|
|
5299
|
-
function
|
|
6231
|
+
function handleReplyComment(args) {
|
|
5300
6232
|
const a = asObject(args);
|
|
5301
6233
|
const roomId = asString(a.roomId, "roomId");
|
|
6234
|
+
const state = getOrError(roomId);
|
|
6235
|
+
return performReplyComment(state, a);
|
|
6236
|
+
}
|
|
6237
|
+
function performAddSuggestion(state, a) {
|
|
5302
6238
|
const replacementText = asString(a.replacementText, "replacementText");
|
|
5303
6239
|
const fromThreadId = asOptionalString(a.fromThreadId, "fromThreadId");
|
|
5304
|
-
const
|
|
6240
|
+
const agentState = asOptionalAgentState(a.state, "state");
|
|
5305
6241
|
let anchorFrom;
|
|
5306
6242
|
let anchorTo;
|
|
5307
6243
|
let originalText;
|
|
@@ -5337,38 +6273,61 @@ ${resolved.currentSectionText}`
|
|
|
5337
6273
|
anchorTo = resolved.to;
|
|
5338
6274
|
originalText = anchor.textToFind;
|
|
5339
6275
|
}
|
|
5340
|
-
const id =
|
|
5341
|
-
const
|
|
5342
|
-
|
|
6276
|
+
const id = nanoid4();
|
|
6277
|
+
const createdAt = Date.now();
|
|
6278
|
+
const suggestion = {
|
|
5343
6279
|
id,
|
|
5344
6280
|
authorName: state.actingAs,
|
|
5345
6281
|
authorColor: state.identity.color,
|
|
5346
6282
|
authorUserId: state.identity.userId,
|
|
5347
6283
|
authorIsAgent: true,
|
|
5348
|
-
createdAt
|
|
6284
|
+
createdAt,
|
|
5349
6285
|
status: "pending",
|
|
5350
6286
|
anchorFrom,
|
|
5351
6287
|
anchorTo,
|
|
5352
6288
|
replacementText,
|
|
5353
6289
|
originalText,
|
|
5354
|
-
replies: []
|
|
6290
|
+
replies: [],
|
|
6291
|
+
...agentState !== void 0 ? { state: agentState } : {}
|
|
6292
|
+
};
|
|
6293
|
+
Y6.transact(state.doc, () => {
|
|
6294
|
+
state.doc.getMap("suggestions").set(id, suggestion);
|
|
6295
|
+
emitActivity(state.doc, {
|
|
6296
|
+
type: "suggestion",
|
|
6297
|
+
threadId: id,
|
|
6298
|
+
authorName: state.actingAs,
|
|
6299
|
+
authorColor: state.identity.color,
|
|
6300
|
+
authorUserId: state.identity.userId,
|
|
6301
|
+
authorIsAgent: true,
|
|
6302
|
+
text: textPreview(replacementText),
|
|
6303
|
+
participantUserIds: getParticipantUserIds(suggestion),
|
|
6304
|
+
createdAt
|
|
6305
|
+
});
|
|
5355
6306
|
});
|
|
5356
6307
|
state.markThreadActive(id);
|
|
5357
6308
|
if (fromThreadId) state.markThreadActive(fromThreadId);
|
|
6309
|
+
if (agentState !== void 0 && agentState !== "ready") {
|
|
6310
|
+
state.publishAgentWork(id, void 0, agentState);
|
|
6311
|
+
}
|
|
5358
6312
|
return okResult({ id });
|
|
5359
6313
|
}
|
|
5360
|
-
function
|
|
6314
|
+
function handleAddSuggestion(args) {
|
|
5361
6315
|
const a = asObject(args);
|
|
5362
6316
|
const roomId = asString(a.roomId, "roomId");
|
|
6317
|
+
const state = getOrError(roomId);
|
|
6318
|
+
return performAddSuggestion(state, a);
|
|
6319
|
+
}
|
|
6320
|
+
function performReplySuggestion(state, a) {
|
|
5363
6321
|
const threadId = asString(a.threadId, "threadId");
|
|
5364
6322
|
const text = asString(a.text, "text");
|
|
5365
|
-
const
|
|
6323
|
+
const agentState = asOptionalAgentState(a.state, "state");
|
|
5366
6324
|
const suggestions = state.doc.getMap("suggestions");
|
|
5367
6325
|
const existing = suggestions.get(threadId);
|
|
5368
6326
|
if (!existing) {
|
|
5369
6327
|
return errorResult(`suggestion not found: ${threadId}`);
|
|
5370
6328
|
}
|
|
5371
|
-
const replyId =
|
|
6329
|
+
const replyId = nanoid4();
|
|
6330
|
+
const createdAt = Date.now();
|
|
5372
6331
|
const reply = {
|
|
5373
6332
|
id: replyId,
|
|
5374
6333
|
authorName: state.actingAs,
|
|
@@ -5376,28 +6335,213 @@ function handleReplySuggestion(args) {
|
|
|
5376
6335
|
authorUserId: state.identity.userId,
|
|
5377
6336
|
authorIsAgent: true,
|
|
5378
6337
|
text,
|
|
5379
|
-
createdAt
|
|
6338
|
+
createdAt,
|
|
6339
|
+
...agentState !== void 0 ? { state: agentState } : {}
|
|
5380
6340
|
};
|
|
5381
|
-
|
|
6341
|
+
const updated = {
|
|
5382
6342
|
...existing,
|
|
5383
6343
|
replies: [...existing.replies ?? [], reply]
|
|
6344
|
+
};
|
|
6345
|
+
Y6.transact(state.doc, () => {
|
|
6346
|
+
suggestions.set(threadId, updated);
|
|
6347
|
+
emitActivity(state.doc, {
|
|
6348
|
+
type: "reply",
|
|
6349
|
+
threadId,
|
|
6350
|
+
replyId,
|
|
6351
|
+
threadKind: "suggestion",
|
|
6352
|
+
authorName: state.actingAs,
|
|
6353
|
+
authorColor: state.identity.color,
|
|
6354
|
+
authorUserId: state.identity.userId,
|
|
6355
|
+
authorIsAgent: true,
|
|
6356
|
+
text: textPreview(text),
|
|
6357
|
+
participantUserIds: getParticipantUserIds(
|
|
6358
|
+
updated
|
|
6359
|
+
),
|
|
6360
|
+
createdAt
|
|
6361
|
+
});
|
|
5384
6362
|
});
|
|
5385
6363
|
state.markThreadActive(threadId);
|
|
6364
|
+
if (agentState !== void 0 && agentState !== "ready") {
|
|
6365
|
+
state.publishAgentWork(threadId, replyId, agentState);
|
|
6366
|
+
} else if (agentState === "ready") {
|
|
6367
|
+
state.clearAgentWork(threadId, void 0);
|
|
6368
|
+
}
|
|
5386
6369
|
return okResult({ replyId });
|
|
5387
6370
|
}
|
|
5388
|
-
function
|
|
6371
|
+
function handleReplySuggestion(args) {
|
|
5389
6372
|
const a = asObject(args);
|
|
5390
6373
|
const roomId = asString(a.roomId, "roomId");
|
|
5391
|
-
const threadId = asString(a.threadId, "threadId");
|
|
5392
6374
|
const state = getOrError(roomId);
|
|
6375
|
+
return performReplySuggestion(state, a);
|
|
6376
|
+
}
|
|
6377
|
+
function performResolveThread(state, a) {
|
|
6378
|
+
const threadId = asString(a.threadId, "threadId");
|
|
5393
6379
|
const comments = state.doc.getMap("comments");
|
|
5394
6380
|
const existing = comments.get(threadId);
|
|
5395
6381
|
if (!existing) {
|
|
5396
6382
|
return errorResult(`comment not found: ${threadId}`);
|
|
5397
6383
|
}
|
|
5398
|
-
|
|
6384
|
+
const createdAt = Date.now();
|
|
6385
|
+
Y6.transact(state.doc, () => {
|
|
6386
|
+
comments.set(threadId, { ...existing, resolved: true });
|
|
6387
|
+
emitActivity(state.doc, {
|
|
6388
|
+
type: "resolve",
|
|
6389
|
+
threadId,
|
|
6390
|
+
authorName: state.actingAs,
|
|
6391
|
+
authorColor: state.identity.color,
|
|
6392
|
+
authorUserId: state.identity.userId,
|
|
6393
|
+
authorIsAgent: true,
|
|
6394
|
+
participantUserIds: getParticipantUserIds(existing),
|
|
6395
|
+
createdAt
|
|
6396
|
+
});
|
|
6397
|
+
});
|
|
5399
6398
|
return okResult({ threadId, resolved: true });
|
|
5400
6399
|
}
|
|
6400
|
+
function handleResolveThread(args) {
|
|
6401
|
+
const a = asObject(args);
|
|
6402
|
+
const roomId = asString(a.roomId, "roomId");
|
|
6403
|
+
const state = getOrError(roomId);
|
|
6404
|
+
return performResolveThread(state, a);
|
|
6405
|
+
}
|
|
6406
|
+
function performAgentStatus(state, a) {
|
|
6407
|
+
const threadId = asString(a.threadId, "threadId");
|
|
6408
|
+
const agentState = asAgentState(a.state, "state");
|
|
6409
|
+
const replyIdArg = asOptionalString(a.replyId, "replyId");
|
|
6410
|
+
const textArg = asOptionalString(a.text, "text");
|
|
6411
|
+
asOptionalString(a.note, "note");
|
|
6412
|
+
const note = typeof a.note === "string" && a.note.length > 0 ? a.note : void 0;
|
|
6413
|
+
const kindArg = a.kind === void 0 ? "comment" : a.kind === "comment" || a.kind === "suggestion" ? a.kind : null;
|
|
6414
|
+
if (kindArg === null) {
|
|
6415
|
+
return errorResult(`kind must be "comment" or "suggestion"`);
|
|
6416
|
+
}
|
|
6417
|
+
const commentsMap = state.doc.getMap("comments");
|
|
6418
|
+
const suggestionsMap = state.doc.getMap("suggestions");
|
|
6419
|
+
const preferredMap = kindArg === "comment" ? commentsMap : suggestionsMap;
|
|
6420
|
+
const fallbackMap = kindArg === "comment" ? suggestionsMap : commentsMap;
|
|
6421
|
+
let parent = preferredMap.get(threadId);
|
|
6422
|
+
let resolvedKind = kindArg;
|
|
6423
|
+
if (!parent) {
|
|
6424
|
+
const fallback = fallbackMap.get(threadId);
|
|
6425
|
+
if (fallback) {
|
|
6426
|
+
parent = fallback;
|
|
6427
|
+
resolvedKind = kindArg === "comment" ? "suggestion" : "comment";
|
|
6428
|
+
}
|
|
6429
|
+
}
|
|
6430
|
+
if (!parent) {
|
|
6431
|
+
return errorResult(`thread not found: ${threadId}`);
|
|
6432
|
+
}
|
|
6433
|
+
const parentMap = resolvedKind === "comment" ? commentsMap : suggestionsMap;
|
|
6434
|
+
const currentWork = state.readAgentWork();
|
|
6435
|
+
for (const entry of currentWork) {
|
|
6436
|
+
const p = commentsMap.get(entry.threadId) ?? suggestionsMap.get(entry.threadId);
|
|
6437
|
+
if (!p) continue;
|
|
6438
|
+
if (entry.replyId !== void 0) {
|
|
6439
|
+
const replies = Array.isArray(p.replies) ? p.replies : [];
|
|
6440
|
+
const targetReply = replies.find(
|
|
6441
|
+
(r) => !!r && typeof r === "object" && r.id === entry.replyId
|
|
6442
|
+
);
|
|
6443
|
+
if (targetReply && targetReply.state === "ready") {
|
|
6444
|
+
state.clearAgentWork(entry.threadId, entry.replyId);
|
|
6445
|
+
}
|
|
6446
|
+
} else {
|
|
6447
|
+
if (p.state === "ready") {
|
|
6448
|
+
state.clearAgentWork(entry.threadId, void 0);
|
|
6449
|
+
}
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
if (replyIdArg !== void 0) {
|
|
6453
|
+
const replies = Array.isArray(parent.replies) ? parent.replies : [];
|
|
6454
|
+
const replyIdx = replies.findIndex(
|
|
6455
|
+
(r) => !!r && typeof r === "object" && r.id === replyIdArg
|
|
6456
|
+
);
|
|
6457
|
+
if (replyIdx < 0) {
|
|
6458
|
+
return errorResult(`reply not found: ${replyIdArg} on thread ${threadId}`);
|
|
6459
|
+
}
|
|
6460
|
+
if (agentState === "ready") {
|
|
6461
|
+
state.clearAgentWork(threadId, replyIdArg);
|
|
6462
|
+
state.clearAgentWork(threadId, void 0);
|
|
6463
|
+
}
|
|
6464
|
+
const existingReply = replies[replyIdx];
|
|
6465
|
+
const nextReply = {
|
|
6466
|
+
...existingReply,
|
|
6467
|
+
state: agentState
|
|
6468
|
+
};
|
|
6469
|
+
if (textArg !== void 0) nextReply.text = textArg;
|
|
6470
|
+
const nextReplies = replies.slice();
|
|
6471
|
+
nextReplies[replyIdx] = nextReply;
|
|
6472
|
+
parentMap.set(threadId, { ...parent, replies: nextReplies });
|
|
6473
|
+
if (agentState !== "ready") {
|
|
6474
|
+
state.publishAgentWork(threadId, replyIdArg, agentState, note);
|
|
6475
|
+
}
|
|
6476
|
+
return okResult({ replyId: replyIdArg });
|
|
6477
|
+
}
|
|
6478
|
+
if (agentState === "ready") {
|
|
6479
|
+
state.clearAgentWork(threadId, void 0);
|
|
6480
|
+
}
|
|
6481
|
+
const nextRecord = { ...parent, state: agentState };
|
|
6482
|
+
if (textArg !== void 0) {
|
|
6483
|
+
if (resolvedKind === "suggestion") {
|
|
6484
|
+
nextRecord.replacementText = textArg;
|
|
6485
|
+
} else {
|
|
6486
|
+
nextRecord.text = textArg;
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6489
|
+
parentMap.set(threadId, nextRecord);
|
|
6490
|
+
if (agentState !== "ready") {
|
|
6491
|
+
state.publishAgentWork(threadId, void 0, agentState, note);
|
|
6492
|
+
}
|
|
6493
|
+
return okResult({ id: threadId });
|
|
6494
|
+
}
|
|
6495
|
+
function handleAgentStatus(args) {
|
|
6496
|
+
const a = asObject(args);
|
|
6497
|
+
const roomId = asString(a.roomId, "roomId");
|
|
6498
|
+
const state = getOrError(roomId);
|
|
6499
|
+
return performAgentStatus(state, a);
|
|
6500
|
+
}
|
|
6501
|
+
function performDone(state, a) {
|
|
6502
|
+
const threadId = asString(a.threadId, "threadId");
|
|
6503
|
+
const replyIdArg = asOptionalString(a.replyId, "replyId");
|
|
6504
|
+
state.clearAgentWork(threadId, void 0);
|
|
6505
|
+
if (replyIdArg !== void 0) {
|
|
6506
|
+
state.clearAgentWork(threadId, replyIdArg);
|
|
6507
|
+
}
|
|
6508
|
+
return okResult({ threadId });
|
|
6509
|
+
}
|
|
6510
|
+
function handleDone(args) {
|
|
6511
|
+
const a = asObject(args);
|
|
6512
|
+
const roomId = asString(a.roomId, "roomId");
|
|
6513
|
+
const state = getOrError(roomId);
|
|
6514
|
+
return performDone(state, a);
|
|
6515
|
+
}
|
|
6516
|
+
async function handleExportToFile(args) {
|
|
6517
|
+
const a = asObject(args);
|
|
6518
|
+
const roomId = asString(a.roomId, "roomId");
|
|
6519
|
+
const filePath = asString(a.path, "path");
|
|
6520
|
+
if (!path3.isAbsolute(filePath)) {
|
|
6521
|
+
return errorResult(
|
|
6522
|
+
`path must be absolute, got: ${filePath}. Resolve to an absolute path before calling.`
|
|
6523
|
+
);
|
|
6524
|
+
}
|
|
6525
|
+
const state = getOrError(roomId);
|
|
6526
|
+
const markdown = serializeDocAsMarkdown(state.doc);
|
|
6527
|
+
try {
|
|
6528
|
+
await fs3.writeFile(filePath, markdown, "utf8");
|
|
6529
|
+
} catch (err) {
|
|
6530
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6531
|
+
return errorResult(`failed to write ${filePath}: ${message}`);
|
|
6532
|
+
}
|
|
6533
|
+
return okResult({
|
|
6534
|
+
roomId,
|
|
6535
|
+
path: filePath,
|
|
6536
|
+
bytesWritten: Buffer.byteLength(markdown, "utf8")
|
|
6537
|
+
});
|
|
6538
|
+
}
|
|
6539
|
+
function _registerRoomForTest(roomId, state) {
|
|
6540
|
+
rooms.set(roomId, state);
|
|
6541
|
+
}
|
|
6542
|
+
function _clearRoomsForTest() {
|
|
6543
|
+
rooms.clear();
|
|
6544
|
+
}
|
|
5401
6545
|
async function dispatchTool(name, args, signal) {
|
|
5402
6546
|
const roomId = args && typeof args === "object" && "roomId" in args ? args.roomId : void 0;
|
|
5403
6547
|
const needsExistingRoom = name !== "composer_create_room" && name !== "composer_join_room";
|
|
@@ -5406,33 +6550,44 @@ async function dispatchTool(name, args, signal) {
|
|
|
5406
6550
|
await state.ensureConnected();
|
|
5407
6551
|
}
|
|
5408
6552
|
try {
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
6553
|
+
try {
|
|
6554
|
+
switch (name) {
|
|
6555
|
+
case "composer_create_room":
|
|
6556
|
+
return await handleCreateRoom(args);
|
|
6557
|
+
case "composer_join_room":
|
|
6558
|
+
return await handleJoinRoom(args);
|
|
6559
|
+
case "composer_attach_room":
|
|
6560
|
+
return handleAttachRoom(args);
|
|
6561
|
+
case "composer_next_event":
|
|
6562
|
+
return await handleNextEvent(args, signal);
|
|
6563
|
+
case "composer_get_section":
|
|
6564
|
+
return handleGetSection(args);
|
|
6565
|
+
case "composer_get_full_doc":
|
|
6566
|
+
return handleGetFullDoc(args);
|
|
6567
|
+
case "composer_get_thread":
|
|
6568
|
+
return handleGetThread(args);
|
|
6569
|
+
case "composer_add_comment":
|
|
6570
|
+
return handleAddComment(args);
|
|
6571
|
+
case "composer_reply_comment":
|
|
6572
|
+
return handleReplyComment(args);
|
|
6573
|
+
case "composer_add_suggestion":
|
|
6574
|
+
return handleAddSuggestion(args);
|
|
6575
|
+
case "composer_reply_suggestion":
|
|
6576
|
+
return handleReplySuggestion(args);
|
|
6577
|
+
case "composer_resolve_thread":
|
|
6578
|
+
return handleResolveThread(args);
|
|
6579
|
+
case "composer_export_to_file":
|
|
6580
|
+
return await handleExportToFile(args);
|
|
6581
|
+
case "composer_agent_status":
|
|
6582
|
+
return handleAgentStatus(args);
|
|
6583
|
+
case "composer_done":
|
|
6584
|
+
return handleDone(args);
|
|
6585
|
+
default:
|
|
6586
|
+
return errorResult(`unknown tool: ${name}`);
|
|
6587
|
+
}
|
|
6588
|
+
} catch (err) {
|
|
6589
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6590
|
+
return errorResult(message);
|
|
5436
6591
|
}
|
|
5437
6592
|
} finally {
|
|
5438
6593
|
state?.scheduleIdleDisconnect(3e4);
|
|
@@ -5587,6 +6742,16 @@ export {
|
|
|
5587
6742
|
loadOrCreateIdentity,
|
|
5588
6743
|
logError,
|
|
5589
6744
|
teardownAllRooms,
|
|
6745
|
+
performAddComment,
|
|
6746
|
+
performReplyComment,
|
|
6747
|
+
performAddSuggestion,
|
|
6748
|
+
performReplySuggestion,
|
|
6749
|
+
performResolveThread,
|
|
6750
|
+
performAgentStatus,
|
|
6751
|
+
performDone,
|
|
6752
|
+
_registerRoomForTest,
|
|
6753
|
+
_clearRoomsForTest,
|
|
6754
|
+
dispatchTool,
|
|
5590
6755
|
startMcpServer,
|
|
5591
6756
|
startMcpHttpServer,
|
|
5592
6757
|
__test_dispatch,
|