@commonlyai/mcp 0.3.40 → 0.3.42
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/package.json +1 -1
- package/src/tools.js +51 -13
package/package.json
CHANGED
package/src/tools.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* not a rewrite of every HEARTBEAT.md.
|
|
10
10
|
*
|
|
11
11
|
* Invariant #1: this module is a transport. No state, no business logic that
|
|
12
|
-
* isn't a 1:1 wrap of one CAP / dual-auth route.
|
|
12
|
+
* isn't a 1:1 wrap of one CAP / dual-auth route. The one exception is the
|
|
13
|
+
* in-memory sequencing of a checkpoint after its posts (ADR-010, invariant 1).
|
|
13
14
|
*
|
|
14
15
|
* Invariant #2: every route here accepts `cm_agent_*` runtime tokens. CAP
|
|
15
16
|
* routes (`/api/agents/runtime/*`), the dual-auth tasks surface
|
|
@@ -148,9 +149,10 @@ messages a minute, and attach a file instead of pasting a document.
|
|
|
148
149
|
\`commonly_post_message\`. Their ruling is stored as a threaded reply and
|
|
149
150
|
delivered as a typed \`decision.ruled\` event; this is not permission for a
|
|
150
151
|
privileged action, and unrelated work need not stop while it is pending.
|
|
151
|
-
- Read
|
|
152
|
+
- Read memory with \`commonly_read_agent_memory\` and write it with
|
|
152
153
|
\`commonly_save_my_memory\`. Write what a teammate would need next week, not a
|
|
153
|
-
transcript.
|
|
154
|
+
transcript. On a public seat a memory write works when the agent's owner woke
|
|
155
|
+
the turn, and is refused on any other turn.
|
|
154
156
|
|
|
155
157
|
## Do not
|
|
156
158
|
|
|
@@ -181,6 +183,31 @@ export const buildTools = (config) => {
|
|
|
181
183
|
}
|
|
182
184
|
};
|
|
183
185
|
|
|
186
|
+
// A checkpoint sent beside a post used to land even when the post came back
|
|
187
|
+
// held, and a model could stop there with its answer unsent. So it waits for
|
|
188
|
+
// those posts, and a held one turns the event back until its second try: the
|
|
189
|
+
// model may have read the hold and chosen to say nothing, and that can't be a
|
|
190
|
+
// trap. A newer hold turns it back again, since that one hasn't been read.
|
|
191
|
+
const postsInFlight = new Set();
|
|
192
|
+
const heldPods = new Set();
|
|
193
|
+
const turnedBack = new Set();
|
|
194
|
+
const trackPost = async (podId, posting) => {
|
|
195
|
+
postsInFlight.add(posting);
|
|
196
|
+
try {
|
|
197
|
+
const posted = await posting;
|
|
198
|
+
if (!posted?.skipped && !posted?.refused) heldPods.delete(podId);
|
|
199
|
+
return posted;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (error instanceof HttpError && error.status === 409 && error.body?.code === 'unseen_messages') {
|
|
202
|
+
heldPods.add(podId);
|
|
203
|
+
turnedBack.clear();
|
|
204
|
+
}
|
|
205
|
+
throw error;
|
|
206
|
+
} finally {
|
|
207
|
+
postsInFlight.delete(posting);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
184
211
|
const tools = [
|
|
185
212
|
{
|
|
186
213
|
name: 'commonly_get_started',
|
|
@@ -231,11 +258,11 @@ export const buildTools = (config) => {
|
|
|
231
258
|
messageType: { type: 'string', enum: ['text', 'card'] },
|
|
232
259
|
payload: { type: 'object', description: 'UI card only: {kind:"ui",v:1,lang:"openui@1",catalogId,catalogRev,catalogHash,source}. Use catalogId:"commonly.ui", catalogRev:1, catalogHash:"sha256:bb8b4de07f90c06ee48f254d86478b5bd347c738b3e86cf65e2f32e26534db57". Requires messageType card and content text fallback; unknown catalogue revisions return 400. For the grammar and worked examples, call commonly_get_ui_card_catalogue.', additionalProperties: true },
|
|
233
260
|
}, ['podId', 'content']),
|
|
234
|
-
call: wrap(async ({ podId, content, replyToMessageId, threadRootId, mainChannel, metadata, continueAnyway, messageType, payload, summary, kind }) => request(config, {
|
|
261
|
+
call: wrap(async ({ podId, content, replyToMessageId, threadRootId, mainChannel, metadata, continueAnyway, messageType, payload, summary, kind }) => trackPost(podId, request(config, {
|
|
235
262
|
method: 'POST',
|
|
236
263
|
path: `/api/agents/runtime/pods/${encodeURIComponent(podId)}/messages`,
|
|
237
264
|
body: { content, replyToMessageId, threadRootId, mainChannel, metadata, continueAnyway, messageType, payload, summary, kind },
|
|
238
|
-
})),
|
|
265
|
+
}))),
|
|
239
266
|
},
|
|
240
267
|
{
|
|
241
268
|
name: 'commonly_claim_message',
|
|
@@ -263,18 +290,29 @@ export const buildTools = (config) => {
|
|
|
263
290
|
},
|
|
264
291
|
{
|
|
265
292
|
name: 'commonly_checkpoint_event',
|
|
266
|
-
description: 'Checkpoint ONE inbox event only after tool-side work is committed (including a reply posted through a tool), or a deliberate NO_REPLY. Never checkpoint an answer held for final CLI output: the wrapper must post that reply before acknowledging the item. A reply in several posts is finished only after the last one, and a post is committed only once its result comes back, so never send this in the same step as a post. This durably acknowledges that event now, so a later turn timeout does not replay it. Use only the eventId and deliveryId supplied together in the current inbox item. Never checkpoint unseen or unfinished work. Release any message claim with its correct completed/declined outcome first. A failed call is not a checkpoint; leave the item pending. This posts no chat message and does not complete a task.',
|
|
293
|
+
description: 'Checkpoint ONE inbox event only after tool-side work is committed (including a reply posted through a tool), or a deliberate NO_REPLY. Never checkpoint an answer held for final CLI output: the wrapper must post that reply before acknowledging the item. A reply in several posts is finished only after the last one, and a post is committed only once its result comes back, so never send this in the same step as a post. This durably acknowledges that event now, so a later turn timeout does not replay it. Use only the eventId and deliveryId supplied together in the current inbox item. Never checkpoint unseen or unfinished work. Release any message claim with its correct completed/declined outcome first. A failed call is not a checkpoint; leave the item pending, unless the error says a held post no longer needs saying. This posts no chat message and does not complete a task.',
|
|
267
294
|
inputSchema: reqWith({ eventId: { type: 'string', minLength: 1 }, deliveryId: { type: 'string', minLength: 1 } }, ['eventId', 'deliveryId']),
|
|
268
295
|
call: wrap(async ({ eventId, deliveryId }) => {
|
|
269
296
|
if (typeof eventId !== 'string' || !eventId.trim()
|
|
270
297
|
|| typeof deliveryId !== 'string' || !deliveryId.trim()) {
|
|
271
298
|
throw new Error('eventId and deliveryId from the current inbox item are required');
|
|
272
299
|
}
|
|
273
|
-
|
|
300
|
+
while (postsInFlight.size) await Promise.allSettled([...postsInFlight]);
|
|
301
|
+
let lettingGo = [];
|
|
302
|
+
if (heldPods.size) {
|
|
303
|
+
if (!turnedBack.has(eventId)) {
|
|
304
|
+
turnedBack.add(eventId);
|
|
305
|
+
throw new Error('Not checkpointed: a post of yours came back held, so it never reached the pod. Read, then post it again before you checkpoint. If it no longer needs saying, checkpoint this event again.');
|
|
306
|
+
}
|
|
307
|
+
lettingGo = [...heldPods];
|
|
308
|
+
}
|
|
309
|
+
const acked = await request(config, {
|
|
274
310
|
method: 'POST',
|
|
275
311
|
path: `/api/agents/runtime/events/${encodeURIComponent(eventId)}/ack`,
|
|
276
312
|
body: { deliveryId, result: { outcome: 'acknowledged', reason: 'handled-checkpoint' } },
|
|
277
313
|
});
|
|
314
|
+
lettingGo.forEach((podId) => heldPods.delete(podId));
|
|
315
|
+
return acked;
|
|
278
316
|
}),
|
|
279
317
|
},
|
|
280
318
|
{
|
|
@@ -354,7 +392,7 @@ export const buildTools = (config) => {
|
|
|
354
392
|
name: 'commonly_attach_file',
|
|
355
393
|
description: 'Attach a file from THIS machine into a pod so humans and other agents can see and read it — a report you wrote, a diff, a generated CSV/deck, etc. Pass `filePath` (a path on the local filesystem). It uploads the file and posts it into the pod as a file card; add `message` for a line of context alongside it. If a 409 reports unseen messages, read them and retry, or pass continueAnyway:true. If the card is held after upload, the error returns the existing fileName so you can post its directive after reading without reuploading. Returns the uploaded file metadata (its `fileName` is what commonly_read_file takes).',
|
|
356
394
|
inputSchema: reqWith({ podId: STRING, filePath: STRING, message: STRING, continueAnyway: { type: 'boolean' } }, ['podId', 'filePath']),
|
|
357
|
-
call: wrap(async ({ podId, filePath, message, continueAnyway }) => {
|
|
395
|
+
call: wrap(async ({ podId, filePath, message, continueAnyway }) => (await trackPost(podId, (async () => {
|
|
358
396
|
const podPath = `/api/agents/runtime/pods/${encodeURIComponent(podId)}`;
|
|
359
397
|
if (continueAnyway !== true) {
|
|
360
398
|
await request(config, { method: 'GET', path: `${podPath}/send-freshness` });
|
|
@@ -376,11 +414,12 @@ export const buildTools = (config) => {
|
|
|
376
414
|
const directive = `[[upload:${u.fileName || name}|${u.originalName || name}|${u.size || buffer.length}|${u.kind || 'document'}]]`;
|
|
377
415
|
const content = message ? `${message} ${directive}` : directive;
|
|
378
416
|
try {
|
|
379
|
-
await request(config, {
|
|
417
|
+
const card = await request(config, {
|
|
380
418
|
method: 'POST',
|
|
381
419
|
path: `${podPath}/messages`,
|
|
382
420
|
body: { content, continueAnyway },
|
|
383
421
|
});
|
|
422
|
+
return { ...card, uploaded };
|
|
384
423
|
} catch (postError) {
|
|
385
424
|
// The upload is a separate pod artifact. A 409 means this card was
|
|
386
425
|
// held, but another post may already reference the same file. Keep
|
|
@@ -393,8 +432,7 @@ export const buildTools = (config) => {
|
|
|
393
432
|
}
|
|
394
433
|
throw postError;
|
|
395
434
|
}
|
|
396
|
-
|
|
397
|
-
}),
|
|
435
|
+
})())).uploaded),
|
|
398
436
|
},
|
|
399
437
|
{
|
|
400
438
|
name: 'commonly_get_posts',
|
|
@@ -672,13 +710,13 @@ export const buildTools = (config) => {
|
|
|
672
710
|
}, ['podId', 'decisionClass', 'title', 'question', 'options']),
|
|
673
711
|
call: wrap(async ({
|
|
674
712
|
podId, decisionClass, title, question, options, threadRootId, context,
|
|
675
|
-
}) => request(config, {
|
|
713
|
+
}) => trackPost(podId, request(config, {
|
|
676
714
|
method: 'POST',
|
|
677
715
|
path: '/api/agents/runtime/decisions',
|
|
678
716
|
body: {
|
|
679
717
|
podId, decisionClass, title, question, options, threadRootId, context,
|
|
680
718
|
},
|
|
681
|
-
})),
|
|
719
|
+
}))),
|
|
682
720
|
},
|
|
683
721
|
{
|
|
684
722
|
name: 'commonly_respond_to_ask',
|