zooid 0.7.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/bin.js +218 -114
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-OKGNOROJ.js → chunk-LOILUENL.js} +389 -51
- package/dist/chunk-LOILUENL.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +13 -9
- package/src/bin.ts +1 -1
- package/src/commands/dev.ts +26 -12
- package/src/daemon/start-daemon.ts +29 -10
- package/src/web/fetch.integration.test.ts +61 -0
- package/src/web/fetch.test.ts +139 -0
- package/src/web/fetch.ts +85 -0
- package/src/web/pin.test.ts +24 -0
- package/src/web/pin.ts +13 -0
- package/src/web/resolve.test.ts +55 -27
- package/src/web/resolve.ts +22 -10
- package/src/web/test-helpers.ts +28 -0
- package/dist/chunk-OKGNOROJ.js.map +0 -1
- package/dist/web/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/dist/web/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/dist/web/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/dist/web/assets/index-BqOX0Pv4.js +0 -305
- package/dist/web/assets/index-C-ZtBp7U.css +0 -1
- package/dist/web/assets/index-Dc8BJYf_.js +0 -118066
- package/dist/web/assets/reaction-picker-emoji-xD2HkULN.js +0 -716
- package/dist/web/favicon.svg +0 -1
- package/dist/web/index.html +0 -14
|
@@ -23383,6 +23383,17 @@ function acpUpdateToAgentEvent(notif) {
|
|
|
23383
23383
|
};
|
|
23384
23384
|
case "plan":
|
|
23385
23385
|
return { type: "plan", sessionId, entries: update.entries };
|
|
23386
|
+
case "available_commands_update": {
|
|
23387
|
+
const u = update;
|
|
23388
|
+
return {
|
|
23389
|
+
type: "available_commands",
|
|
23390
|
+
sessionId,
|
|
23391
|
+
commands: (u.availableCommands ?? []).map((c) => ({
|
|
23392
|
+
name: c.name,
|
|
23393
|
+
description: c.description
|
|
23394
|
+
}))
|
|
23395
|
+
};
|
|
23396
|
+
}
|
|
23386
23397
|
default:
|
|
23387
23398
|
return null;
|
|
23388
23399
|
}
|
|
@@ -24900,6 +24911,18 @@ var MatrixClient = class {
|
|
|
24900
24911
|
}
|
|
24901
24912
|
throw new Error(`invite(${opts.targetUserId}) failed: ${r.status}`);
|
|
24902
24913
|
}
|
|
24914
|
+
async leaveRoom(roomId, asUserId, opts) {
|
|
24915
|
+
const url2 = `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/leave?user_id=${encodeURIComponent(asUserId)}`;
|
|
24916
|
+
const r = await this.fetch(url2, {
|
|
24917
|
+
method: "POST",
|
|
24918
|
+
headers: {
|
|
24919
|
+
Authorization: `Bearer ${this.asToken}`,
|
|
24920
|
+
"content-type": "application/json"
|
|
24921
|
+
},
|
|
24922
|
+
body: JSON.stringify(opts?.reason ? { reason: opts.reason } : {})
|
|
24923
|
+
});
|
|
24924
|
+
if (!r.ok) throw new Error(`leaveRoom(${roomId}, ${asUserId}) failed: ${r.status}`);
|
|
24925
|
+
}
|
|
24903
24926
|
async joinRoom(roomIdOrAlias, asUserId) {
|
|
24904
24927
|
const url2 = `${this.homeserver}/_matrix/client/v3/join/${encodeURIComponent(roomIdOrAlias)}?user_id=${encodeURIComponent(asUserId)}`;
|
|
24905
24928
|
const r = await this.fetch(url2, {
|
|
@@ -25134,14 +25157,29 @@ var MatrixContextProvider = class {
|
|
|
25134
25157
|
}
|
|
25135
25158
|
toMessage(ev) {
|
|
25136
25159
|
if (ev.type !== "m.room.message") return null;
|
|
25137
|
-
|
|
25160
|
+
const msgtype = ev.content?.msgtype;
|
|
25161
|
+
const body = ev.content?.body;
|
|
25138
25162
|
const agent = this.opts.agentBots.get(ev.sender);
|
|
25139
|
-
const relatesTo = ev.content["m.relates_to"];
|
|
25163
|
+
const relatesTo = ev.content?.["m.relates_to"];
|
|
25140
25164
|
const threadId = relatesTo?.rel_type === "m.thread" && relatesTo.event_id ? relatesTo.event_id : void 0;
|
|
25165
|
+
if (msgtype === "m.image" || msgtype === "m.file" || msgtype === "m.video" || msgtype === "m.audio") {
|
|
25166
|
+
const kind = msgtype.slice(2);
|
|
25167
|
+
const name = typeof body === "string" && body ? body : "untitled";
|
|
25168
|
+
return {
|
|
25169
|
+
id: ev.event_id,
|
|
25170
|
+
sender: ev.sender,
|
|
25171
|
+
text: `[${kind}: ${name}]`,
|
|
25172
|
+
timestamp: new Date(ev.origin_server_ts).toISOString(),
|
|
25173
|
+
is_agent: agent !== void 0,
|
|
25174
|
+
...agent !== void 0 ? { agent_name: agent } : {},
|
|
25175
|
+
...threadId !== void 0 ? { thread_id: threadId } : {}
|
|
25176
|
+
};
|
|
25177
|
+
}
|
|
25178
|
+
if (msgtype !== "m.text" || typeof body !== "string") return null;
|
|
25141
25179
|
return {
|
|
25142
25180
|
id: ev.event_id,
|
|
25143
25181
|
sender: ev.sender,
|
|
25144
|
-
text:
|
|
25182
|
+
text: body,
|
|
25145
25183
|
timestamp: new Date(ev.origin_server_ts).toISOString(),
|
|
25146
25184
|
is_agent: agent !== void 0,
|
|
25147
25185
|
...agent !== void 0 ? { agent_name: agent } : {},
|
|
@@ -25215,6 +25253,10 @@ function stripMention(body, userId) {
|
|
|
25215
25253
|
}
|
|
25216
25254
|
|
|
25217
25255
|
// ../transport-matrix/src/router.ts
|
|
25256
|
+
var MEDIA_MSGTYPES = /* @__PURE__ */ new Set(["m.image", "m.file", "m.video", "m.audio"]);
|
|
25257
|
+
function isMediaMsgtype(t) {
|
|
25258
|
+
return t !== void 0 && MEDIA_MSGTYPES.has(t);
|
|
25259
|
+
}
|
|
25218
25260
|
function inboundThreadRoot(event) {
|
|
25219
25261
|
const r = event.content?.["m.relates_to"];
|
|
25220
25262
|
return r?.rel_type === "m.thread" && r.event_id ? r.event_id : void 0;
|
|
@@ -25222,6 +25264,7 @@ function inboundThreadRoot(event) {
|
|
|
25222
25264
|
function route(event, agents, threadStates) {
|
|
25223
25265
|
if (event.type !== "m.room.message") return [];
|
|
25224
25266
|
if (!event.content?.msgtype) return [];
|
|
25267
|
+
if (isMediaMsgtype(event.content.msgtype)) return [];
|
|
25225
25268
|
const mentions = new Set(extractMentions(event));
|
|
25226
25269
|
const matches = [];
|
|
25227
25270
|
const threadRoot = inboundThreadRoot(event);
|
|
@@ -25260,10 +25303,12 @@ async function ensureWorkforceSpace(opts) {
|
|
|
25260
25303
|
name: display,
|
|
25261
25304
|
preset: opts.preset,
|
|
25262
25305
|
creation_content: { type: "m.space" },
|
|
25263
|
-
//
|
|
25264
|
-
//
|
|
25265
|
-
//
|
|
25266
|
-
initial_state: [
|
|
25306
|
+
// Pin the join rule regardless of preset. Defaults to invite so the space
|
|
25307
|
+
// can't be walked into (which would otherwise satisfy every restricted
|
|
25308
|
+
// child room's allow); `zooid dev` opts into `public` for local-only use.
|
|
25309
|
+
initial_state: [
|
|
25310
|
+
{ type: "m.room.join_rules", state_key: "", content: { join_rule: opts.joinRule ?? "invite" } }
|
|
25311
|
+
]
|
|
25267
25312
|
};
|
|
25268
25313
|
if (opts.admins && opts.admins.length > 0) {
|
|
25269
25314
|
body.invite = opts.admins;
|
|
@@ -25482,6 +25527,15 @@ function toPlanBody(evt) {
|
|
|
25482
25527
|
entries: evt.entries
|
|
25483
25528
|
};
|
|
25484
25529
|
}
|
|
25530
|
+
function toAvailableCommandsBody(evt) {
|
|
25531
|
+
return {
|
|
25532
|
+
session_id: evt.sessionId,
|
|
25533
|
+
available_commands: evt.commands.map((c) => ({
|
|
25534
|
+
name: c.name,
|
|
25535
|
+
description: c.description
|
|
25536
|
+
}))
|
|
25537
|
+
};
|
|
25538
|
+
}
|
|
25485
25539
|
var RECOVERY_URLS = {
|
|
25486
25540
|
auth_missing: "https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over",
|
|
25487
25541
|
auth_invalid: "https://zooid.dev/docs/guides/run-in-container#authentication-that-carries-over",
|
|
@@ -25576,8 +25630,180 @@ function toMatrixHtml(markdown) {
|
|
|
25576
25630
|
});
|
|
25577
25631
|
}
|
|
25578
25632
|
|
|
25633
|
+
// ../transport-matrix/src/pending-media.ts
|
|
25634
|
+
var MAX_MEDIA_PER_TURN = 8;
|
|
25635
|
+
var PendingMediaStore = class {
|
|
25636
|
+
queues = /* @__PURE__ */ new Map();
|
|
25637
|
+
key(roomId, threadKey) {
|
|
25638
|
+
return `${roomId} ${threadKey ?? "main"}`;
|
|
25639
|
+
}
|
|
25640
|
+
add(roomId, threadKey, item) {
|
|
25641
|
+
const k = this.key(roomId, threadKey);
|
|
25642
|
+
const q = this.queues.get(k) ?? [];
|
|
25643
|
+
q.push(item);
|
|
25644
|
+
while (q.length > MAX_MEDIA_PER_TURN) q.shift();
|
|
25645
|
+
this.queues.set(k, q);
|
|
25646
|
+
}
|
|
25647
|
+
drain(roomId, threadKey, sender) {
|
|
25648
|
+
const k = this.key(roomId, threadKey);
|
|
25649
|
+
const q = this.queues.get(k) ?? [];
|
|
25650
|
+
const mine = q.filter((i) => i.sender === sender);
|
|
25651
|
+
const rest = q.filter((i) => i.sender !== sender);
|
|
25652
|
+
if (rest.length) this.queues.set(k, rest);
|
|
25653
|
+
else this.queues.delete(k);
|
|
25654
|
+
return mine;
|
|
25655
|
+
}
|
|
25656
|
+
};
|
|
25657
|
+
|
|
25658
|
+
// ../transport-matrix/src/media-client.ts
|
|
25659
|
+
var MAX_INLINE_IMAGE_BYTES = 524288;
|
|
25660
|
+
var INLINE_IMAGE_MIMES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
25661
|
+
var MAX_DOWNLOAD_BYTES = 33554432;
|
|
25662
|
+
function parseMxcUri(uri) {
|
|
25663
|
+
const m = /^mxc:\/\/([^/]+)\/(.+)$/.exec(uri);
|
|
25664
|
+
return m ? { serverName: m[1], mediaId: m[2] } : null;
|
|
25665
|
+
}
|
|
25666
|
+
var MediaClient = class {
|
|
25667
|
+
homeserver;
|
|
25668
|
+
asToken;
|
|
25669
|
+
fetch;
|
|
25670
|
+
constructor(opts) {
|
|
25671
|
+
this.homeserver = opts.homeserver.replace(/\/$/, "");
|
|
25672
|
+
this.asToken = opts.asToken;
|
|
25673
|
+
this.fetch = opts.fetch ?? globalThis.fetch;
|
|
25674
|
+
}
|
|
25675
|
+
async download(input) {
|
|
25676
|
+
const parsed = parseMxcUri(input.mxcUri);
|
|
25677
|
+
if (!parsed) throw new Error(`not an mxc uri: ${input.mxcUri}`);
|
|
25678
|
+
const url2 = `${this.homeserver}/_matrix/client/v1/media/download/${encodeURIComponent(parsed.serverName)}/${encodeURIComponent(parsed.mediaId)}?user_id=${encodeURIComponent(input.asUserId)}`;
|
|
25679
|
+
const r = await this.fetch(url2, {
|
|
25680
|
+
headers: { Authorization: `Bearer ${this.asToken}` }
|
|
25681
|
+
});
|
|
25682
|
+
if (!r.ok) throw new Error(`media download failed: ${r.status}`);
|
|
25683
|
+
const buf = new Uint8Array(await r.arrayBuffer());
|
|
25684
|
+
const max = input.maxBytes ?? MAX_DOWNLOAD_BYTES;
|
|
25685
|
+
if (buf.byteLength > max) {
|
|
25686
|
+
throw new Error(`media too large: ${buf.byteLength} > ${max}`);
|
|
25687
|
+
}
|
|
25688
|
+
return { data: buf, contentType: r.headers.get("content-type") ?? "application/octet-stream" };
|
|
25689
|
+
}
|
|
25690
|
+
async upload(input) {
|
|
25691
|
+
const params = new URLSearchParams();
|
|
25692
|
+
if (input.filename) params.set("filename", input.filename);
|
|
25693
|
+
params.set("user_id", input.asUserId);
|
|
25694
|
+
const r = await this.fetch(`${this.homeserver}/_matrix/media/v3/upload?${params}`, {
|
|
25695
|
+
method: "POST",
|
|
25696
|
+
headers: { Authorization: `Bearer ${this.asToken}`, "Content-Type": input.contentType },
|
|
25697
|
+
body: input.data
|
|
25698
|
+
});
|
|
25699
|
+
if (!r.ok) throw new Error(`media upload failed: ${r.status}`);
|
|
25700
|
+
return await r.json();
|
|
25701
|
+
}
|
|
25702
|
+
};
|
|
25703
|
+
|
|
25704
|
+
// ../transport-matrix/src/attachments.ts
|
|
25705
|
+
import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
25706
|
+
import { join as join4 } from "path";
|
|
25707
|
+
import { posix } from "path";
|
|
25708
|
+
function sanitize(s, fallback) {
|
|
25709
|
+
const cleaned = s.replace(/[^A-Za-z0-9._-]/g, "").replace(/^\.+/, "");
|
|
25710
|
+
return cleaned || fallback;
|
|
25711
|
+
}
|
|
25712
|
+
function writeAttachment(input) {
|
|
25713
|
+
const dir = sanitize(input.eventId, "event");
|
|
25714
|
+
const name = sanitize(input.filename, "file");
|
|
25715
|
+
const hostDir = join4(input.workspaceDir, ".zooid", "attachments", dir);
|
|
25716
|
+
mkdirSync2(hostDir, { recursive: true });
|
|
25717
|
+
const hostPath = join4(hostDir, name);
|
|
25718
|
+
writeFileSync(hostPath, input.data);
|
|
25719
|
+
const agentPath = posix.join(input.agentWorkspacePath, ".zooid", "attachments", dir, name);
|
|
25720
|
+
return { hostPath, agentPath };
|
|
25721
|
+
}
|
|
25722
|
+
|
|
25579
25723
|
// ../transport-matrix/src/transport.ts
|
|
25580
25724
|
var STARTUP_GRACE_MS = 5e3;
|
|
25725
|
+
async function buildMediaBlocks(items, opts) {
|
|
25726
|
+
const blocks = [];
|
|
25727
|
+
const pathLines = [];
|
|
25728
|
+
if (!opts.media || items.length === 0) return { blocks, pathLines };
|
|
25729
|
+
for (const item of items) {
|
|
25730
|
+
try {
|
|
25731
|
+
const isInlineCandidate = item.msgtype === "m.image" && INLINE_IMAGE_MIMES.includes(item.info?.mimetype ?? "") && (item.info?.size === void 0 || item.info.size <= MAX_INLINE_IMAGE_BYTES);
|
|
25732
|
+
if (isInlineCandidate) {
|
|
25733
|
+
const { data, contentType } = await opts.media.download({
|
|
25734
|
+
mxcUri: item.url,
|
|
25735
|
+
asUserId: opts.agent.userId
|
|
25736
|
+
});
|
|
25737
|
+
if (data.byteLength <= MAX_INLINE_IMAGE_BYTES) {
|
|
25738
|
+
blocks.push({
|
|
25739
|
+
type: "image",
|
|
25740
|
+
data: Buffer.from(data).toString("base64"),
|
|
25741
|
+
mimeType: contentType
|
|
25742
|
+
});
|
|
25743
|
+
continue;
|
|
25744
|
+
}
|
|
25745
|
+
if (opts.agent.workspaceDir) {
|
|
25746
|
+
const paths = opts.writeAttachmentFn({
|
|
25747
|
+
workspaceDir: opts.agent.workspaceDir,
|
|
25748
|
+
agentWorkspacePath: opts.agent.agentWorkspacePath ?? opts.agent.workspaceDir,
|
|
25749
|
+
eventId: item.eventId,
|
|
25750
|
+
filename: item.filename ?? item.body,
|
|
25751
|
+
data
|
|
25752
|
+
});
|
|
25753
|
+
blocks.push({
|
|
25754
|
+
type: "resource_link",
|
|
25755
|
+
uri: `file://${paths.agentPath}`,
|
|
25756
|
+
name: item.filename ?? item.body
|
|
25757
|
+
});
|
|
25758
|
+
pathLines.push(`Attached file: ${paths.agentPath}`);
|
|
25759
|
+
}
|
|
25760
|
+
} else {
|
|
25761
|
+
if (!opts.agent.workspaceDir) continue;
|
|
25762
|
+
const { data } = await opts.media.download({
|
|
25763
|
+
mxcUri: item.url,
|
|
25764
|
+
asUserId: opts.agent.userId
|
|
25765
|
+
});
|
|
25766
|
+
const paths = opts.writeAttachmentFn({
|
|
25767
|
+
workspaceDir: opts.agent.workspaceDir,
|
|
25768
|
+
agentWorkspacePath: opts.agent.agentWorkspacePath ?? opts.agent.workspaceDir,
|
|
25769
|
+
eventId: item.eventId,
|
|
25770
|
+
filename: item.filename ?? item.body,
|
|
25771
|
+
data
|
|
25772
|
+
});
|
|
25773
|
+
blocks.push({
|
|
25774
|
+
type: "resource_link",
|
|
25775
|
+
uri: `file://${paths.agentPath}`,
|
|
25776
|
+
name: item.filename ?? item.body,
|
|
25777
|
+
mimeType: item.info?.mimetype,
|
|
25778
|
+
size: item.info?.size
|
|
25779
|
+
});
|
|
25780
|
+
pathLines.push(`Attached file: ${paths.agentPath}`);
|
|
25781
|
+
}
|
|
25782
|
+
} catch (err) {
|
|
25783
|
+
opts.onError(item, err);
|
|
25784
|
+
}
|
|
25785
|
+
}
|
|
25786
|
+
return { blocks, pathLines };
|
|
25787
|
+
}
|
|
25788
|
+
async function sendMediaError(ctx, _err, message, client) {
|
|
25789
|
+
await client.sendCustomEvent({
|
|
25790
|
+
roomId: ctx.roomId,
|
|
25791
|
+
asUserId: ctx.agent.userId,
|
|
25792
|
+
eventType: "dev.zooid.error",
|
|
25793
|
+
content: toErrorBody(
|
|
25794
|
+
{
|
|
25795
|
+
kind: "error",
|
|
25796
|
+
agentId: ctx.agent.name,
|
|
25797
|
+
sessionId: null,
|
|
25798
|
+
turnId: null,
|
|
25799
|
+
code: "media_failed",
|
|
25800
|
+
message: message.slice(0, 250),
|
|
25801
|
+
transient: false
|
|
25802
|
+
},
|
|
25803
|
+
ctx.threadRoot
|
|
25804
|
+
)
|
|
25805
|
+
}).catch((e) => console.warn(`[matrix:${ctx.agent.name}] dev.zooid.error send failed:`, e));
|
|
25806
|
+
}
|
|
25581
25807
|
var SEEN_EVENT_CAP = 5e3;
|
|
25582
25808
|
var DRAIN_QUIET_MS = 300;
|
|
25583
25809
|
var DRAIN_MAX_MS = 3e4;
|
|
@@ -25587,10 +25813,18 @@ function inboundThreadRoot2(evt) {
|
|
|
25587
25813
|
return r?.rel_type === "m.thread" && r.event_id ? r.event_id : void 0;
|
|
25588
25814
|
}
|
|
25589
25815
|
function createMatrixTransport(opts) {
|
|
25590
|
-
const { agents, approvals, client, bindings, hsToken, adminUserId } = opts;
|
|
25816
|
+
const { agents, approvals, client, bindings, hsToken, adminUserId, botUserId } = opts;
|
|
25591
25817
|
const drainQuietMs = opts.drainQuietMs ?? DRAIN_QUIET_MS;
|
|
25592
25818
|
const drainMaxMs = opts.drainMaxMs ?? DRAIN_MAX_MS;
|
|
25819
|
+
const mediaClient = opts.media;
|
|
25820
|
+
const writeAttachmentFn = opts.writeAttachmentFn ?? writeAttachment;
|
|
25821
|
+
const pendingMedia = new PendingMediaStore();
|
|
25593
25822
|
const pool = new BotPool(client, bindings);
|
|
25823
|
+
const ourBotUserIds = /* @__PURE__ */ new Set([
|
|
25824
|
+
...botUserId ? [botUserId] : [],
|
|
25825
|
+
...bindings.map((b) => b.userId)
|
|
25826
|
+
]);
|
|
25827
|
+
const DECLINE_REASON = "Bots are placed in rooms only by the zooid daemon (workforce-as-code). Ad-hoc invites are declined \u2014 add the bot to the room in zooid.yaml.";
|
|
25594
25828
|
const sessions = /* @__PURE__ */ new Map();
|
|
25595
25829
|
const buffers = /* @__PURE__ */ new Map();
|
|
25596
25830
|
const bufferMessageIds = /* @__PURE__ */ new Map();
|
|
@@ -25598,30 +25832,99 @@ function createMatrixTransport(opts) {
|
|
|
25598
25832
|
const threadStates = /* @__PURE__ */ new Map();
|
|
25599
25833
|
const cutoffTs = Date.now() - STARTUP_GRACE_MS;
|
|
25600
25834
|
const seenEventIds = /* @__PURE__ */ new Set();
|
|
25835
|
+
const flushedCounts = /* @__PURE__ */ new Map();
|
|
25836
|
+
const pendingCommands = /* @__PURE__ */ new Map();
|
|
25837
|
+
const buildTextContent = (text) => {
|
|
25838
|
+
const content = {
|
|
25839
|
+
msgtype: "m.text",
|
|
25840
|
+
body: text
|
|
25841
|
+
};
|
|
25842
|
+
const html = toMatrixHtml(text);
|
|
25843
|
+
if (html) {
|
|
25844
|
+
const escapedPlain = "<p>" + text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "</p>";
|
|
25845
|
+
const norm = (s) => s.replace(/\s+/g, " ").trim();
|
|
25846
|
+
if (norm(html) !== norm(escapedPlain)) {
|
|
25847
|
+
content.format = "org.matrix.custom.html";
|
|
25848
|
+
content.formatted_body = html;
|
|
25849
|
+
}
|
|
25850
|
+
}
|
|
25851
|
+
return content;
|
|
25852
|
+
};
|
|
25853
|
+
const flushBuffer = (sessionId) => {
|
|
25854
|
+
const ctx = sessions.get(sessionId);
|
|
25855
|
+
const text = buffers.get(sessionId) ?? "";
|
|
25856
|
+
if (!ctx || text.length === 0) return false;
|
|
25857
|
+
buffers.set(sessionId, "");
|
|
25858
|
+
flushedCounts.set(sessionId, (flushedCounts.get(sessionId) ?? 0) + 1);
|
|
25859
|
+
const content = buildTextContent(text);
|
|
25860
|
+
const tail = (sendQueue.get(sessionId) ?? Promise.resolve()).then(async () => {
|
|
25861
|
+
try {
|
|
25862
|
+
await client.sendMessage({
|
|
25863
|
+
roomId: ctx.roomId,
|
|
25864
|
+
asUserId: ctx.agent.userId,
|
|
25865
|
+
content,
|
|
25866
|
+
threadRoot: ctx.threadRoot
|
|
25867
|
+
});
|
|
25868
|
+
} catch (err) {
|
|
25869
|
+
console.warn(`[matrix:${ctx.agent.name}] sendMessage flush failed:`, err);
|
|
25870
|
+
}
|
|
25871
|
+
});
|
|
25872
|
+
sendQueue.set(sessionId, tail);
|
|
25873
|
+
return true;
|
|
25874
|
+
};
|
|
25601
25875
|
agents.onEvent = async (name, event) => {
|
|
25602
25876
|
const ctx = sessions.get(event.sessionId);
|
|
25603
25877
|
if (!ctx) {
|
|
25604
|
-
|
|
25878
|
+
if (event.type === "available_commands") {
|
|
25879
|
+
pendingCommands.set(event.sessionId, event);
|
|
25880
|
+
} else {
|
|
25881
|
+
console.warn(`[matrix:${name}] no session ctx for ${event.sessionId}`);
|
|
25882
|
+
}
|
|
25605
25883
|
return;
|
|
25606
25884
|
}
|
|
25607
25885
|
if (event.type === "agent_message_chunk") {
|
|
25608
25886
|
const block = event.content;
|
|
25609
25887
|
if (block.type === "text" && typeof block.text === "string") {
|
|
25610
|
-
const current = buffers.get(event.sessionId) ?? "";
|
|
25611
25888
|
const prevMessageId = bufferMessageIds.get(event.sessionId);
|
|
25612
25889
|
const messageChanged = event.messageId !== void 0 && prevMessageId !== void 0 && event.messageId !== prevMessageId;
|
|
25613
|
-
const needsBreak = current.length > 0 && (block.text === "" || messageChanged);
|
|
25614
|
-
const prefix = needsBreak ? "\n\n" : "";
|
|
25615
|
-
buffers.set(event.sessionId, current + prefix + block.text);
|
|
25616
25890
|
if (event.messageId !== void 0)
|
|
25617
25891
|
bufferMessageIds.set(event.sessionId, event.messageId);
|
|
25892
|
+
if (messageChanged) flushBuffer(event.sessionId);
|
|
25893
|
+
const current = buffers.get(event.sessionId) ?? "";
|
|
25894
|
+
const needsBreak = current.length > 0 && block.text === "";
|
|
25895
|
+
const prefix = needsBreak ? "\n\n" : "";
|
|
25896
|
+
buffers.set(event.sessionId, current + prefix + block.text);
|
|
25897
|
+
} else if (block.type === "image" && typeof block.data === "string" && typeof block.mimeType === "string" && mediaClient) {
|
|
25898
|
+
const ctx2 = sessions.get(event.sessionId);
|
|
25899
|
+
if (ctx2) {
|
|
25900
|
+
const bytes = Buffer.from(block.data, "base64");
|
|
25901
|
+
const ext = (block.mimeType.split("/")[1] ?? "png").replace(/[^a-z0-9]/gi, "");
|
|
25902
|
+
const filename = `image.${ext}`;
|
|
25903
|
+
void mediaClient.upload({ data: bytes, contentType: block.mimeType, filename, asUserId: ctx2.agent.userId }).then(
|
|
25904
|
+
({ content_uri }) => client.sendMessage({
|
|
25905
|
+
roomId: ctx2.roomId,
|
|
25906
|
+
asUserId: ctx2.agent.userId,
|
|
25907
|
+
threadRoot: ctx2.threadRoot,
|
|
25908
|
+
content: {
|
|
25909
|
+
msgtype: "m.image",
|
|
25910
|
+
body: filename,
|
|
25911
|
+
url: content_uri,
|
|
25912
|
+
info: { mimetype: block.mimeType, size: bytes.length }
|
|
25913
|
+
}
|
|
25914
|
+
})
|
|
25915
|
+
).catch((err) => {
|
|
25916
|
+
console.warn(`[matrix:${name}] outbound image upload failed:`, err);
|
|
25917
|
+
void sendMediaError(ctx2, err, "agent image upload failed", client);
|
|
25918
|
+
});
|
|
25919
|
+
}
|
|
25618
25920
|
} else {
|
|
25619
25921
|
console.warn(`[matrix:${name}] dropped chunk block type=${block.type}`, block);
|
|
25620
25922
|
}
|
|
25621
25923
|
return;
|
|
25622
25924
|
}
|
|
25623
|
-
|
|
25624
|
-
const
|
|
25925
|
+
flushBuffer(event.sessionId);
|
|
25926
|
+
const eventType = event.type === "tool_call" ? "dev.zooid.tool_call" : event.type === "tool_call_update" ? "dev.zooid.tool_call_update" : event.type === "available_commands" ? "dev.zooid.available_commands_update" : "dev.zooid.plan";
|
|
25927
|
+
const body = event.type === "tool_call" ? toToolCallBody(event) : event.type === "tool_call_update" ? toUpdateBody(event) : event.type === "available_commands" ? toAvailableCommandsBody(event) : toPlanBody(event);
|
|
25625
25928
|
body["m.relates_to"] = { rel_type: "m.thread", event_id: ctx.threadRoot };
|
|
25626
25929
|
const tail = (sendQueue.get(event.sessionId) ?? Promise.resolve()).then(async () => {
|
|
25627
25930
|
try {
|
|
@@ -25660,7 +25963,7 @@ function createMatrixTransport(opts) {
|
|
|
25660
25963
|
void client.sendCustomEvent({
|
|
25661
25964
|
roomId: ctx.roomId,
|
|
25662
25965
|
asUserId: ctx.agent.userId,
|
|
25663
|
-
eventType: "
|
|
25966
|
+
eventType: "dev.zooid.approval_request",
|
|
25664
25967
|
content
|
|
25665
25968
|
});
|
|
25666
25969
|
});
|
|
@@ -25694,20 +25997,33 @@ function createMatrixTransport(opts) {
|
|
|
25694
25997
|
);
|
|
25695
25998
|
continue;
|
|
25696
25999
|
}
|
|
25697
|
-
if (evt.type === "
|
|
26000
|
+
if (evt.type === "m.room.member" && evt.content?.membership === "invite") {
|
|
26001
|
+
const target = evt.state_key;
|
|
26002
|
+
const inviter = evt.sender;
|
|
26003
|
+
if (target && evt.room_id && ourBotUserIds.has(target) && (!inviter || !ourBotUserIds.has(inviter))) {
|
|
26004
|
+
console.log(
|
|
26005
|
+
`[matrix] declining ad-hoc invite for ${target} in ${evt.room_id} from ${inviter ?? "unknown"}`
|
|
26006
|
+
);
|
|
26007
|
+
await client.leaveRoom(evt.room_id, target, { reason: DECLINE_REASON }).catch(
|
|
26008
|
+
(err) => console.warn(`[matrix] leaveRoom(${evt.room_id}, ${target}) failed:`, err)
|
|
26009
|
+
);
|
|
26010
|
+
}
|
|
26011
|
+
continue;
|
|
26012
|
+
}
|
|
26013
|
+
if (evt.type === "dev.zooid.session_reset") {
|
|
25698
26014
|
const relates = evt.content?.["m.relates_to"];
|
|
25699
26015
|
const threadRoot = relates?.rel_type === "m.thread" && relates.event_id ? relates.event_id : void 0;
|
|
25700
26016
|
if (!threadRoot) {
|
|
25701
|
-
console.log("[matrix] dropping
|
|
26017
|
+
console.log("[matrix] dropping dev.zooid.session_reset without thread relation");
|
|
25702
26018
|
continue;
|
|
25703
26019
|
}
|
|
25704
|
-
console.log(`[matrix] inbound
|
|
26020
|
+
console.log(`[matrix] inbound dev.zooid.session_reset in ${evt.room_id} thread=${threadRoot}`);
|
|
25705
26021
|
for (const a of bindings) {
|
|
25706
26022
|
agents.endSession(a.name, threadRoot);
|
|
25707
26023
|
}
|
|
25708
26024
|
continue;
|
|
25709
26025
|
}
|
|
25710
|
-
if (evt.type === "
|
|
26026
|
+
if (evt.type === "dev.zooid.interrupt") {
|
|
25711
26027
|
const content = evt.content ?? {};
|
|
25712
26028
|
const relates = evt.content?.["m.relates_to"];
|
|
25713
26029
|
const threadRoot = relates?.rel_type === "m.thread" && relates.event_id ? relates.event_id : void 0;
|
|
@@ -25729,7 +26045,7 @@ function createMatrixTransport(opts) {
|
|
|
25729
26045
|
continue;
|
|
25730
26046
|
}
|
|
25731
26047
|
if (!content.session_id) {
|
|
25732
|
-
console.warn(`[matrix]
|
|
26048
|
+
console.warn(`[matrix] dev.zooid.interrupt missing session_id (event_id=${evt.event_id})`);
|
|
25733
26049
|
continue;
|
|
25734
26050
|
}
|
|
25735
26051
|
const ctx = sessions.get(content.session_id);
|
|
@@ -25744,7 +26060,7 @@ function createMatrixTransport(opts) {
|
|
|
25744
26060
|
});
|
|
25745
26061
|
continue;
|
|
25746
26062
|
}
|
|
25747
|
-
if (evt.type === "
|
|
26063
|
+
if (evt.type === "dev.zooid.approval_response") {
|
|
25748
26064
|
const content = evt.content ?? {};
|
|
25749
26065
|
if (!content.session_id || !content.approval_id || !content.decision) continue;
|
|
25750
26066
|
const decision = content.option_id ? { decision: content.decision, optionId: content.option_id } : { decision: content.decision };
|
|
@@ -25757,6 +26073,18 @@ function createMatrixTransport(opts) {
|
|
|
25757
26073
|
continue;
|
|
25758
26074
|
}
|
|
25759
26075
|
logInbound(evt);
|
|
26076
|
+
if (evt.type === "m.room.message" && isMediaMsgtype(evt.content?.msgtype) && evt.room_id && evt.event_id && evt.sender && evt.content?.url && !bindings.some((b) => b.userId === evt.sender)) {
|
|
26077
|
+
pendingMedia.add(evt.room_id, inboundThreadRoot2(evt), {
|
|
26078
|
+
eventId: evt.event_id,
|
|
26079
|
+
sender: evt.sender,
|
|
26080
|
+
msgtype: evt.content.msgtype,
|
|
26081
|
+
body: evt.content.body ?? "",
|
|
26082
|
+
filename: evt.content.filename,
|
|
26083
|
+
url: evt.content.url,
|
|
26084
|
+
info: evt.content.info
|
|
26085
|
+
});
|
|
26086
|
+
continue;
|
|
26087
|
+
}
|
|
25760
26088
|
const promotedRoot = inboundThreadRoot2(evt) ?? evt.event_id;
|
|
25761
26089
|
const inboundRel = inboundThreadRoot2(evt);
|
|
25762
26090
|
if (evt.type === "m.room.message" && inboundRel && !threadStates.has(inboundRel) && evt.room_id) {
|
|
@@ -25822,9 +26150,9 @@ function createMatrixTransport(opts) {
|
|
|
25822
26150
|
void client.sendCustomEvent({
|
|
25823
26151
|
roomId: evt.room_id,
|
|
25824
26152
|
asUserId: a.userId,
|
|
25825
|
-
eventType: "
|
|
26153
|
+
eventType: "dev.zooid.error",
|
|
25826
26154
|
content: body2
|
|
25827
|
-
}).catch((e) => console.warn(`[matrix:${a.name}]
|
|
26155
|
+
}).catch((e) => console.warn(`[matrix:${a.name}] dev.zooid.error send failed:`, e));
|
|
25828
26156
|
});
|
|
25829
26157
|
}
|
|
25830
26158
|
}
|
|
@@ -25858,6 +26186,12 @@ function createMatrixTransport(opts) {
|
|
|
25858
26186
|
sessions.set(sessionId, { agent, roomId: evt.room_id, threadRoot });
|
|
25859
26187
|
buffers.set(sessionId, "");
|
|
25860
26188
|
bufferMessageIds.delete(sessionId);
|
|
26189
|
+
flushedCounts.set(sessionId, 0);
|
|
26190
|
+
const stashedCommands = pendingCommands.get(sessionId);
|
|
26191
|
+
if (stashedCommands) {
|
|
26192
|
+
pendingCommands.delete(sessionId);
|
|
26193
|
+
void agents.onEvent?.(agent.name, stashedCommands);
|
|
26194
|
+
}
|
|
25861
26195
|
const roomId = evt.room_id;
|
|
25862
26196
|
const TYPING_TTL_MS = 3e4;
|
|
25863
26197
|
const TYPING_REFRESH_MS = 25e3;
|
|
@@ -25873,42 +26207,43 @@ function createMatrixTransport(opts) {
|
|
|
25873
26207
|
try {
|
|
25874
26208
|
const rawBody = evt.content?.body ?? "";
|
|
25875
26209
|
const promptText = stripMention(rawBody, agent.userId);
|
|
26210
|
+
const pendingItems = pendingMedia.drain(
|
|
26211
|
+
evt.room_id,
|
|
26212
|
+
inboundThreadRoot2(evt),
|
|
26213
|
+
evt.sender ?? ""
|
|
26214
|
+
);
|
|
26215
|
+
const { blocks, pathLines } = await buildMediaBlocks(pendingItems, {
|
|
26216
|
+
agent,
|
|
26217
|
+
media: mediaClient,
|
|
26218
|
+
writeAttachmentFn,
|
|
26219
|
+
onError: (item, err) => {
|
|
26220
|
+
console.warn(`[matrix:${agent.name}] media_failed for ${item.body}:`, err);
|
|
26221
|
+
void sendMediaError(
|
|
26222
|
+
{ agent, roomId: evt.room_id, threadRoot },
|
|
26223
|
+
err,
|
|
26224
|
+
`Could not process attachment: ${item.body}`,
|
|
26225
|
+
client
|
|
26226
|
+
);
|
|
26227
|
+
}
|
|
26228
|
+
});
|
|
26229
|
+
const fullPromptText = [promptText, ...pathLines].filter(Boolean).join("\n");
|
|
25876
26230
|
await agents.prompt(agent.name, {
|
|
25877
26231
|
threadId: sessionKey,
|
|
25878
26232
|
channelId: evt.room_id,
|
|
25879
|
-
content: [{ type: "text", text:
|
|
26233
|
+
content: [...blocks, { type: "text", text: fullPromptText }]
|
|
25880
26234
|
});
|
|
25881
26235
|
const drainStart = Date.now();
|
|
25882
26236
|
let drained = buffers.get(sessionId) ?? "";
|
|
25883
26237
|
while (drainQuietMs > 0 && Date.now() - drainStart < drainMaxMs) {
|
|
25884
26238
|
await delay(drainQuietMs);
|
|
25885
26239
|
const next = buffers.get(sessionId) ?? "";
|
|
25886
|
-
if (next === drained && next.length > 0)
|
|
26240
|
+
if (next === drained && (next.length > 0 || (flushedCounts.get(sessionId) ?? 0) > 0))
|
|
26241
|
+
break;
|
|
25887
26242
|
drained = next;
|
|
25888
26243
|
}
|
|
25889
|
-
|
|
25890
|
-
|
|
25891
|
-
|
|
25892
|
-
const content = {
|
|
25893
|
-
msgtype: "m.text",
|
|
25894
|
-
body: text
|
|
25895
|
-
};
|
|
25896
|
-
if (html) {
|
|
25897
|
-
const escapedPlain = "<p>" + text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "</p>";
|
|
25898
|
-
const norm = (s) => s.replace(/\s+/g, " ").trim();
|
|
25899
|
-
if (norm(html) !== norm(escapedPlain)) {
|
|
25900
|
-
content.format = "org.matrix.custom.html";
|
|
25901
|
-
content.formatted_body = html;
|
|
25902
|
-
}
|
|
25903
|
-
}
|
|
25904
|
-
await client.sendMessage({
|
|
25905
|
-
roomId: evt.room_id,
|
|
25906
|
-
asUserId: agent.userId,
|
|
25907
|
-
content,
|
|
25908
|
-
threadRoot
|
|
25909
|
-
// every reply threads, full stop
|
|
25910
|
-
});
|
|
25911
|
-
} else {
|
|
26244
|
+
flushBuffer(sessionId);
|
|
26245
|
+
await (sendQueue.get(sessionId) ?? Promise.resolve());
|
|
26246
|
+
if ((flushedCounts.get(sessionId) ?? 0) === 0) {
|
|
25912
26247
|
console.warn(
|
|
25913
26248
|
`[matrix:${agent.name}] turn finished with empty buffer (session=${sessionId}); nothing sent to ${evt.room_id}`
|
|
25914
26249
|
);
|
|
@@ -25919,6 +26254,8 @@ function createMatrixTransport(opts) {
|
|
|
25919
26254
|
await safePresence("online");
|
|
25920
26255
|
buffers.delete(sessionId);
|
|
25921
26256
|
bufferMessageIds.delete(sessionId);
|
|
26257
|
+
flushedCounts.delete(sessionId);
|
|
26258
|
+
sendQueue.delete(sessionId);
|
|
25922
26259
|
}
|
|
25923
26260
|
}
|
|
25924
26261
|
return {
|
|
@@ -26004,7 +26341,7 @@ async function publishWorkforce(opts) {
|
|
|
26004
26341
|
await opts.client.sendStateEvent({
|
|
26005
26342
|
roomId: opts.spaceRoomId,
|
|
26006
26343
|
asUserId: opts.asUserId,
|
|
26007
|
-
eventType: "
|
|
26344
|
+
eventType: "dev.zooid.workforce",
|
|
26008
26345
|
stateKey: "",
|
|
26009
26346
|
content: buildWorkforceRoster(opts.agents)
|
|
26010
26347
|
});
|
|
@@ -31591,10 +31928,11 @@ export {
|
|
|
31591
31928
|
renderRegistration,
|
|
31592
31929
|
ensureWorkforceSpace,
|
|
31593
31930
|
ensureDefaultChannel,
|
|
31931
|
+
MediaClient,
|
|
31594
31932
|
createMatrixTransport,
|
|
31595
31933
|
startWorkforcePublisher,
|
|
31596
31934
|
SpawnRegistry,
|
|
31597
31935
|
startDaemonSocketServer,
|
|
31598
31936
|
buildAcpRegistry
|
|
31599
31937
|
};
|
|
31600
|
-
//# sourceMappingURL=chunk-
|
|
31938
|
+
//# sourceMappingURL=chunk-LOILUENL.js.map
|