@sentry/junior 0.266.0 → 0.268.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/{actor-tlxzqaRe.js → actor-CP9K2Idu.js} +9 -1
- package/dist/{adapter-eRFA_naf.js → adapter-04AcD2SO.js} +5 -7
- package/dist/{admin-tILpP51p.js → admin-DIU1QyG3.js} +1 -1
- package/dist/{agent-invocations-CxKldAeB.js → agent-invocations-C1RNOCs8.js} +2 -0
- package/dist/{agent-runner-BtYK7bht.js → agent-runner-CAYfFmU7.js} +2636 -601
- package/dist/api/schema.d.ts +2 -5454
- package/dist/api/schema.js +6 -5
- package/dist/api.d.ts +6 -1
- package/dist/api.js +255 -1208
- package/dist/{app-Csbi9dW7.d.ts → app-z6Uuyq3j.d.ts} +23 -4
- package/dist/app.d.ts +2 -2
- package/dist/app.js +1869 -378
- package/dist/{automation-BKpY9LPa.js → automation-CmeFBSEh.js} +42 -14
- package/dist/{briefs-DgfWMU7e.js → briefs-DQbV2jNr.js} +4 -4
- package/dist/{catalog-runtime-DAJjAbSx.js → catalog-runtime-BD9GSpc6.js} +1 -1
- package/dist/cli/chat.js +15 -15
- package/dist/cli/check.js +3 -3
- package/dist/cli/init.js +0 -1
- package/dist/cli/main.js +3 -3
- package/dist/cli/plugins.js +4 -4
- package/dist/cli/snapshot-create.js +4 -4
- package/dist/cli/upgrade.d.ts +1 -1
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-B0YZfxNy.js → client-C1yDYjoS.js} +2 -2
- package/dist/{config-C0UwYqDK.js → config-DrTeF80T.js} +32 -10
- package/dist/{connections-Bwu3VORi.js → connections-D46f8vvi.js} +12 -12
- package/dist/{conversation-DF4rUcjb.js → conversation-DSo310Ps.js} +8 -1
- package/dist/{conversation-privacy-CY7JVjxn.js → conversation-privacy-CxlOCiUo.js} +1 -1
- package/dist/{credential-sync-VGAMUTGp.js → credential-sync-sm9fegLY.js} +29 -34
- package/dist/{credentialed-enF5Bf3y.js → credentialed-lukuD-cc.js} +2933 -1026
- package/dist/db-BU4LQD8s.d.ts +6 -0
- package/dist/{executor-TnWbvL09.js → executor-4vhvsMZJ.js} +83 -6
- package/dist/instrumentation.js +6 -2
- package/dist/{assistant-reply-UyzAfZQo.js → interruption-marker-CkncDT8d.js} +8 -2
- package/dist/{job-queue-DBpHmKnd.js → job-queue-In9QKz-I.js} +128 -3
- package/dist/{web-input-B-bMjD1V.js → list-BkgnXEEB.js} +1611 -817
- package/dist/{logging-Ce0mXZ8J.js → logging-DLSpkSSr.js} +11 -3
- package/dist/{message-VZ3DFpGb.js → message-BObyg59s.js} +4 -4
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-CpqWLjVY.js → oauth-callback-server-B-WbSdoH.js} +2 -2
- package/dist/{prompt-BcCGlmg2.js → prompt-BW9jFsqZ.js} +1 -1
- package/dist/{registry-1BEIPdbT.js → registry-BgecKva0.js} +2 -2
- package/dist/{web-message-id-CJSUiksE.js → reporting-window-DVCD_10C.js} +1 -10
- package/dist/{resolve-BSHHkEGE.js → resolve-Cpa4Bu1O.js} +3 -3
- package/dist/{result-CwOpzqdh.js → result-D3TSmZZ8.js} +9 -9
- package/dist/{runner-CEaX04kz.js → runner-DS5OKhEp.js} +11 -11
- package/dist/{sandbox-egress-signals-BwtGHwqa.js → sandbox-egress-signals-CnDjY5rx.js} +1 -1
- package/dist/{schema-RsaBHxRH.js → schema-0050HFnB.js} +82 -5
- package/dist/schema-ZngqcOWM.d.ts +5813 -0
- package/dist/{service-account-6wC76eBZ.js → service-account-CKV2WTY7.js} +3 -3
- package/dist/{service-account-Dg4lIbHm.js → service-account-DGjnDw9y.js} +3 -3
- package/dist/{session-stop-tZOw9AJD.js → session-stop-Dam1EI7B.js} +527 -134
- package/dist/{skills-DzyGN7wd.js → skills-CfAnfImD.js} +2 -2
- package/dist/{spawn-6rokq7l_.js → spawn-cmq9p4Ly.js} +2 -2
- package/dist/{store-DcKkzrdt.js → store-C5PN02QO.js} +4 -4
- package/dist/{store-CG0VJyye.d.ts → store-xMuuXRE0.d.ts} +23 -9
- package/dist/store-zPEoJPBB.js +295 -0
- package/dist/{task-queue-Bg4RS2Gy.js → task-queue-_mf28dSu.js} +10 -5
- package/dist/{task-runner-8N5LIkSg.js → task-runner-DMI2prDO.js} +25 -14
- package/dist/{tasks-DDT-J3Rf.js → tasks-SB9z-VTU.js} +1551 -54
- package/dist/{thread-state-cprEaxgl.js → thread-state-BbbPh0Sn.js} +8 -18
- package/dist/{turn-failure-response-C8sMdsdX.js → turn-failure-response-BKKmnMB5.js} +11 -16
- package/dist/{turn-lifecycle-DaBVoz7h.js → turn-lifecycle-oP0kVeGz.js} +1 -1
- package/dist/{validation-C2lllinq.js → validation--JsAGLwc.js} +1 -1
- package/dist/{version-oaLfxxnf.js → version-jHwJp4hC.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{viewer-BpE6Z3qf.js → viewer-CpXTGlep.js} +329 -230
- package/dist/web-message-id-DlHNANtd.js +132 -0
- package/dist/{work-VX1SHWha.js → work-3N-GHY9m.js} +10 -10
- package/dist/{worker-hook-y_Y.js → worker-CCdbcItd.js} +30 -19
- package/migrations/0064_any_resource_event_triggers.sql +2 -0
- package/migrations/0065_user_customization.sql +1 -0
- package/migrations/0066_review_items.sql +53 -0
- package/migrations/0067_review_item_dedupe_tenant.sql +2 -0
- package/migrations/0068_review_item_slack_cards.sql +10 -0
- package/migrations/meta/0064_snapshot.json +3505 -0
- package/migrations/meta/0065_snapshot.json +3511 -0
- package/migrations/meta/0066_snapshot.json +4099 -0
- package/migrations/meta/0067_snapshot.json +3921 -0
- package/migrations/meta/0068_snapshot.json +4176 -0
- package/migrations/meta/_journal.json +36 -1
- package/package.json +19 -4
- package/dist/db-BnRqQ7G5.d.ts +0 -5
- package/dist/images-BKQqSi5W.js +0 -64
- package/dist/store-FALy-JQb.js +0 -1424
- package/dist/turn-session-routing-a9CHX-w6.js +0 -76
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as botConfig } from "./config-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { s as juniorConversations } from "./agent-invocations-
|
|
7
|
-
import "./executor-
|
|
8
|
-
import { h as credentialSubjectSchema } from "./registry-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { r as EVENT_AUTHOR_ID } from "./prompt-
|
|
12
|
-
import {
|
|
13
|
-
import { r as appendAndEnqueueInboundMessage } from "./store-
|
|
2
|
+
import { F as eventCatalogEntry, Gn as isToolResultError, I as eventGuidance, Kn as isToolResultMessage, M as CORE_EVENT_NAMESPACE, Un as isAssistantMessage, qn as normalizeToolNameFromResult } from "./tasks-SB9z-VTU.js";
|
|
3
|
+
import { n as botConfig } from "./config-DrTeF80T.js";
|
|
4
|
+
import { h as logWarn, m as logInfo, p as logException } from "./logging-DLSpkSSr.js";
|
|
5
|
+
import { h as getSqlExecutor, p as getConversationStore } from "./viewer-CpXTGlep.js";
|
|
6
|
+
import { s as juniorConversations } from "./agent-invocations-C1RNOCs8.js";
|
|
7
|
+
import "./executor-4vhvsMZJ.js";
|
|
8
|
+
import { h as credentialSubjectSchema } from "./registry-BgecKva0.js";
|
|
9
|
+
import { $t as verifyTimerWatchCredentialSubject, G as normalizeSlackReplyMarkdown, U as escapeSlackMrkdwnText, W as formatSlackLink, Yn as buildSentryConversationUrl, hn as getDashboardObjectIconLink, l as getPluginSlackConversationLink, mn as getDashboardConversationLink, p as getPlugins, pn as JUNIOR_THREAD_STATE_TTL_MS, tt as postSlackMessage, yn as ToolInputError } from "./credentialed-lukuD-cc.js";
|
|
10
|
+
import { i as getStateAdapter } from "./adapter-04AcD2SO.js";
|
|
11
|
+
import { r as EVENT_AUTHOR_ID } from "./prompt-BW9jFsqZ.js";
|
|
12
|
+
import { i as sanitizeAssistantText, t as getInterruptionMarker } from "./interruption-marker-CkncDT8d.js";
|
|
13
|
+
import { r as appendAndEnqueueInboundMessage } from "./store-zPEoJPBB.js";
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
import { eq, inArray } from "drizzle-orm";
|
|
16
|
-
import {
|
|
16
|
+
import { eventMatchFieldsSchema, objectFactFields, objectPresentation, objectTypeSchema } from "@sentry/junior-plugin-api";
|
|
17
17
|
// src/chat/automation-result.ts
|
|
18
18
|
/**
|
|
19
19
|
* Automation run result.
|
|
@@ -48,6 +48,17 @@ function deliversFinalText(dispatch) {
|
|
|
48
48
|
return dispatch?.outcomes?.length !== 0 && !dispatch?.declaresResult;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
+
* Return whether the runtime may post a failure reply or a status notice,
|
|
52
|
+
* such as an auth prompt, for a run outside `finishedRunReply`. The rule is
|
|
53
|
+
* the same as for final text. A chat Turn may post, also in a thread that an
|
|
54
|
+
* Automation Conversation owns. A dispatch with no outcomes posts nothing. An
|
|
55
|
+
* Automation run posts only a declared message; its failed execution shows on
|
|
56
|
+
* the Automation.
|
|
57
|
+
*/
|
|
58
|
+
function postsRunNotices(dispatch) {
|
|
59
|
+
return deliversFinalText(dispatch);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
51
62
|
* Return the text to post after a run ends, if any. A chat Turn already
|
|
52
63
|
* delivered a successful reply, so it posts only its failure reply. An
|
|
53
64
|
* Automation run posts only a declared message. It never posts a failure
|
|
@@ -271,6 +282,467 @@ async function recordFinishedTurnForAutomatedLimit(args) {
|
|
|
271
282
|
state: args.state
|
|
272
283
|
});
|
|
273
284
|
}
|
|
285
|
+
// src/chat/slack/automation-card.ts
|
|
286
|
+
function preview(text, length) {
|
|
287
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
288
|
+
return compact.length > length ? `${compact.slice(0, length - 1)}…` : compact;
|
|
289
|
+
}
|
|
290
|
+
/** Show an Automation as a native Work Object, not an operation receipt. */
|
|
291
|
+
function renderSlackAutomationCard(card) {
|
|
292
|
+
const title = preview(card.title, 160);
|
|
293
|
+
const iconUrl = getDashboardObjectIconLink("workflow");
|
|
294
|
+
const trigger = preview(card.trigger, 500);
|
|
295
|
+
const warning = card.warning ? preview(card.warning, 500) : null;
|
|
296
|
+
const text = [
|
|
297
|
+
"Automation",
|
|
298
|
+
card.url ? formatSlackLink(card.url, title) : escapeSlackMrkdwnText(title),
|
|
299
|
+
escapeSlackMrkdwnText(trigger),
|
|
300
|
+
warning ? escapeSlackMrkdwnText(warning) : null
|
|
301
|
+
].filter(Boolean).join("\n");
|
|
302
|
+
if (!card.url) return {
|
|
303
|
+
entity: null,
|
|
304
|
+
text
|
|
305
|
+
};
|
|
306
|
+
return {
|
|
307
|
+
text,
|
|
308
|
+
entity: {
|
|
309
|
+
entity_type: "slack#/entities/item",
|
|
310
|
+
external_ref: {
|
|
311
|
+
id: card.id,
|
|
312
|
+
type: "automation"
|
|
313
|
+
},
|
|
314
|
+
url: card.url,
|
|
315
|
+
entity_payload: {
|
|
316
|
+
attributes: {
|
|
317
|
+
title: { text: title },
|
|
318
|
+
display_type: "Automation",
|
|
319
|
+
product_icon: iconUrl ? {
|
|
320
|
+
url: iconUrl,
|
|
321
|
+
alt_text: "Automation"
|
|
322
|
+
} : void 0
|
|
323
|
+
},
|
|
324
|
+
custom_fields: [{
|
|
325
|
+
key: "trigger",
|
|
326
|
+
label: "When",
|
|
327
|
+
type: "string",
|
|
328
|
+
value: trigger,
|
|
329
|
+
long: true
|
|
330
|
+
}, ...warning ? [{
|
|
331
|
+
key: "warning",
|
|
332
|
+
label: "Needs attention",
|
|
333
|
+
type: "string",
|
|
334
|
+
value: warning,
|
|
335
|
+
long: true
|
|
336
|
+
}] : []]
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
// src/chat/slack/object-card.ts
|
|
342
|
+
function descriptionPreview(value) {
|
|
343
|
+
const text = value?.replace(/\r\n/g, "\n").trim() ?? "";
|
|
344
|
+
const preview = text.split("\n").slice(0, 6).join("\n").slice(0, 500);
|
|
345
|
+
return preview.length < text.length ? `${preview.slice(0, 499).trimEnd()}…` : preview;
|
|
346
|
+
}
|
|
347
|
+
/** Render a compact Task or Item preview, or the saved facts for its detail panel. */
|
|
348
|
+
function renderSlackObjectCard(card, conversationId, surface = "preview") {
|
|
349
|
+
if (card.objectType === "automation" && card.plugin === "junior") return renderSlackAutomationCard({
|
|
350
|
+
id: card.key,
|
|
351
|
+
title: card.title,
|
|
352
|
+
url: card.url,
|
|
353
|
+
trigger: card.trigger ?? "",
|
|
354
|
+
warning: card.warning ?? null
|
|
355
|
+
});
|
|
356
|
+
const presentation = objectPresentation(card);
|
|
357
|
+
const type = card.displayType ?? presentation.label;
|
|
358
|
+
const iconUrl = getDashboardObjectIconLink(presentation.icon);
|
|
359
|
+
const title = card.title.slice(0, 160);
|
|
360
|
+
const details = surface === "details";
|
|
361
|
+
const deployment = card.objectType === "deployment" || card.objectType === "item" && card.facts?.type === "deployment";
|
|
362
|
+
const calendarEvent = card.objectType === "calendar_event";
|
|
363
|
+
const status = details || deployment || calendarEvent ? card.status : void 0;
|
|
364
|
+
const warning = details || deployment ? card.warning : void 0;
|
|
365
|
+
const facts = objectFactFields(card.facts).filter((field) => details || calendarEvent || deployment && field.key === "environment");
|
|
366
|
+
const description = details ? card.description?.trim() ?? "" : calendarEvent ? "" : descriptionPreview(card.description);
|
|
367
|
+
const text = [
|
|
368
|
+
escapeSlackMrkdwnText(type),
|
|
369
|
+
card.url ? formatSlackLink(card.url, title) : escapeSlackMrkdwnText(title),
|
|
370
|
+
escapeSlackMrkdwnText(card.label),
|
|
371
|
+
status ? escapeSlackMrkdwnText(status) : null,
|
|
372
|
+
description ? escapeSlackMrkdwnText(description) : null,
|
|
373
|
+
warning ? escapeSlackMrkdwnText(`Needs attention: ${warning}`) : null,
|
|
374
|
+
...facts.map((field) => escapeSlackMrkdwnText(`${field.label}: ${field.value}`))
|
|
375
|
+
].filter(Boolean).join("\n");
|
|
376
|
+
if (!card.url) return {
|
|
377
|
+
entity: null,
|
|
378
|
+
text
|
|
379
|
+
};
|
|
380
|
+
const task = card.objectType === "task";
|
|
381
|
+
const attributes = {
|
|
382
|
+
title: { text: title },
|
|
383
|
+
display_id: card.label,
|
|
384
|
+
display_type: type,
|
|
385
|
+
product_icon: iconUrl ? {
|
|
386
|
+
url: iconUrl,
|
|
387
|
+
alt_text: `${type}${status ? `: ${status}` : ""}`
|
|
388
|
+
} : void 0,
|
|
389
|
+
product_name: card.plugin
|
|
390
|
+
};
|
|
391
|
+
const customFields = [
|
|
392
|
+
...description ? [{
|
|
393
|
+
key: "description",
|
|
394
|
+
label: "Description",
|
|
395
|
+
type: "string",
|
|
396
|
+
value: description,
|
|
397
|
+
long: true,
|
|
398
|
+
format: "markdown"
|
|
399
|
+
}] : [],
|
|
400
|
+
...facts.map((field) => ({
|
|
401
|
+
...field,
|
|
402
|
+
type: "string"
|
|
403
|
+
})),
|
|
404
|
+
...warning ? [{
|
|
405
|
+
key: "warning",
|
|
406
|
+
label: "Needs attention",
|
|
407
|
+
type: "string",
|
|
408
|
+
value: warning
|
|
409
|
+
}] : []
|
|
410
|
+
];
|
|
411
|
+
return {
|
|
412
|
+
text,
|
|
413
|
+
entity: {
|
|
414
|
+
...task ? {
|
|
415
|
+
entity_type: "slack#/entities/task",
|
|
416
|
+
entity_payload: {
|
|
417
|
+
attributes,
|
|
418
|
+
display_order: [...status ? ["status"] : [], ...customFields.map((field) => field.key)],
|
|
419
|
+
fields: status ? { status: { value: status } } : void 0,
|
|
420
|
+
custom_fields: customFields
|
|
421
|
+
}
|
|
422
|
+
} : {
|
|
423
|
+
entity_type: "slack#/entities/item",
|
|
424
|
+
entity_payload: {
|
|
425
|
+
attributes,
|
|
426
|
+
display_order: [...status ? ["status"] : [], ...customFields.map((field) => field.key)],
|
|
427
|
+
custom_fields: [...status ? [{
|
|
428
|
+
key: "status",
|
|
429
|
+
label: "Status",
|
|
430
|
+
type: "string",
|
|
431
|
+
value: status
|
|
432
|
+
}] : [], ...customFields]
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
external_ref: {
|
|
436
|
+
id: Buffer.from(JSON.stringify([
|
|
437
|
+
conversationId,
|
|
438
|
+
card.plugin,
|
|
439
|
+
card.key
|
|
440
|
+
]), "utf8").toString("base64url"),
|
|
441
|
+
type: "annotation"
|
|
442
|
+
},
|
|
443
|
+
url: card.url
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
// src/chat/slack/cards.ts
|
|
448
|
+
/** Select the Slack object renderer for a built-in card. */
|
|
449
|
+
function renderSlackCard(card, conversationId) {
|
|
450
|
+
switch (card.kind) {
|
|
451
|
+
case "object": return renderSlackObjectCard(card, conversationId);
|
|
452
|
+
case "automation": return renderSlackAutomationCard(card);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// src/chat/slack/footer.ts
|
|
456
|
+
/** Render compact reply attribution for the Slack footer. */
|
|
457
|
+
function formatReplyAttribution(attribution) {
|
|
458
|
+
return attribution.detail ? `${attribution.label} · ${attribution.detail}` : attribution.label;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Build the compact conversation footer for visible Slack reply surfaces.
|
|
462
|
+
*
|
|
463
|
+
* Detailed turn metrics stay in the dashboard instead of Slack-visible copy.
|
|
464
|
+
*/
|
|
465
|
+
function buildSlackReplyFooter(args) {
|
|
466
|
+
const items = [];
|
|
467
|
+
const conversationId = args.conversationId?.trim();
|
|
468
|
+
if (conversationId) {
|
|
469
|
+
const idItem = {
|
|
470
|
+
label: "ID",
|
|
471
|
+
value: conversationId
|
|
472
|
+
};
|
|
473
|
+
const conversationUrl = getPluginSlackConversationLink(conversationId)?.url ?? getDashboardConversationLink(conversationId) ?? buildSentryConversationUrl(conversationId);
|
|
474
|
+
if (conversationUrl) idItem.url = conversationUrl;
|
|
475
|
+
items.push(idItem);
|
|
476
|
+
}
|
|
477
|
+
return items.length > 0 || args.replyAttribution ? {
|
|
478
|
+
...args.replyAttribution ? { attribution: args.replyAttribution } : void 0,
|
|
479
|
+
items
|
|
480
|
+
} : void 0;
|
|
481
|
+
}
|
|
482
|
+
/** Build Slack blocks for a reply chunk using the Slack-flavored markdown block for the body. */
|
|
483
|
+
function buildSlackReplyBlocks(text, footer) {
|
|
484
|
+
const blocks = text.trim() ? [{
|
|
485
|
+
type: "markdown",
|
|
486
|
+
text
|
|
487
|
+
}] : [];
|
|
488
|
+
if (footer && (footer.attribution || footer.items.length > 0)) {
|
|
489
|
+
const attributionElements = footer.attribution ? [{
|
|
490
|
+
type: "plain_text",
|
|
491
|
+
text: formatReplyAttribution(footer.attribution)
|
|
492
|
+
}] : [];
|
|
493
|
+
blocks.push({
|
|
494
|
+
type: "context",
|
|
495
|
+
elements: [...attributionElements, ...footer.items.map((item) => ({
|
|
496
|
+
type: "mrkdwn",
|
|
497
|
+
text: item.url ? `*${escapeSlackMrkdwnText(item.label)}:* ${formatSlackLink(item.url, item.value)}` : `*${escapeSlackMrkdwnText(item.label)}:* ${escapeSlackMrkdwnText(item.value)}`
|
|
498
|
+
}))]
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return blocks.length ? blocks : void 0;
|
|
502
|
+
}
|
|
503
|
+
// src/chat/slack/output.ts
|
|
504
|
+
const MAX_INLINE_CHARS = 2200;
|
|
505
|
+
const MAX_INLINE_LINES = 45;
|
|
506
|
+
const CONTINUED_MARKER = "\n\n[Continued below]";
|
|
507
|
+
function countSlackLines(text) {
|
|
508
|
+
if (!text) return 0;
|
|
509
|
+
return text.split("\n").length;
|
|
510
|
+
}
|
|
511
|
+
function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
512
|
+
return text.length <= maxChars && countSlackLines(text) <= maxLines;
|
|
513
|
+
}
|
|
514
|
+
function findSplitIndex(text, maxChars) {
|
|
515
|
+
if (text.length <= maxChars) return text.length;
|
|
516
|
+
const bounded = text.slice(0, maxChars);
|
|
517
|
+
const newlineIndex = bounded.lastIndexOf("\n");
|
|
518
|
+
if (newlineIndex > 0) return newlineIndex;
|
|
519
|
+
const spaceIndex = bounded.lastIndexOf(" ");
|
|
520
|
+
if (spaceIndex > 0) return spaceIndex;
|
|
521
|
+
return maxChars;
|
|
522
|
+
}
|
|
523
|
+
function splitByLineBudget(text, maxLines) {
|
|
524
|
+
if (maxLines <= 0) return "";
|
|
525
|
+
const lines = text.split("\n");
|
|
526
|
+
if (lines.length <= maxLines) return text;
|
|
527
|
+
return lines.slice(0, maxLines).join("\n");
|
|
528
|
+
}
|
|
529
|
+
function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
530
|
+
return {
|
|
531
|
+
maxChars: Math.max(1, maxChars - suffix.length),
|
|
532
|
+
maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function forceSplitBudget(text, budget) {
|
|
536
|
+
const lineCount = countSlackLines(text);
|
|
537
|
+
return {
|
|
538
|
+
maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
|
|
539
|
+
maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function getFenceContinuation(text) {
|
|
543
|
+
let open;
|
|
544
|
+
for (const line of text.split("\n")) {
|
|
545
|
+
const trimmed = line.trimStart();
|
|
546
|
+
const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
|
|
547
|
+
if (!openerMatch?.[1]) continue;
|
|
548
|
+
if (!open) {
|
|
549
|
+
open = {
|
|
550
|
+
fence: openerMatch[1],
|
|
551
|
+
openerLine: trimmed
|
|
552
|
+
};
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) open = void 0;
|
|
556
|
+
}
|
|
557
|
+
if (!open) return null;
|
|
558
|
+
return {
|
|
559
|
+
closeSuffix: text.endsWith("\n") ? open.fence : `\n${open.fence}`,
|
|
560
|
+
reopenPrefix: `${open.openerLine}\n`
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function appendSlackSuffix(text, marker) {
|
|
564
|
+
return `${text}${getFenceContinuation(text)?.closeSuffix ?? ""}${marker}`;
|
|
565
|
+
}
|
|
566
|
+
function stripTrailingContinuationMarker(text) {
|
|
567
|
+
return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -19) : text;
|
|
568
|
+
}
|
|
569
|
+
function takeSlackContinuationChunk(text, budget) {
|
|
570
|
+
let { prefix, rest } = takeSlackInlinePrefix(text, budget);
|
|
571
|
+
if (!rest) ({prefix, rest} = takeSlackInlinePrefix(text, forceSplitBudget(text, budget)));
|
|
572
|
+
let carryover = rest ? getFenceContinuation(prefix) : null;
|
|
573
|
+
if (!carryover) return {
|
|
574
|
+
prefix,
|
|
575
|
+
rest
|
|
576
|
+
};
|
|
577
|
+
const carryoverBudget = reserveInlineBudgetForSuffix(`${carryover.closeSuffix}${CONTINUED_MARKER}`);
|
|
578
|
+
({prefix, rest} = takeSlackInlinePrefix(text, carryoverBudget));
|
|
579
|
+
if (!rest) ({prefix, rest} = takeSlackInlinePrefix(text, forceSplitBudget(text, carryoverBudget)));
|
|
580
|
+
carryover = rest ? getFenceContinuation(prefix) : null;
|
|
581
|
+
if (!carryover) return {
|
|
582
|
+
prefix,
|
|
583
|
+
rest
|
|
584
|
+
};
|
|
585
|
+
return {
|
|
586
|
+
prefix,
|
|
587
|
+
rest: `${carryover.reopenPrefix}${rest}`
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/** Return the first continuation-safe chunk plus any remaining text. */
|
|
591
|
+
function takeSlackContinuationPrefix(text, options) {
|
|
592
|
+
const budget = {
|
|
593
|
+
maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
|
|
594
|
+
maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
|
|
595
|
+
};
|
|
596
|
+
const { prefix, rest } = (() => {
|
|
597
|
+
if (options?.forceSplit) return takeSlackContinuationChunk(text, budget);
|
|
598
|
+
const initial = takeSlackInlinePrefix(text, budget);
|
|
599
|
+
return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
|
|
600
|
+
})();
|
|
601
|
+
return {
|
|
602
|
+
prefix,
|
|
603
|
+
renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
|
|
604
|
+
rest
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Take the largest Slack-safe inline prefix from `text` under the configured
|
|
609
|
+
* character and line budgets. Returns the consumed prefix plus remaining text.
|
|
610
|
+
*/
|
|
611
|
+
function takeSlackInlinePrefix(text, options) {
|
|
612
|
+
const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
|
|
613
|
+
const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
|
|
614
|
+
const normalized = text.replace(/\r\n?/g, "\n");
|
|
615
|
+
if (!normalized) return {
|
|
616
|
+
prefix: "",
|
|
617
|
+
rest: ""
|
|
618
|
+
};
|
|
619
|
+
if (fitsInlineBudget(normalized, maxChars, maxLines)) return {
|
|
620
|
+
prefix: normalized,
|
|
621
|
+
rest: ""
|
|
622
|
+
};
|
|
623
|
+
const lineBounded = splitByLineBudget(normalized, maxLines);
|
|
624
|
+
const cutIndex = findSplitIndex(lineBounded, maxChars);
|
|
625
|
+
const prefix = lineBounded.slice(0, cutIndex).trimEnd();
|
|
626
|
+
if (prefix) return {
|
|
627
|
+
prefix,
|
|
628
|
+
rest: normalized.slice(prefix.length).trimStart()
|
|
629
|
+
};
|
|
630
|
+
const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
|
|
631
|
+
return {
|
|
632
|
+
prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
|
|
633
|
+
rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Split a normalized Slack reply into multiple inline-safe thread messages.
|
|
638
|
+
*
|
|
639
|
+
* Replies split across more than two messages receive explicit continuation
|
|
640
|
+
* markers on non-final chunks. When
|
|
641
|
+
* `interrupted` is true, the final chunk receives an interruption marker.
|
|
642
|
+
*/
|
|
643
|
+
function splitSlackReplyText(text, options) {
|
|
644
|
+
const normalized = normalizeSlackReplyMarkdown(text);
|
|
645
|
+
if (!normalized) return [];
|
|
646
|
+
const chunks = [];
|
|
647
|
+
const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
648
|
+
let remaining = normalized;
|
|
649
|
+
while (remaining) {
|
|
650
|
+
if (options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining)) {
|
|
651
|
+
chunks.push(options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining);
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
|
|
655
|
+
...continuationBudget,
|
|
656
|
+
forceSplit: true
|
|
657
|
+
});
|
|
658
|
+
chunks.push(renderedPrefix);
|
|
659
|
+
remaining = rest;
|
|
660
|
+
}
|
|
661
|
+
if (chunks.length === 2) chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
|
|
662
|
+
return chunks;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Reserve enough inline budget for a continuation suffix on the current chunk.
|
|
666
|
+
*/
|
|
667
|
+
function getSlackContinuationBudget() {
|
|
668
|
+
return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
669
|
+
}
|
|
670
|
+
// src/chat/slack/reply.ts
|
|
671
|
+
/**
|
|
672
|
+
* Resolve where a Turn posts in Slack.
|
|
673
|
+
*
|
|
674
|
+
* The channel is always the Destination channel. The thread comes from the
|
|
675
|
+
* Turn Source, then the Conversation Location, then the Destination, and each
|
|
676
|
+
* one counts only when it names that channel. Do not derive the target from
|
|
677
|
+
* the Conversation id: an Automation or Watch Conversation can own a Slack
|
|
678
|
+
* thread, and its id is not a Slack thread id.
|
|
679
|
+
*/
|
|
680
|
+
function resolveSlackReplyTarget(args) {
|
|
681
|
+
const { channelId } = args.destination;
|
|
682
|
+
const sourceThreadTs = args.source?.kind === "slack" && args.source.channelId === channelId ? args.source.threadTs : void 0;
|
|
683
|
+
const locationThreadTs = args.location?.provider === "slack" && args.location.channelId === channelId ? args.location.threadTs : void 0;
|
|
684
|
+
const threadTs = sourceThreadTs ?? locationThreadTs ?? args.destination.threadTs;
|
|
685
|
+
return {
|
|
686
|
+
channelId,
|
|
687
|
+
...threadTs ? { threadTs } : void 0
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Send one destination-visible Slack reply.
|
|
692
|
+
*
|
|
693
|
+
* Chunks oversized text, builds compact attribution and conversation footer
|
|
694
|
+
* context, and posts through the shared Slack outbound boundary.
|
|
695
|
+
*/
|
|
696
|
+
async function sendSlackReply(args) {
|
|
697
|
+
const posts = splitSlackReplyText(args.text).map((text) => ({
|
|
698
|
+
text,
|
|
699
|
+
cards: []
|
|
700
|
+
}));
|
|
701
|
+
const cards = (args.cards ?? []).map((card) => renderSlackCard(card, args.conversationId));
|
|
702
|
+
for (let index = 0; index < cards.length; index += 5) {
|
|
703
|
+
const group = cards.slice(index, index + 5);
|
|
704
|
+
const lastPost = posts.at(-1);
|
|
705
|
+
if (index === 0 && lastPost) lastPost.cards = group;
|
|
706
|
+
else posts.push({
|
|
707
|
+
text: "",
|
|
708
|
+
cards: group
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
const footer = buildSlackReplyFooter({
|
|
712
|
+
conversationId: args.conversationId,
|
|
713
|
+
replyAttribution: args.replyAttribution
|
|
714
|
+
});
|
|
715
|
+
const messageTs = [];
|
|
716
|
+
let threadTs = args.threadTs;
|
|
717
|
+
for (const [index, post] of posts.entries()) {
|
|
718
|
+
const { text, cards } = post;
|
|
719
|
+
const isFinalChunk = index === posts.length - 1;
|
|
720
|
+
const blocks = buildSlackReplyBlocks(text, void 0) ?? [];
|
|
721
|
+
for (const card of cards) if (!card.entity) blocks.push({
|
|
722
|
+
type: "section",
|
|
723
|
+
text: {
|
|
724
|
+
type: "mrkdwn",
|
|
725
|
+
text: card.text
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
if (isFinalChunk || !text.trim()) blocks.push(...buildSlackReplyBlocks("", footer) ?? []);
|
|
729
|
+
const entities = cards.flatMap((card) => card.entity ? [card.entity] : []);
|
|
730
|
+
const accessibleText = [text, ...cards.map((card) => card.text)].filter(Boolean).join("\n\n");
|
|
731
|
+
const fallbackText = isFinalChunk && args.replyAttribution ? `${accessibleText}\n\n${escapeSlackMrkdwnText(formatReplyAttribution(args.replyAttribution))}` : accessibleText;
|
|
732
|
+
const response = await postSlackMessage({
|
|
733
|
+
channelId: args.channelId,
|
|
734
|
+
threadTs,
|
|
735
|
+
text: fallbackText,
|
|
736
|
+
...blocks.length ? { blocks } : void 0,
|
|
737
|
+
...entities.length ? { entities } : void 0
|
|
738
|
+
});
|
|
739
|
+
if (response.ts) {
|
|
740
|
+
messageTs.push(response.ts);
|
|
741
|
+
threadTs ??= response.ts;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return messageTs;
|
|
745
|
+
}
|
|
274
746
|
// src/chat/slack/automated-turn-limit-notice.ts
|
|
275
747
|
/**
|
|
276
748
|
* Pause notice for the consecutive automated-turn limit.
|
|
@@ -301,10 +773,14 @@ async function postAutomatedTurnLimitNoticeForConversation(args) {
|
|
|
301
773
|
});
|
|
302
774
|
return false;
|
|
303
775
|
}
|
|
304
|
-
await
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
776
|
+
await sendSlackReply({
|
|
777
|
+
...resolveSlackReplyTarget({
|
|
778
|
+
destination: location,
|
|
779
|
+
location,
|
|
780
|
+
source: args.source
|
|
781
|
+
}),
|
|
782
|
+
conversationId: args.conversationId,
|
|
783
|
+
text: buildAutomatedTurnLimitResponse({ maxTurns: args.maxTurns })
|
|
308
784
|
});
|
|
309
785
|
return true;
|
|
310
786
|
} catch (error) {
|
|
@@ -323,7 +799,7 @@ async function maybePostAutomatedTurnLimitNotice(args) {
|
|
|
323
799
|
conversationId: args.conversationId,
|
|
324
800
|
maxTurns: args.maxTurns,
|
|
325
801
|
nowMs: args.nowMs,
|
|
326
|
-
|
|
802
|
+
source: args.source
|
|
327
803
|
});
|
|
328
804
|
}
|
|
329
805
|
// src/chat/task-input.ts
|
|
@@ -373,7 +849,7 @@ function renderTaskInput(args) {
|
|
|
373
849
|
"This is a task, not a message from a person.",
|
|
374
850
|
"",
|
|
375
851
|
...about ? [`About: ${oneLine(about)}`] : [],
|
|
376
|
-
...creator ? [`Created by: ${oneLine(creator)}. "
|
|
852
|
+
...creator ? [`Created by: ${oneLine(creator)}. This person is the Automation creator. "The Automation creator", "me", and "my" in the instructions mean this person. Where the instructions refer to this person, write this mention in the message. If they do not, do not mention this person.`] : [],
|
|
377
853
|
...people.length > 0 ? [`People involved: ${people.join(", ")}`] : [],
|
|
378
854
|
...args.doneBy ? [doneByLine(args.doneBy, people)] : [],
|
|
379
855
|
`Instructions: ${instructions}`
|
|
@@ -439,106 +915,6 @@ function renderTaskBatchInput(args) {
|
|
|
439
915
|
function joinMailboxText(messages) {
|
|
440
916
|
return messages.map((message) => message.input.text.trim()).filter(Boolean).join("\n\n");
|
|
441
917
|
}
|
|
442
|
-
// src/chat/events/catalog.ts
|
|
443
|
-
/** Core-owned event namespace. Not a plugin publisher. */
|
|
444
|
-
const CORE_EVENT_NAMESPACE = "junior";
|
|
445
|
-
/** True when the catalog has any enabled registration. */
|
|
446
|
-
function hasEventCatalogEntries(catalog) {
|
|
447
|
-
return Object.keys(catalog).length > 0;
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Catalog entries that durable event automations may select.
|
|
451
|
-
*
|
|
452
|
-
* Resource types with `automation: false` stay out of create/update schemas
|
|
453
|
-
* and trigger availability. A namespace with no such resource type left is
|
|
454
|
-
* removed.
|
|
455
|
-
*/
|
|
456
|
-
function automationEventCatalog(catalog) {
|
|
457
|
-
return Object.fromEntries(Object.entries(catalog).flatMap(([namespace, registration]) => {
|
|
458
|
-
const resourceTypes = registration.resourceTypes.filter((resourceType) => registration.limits?.[resourceType.type]?.automation !== false);
|
|
459
|
-
return resourceTypes.length > 0 ? [[namespace, {
|
|
460
|
-
...registration,
|
|
461
|
-
resourceTypes
|
|
462
|
-
}]] : [];
|
|
463
|
-
}));
|
|
464
|
-
}
|
|
465
|
-
/** Return why watchEvents cannot watch one resource type, if it cannot. */
|
|
466
|
-
function eventWatchUnavailable(catalog, namespace, resourceType) {
|
|
467
|
-
const limits = catalog[namespace]?.limits?.[resourceType];
|
|
468
|
-
return limits?.watch === false ? limits.watchUnavailableMessage : void 0;
|
|
469
|
-
}
|
|
470
|
-
function enumSchema(values, unavailableMessage) {
|
|
471
|
-
if (values.length === 0) return z.string().refine(() => false, unavailableMessage);
|
|
472
|
-
return z.enum(values);
|
|
473
|
-
}
|
|
474
|
-
/** Build the model-facing namespace enum from enabled event plugins. */
|
|
475
|
-
function eventNamespaceSchema(catalog) {
|
|
476
|
-
return enumSchema(Object.keys(catalog).sort(), "No event namespaces are enabled.");
|
|
477
|
-
}
|
|
478
|
-
/** Build the model-facing event enum from enabled plugin registrations. */
|
|
479
|
-
function registeredEventTypeSchema(catalog) {
|
|
480
|
-
return enumSchema([...new Set(Object.values(catalog).flatMap((registration) => registration.resourceTypes.flatMap((resourceType) => resourceType.supportedEvents)))].sort(), "No event types are enabled.");
|
|
481
|
-
}
|
|
482
|
-
/** Build the model-facing resource type enum from enabled plugin registrations. */
|
|
483
|
-
function registeredResourceTypeSchema(catalog) {
|
|
484
|
-
return enumSchema([...new Set(Object.values(catalog).flatMap((registration) => registration.resourceTypes.map((resourceType) => resourceType.type)))].sort(), "No resource types are enabled.");
|
|
485
|
-
}
|
|
486
|
-
/** Return app guidance for one registered resource type and event type. */
|
|
487
|
-
function eventGuidance(catalog, namespace, resourceType, eventType) {
|
|
488
|
-
return catalog[namespace]?.resourceTypes.find((candidate) => candidate.type === resourceType)?.guidance?.[eventType];
|
|
489
|
-
}
|
|
490
|
-
/** Build the match object the tools accept for one resource type. */
|
|
491
|
-
function registeredEventMatchSchema() {
|
|
492
|
-
return eventMatchSchema.optional().describe("Optional filters: exact values for the resource type matchFields. A single value must equal the event data. A list means the event value may be any one entry. For a field with list: true, the event value is a list, and it matches when it includes the value or any entry. A field with events is set only on those events, so every selected event must be in that list. Omit a key to ignore it. Unmatched events are dropped with no agent turn.");
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* List why a selection cannot work with the enabled plugins. Use it for new
|
|
496
|
-
* selections and for saved triggers, which can go stale after a plugin renames
|
|
497
|
-
* or removes a resource type, event, or filter.
|
|
498
|
-
*/
|
|
499
|
-
function registeredEventTriggerProblems(catalog, selection, options) {
|
|
500
|
-
return eventTriggerProblems(catalog[selection.namespace]?.resourceTypes ?? [], selection, options);
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Describe a selection with the enabled plugins, for the dashboard and Slack.
|
|
504
|
-
* Its problems are the same as `registeredEventTriggerProblems`.
|
|
505
|
-
*/
|
|
506
|
-
function describeRegisteredEventTrigger(catalog, selection) {
|
|
507
|
-
return describeEventTrigger(catalog[selection.namespace]?.resourceTypes ?? [], selection);
|
|
508
|
-
}
|
|
509
|
-
function uniqueMatchValues(match) {
|
|
510
|
-
if (!match || Object.keys(match).length === 0) return void 0;
|
|
511
|
-
return Object.fromEntries(Object.entries(match).map(([key, value]) => [key, Array.isArray(value) ? [...new Set(value)] : value]));
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* Reject an event automation trigger or a Watch that cannot work, with every
|
|
515
|
-
* problem in one message for the agent. Return its match without repeated
|
|
516
|
-
* list values.
|
|
517
|
-
*/
|
|
518
|
-
function requireValidEventTrigger(catalog, selection, target) {
|
|
519
|
-
const problems = registeredEventTriggerProblems(catalog, selection, { inputKeys: true });
|
|
520
|
-
if (problems.length > 0) {
|
|
521
|
-
const resource = `${catalog[selection.namespace]?.displayName ?? selection.namespace} ${selection.resourceType.replaceAll("_", " ")}`;
|
|
522
|
-
throw new ToolInputError([target === "watch" ? `This ${resource} watch cannot be created.` : `This ${resource} trigger cannot be saved.`, ...problems.map((problem) => problem.message)].join(" "));
|
|
523
|
-
}
|
|
524
|
-
return uniqueMatchValues(selection.match);
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* Normalize one selector with the convention declared by its plugin.
|
|
528
|
-
*
|
|
529
|
-
* Reject a selector that does not fit its resource type. Resource types can
|
|
530
|
-
* share event types, so only the identifier shows which one a selector names.
|
|
531
|
-
*/
|
|
532
|
-
function requireEventIdentifier(catalog, input) {
|
|
533
|
-
const registration = catalog[input.namespace];
|
|
534
|
-
const identifier = normalizeEventIdentifier(registration, input.identifier);
|
|
535
|
-
const resourceTypes = registration?.resourceTypes ?? [];
|
|
536
|
-
const fits = (resourceType) => resourceType.identifier !== void 0 && new RegExp(resourceType.identifier.pattern).test(identifier);
|
|
537
|
-
const resourceType = resourceTypes.find((candidate) => candidate.type === input.resourceType);
|
|
538
|
-
if (!resourceType?.identifier || fits(resourceType)) return identifier;
|
|
539
|
-
const fitting = resourceTypes.filter(fits).map((candidate) => candidate.type);
|
|
540
|
-
throw new ToolInputError([`Identifier "${identifier}" does not fit resourceType "${input.resourceType}" in namespace "${input.namespace}". Expected format: "${resourceType.identifier.format}".`, ...fitting.length > 0 ? [`If the identifier is correct, use one of these resourceType values: ${fitting.map((type) => `"${type}"`).join(", ")}.`] : []].join(" "));
|
|
541
|
-
}
|
|
542
918
|
// src/chat/sandbox/snapshot/events.ts
|
|
543
919
|
const WORKSPACE_SNAPSHOT_NAMESPACE = CORE_EVENT_NAMESPACE;
|
|
544
920
|
const WORKSPACE_SNAPSHOT_RESOURCE_TYPE = "workspace_snapshot";
|
|
@@ -556,12 +932,18 @@ function workspaceSnapshotEvents() {
|
|
|
556
932
|
resourceTypes: [{
|
|
557
933
|
type: WORKSPACE_SNAPSHOT_RESOURCE_TYPE,
|
|
558
934
|
supportedEvents: [WORKSPACE_SNAPSHOT_READY_EVENT, WORKSPACE_SNAPSHOT_FAILED_EVENT],
|
|
559
|
-
suggestedEvents: []
|
|
560
|
-
guidance: {
|
|
561
|
-
[WORKSPACE_SNAPSHOT_READY_EVENT]: "Call switchWorkspace again with the same Workspace name. Do not create another watch when switchWorkspace already returned a subscription.",
|
|
562
|
-
[WORKSPACE_SNAPSHOT_FAILED_EVENT]: "Report the snapshot failure. Do not keep waiting for this Workspace unless the user asks to retry."
|
|
563
|
-
}
|
|
935
|
+
suggestedEvents: []
|
|
564
936
|
}],
|
|
937
|
+
events: {
|
|
938
|
+
[WORKSPACE_SNAPSHOT_READY_EVENT]: {
|
|
939
|
+
reach: "resource",
|
|
940
|
+
guidance: "Call switchWorkspace again with the same Workspace name. Do not create another watch when switchWorkspace already returned a subscription."
|
|
941
|
+
},
|
|
942
|
+
[WORKSPACE_SNAPSHOT_FAILED_EVENT]: {
|
|
943
|
+
reach: "resource",
|
|
944
|
+
guidance: "Report the snapshot failure. Do not keep waiting for this Workspace unless the user asks to retry."
|
|
945
|
+
}
|
|
946
|
+
},
|
|
565
947
|
limits: { [WORKSPACE_SNAPSHOT_RESOURCE_TYPE]: { automation: false } }
|
|
566
948
|
};
|
|
567
949
|
}
|
|
@@ -630,9 +1012,12 @@ function slackChannelMessageEvents() {
|
|
|
630
1012
|
},
|
|
631
1013
|
supportedEvents: [MESSAGE_POSTED_EVENT],
|
|
632
1014
|
suggestedEvents: [],
|
|
633
|
-
matchFields: CHANNEL_MESSAGE_MATCH_FIELDS
|
|
634
|
-
guidance: { [MESSAGE_POSTED_EVENT]: "A person or bot posted a new top-level message in this channel. A bot post runs the automation only when match names the bot in userId. A reply goes in that message's thread. If the instructions do not apply to this message, end with no action." }
|
|
1015
|
+
matchFields: CHANNEL_MESSAGE_MATCH_FIELDS
|
|
635
1016
|
}],
|
|
1017
|
+
events: { [MESSAGE_POSTED_EVENT]: {
|
|
1018
|
+
reach: "resource",
|
|
1019
|
+
guidance: "A person or bot posted a new top-level message in this channel. A bot post runs the automation only when match names the bot in userId. A reply goes in that message's thread. If the instructions do not apply to this message, end with no action."
|
|
1020
|
+
} },
|
|
636
1021
|
limits: { [CHANNEL_RESOURCE_TYPE]: {
|
|
637
1022
|
watch: false,
|
|
638
1023
|
watchUnavailableMessage: "Slack channel messages can trigger only an event automation in that channel. Use createEventAutomation."
|
|
@@ -689,12 +1074,16 @@ function slackChannelMessageEvent(input) {
|
|
|
689
1074
|
/**
|
|
690
1075
|
* Decide whether a channel-message Event may run one automation.
|
|
691
1076
|
*
|
|
1077
|
+
* A message runs only automations that post in its own channel, so private
|
|
1078
|
+
* channel text cannot reach another channel. The trigger identifier already
|
|
1079
|
+
* names that channel, and this check keeps the rule when it does not.
|
|
692
1080
|
* A bot post runs only automations whose match names its author in `userId`.
|
|
693
1081
|
* Bots can post often and would use up the run limit of every other
|
|
694
1082
|
* automation in the channel.
|
|
695
1083
|
*/
|
|
696
1084
|
function slackChannelMessageRunsAutomation(event, automation) {
|
|
697
1085
|
if (event.namespace !== "slack") return true;
|
|
1086
|
+
if (event.data?.channelId !== automation.destination.channelId) return false;
|
|
698
1087
|
if (event.data?.postedByBot !== true) return true;
|
|
699
1088
|
return automation.trigger.match?.userId !== void 0;
|
|
700
1089
|
}
|
|
@@ -715,13 +1104,17 @@ function slackChannelMessageReplyThreadTs(event) {
|
|
|
715
1104
|
function getEventCatalog() {
|
|
716
1105
|
const snapshot = workspaceSnapshotEvents();
|
|
717
1106
|
const catalog = {
|
|
718
|
-
[CORE_EVENT_NAMESPACE]: {
|
|
1107
|
+
[CORE_EVENT_NAMESPACE]: eventCatalogEntry({
|
|
719
1108
|
displayName: botConfig.userName,
|
|
720
1109
|
resourceTypes: [...snapshot.resourceTypes, {
|
|
721
1110
|
type: "timer",
|
|
722
1111
|
supportedEvents: ["timer.fired"],
|
|
723
1112
|
suggestedEvents: []
|
|
724
1113
|
}],
|
|
1114
|
+
events: {
|
|
1115
|
+
...snapshot.events,
|
|
1116
|
+
"timer.fired": { reach: "resource" }
|
|
1117
|
+
},
|
|
725
1118
|
limits: {
|
|
726
1119
|
...snapshot.limits,
|
|
727
1120
|
timer: {
|
|
@@ -730,20 +1123,20 @@ function getEventCatalog() {
|
|
|
730
1123
|
watchUnavailableMessage: "Use watchTimer to create a timer Watch."
|
|
731
1124
|
}
|
|
732
1125
|
}
|
|
733
|
-
},
|
|
734
|
-
[SLACK_EVENT_NAMESPACE]: {
|
|
1126
|
+
}),
|
|
1127
|
+
[SLACK_EVENT_NAMESPACE]: eventCatalogEntry({
|
|
735
1128
|
...slackChannelMessageEvents(),
|
|
736
1129
|
displayName: "Slack"
|
|
737
|
-
}
|
|
1130
|
+
})
|
|
738
1131
|
};
|
|
739
1132
|
for (const plugin of getPlugins()) {
|
|
740
1133
|
const registration = plugin.events;
|
|
741
1134
|
if (!registration || registration.isEnabled?.() === false) continue;
|
|
742
1135
|
if (plugin.manifest.name === "junior" || plugin.manifest.name === "slack") throw new Error(`Plugin "${plugin.manifest.name}" cannot register events; that namespace is reserved for core events`);
|
|
743
|
-
catalog[plugin.manifest.name] = {
|
|
1136
|
+
catalog[plugin.manifest.name] = eventCatalogEntry({
|
|
744
1137
|
...registration,
|
|
745
1138
|
displayName: plugin.manifest.displayName
|
|
746
|
-
};
|
|
1139
|
+
});
|
|
747
1140
|
}
|
|
748
1141
|
return catalog;
|
|
749
1142
|
}
|
|
@@ -769,7 +1162,7 @@ const eventTaskFactsSchema = z.object({
|
|
|
769
1162
|
* contract. Stable delivery rules live in runtime and docs, not this prompt.
|
|
770
1163
|
*/
|
|
771
1164
|
function eventTaskFacts(subscription, event, people = [], doneBy) {
|
|
772
|
-
const guidance = [eventGuidance(getEventCatalog(), event.namespace,
|
|
1165
|
+
const guidance = [eventGuidance(getEventCatalog(), event.namespace, event.eventType), subscription.guidance?.[event.eventType]].filter((text) => Boolean(text?.trim())).join(" ");
|
|
773
1166
|
return {
|
|
774
1167
|
about: subscription.label,
|
|
775
1168
|
instructions: subscription.intent,
|
|
@@ -996,4 +1389,4 @@ async function skipInputBeforeConversationStop(context) {
|
|
|
996
1389
|
await context.attempt.ack();
|
|
997
1390
|
return { status: "completed" };
|
|
998
1391
|
}
|
|
999
|
-
export {
|
|
1392
|
+
export { automationResultSchema as A, maybePostAutomatedTurnLimitNotice as C, recordFinishedTurnForAutomatedLimit as D, renderSlackObjectCard as E, remindMissingAutomationResult as F, runDispatchOutcome as I, finishedRunReply as M, postsRunNotices as N, resetAutomatedTurnLimit as O, readAutomationResult as P, renderTaskInput as S, sendSlackReply as T, WORKSPACE_SNAPSHOT_FAILED_EVENT as _, EVENT_WAIT_MS as a, workspaceSnapshotWatch as b, isEventMailboxMetadata as c, getEventCatalog as d, SLACK_EVENT_NAMESPACE as f, slackChannelMessageRunsAutomation as g, slackChannelMessageReplyThreadTs as h, skipInputBeforeConversationStop as i, deliversFinalText as j, FINISH_AUTOMATION_RUN_TOOL_NAME as k, renderEventBatchText as l, slackChannelMessageEvent as m, markConversationsStopped as n, enqueueEventNotification as o, requireSlackChannelMessageScope as p, session_stop_exports as r, eventBatchMessageEvents as s, getConversationStoppedAtMs as t, timerWatchCredentialSubject as u, WORKSPACE_SNAPSHOT_READY_EVENT as v, resolveSlackReplyTarget as w, joinMailboxText as x, workspaceSnapshotFinishedEvent as y };
|