@cfio/cohort-sync 0.32.0 → 0.33.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/dist/index.js +447 -18
- package/dist/openclaw.plugin.json +3 -1
- package/dist/package.json +1 -1
- package/openclaw.plugin.json +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2615,6 +2615,9 @@ __export(type_exports2, {
|
|
|
2615
2615
|
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.48/node_modules/@sinclair/typebox/build/esm/type/type/index.mjs
|
|
2616
2616
|
var Type = type_exports2;
|
|
2617
2617
|
|
|
2618
|
+
// index.ts
|
|
2619
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
2620
|
+
|
|
2618
2621
|
// src/hooks.ts
|
|
2619
2622
|
import fs2 from "node:fs";
|
|
2620
2623
|
import os2 from "node:os";
|
|
@@ -12000,6 +12003,8 @@ var failCommandRef = makeFunctionReference("gatewayCommands:failCommand");
|
|
|
12000
12003
|
var getChannelsForPlugin = makeFunctionReference("cloudGatewayChannels:listForPlugin");
|
|
12001
12004
|
var addCommentFromPluginRef = makeFunctionReference("comments:addCommentFromPlugin");
|
|
12002
12005
|
var addRoomMessageFromPluginRef = makeFunctionReference("rooms:addMessageFromPlugin");
|
|
12006
|
+
var generateWireUploadUrlFromPluginRef = makeFunctionReference("wire:generateUploadUrl");
|
|
12007
|
+
var publishWireFromPluginRef = makeFunctionReference("wire:publish");
|
|
12003
12008
|
var startModerationSessionFromPluginRef = makeFunctionReference("moderationSessions:startFromPlugin");
|
|
12004
12009
|
var advanceModerationSessionFromPluginRef = makeFunctionReference("moderationSessions:advanceFromPlugin");
|
|
12005
12010
|
var cancelModerationSessionFromPluginRef = makeFunctionReference("moderationSessions:cancelFromPlugin");
|
|
@@ -12133,6 +12138,60 @@ async function callAddRoomMessageFromPlugin(apiKey2, args) {
|
|
|
12133
12138
|
throw err;
|
|
12134
12139
|
}
|
|
12135
12140
|
}
|
|
12141
|
+
async function callGenerateWireUploadUrlFromPlugin(apiKey2, args) {
|
|
12142
|
+
if (authCircuitOpen) {
|
|
12143
|
+
throw new Error(
|
|
12144
|
+
'cohort-sync: API key rejected \u2014 all outbound mutations disabled until gateway restart.\n 1. Create a new key at https://my.cohort.bot/settings/api-keys\n 2. Run: openclaw config set plugins.entries.cohort-sync.config.apiKey "ch_live_..."'
|
|
12145
|
+
);
|
|
12146
|
+
}
|
|
12147
|
+
const c = getClient();
|
|
12148
|
+
if (!c) {
|
|
12149
|
+
throw new Error("Convex client not initialized \u2014 subscription may not be active");
|
|
12150
|
+
}
|
|
12151
|
+
try {
|
|
12152
|
+
return await c.mutation(generateWireUploadUrlFromPluginRef, {
|
|
12153
|
+
apiKeyHash: hashApiKey(apiKey2),
|
|
12154
|
+
agentName: args.agentName
|
|
12155
|
+
});
|
|
12156
|
+
} catch (err) {
|
|
12157
|
+
if (isUnauthorizedError(err)) {
|
|
12158
|
+
tripAuthCircuit();
|
|
12159
|
+
}
|
|
12160
|
+
throw err;
|
|
12161
|
+
}
|
|
12162
|
+
}
|
|
12163
|
+
async function callPublishWireFromPlugin(apiKey2, args) {
|
|
12164
|
+
if (authCircuitOpen) {
|
|
12165
|
+
throw new Error(
|
|
12166
|
+
'cohort-sync: API key rejected \u2014 all outbound mutations disabled until gateway restart.\n 1. Create a new key at https://my.cohort.bot/settings/api-keys\n 2. Run: openclaw config set plugins.entries.cohort-sync.config.apiKey "ch_live_..."'
|
|
12167
|
+
);
|
|
12168
|
+
}
|
|
12169
|
+
const c = getClient();
|
|
12170
|
+
if (!c) {
|
|
12171
|
+
throw new Error("Convex client not initialized \u2014 subscription may not be active");
|
|
12172
|
+
}
|
|
12173
|
+
try {
|
|
12174
|
+
return await c.mutation(publishWireFromPluginRef, {
|
|
12175
|
+
apiKeyHash: hashApiKey(apiKey2),
|
|
12176
|
+
agentName: args.agentName,
|
|
12177
|
+
kind: args.kind,
|
|
12178
|
+
title: args.title,
|
|
12179
|
+
brief: args.brief,
|
|
12180
|
+
...args.body !== void 0 ? { body: args.body } : {},
|
|
12181
|
+
...args.storageId !== void 0 ? { storageId: args.storageId } : {},
|
|
12182
|
+
...args.mimeType !== void 0 ? { mimeType: args.mimeType } : {},
|
|
12183
|
+
...args.filename !== void 0 ? { filename: args.filename } : {},
|
|
12184
|
+
...args.altText !== void 0 ? { altText: args.altText } : {},
|
|
12185
|
+
...args.roomId !== void 0 ? { roomId: args.roomId } : {},
|
|
12186
|
+
...args.taskId !== void 0 ? { taskId: args.taskId } : {}
|
|
12187
|
+
});
|
|
12188
|
+
} catch (err) {
|
|
12189
|
+
if (isUnauthorizedError(err)) {
|
|
12190
|
+
tripAuthCircuit();
|
|
12191
|
+
}
|
|
12192
|
+
throw err;
|
|
12193
|
+
}
|
|
12194
|
+
}
|
|
12136
12195
|
async function callStartModerationSessionFromPlugin(apiKey2, args) {
|
|
12137
12196
|
if (authCircuitOpen) {
|
|
12138
12197
|
throw new Error(
|
|
@@ -12279,21 +12338,24 @@ var ATMENTION_RESPONSE_PROMPT = `YOU WERE DIRECTLY @-MENTIONED. RESPOND.
|
|
|
12279
12338
|
- Do not stop because Vercel CLI is not authenticated; Cohort deploys through GitHub Actions after merge.`;
|
|
12280
12339
|
var ROOM_MESSAGE_RESPONSE_PROMPT = `YOU WERE DIRECTLY ASKED IN A COHORT ROOM. RESPOND.
|
|
12281
12340
|
|
|
12282
|
-
- If you are the Room moderator and a human asks for a
|
|
12341
|
+
- If you are the Room moderator and a human asks for a question to be routed, FIRST use cohort_room_start_moderation_session with mode: "moderated_qna", one best participant, and question_text; use cohort_room_advance_moderation_session(action: "followup") for additional experts one at a time, then confirm with the asker before complete.
|
|
12342
|
+
- If you are the Room moderator and a human asks for a roundtable, standup, report-in, or panel response, FIRST use cohort_room_start_moderation_session with mode: "round_robin" to run it as a managed session, then drive it with cohort_room_advance_moderation_session; do not only name agents in prose.
|
|
12283
12343
|
- Otherwise, use the cohort_room_message tool to post a reply in the Room. Do NOT just think silently and exit.
|
|
12284
12344
|
- Reply in your own voice (see your persona in IDENTITY.md).
|
|
12285
|
-
- If the human asks for a roundtable, standup, report-in, or panel response, contribute your own concise update.
|
|
12345
|
+
- If the human asks for an answer, answer it. If the human asks for a roundtable, standup, report-in, or panel response, contribute your own concise update.
|
|
12286
12346
|
- If the moderator asks you to answer, answer. If another agent asks for your report, provide it. If your prompt includes a turn_id, echo it in cohort_room_message.
|
|
12287
12347
|
- A brief, honest reply is better than no reply. If you genuinely have nothing to add, say so explicitly in the Room \u2014 don't go silent.`;
|
|
12288
12348
|
var TOOLS_REFERENCE = `
|
|
12289
12349
|
TOOLS: Use these \u2014 do NOT call the REST API directly.
|
|
12290
12350
|
- cohort_comment(task_number, comment) \u2014 post a comment
|
|
12291
12351
|
- cohort_room_message(room_id, message, turn_id?) \u2014 post a message in a Cohort Room; pass turn_id when your prompt included one so the reply is attributed to that moderation turn
|
|
12292
|
-
- cohort_room_start_moderation_session(room_id, mode, objective, participant_names) \u2014 moderator only: start a managed
|
|
12293
|
-
- cohort_room_advance_moderation_session(session_id, action, ...) \u2014 moderator only: advance, retry, skip, accept, followup, or complete the session
|
|
12352
|
+
- cohort_room_start_moderation_session(room_id, mode, objective, participant_names) \u2014 moderator only: start a managed session; round_robin = ask every participant to report in, moderated_qna = route a question to one selected agent
|
|
12353
|
+
- cohort_room_advance_moderation_session(session_id, action, ...) \u2014 moderator only: advance, retry, skip, accept, followup, or complete the session; followup asks one additional Q&A expert after the first answer
|
|
12294
12354
|
- cohort_room_cancel_moderation_session(session_id, reason?) \u2014 moderator only: cancel an in-flight session
|
|
12295
12355
|
- cohort_room_prompt_agent(roomId, agentName, prompt) \u2014 moderator only, low-level: ask one Room agent to respond (prefer moderation sessions for multi-step workflows)
|
|
12296
12356
|
- cohort_room_prompt_agents(roomId, agentNames, prompt) \u2014 moderator only, low-level: ask multiple Room agents to respond (prefer moderation sessions for multi-step workflows)
|
|
12357
|
+
- wire_publish(kind, title, brief, body?, image_base64?, mime_type?, filename?, alt_text?, room_id?, task_id?, in_response_to_steer_id?, delivery_note?) - publish a report or image to Cohort Wire; include in_response_to_steer_id when a WIRE REQUEST block asks for it
|
|
12358
|
+
- wire_decline(steer_id, reason) - decline a Wire request you cannot complete
|
|
12297
12359
|
- cohort_task(task_number) \u2014 fetch full task details + comments
|
|
12298
12360
|
- cohort_transition(task_number, status) \u2014 change status
|
|
12299
12361
|
- cohort_assign(task_number, assignee) \u2014 assign/unassign
|
|
@@ -12322,6 +12384,16 @@ var HANDOFF_ACTION_TERMS = [
|
|
|
12322
12384
|
"gather",
|
|
12323
12385
|
"ask"
|
|
12324
12386
|
];
|
|
12387
|
+
var QUESTION_ROUTING_TERMS = [
|
|
12388
|
+
"question for the team",
|
|
12389
|
+
"route it",
|
|
12390
|
+
"route this",
|
|
12391
|
+
"ask whoever",
|
|
12392
|
+
"whoever's best suited",
|
|
12393
|
+
"whoever is best suited",
|
|
12394
|
+
"who's best suited",
|
|
12395
|
+
"who is best suited"
|
|
12396
|
+
];
|
|
12325
12397
|
function sanitizePreview(raw) {
|
|
12326
12398
|
return raw.replace(/<\/?user_comment>/gi, "");
|
|
12327
12399
|
}
|
|
@@ -12337,11 +12409,24 @@ function isHumanHandoffRequest(n) {
|
|
|
12337
12409
|
const hasAction = HANDOFF_ACTION_TERMS.some((term) => preview.includes(term));
|
|
12338
12410
|
return hasTopic && hasAction;
|
|
12339
12411
|
}
|
|
12412
|
+
function isHumanQuestionRoutingRequest(n) {
|
|
12413
|
+
if (n.actorType !== "human") {
|
|
12414
|
+
return false;
|
|
12415
|
+
}
|
|
12416
|
+
const preview = (n.preview ?? "").toLowerCase();
|
|
12417
|
+
return QUESTION_ROUTING_TERMS.some((term) => preview.includes(term)) || /\bhave\s+[a-z][a-z0-9_-]*\s+answer\b/.test(preview);
|
|
12418
|
+
}
|
|
12340
12419
|
function roomMessageCta(n) {
|
|
12341
12420
|
const roomId = n.roomId ?? "unknown";
|
|
12421
|
+
if (n.wireSteerContext != null && renderWireSteerContext(n.wireSteerContext) !== "") {
|
|
12422
|
+
return "Follow the WIRE REQUEST block in this message \u2014 it is authoritative.";
|
|
12423
|
+
}
|
|
12342
12424
|
if (n.moderationContext != null && renderModerationContext(n.moderationContext) !== "") {
|
|
12343
12425
|
return "Follow the MODERATION SESSION block in this message \u2014 it is authoritative.";
|
|
12344
12426
|
}
|
|
12427
|
+
if (isHumanQuestionRoutingRequest(n)) {
|
|
12428
|
+
return `You are moderating a Room Q&A. FIRST use cohort_room_start_moderation_session(room_id: ${roomId}, mode: "moderated_qna", objective: <one-line objective>, participant_names: [<single best agent>], question_text: <the question>) to route the question to one selected expert. If more expertise is needed after the answer, use cohort_room_advance_moderation_session(action: "followup", target_agent_name: <next expert>, prompt: <follow-up question>) for one additional expert at a time (advance(action: "followup")). Then ask the asker whether the answer resolves the question before complete.`;
|
|
12429
|
+
}
|
|
12345
12430
|
if (isHumanHandoffRequest(n)) {
|
|
12346
12431
|
return `You are moderating a Room round robin. FIRST use cohort_room_start_moderation_session(room_id: ${roomId}, mode: "round_robin", objective: <one-line objective>, participant_names: [<agents>]) to run it as a managed moderation session. Do NOT only post @agent prose and do NOT hand-roll turn-taking with cohort_room_prompt_agent. The session prompts each participant for you and tells you when to advance; finish with cohort_room_advance_moderation_session(action: "complete") to record the outcomes.`;
|
|
12347
12432
|
}
|
|
@@ -12384,6 +12469,47 @@ function renderModerationContext(ctx) {
|
|
|
12384
12469
|
lines.push("[/MODERATION SESSION]");
|
|
12385
12470
|
return lines.join("\n");
|
|
12386
12471
|
}
|
|
12472
|
+
function renderWireSteerContext(ctx) {
|
|
12473
|
+
if (ctx.version !== 1) return "";
|
|
12474
|
+
const artifact = ctx.artifact;
|
|
12475
|
+
const lines = [
|
|
12476
|
+
`[WIRE REQUEST ${ctx.steerId}]`,
|
|
12477
|
+
`Mode: ${ctx.mode} | Contract: ${ctx.contract} | Source: ${artifact.type} ${ctx.wireItemId}`,
|
|
12478
|
+
`Title: ${artifact.title}`,
|
|
12479
|
+
`Brief: ${artifact.brief}`
|
|
12480
|
+
];
|
|
12481
|
+
if (artifact.body) {
|
|
12482
|
+
lines.push("Artifact body:", artifact.body);
|
|
12483
|
+
}
|
|
12484
|
+
if (artifact.imageUrl) {
|
|
12485
|
+
lines.push(`Artifact URL: ${artifact.imageUrl}`);
|
|
12486
|
+
}
|
|
12487
|
+
lines.push(`Instruction: ${ctx.instruction}`);
|
|
12488
|
+
if (ctx.comments.length > 0) {
|
|
12489
|
+
lines.push(
|
|
12490
|
+
"Recent thread:",
|
|
12491
|
+
...ctx.comments.map(
|
|
12492
|
+
(comment) => `- ${comment.authorName} (${comment.authorType}): ${comment.body}`
|
|
12493
|
+
)
|
|
12494
|
+
);
|
|
12495
|
+
} else {
|
|
12496
|
+
lines.push("Recent thread: (none)");
|
|
12497
|
+
}
|
|
12498
|
+
const provenance = [
|
|
12499
|
+
ctx.provenance.roomId ? `roomId=${ctx.provenance.roomId}` : "",
|
|
12500
|
+
ctx.provenance.taskId ? `taskId=${ctx.provenance.taskId}` : "",
|
|
12501
|
+
ctx.provenance.sessionId ? `sessionId=${ctx.provenance.sessionId}` : "",
|
|
12502
|
+
ctx.provenance.revisesItemId ? `revisesItemId=${ctx.provenance.revisesItemId}` : "",
|
|
12503
|
+
ctx.provenance.derivedFromItemId ? `derivedFromItemId=${ctx.provenance.derivedFromItemId}` : ""
|
|
12504
|
+
].filter(Boolean);
|
|
12505
|
+
lines.push(`Provenance: ${provenance.length > 0 ? provenance.join(" | ") : "(none)"}`);
|
|
12506
|
+
lines.push(`NEXT ACTION: ${ctx.deliveryInstruction}`);
|
|
12507
|
+
lines.push(
|
|
12508
|
+
"Do NOT deliver by commenting. Use wire_publish with inResponseToSteerId so Cohort can link the result.",
|
|
12509
|
+
"[/WIRE REQUEST]"
|
|
12510
|
+
);
|
|
12511
|
+
return lines.join("\n");
|
|
12512
|
+
}
|
|
12387
12513
|
function buildNotificationMessage(n) {
|
|
12388
12514
|
let header;
|
|
12389
12515
|
let cta;
|
|
@@ -12441,8 +12567,9 @@ Scope: ${truncated}`;
|
|
|
12441
12567
|
}
|
|
12442
12568
|
let prompt;
|
|
12443
12569
|
if (n.type === "room_message") {
|
|
12444
|
-
const
|
|
12445
|
-
const
|
|
12570
|
+
const wireSteerBlock = n.wireSteerContext != null ? renderWireSteerContext(n.wireSteerContext) : "";
|
|
12571
|
+
const moderationBlock = !wireSteerBlock && n.moderationContext != null ? renderModerationContext(n.moderationContext) : "";
|
|
12572
|
+
const preamble = wireSteerBlock || moderationBlock || n.behavioralPrompt;
|
|
12446
12573
|
prompt = preamble ? `${preamble}
|
|
12447
12574
|
|
|
12448
12575
|
${ROOM_MESSAGE_RESPONSE_PROMPT}` : ROOM_MESSAGE_RESPONSE_PROMPT;
|
|
@@ -13990,7 +14117,7 @@ function dumpEvent(event) {
|
|
|
13990
14117
|
function positiveNumber(value) {
|
|
13991
14118
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
13992
14119
|
}
|
|
13993
|
-
var PLUGIN_VERSION = true ? "0.
|
|
14120
|
+
var PLUGIN_VERSION = true ? "0.33.0" : "unknown";
|
|
13994
14121
|
function resolveGatewayToken(api) {
|
|
13995
14122
|
const token2 = api.config?.gateway?.auth?.token;
|
|
13996
14123
|
return typeof token2 === "string" ? token2 : null;
|
|
@@ -15010,6 +15137,143 @@ var POCKET_GUIDE = `# Cohort Agent Guide (Pocket Version)
|
|
|
15010
15137
|
function textResult(text, details) {
|
|
15011
15138
|
return { content: [{ type: "text", text }], details: details ?? void 0 };
|
|
15012
15139
|
}
|
|
15140
|
+
var WIRE_TITLE_MAX = 200;
|
|
15141
|
+
var WIRE_BRIEF_MAX = 2e3;
|
|
15142
|
+
var WIRE_BODY_MAX = 32e3;
|
|
15143
|
+
var WIRE_FILENAME_MAX = 200;
|
|
15144
|
+
var WIRE_MIME_TYPE_MAX = 100;
|
|
15145
|
+
var WIRE_ALT_TEXT_MAX = 1e3;
|
|
15146
|
+
var WIRE_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
15147
|
+
function wireInvalid(message) {
|
|
15148
|
+
return textResult(`Cannot publish to Wire: ${message}.`);
|
|
15149
|
+
}
|
|
15150
|
+
function requiredWireString(value, field, maxLength) {
|
|
15151
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
15152
|
+
return { ok: false, message: `${field} is required` };
|
|
15153
|
+
}
|
|
15154
|
+
const trimmed = value.trim();
|
|
15155
|
+
if (trimmed.length > maxLength) {
|
|
15156
|
+
return { ok: false, message: `${field} too long (max ${maxLength} characters)` };
|
|
15157
|
+
}
|
|
15158
|
+
return { ok: true, value: trimmed };
|
|
15159
|
+
}
|
|
15160
|
+
function optionalWireString(value, field, maxLength) {
|
|
15161
|
+
if (value === void 0 || value === null) {
|
|
15162
|
+
return { ok: true };
|
|
15163
|
+
}
|
|
15164
|
+
if (typeof value !== "string") {
|
|
15165
|
+
return { ok: false, message: `${field} must be a string` };
|
|
15166
|
+
}
|
|
15167
|
+
const trimmed = value.trim();
|
|
15168
|
+
if (!trimmed) {
|
|
15169
|
+
return { ok: true };
|
|
15170
|
+
}
|
|
15171
|
+
if (trimmed.length > maxLength) {
|
|
15172
|
+
return { ok: false, message: `${field} too long (max ${maxLength} characters)` };
|
|
15173
|
+
}
|
|
15174
|
+
return { ok: true, value: trimmed };
|
|
15175
|
+
}
|
|
15176
|
+
function decodeWireImageBase64(value) {
|
|
15177
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
15178
|
+
return { ok: false, message: "image_base64 is required" };
|
|
15179
|
+
}
|
|
15180
|
+
const dataUrlMatch = /^data:[^;,]+;base64,([\s\S]*)$/i.exec(value.trim());
|
|
15181
|
+
const normalized = (dataUrlMatch ? dataUrlMatch[1] : value).replace(/\s/g, "");
|
|
15182
|
+
if (!normalized) {
|
|
15183
|
+
return { ok: false, message: "image_base64 is required" };
|
|
15184
|
+
}
|
|
15185
|
+
if (normalized.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(normalized)) {
|
|
15186
|
+
return { ok: false, message: "image_base64 must be valid base64" };
|
|
15187
|
+
}
|
|
15188
|
+
const bytes = Buffer2.from(normalized, "base64");
|
|
15189
|
+
if (bytes.byteLength === 0) {
|
|
15190
|
+
return { ok: false, message: "image_base64 is required" };
|
|
15191
|
+
}
|
|
15192
|
+
if (bytes.byteLength > WIRE_IMAGE_MAX_BYTES) {
|
|
15193
|
+
return { ok: false, message: `image too large (max ${WIRE_IMAGE_MAX_BYTES} bytes)` };
|
|
15194
|
+
}
|
|
15195
|
+
return { ok: true, bytes };
|
|
15196
|
+
}
|
|
15197
|
+
function getStorageIdFromUploadResponse(body) {
|
|
15198
|
+
try {
|
|
15199
|
+
const parsed = JSON.parse(body);
|
|
15200
|
+
if (typeof parsed !== "object" || parsed === null || !("storageId" in parsed)) {
|
|
15201
|
+
return null;
|
|
15202
|
+
}
|
|
15203
|
+
const storageId = parsed.storageId;
|
|
15204
|
+
return typeof storageId === "string" && storageId.trim() ? storageId.trim() : null;
|
|
15205
|
+
} catch {
|
|
15206
|
+
return null;
|
|
15207
|
+
}
|
|
15208
|
+
}
|
|
15209
|
+
function formatMetric(metric) {
|
|
15210
|
+
return `${metric.current}/${metric.target} ${metric.unit}`;
|
|
15211
|
+
}
|
|
15212
|
+
function redactSecrets(text) {
|
|
15213
|
+
return text.replace(/ch_(?:live|test)_[A-Za-z0-9_-]+/g, "[redacted]");
|
|
15214
|
+
}
|
|
15215
|
+
async function safeHttpError(response) {
|
|
15216
|
+
try {
|
|
15217
|
+
const body = await response.text();
|
|
15218
|
+
const parsed = JSON.parse(body);
|
|
15219
|
+
if (parsed && typeof parsed === "object") {
|
|
15220
|
+
const record = parsed;
|
|
15221
|
+
const message = typeof record.message === "string" ? record.message : record.error && typeof record.error === "object" && typeof record.error.message === "string" ? record.error.message : typeof record.error === "string" ? record.error : null;
|
|
15222
|
+
return message ? `: ${redactSecrets(message).slice(0, 300)}` : "";
|
|
15223
|
+
}
|
|
15224
|
+
} catch {
|
|
15225
|
+
}
|
|
15226
|
+
return "";
|
|
15227
|
+
}
|
|
15228
|
+
function renderTaskContext(context) {
|
|
15229
|
+
if (!context) return "";
|
|
15230
|
+
const lines = [];
|
|
15231
|
+
if (context.initiative) {
|
|
15232
|
+
const suffix = context.initiative.description ? ` \u2014 ${context.initiative.description}` : "";
|
|
15233
|
+
lines.push(`**Initiative:** ${context.initiative.name}${suffix}`);
|
|
15234
|
+
}
|
|
15235
|
+
if (context.goal) {
|
|
15236
|
+
const metric = context.goal.metric ? ` \u2014 ${formatMetric(context.goal.metric)}` : "";
|
|
15237
|
+
lines.push(`**Goal:** ${context.goal.title} (${context.goal.status})${metric}`);
|
|
15238
|
+
if (context.goal.test) {
|
|
15239
|
+
lines.push(`**Test:** ${context.goal.test}`);
|
|
15240
|
+
}
|
|
15241
|
+
}
|
|
15242
|
+
if (context.project) {
|
|
15243
|
+
const projectDetails = [context.project.status];
|
|
15244
|
+
if (context.project.progress) {
|
|
15245
|
+
projectDetails.push(`${context.project.progress.done}/${context.project.progress.total} tasks done`);
|
|
15246
|
+
}
|
|
15247
|
+
if (context.project.targetDate) {
|
|
15248
|
+
projectDetails.push(`target ${context.project.targetDate}`);
|
|
15249
|
+
}
|
|
15250
|
+
lines.push(`**Project:** ${context.project.title} (${projectDetails.join(", ")})`);
|
|
15251
|
+
}
|
|
15252
|
+
return lines.length > 0 ? ["## Why this matters", ...lines].join("\n") : "";
|
|
15253
|
+
}
|
|
15254
|
+
function renderGoal(goal) {
|
|
15255
|
+
const lines = [
|
|
15256
|
+
`# Goal: ${goal.title}`,
|
|
15257
|
+
`**Status:** ${goal.status}`
|
|
15258
|
+
];
|
|
15259
|
+
if (goal.description) {
|
|
15260
|
+
lines.push("", "## Description", goal.description);
|
|
15261
|
+
}
|
|
15262
|
+
if (goal.test) {
|
|
15263
|
+
lines.push("", `**Test:** ${goal.test}`);
|
|
15264
|
+
}
|
|
15265
|
+
if (goal.metric) {
|
|
15266
|
+
lines.push(`**Metric:** ${formatMetric(goal.metric)}`);
|
|
15267
|
+
}
|
|
15268
|
+
if (goal.lastVerification) {
|
|
15269
|
+
const result = goal.lastVerification.passed ? "passed" : "failed";
|
|
15270
|
+
lines.push(
|
|
15271
|
+
`**Last verification:** ${result} by ${goal.lastVerification.byName} (${goal.lastVerification.byType}) at ${goal.lastVerification.at}`,
|
|
15272
|
+
`**Evidence:** ${goal.lastVerification.evidence}`
|
|
15273
|
+
);
|
|
15274
|
+
}
|
|
15275
|
+
return lines.join("\n");
|
|
15276
|
+
}
|
|
15013
15277
|
var sharedHookState = null;
|
|
15014
15278
|
var plugin = definePluginEntry({
|
|
15015
15279
|
id: "cohort-sync",
|
|
@@ -15203,21 +15467,127 @@ Do not attempt more comments until tomorrow.`);
|
|
|
15203
15467
|
}
|
|
15204
15468
|
};
|
|
15205
15469
|
});
|
|
15470
|
+
api.registerTool((toolCtx) => {
|
|
15471
|
+
const agentId = toolCtx.agentId ?? "main";
|
|
15472
|
+
return {
|
|
15473
|
+
name: "wire_publish",
|
|
15474
|
+
label: "wire_publish",
|
|
15475
|
+
description: "Publish a report or image to Cohort Wire. For images, pass base64 bytes plus image metadata; the tool uploads the file before publishing.",
|
|
15476
|
+
parameters: Type.Object({
|
|
15477
|
+
kind: Type.Union([
|
|
15478
|
+
Type.Literal("report"),
|
|
15479
|
+
Type.Literal("image")
|
|
15480
|
+
], { description: 'Publish kind: "report" for text documents, "image" for image files.' }),
|
|
15481
|
+
title: Type.String({ description: `Title shown in Wire, max ${WIRE_TITLE_MAX} characters.` }),
|
|
15482
|
+
brief: Type.String({ description: `Short summary shown in Wire, max ${WIRE_BRIEF_MAX} characters.` }),
|
|
15483
|
+
body: Type.Optional(Type.String({ description: `Report markdown/body text, max ${WIRE_BODY_MAX} characters. Used with kind=report.` })),
|
|
15484
|
+
image_base64: Type.Optional(Type.String({ description: `Base64 image bytes, max ${WIRE_IMAGE_MAX_BYTES} decoded bytes. Used with kind=image.` })),
|
|
15485
|
+
mime_type: Type.Optional(Type.String({ description: `Image MIME type such as image/png, max ${WIRE_MIME_TYPE_MAX} characters. Used with kind=image.` })),
|
|
15486
|
+
filename: Type.Optional(Type.String({ description: `Original image filename, max ${WIRE_FILENAME_MAX} characters. Used with kind=image.` })),
|
|
15487
|
+
alt_text: Type.Optional(Type.String({ description: `Accessible image description, max ${WIRE_ALT_TEXT_MAX} characters. Used with kind=image.` })),
|
|
15488
|
+
room_id: Type.Optional(Type.String({ description: "Optional Cohort Room ID to link this Wire item to." })),
|
|
15489
|
+
task_id: Type.Optional(Type.String({ description: "Optional Cohort task ID to link this Wire item to." }))
|
|
15490
|
+
}),
|
|
15491
|
+
async execute(_toolCallId, params) {
|
|
15492
|
+
const rt = getToolRuntime();
|
|
15493
|
+
if (!rt.isReady) {
|
|
15494
|
+
return textResult("wire_publish is not ready yet - the plugin is still starting up. Try again in a few seconds.");
|
|
15495
|
+
}
|
|
15496
|
+
const title = requiredWireString(params.title, "title", WIRE_TITLE_MAX);
|
|
15497
|
+
if (!title.ok) return wireInvalid(title.message);
|
|
15498
|
+
const brief = requiredWireString(params.brief, "brief", WIRE_BRIEF_MAX);
|
|
15499
|
+
if (!brief.ok) return wireInvalid(brief.message);
|
|
15500
|
+
const roomId = optionalWireString(params.room_id, "room_id", WIRE_TITLE_MAX);
|
|
15501
|
+
if (!roomId.ok) return wireInvalid(roomId.message);
|
|
15502
|
+
const taskId = optionalWireString(params.task_id, "task_id", WIRE_TITLE_MAX);
|
|
15503
|
+
if (!taskId.ok) return wireInvalid(taskId.message);
|
|
15504
|
+
const agentName = rt.resolveAgentName(agentId);
|
|
15505
|
+
if (params.kind === "report") {
|
|
15506
|
+
const body = optionalWireString(params.body, "body", WIRE_BODY_MAX);
|
|
15507
|
+
if (!body.ok) return wireInvalid(body.message);
|
|
15508
|
+
try {
|
|
15509
|
+
const result = await callPublishWireFromPlugin(rt.apiKey, {
|
|
15510
|
+
agentName,
|
|
15511
|
+
kind: "report",
|
|
15512
|
+
title: title.value,
|
|
15513
|
+
brief: brief.value,
|
|
15514
|
+
...body.value !== void 0 ? { body: body.value } : {},
|
|
15515
|
+
...roomId.value !== void 0 ? { roomId: roomId.value } : {},
|
|
15516
|
+
...taskId.value !== void 0 ? { taskId: taskId.value } : {}
|
|
15517
|
+
});
|
|
15518
|
+
return textResult(`Published report to Wire.
|
|
15519
|
+
Wire item: ${result.wireItemId}`, result);
|
|
15520
|
+
} catch (err) {
|
|
15521
|
+
const msg = getConvexAppErrorMessage(err) ?? (err instanceof Error ? err.message : String(err));
|
|
15522
|
+
return textResult(`Failed to publish to Wire: ${msg}`);
|
|
15523
|
+
}
|
|
15524
|
+
}
|
|
15525
|
+
if (params.kind !== "image") {
|
|
15526
|
+
return wireInvalid('kind must be "report" or "image"');
|
|
15527
|
+
}
|
|
15528
|
+
const mimeType = requiredWireString(params.mime_type, "mime_type", WIRE_MIME_TYPE_MAX);
|
|
15529
|
+
if (!mimeType.ok) return wireInvalid(mimeType.message);
|
|
15530
|
+
if (!mimeType.value.startsWith("image/")) {
|
|
15531
|
+
return wireInvalid("mime_type must start with image/");
|
|
15532
|
+
}
|
|
15533
|
+
const filename = requiredWireString(params.filename, "filename", WIRE_FILENAME_MAX);
|
|
15534
|
+
if (!filename.ok) return wireInvalid(filename.message);
|
|
15535
|
+
const altText = requiredWireString(params.alt_text, "alt_text", WIRE_ALT_TEXT_MAX);
|
|
15536
|
+
if (!altText.ok) return wireInvalid(altText.message);
|
|
15537
|
+
const image = decodeWireImageBase64(params.image_base64);
|
|
15538
|
+
if (!image.ok) return wireInvalid(image.message);
|
|
15539
|
+
try {
|
|
15540
|
+
const uploadUrl = await callGenerateWireUploadUrlFromPlugin(rt.apiKey, { agentName });
|
|
15541
|
+
const uploadResponse = await fetch(uploadUrl, {
|
|
15542
|
+
method: "POST",
|
|
15543
|
+
headers: { "Content-Type": mimeType.value },
|
|
15544
|
+
body: new Blob([image.bytes], { type: mimeType.value }),
|
|
15545
|
+
signal: AbortSignal.timeout(3e4)
|
|
15546
|
+
});
|
|
15547
|
+
const uploadBody = await uploadResponse.text();
|
|
15548
|
+
if (!uploadResponse.ok) {
|
|
15549
|
+
return textResult(`Failed to upload image to Wire: ${uploadResponse.status} ${uploadBody.slice(0, 300)}`);
|
|
15550
|
+
}
|
|
15551
|
+
const storageId = getStorageIdFromUploadResponse(uploadBody);
|
|
15552
|
+
if (!storageId) {
|
|
15553
|
+
return textResult("Failed to upload image to Wire: upload response did not include storageId.");
|
|
15554
|
+
}
|
|
15555
|
+
const result = await callPublishWireFromPlugin(rt.apiKey, {
|
|
15556
|
+
agentName,
|
|
15557
|
+
kind: "image",
|
|
15558
|
+
title: title.value,
|
|
15559
|
+
brief: brief.value,
|
|
15560
|
+
storageId,
|
|
15561
|
+
mimeType: mimeType.value,
|
|
15562
|
+
filename: filename.value,
|
|
15563
|
+
altText: altText.value,
|
|
15564
|
+
...roomId.value !== void 0 ? { roomId: roomId.value } : {},
|
|
15565
|
+
...taskId.value !== void 0 ? { taskId: taskId.value } : {}
|
|
15566
|
+
});
|
|
15567
|
+
return textResult(`Published image to Wire.
|
|
15568
|
+
Wire item: ${result.wireItemId}`, result);
|
|
15569
|
+
} catch (err) {
|
|
15570
|
+
const msg = getConvexAppErrorMessage(err) ?? (err instanceof Error ? err.message : String(err));
|
|
15571
|
+
return textResult(`Failed to publish to Wire: ${msg}`);
|
|
15572
|
+
}
|
|
15573
|
+
}
|
|
15574
|
+
};
|
|
15575
|
+
});
|
|
15206
15576
|
api.registerTool((toolCtx) => {
|
|
15207
15577
|
const agentId = toolCtx.agentId ?? "main";
|
|
15208
15578
|
return {
|
|
15209
15579
|
name: "cohort_room_start_moderation_session",
|
|
15210
15580
|
label: "cohort_room_start_moderation_session",
|
|
15211
|
-
description:
|
|
15581
|
+
description: 'Start a managed moderation session in a Cohort Room. Use round_robin = ask every participant to report in; use moderated_qna = route a question to one selected agent, wait for the answer, ask the asker whether it resolves the question, then complete. For multiple experts in Q&A, start with one best agent and use action: "followup" for additional experts one at a time. Returns the session state block. The returned block includes the session_id \u2014 pass it to cohort_room_advance_moderation_session for every subsequent action.',
|
|
15212
15582
|
parameters: Type.Object({
|
|
15213
15583
|
room_id: Type.String({ description: "Room ID supplied by Cohort, e.g. rooms:abc123" }),
|
|
15214
15584
|
mode: Type.Union([
|
|
15215
15585
|
Type.Literal("round_robin"),
|
|
15216
15586
|
Type.Literal("moderated_qna")
|
|
15217
|
-
], { description: 'Session mode: "round_robin"
|
|
15587
|
+
], { description: 'Session mode: "round_robin" means every participant reports in; "moderated_qna" means one selected participant answers a question, with serial followups for more experts.' }),
|
|
15218
15588
|
objective: Type.String({ description: "One-line objective for the session, e.g. 'Daily standup: blockers and progress'" }),
|
|
15219
|
-
participant_names: Type.Array(Type.String(), { description: "Cohort agent names to include, in speaking order" }),
|
|
15220
|
-
question_text: Type.Optional(Type.String({ description: "moderated_qna only: the question to ask the participant" })),
|
|
15589
|
+
participant_names: Type.Array(Type.String(), { description: "Cohort agent names to include, in speaking order; moderated_qna should include exactly one best agent" }),
|
|
15590
|
+
question_text: Type.Optional(Type.String({ description: "moderated_qna only: the question to ask the selected participant" })),
|
|
15221
15591
|
auto_advance: Type.Optional(Type.Boolean({ description: "round_robin only: automatically prompt the next participant after each matched response" }))
|
|
15222
15592
|
}),
|
|
15223
15593
|
async execute(_toolCallId, params) {
|
|
@@ -15249,7 +15619,7 @@ Do not attempt more comments until tomorrow.`);
|
|
|
15249
15619
|
return {
|
|
15250
15620
|
name: "cohort_room_advance_moderation_session",
|
|
15251
15621
|
label: "cohort_room_advance_moderation_session",
|
|
15252
|
-
description: 'Advance a moderation session you are running: "next" prompts the next participant, "retry"/"skip"/"accept" resolve a mismatched turn,
|
|
15622
|
+
description: 'Advance a moderation session you are running: "next" prompts the next round-robin participant, "retry"/"skip"/"accept" resolve a mismatched turn, followup asks one additional Q&A expert after the first answer, and "complete" ends the session. For moderated_qna, confirm with the asker before complete. Outcomes passed with action "complete" (summary, decisions, proposed_tasks, follow_ups) become the session recap; proposed_tasks surface for human approval. Returns the updated session state block.',
|
|
15253
15623
|
parameters: Type.Object({
|
|
15254
15624
|
session_id: Type.String({ description: "Moderation session ID returned by cohort_room_start_moderation_session" }),
|
|
15255
15625
|
action: Type.Union([
|
|
@@ -15259,7 +15629,7 @@ Do not attempt more comments until tomorrow.`);
|
|
|
15259
15629
|
Type.Literal("accept"),
|
|
15260
15630
|
Type.Literal("followup"),
|
|
15261
15631
|
Type.Literal("complete")
|
|
15262
|
-
], { description: "next = prompt the next participant (from waiting); retry = re-prompt the same agent; skip = skip the expected agent and move on; accept = accept an out-of-order reply as the response; followup = route
|
|
15632
|
+
], { description: "next = prompt the next round-robin participant (from waiting); retry = re-prompt the same agent; skip = skip the expected agent and move on; accept = accept an out-of-order reply as the response; followup = route one additional Q&A question to one target agent; complete = finish after asker confirmation when in Q&A (pass summary/decisions/proposed_tasks to create the recap)." }),
|
|
15263
15633
|
prompt: Type.Optional(Type.String({ description: "Custom prompt for the turn (next/retry/followup)" })),
|
|
15264
15634
|
target_agent_name: Type.Optional(Type.String({ description: "followup only: the agent to ask" })),
|
|
15265
15635
|
summary: Type.Optional(Type.String({ description: "complete only: session recap summary" })),
|
|
@@ -15457,11 +15827,13 @@ ${body}`,
|
|
|
15457
15827
|
`# Task #${task.taskNumber}: ${task.title}`,
|
|
15458
15828
|
`**Status:** ${task.status} | **Priority:** ${task.priority ?? "none"} | **Effort:** ${task.effort ?? "none"}`,
|
|
15459
15829
|
`**Assigned to:** ${task.assignedTo ?? "unassigned"}`,
|
|
15460
|
-
`**Created:** ${task.createdAt}
|
|
15461
|
-
"",
|
|
15462
|
-
"## Description",
|
|
15463
|
-
task.description || "(no description)"
|
|
15830
|
+
`**Created:** ${task.createdAt}`
|
|
15464
15831
|
];
|
|
15832
|
+
const contextBlock = renderTaskContext(task.context);
|
|
15833
|
+
if (contextBlock) {
|
|
15834
|
+
lines.push("", contextBlock);
|
|
15835
|
+
}
|
|
15836
|
+
lines.push("", "## Description", task.description || "(no description)");
|
|
15465
15837
|
if (params.include_comments !== false) {
|
|
15466
15838
|
const limit = params.comment_limit ?? 10;
|
|
15467
15839
|
const commentsRes = await fetch(
|
|
@@ -15492,6 +15864,62 @@ ${body}`,
|
|
|
15492
15864
|
}
|
|
15493
15865
|
};
|
|
15494
15866
|
});
|
|
15867
|
+
api.registerTool(() => {
|
|
15868
|
+
return {
|
|
15869
|
+
name: "cohort_goal",
|
|
15870
|
+
label: "cohort_goal",
|
|
15871
|
+
description: "Fetch a Cohort goal \u2014 including its test \u2014 or submit a verification result after running the test. A goal's test tells you how to check whether the outcome is achieved.",
|
|
15872
|
+
parameters: Type.Object({
|
|
15873
|
+
goal_id: Type.String({ description: "Goal ID from Cohort." }),
|
|
15874
|
+
verify: Type.Optional(Type.Object({
|
|
15875
|
+
passed: Type.Boolean({ description: "Whether the goal test passed." }),
|
|
15876
|
+
evidence: Type.String({ description: "Evidence from running the test." }),
|
|
15877
|
+
metric_current: Type.Optional(Type.Number({ description: "Updated current metric value, if the goal has a metric." }))
|
|
15878
|
+
}))
|
|
15879
|
+
}),
|
|
15880
|
+
async execute(_toolCallId, params) {
|
|
15881
|
+
const rt = getToolRuntime();
|
|
15882
|
+
if (!rt.isReady) {
|
|
15883
|
+
return textResult("cohort_goal is not ready yet \u2014 the plugin is still starting up.");
|
|
15884
|
+
}
|
|
15885
|
+
const goalUrl = `${rt.apiUrl}/api/v1/goals/${encodeURIComponent(params.goal_id)}`;
|
|
15886
|
+
try {
|
|
15887
|
+
const response = await fetch(
|
|
15888
|
+
params.verify ? `${goalUrl}/verify` : goalUrl,
|
|
15889
|
+
{
|
|
15890
|
+
method: params.verify ? "POST" : "GET",
|
|
15891
|
+
headers: {
|
|
15892
|
+
"Authorization": `Bearer ${rt.apiKey}`,
|
|
15893
|
+
...params.verify ? { "Content-Type": "application/json" } : {}
|
|
15894
|
+
},
|
|
15895
|
+
...params.verify ? {
|
|
15896
|
+
body: JSON.stringify({
|
|
15897
|
+
passed: params.verify.passed,
|
|
15898
|
+
evidence: params.verify.evidence,
|
|
15899
|
+
...params.verify.metric_current !== void 0 ? { metricCurrent: params.verify.metric_current } : {}
|
|
15900
|
+
})
|
|
15901
|
+
} : {},
|
|
15902
|
+
signal: AbortSignal.timeout(1e4)
|
|
15903
|
+
}
|
|
15904
|
+
);
|
|
15905
|
+
if (!response.ok) {
|
|
15906
|
+
const message = await safeHttpError(response);
|
|
15907
|
+
return textResult(`Failed to fetch goal ${params.goal_id}: ${response.status}${message}`);
|
|
15908
|
+
}
|
|
15909
|
+
const goal = await response.json();
|
|
15910
|
+
if (!params.verify) {
|
|
15911
|
+
return textResult(renderGoal(goal), goal);
|
|
15912
|
+
}
|
|
15913
|
+
const explanation = goal.status === "verification_pending" ? "Reported. A human will confirm before the goal is marked met." : goal.status === "met" ? "Goal marked met." : `Verification recorded. Goal status: ${goal.status}.`;
|
|
15914
|
+
return textResult(`${explanation}
|
|
15915
|
+
|
|
15916
|
+
${renderGoal(goal)}`, goal);
|
|
15917
|
+
} catch (err) {
|
|
15918
|
+
return textResult(`Failed to fetch goal ${params.goal_id}: ${err instanceof Error ? redactSecrets(err.message) : "Unknown error"}`);
|
|
15919
|
+
}
|
|
15920
|
+
}
|
|
15921
|
+
};
|
|
15922
|
+
});
|
|
15495
15923
|
api.registerTool((toolCtx) => {
|
|
15496
15924
|
const agentId = toolCtx.agentId ?? "main";
|
|
15497
15925
|
return {
|
|
@@ -15599,5 +16027,6 @@ ${body}`,
|
|
|
15599
16027
|
});
|
|
15600
16028
|
var index_default = plugin;
|
|
15601
16029
|
export {
|
|
15602
|
-
index_default as default
|
|
16030
|
+
index_default as default,
|
|
16031
|
+
renderTaskContext
|
|
15603
16032
|
};
|
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
"cohort_room_start_moderation_session",
|
|
18
18
|
"cohort_room_advance_moderation_session",
|
|
19
19
|
"cohort_room_cancel_moderation_session",
|
|
20
|
+
"wire_publish",
|
|
20
21
|
"cohort_context",
|
|
21
22
|
"cohort_briefing_context",
|
|
22
23
|
"cohort_briefing",
|
|
23
24
|
"cohort_task",
|
|
25
|
+
"cohort_goal",
|
|
24
26
|
"cohort_transition",
|
|
25
27
|
"cohort_assign"
|
|
26
28
|
]
|
|
@@ -80,5 +82,5 @@
|
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
},
|
|
83
|
-
"version": "0.
|
|
85
|
+
"version": "0.33.0"
|
|
84
86
|
}
|
package/dist/package.json
CHANGED
package/openclaw.plugin.json
CHANGED
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
"cohort_room_start_moderation_session",
|
|
16
16
|
"cohort_room_advance_moderation_session",
|
|
17
17
|
"cohort_room_cancel_moderation_session",
|
|
18
|
+
"wire_publish",
|
|
18
19
|
"cohort_context",
|
|
19
20
|
"cohort_briefing_context",
|
|
20
21
|
"cohort_briefing",
|
|
21
22
|
"cohort_task",
|
|
23
|
+
"cohort_goal",
|
|
22
24
|
"cohort_transition",
|
|
23
25
|
"cohort_assign"
|
|
24
26
|
]
|
package/package.json
CHANGED