@cfio/cohort-sync 0.32.1 → 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 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(
@@ -12295,6 +12354,8 @@ TOOLS: Use these \u2014 do NOT call the REST API directly.
12295
12354
  - cohort_room_cancel_moderation_session(session_id, reason?) \u2014 moderator only: cancel an in-flight session
12296
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)
12297
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
12298
12359
  - cohort_task(task_number) \u2014 fetch full task details + comments
12299
12360
  - cohort_transition(task_number, status) \u2014 change status
12300
12361
  - cohort_assign(task_number, assignee) \u2014 assign/unassign
@@ -12357,6 +12418,9 @@ function isHumanQuestionRoutingRequest(n) {
12357
12418
  }
12358
12419
  function roomMessageCta(n) {
12359
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
+ }
12360
12424
  if (n.moderationContext != null && renderModerationContext(n.moderationContext) !== "") {
12361
12425
  return "Follow the MODERATION SESSION block in this message \u2014 it is authoritative.";
12362
12426
  }
@@ -12405,6 +12469,47 @@ function renderModerationContext(ctx) {
12405
12469
  lines.push("[/MODERATION SESSION]");
12406
12470
  return lines.join("\n");
12407
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
+ }
12408
12513
  function buildNotificationMessage(n) {
12409
12514
  let header;
12410
12515
  let cta;
@@ -12462,8 +12567,9 @@ Scope: ${truncated}`;
12462
12567
  }
12463
12568
  let prompt;
12464
12569
  if (n.type === "room_message") {
12465
- const moderationBlock = n.moderationContext != null ? renderModerationContext(n.moderationContext) : "";
12466
- const preamble = moderationBlock || n.behavioralPrompt;
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;
12467
12573
  prompt = preamble ? `${preamble}
12468
12574
 
12469
12575
  ${ROOM_MESSAGE_RESPONSE_PROMPT}` : ROOM_MESSAGE_RESPONSE_PROMPT;
@@ -14011,7 +14117,7 @@ function dumpEvent(event) {
14011
14117
  function positiveNumber(value) {
14012
14118
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
14013
14119
  }
14014
- var PLUGIN_VERSION = true ? "0.32.1" : "unknown";
14120
+ var PLUGIN_VERSION = true ? "0.33.0" : "unknown";
14015
14121
  function resolveGatewayToken(api) {
14016
14122
  const token2 = api.config?.gateway?.auth?.token;
14017
14123
  return typeof token2 === "string" ? token2 : null;
@@ -15031,6 +15137,143 @@ var POCKET_GUIDE = `# Cohort Agent Guide (Pocket Version)
15031
15137
  function textResult(text, details) {
15032
15138
  return { content: [{ type: "text", text }], details: details ?? void 0 };
15033
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
+ }
15034
15277
  var sharedHookState = null;
15035
15278
  var plugin = definePluginEntry({
15036
15279
  id: "cohort-sync",
@@ -15224,6 +15467,112 @@ Do not attempt more comments until tomorrow.`);
15224
15467
  }
15225
15468
  };
15226
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
+ });
15227
15576
  api.registerTool((toolCtx) => {
15228
15577
  const agentId = toolCtx.agentId ?? "main";
15229
15578
  return {
@@ -15478,11 +15827,13 @@ ${body}`,
15478
15827
  `# Task #${task.taskNumber}: ${task.title}`,
15479
15828
  `**Status:** ${task.status} | **Priority:** ${task.priority ?? "none"} | **Effort:** ${task.effort ?? "none"}`,
15480
15829
  `**Assigned to:** ${task.assignedTo ?? "unassigned"}`,
15481
- `**Created:** ${task.createdAt}`,
15482
- "",
15483
- "## Description",
15484
- task.description || "(no description)"
15830
+ `**Created:** ${task.createdAt}`
15485
15831
  ];
15832
+ const contextBlock = renderTaskContext(task.context);
15833
+ if (contextBlock) {
15834
+ lines.push("", contextBlock);
15835
+ }
15836
+ lines.push("", "## Description", task.description || "(no description)");
15486
15837
  if (params.include_comments !== false) {
15487
15838
  const limit = params.comment_limit ?? 10;
15488
15839
  const commentsRes = await fetch(
@@ -15513,6 +15864,62 @@ ${body}`,
15513
15864
  }
15514
15865
  };
15515
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
+ });
15516
15923
  api.registerTool((toolCtx) => {
15517
15924
  const agentId = toolCtx.agentId ?? "main";
15518
15925
  return {
@@ -15620,5 +16027,6 @@ ${body}`,
15620
16027
  });
15621
16028
  var index_default = plugin;
15622
16029
  export {
15623
- index_default as default
16030
+ index_default as default,
16031
+ renderTaskContext
15624
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.32.1"
85
+ "version": "0.33.0"
84
86
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfio/cohort-sync",
3
- "version": "0.32.1",
3
+ "version": "0.33.0",
4
4
  "description": "OpenClaw plugin — syncs agent telemetry, sessions, and activity to the Cohort dashboard",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfio/cohort-sync",
3
- "version": "0.32.1",
3
+ "version": "0.33.0",
4
4
  "description": "OpenClaw plugin — syncs agent telemetry, sessions, and activity to the Cohort dashboard",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.cohort.bot/gateway",