crewly 1.20.324 → 1.20.326
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/config/roles/orchestrator/prompt.md +1 -0
- package/config/skills/orchestrator/create-channel/SKILL.md +42 -0
- package/config/skills/orchestrator/create-channel/execute.sh +62 -0
- package/config/skills/orchestrator/create-channel/execute.test.sh +29 -0
- package/dist/backend/backend/src/constants.d.ts +4 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +4 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/channels/channels.routes.d.ts +6 -2
- package/dist/backend/backend/src/controllers/channels/channels.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/channels/channels.routes.js +37 -6
- package/dist/backend/backend/src/controllers/channels/channels.routes.js.map +1 -1
- package/dist/backend/backend/src/middleware/caller-identity.middleware.d.ts +23 -0
- package/dist/backend/backend/src/middleware/caller-identity.middleware.d.ts.map +1 -1
- package/dist/backend/backend/src/middleware/caller-identity.middleware.js +31 -1
- package/dist/backend/backend/src/middleware/caller-identity.middleware.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +43 -1
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/channels/crewly-channel-compose.service.d.ts +134 -0
- package/dist/backend/backend/src/services/channels/crewly-channel-compose.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/channels/crewly-channel-compose.service.js +222 -0
- package/dist/backend/backend/src/services/channels/crewly-channel-compose.service.js.map +1 -0
- package/dist/backend/backend/src/services/channels/crewly-channel.service.d.ts +3 -0
- package/dist/backend/backend/src/services/channels/crewly-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/channels/crewly-channel.service.js +17 -4
- package/dist/backend/backend/src/services/channels/crewly-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-directory.service.d.ts +6 -0
- package/dist/backend/backend/src/services/slack/slack-directory.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-directory.service.js +2 -0
- package/dist/backend/backend/src/services/slack/slack-directory.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +8 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts +13 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js +12 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +23 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +100 -3
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +3 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +24 -0
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/types/slack.types.d.ts +14 -0
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +4 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +4 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-directory.service.d.ts +6 -0
- package/dist/cli/backend/src/services/slack/slack-directory.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-directory.service.js +2 -0
- package/dist/cli/backend/src/services/slack/slack-directory.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +23 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +100 -3
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +3 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +24 -0
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +14 -0
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/package.json +1 -1
|
@@ -405,6 +405,8 @@ export class SlackTeamChannelService {
|
|
|
405
405
|
unsubscribeIdentity = null;
|
|
406
406
|
/** Slack `channel:ts` of messages already routed → the persisted turn. */
|
|
407
407
|
seenInbound = new Map();
|
|
408
|
+
/** Room messages already handed back to the orchestrator. */
|
|
409
|
+
orcHandedBack = new Set();
|
|
408
410
|
/** The last room presence Cloud sent per Slack channel — names for a hand-off. */
|
|
409
411
|
lastRooms = new Map();
|
|
410
412
|
/** People (not agents) by lower-cased name → Slack user id, for `@Name` in agent replies. */
|
|
@@ -1022,13 +1024,26 @@ export class SlackTeamChannelService {
|
|
|
1022
1024
|
}
|
|
1023
1025
|
}
|
|
1024
1026
|
else {
|
|
1025
|
-
|
|
1027
|
+
// 2026-10-13: a room another machine created (room-join request): link
|
|
1028
|
+
// THAT Slack channel. Asking Slack to create by name could make a
|
|
1029
|
+
// second channel when Slack normalised the name differently.
|
|
1030
|
+
let channel;
|
|
1031
|
+
if (input.existingChannelId) {
|
|
1032
|
+
const found = await this.deps.slack.getChannelInfo(input.existingChannelId);
|
|
1033
|
+
if (!found)
|
|
1034
|
+
throw new Error(`Slack channel ${input.existingChannelId} was not found`);
|
|
1035
|
+
await this.deps.slack.joinChannel(found.id).catch(() => undefined);
|
|
1036
|
+
channel = found;
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
channel = await this.deps.slack.createChannel(derived);
|
|
1040
|
+
}
|
|
1026
1041
|
// createChannel hands back an existing channel when the name is taken;
|
|
1027
1042
|
// a team's channel must not be turned into a second room.
|
|
1028
1043
|
const clash = current.mappings.find((m) => m.slackChannelId === channel.id);
|
|
1029
1044
|
if (clash)
|
|
1030
1045
|
throw new Error(`#${channel.name} is already linked to ${isAdhocMapping(clash) ? 'another channel' : 'a team'}`);
|
|
1031
|
-
const ownerInvited = await this.inviteOwner(channel.id, channel.name);
|
|
1046
|
+
const ownerInvited = input.existingChannelId ? false : await this.inviteOwner(channel.id, channel.name);
|
|
1032
1047
|
if (input.purpose.trim()) {
|
|
1033
1048
|
await this.deps.slack.setChannelPurpose(channel.id, input.purpose.trim()).catch((err) => {
|
|
1034
1049
|
this.logger.debug('setPurpose failed (non-critical)', { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -1458,8 +1473,43 @@ export class SlackTeamChannelService {
|
|
|
1458
1473
|
const declinedOnly = declinedAll.length > 0 && resolved.mentions.every((sess) => declinedAll.includes(sess));
|
|
1459
1474
|
for (const sess of declinedAll)
|
|
1460
1475
|
resolved.mentions.splice(resolved.mentions.indexOf(sess), 1);
|
|
1461
|
-
|
|
1476
|
+
// This machine's orchestrator @'d in a room (2026-10-13, #pro-crewly-marketing:
|
|
1477
|
+
// Ella's "<@orc> Steve approved a new team on your machine…" reached the
|
|
1478
|
+
// Air, Cloud routed it by `agent-author`, and nobody acted). The orc is
|
|
1479
|
+
// deliberately not a room member (leads, rosters and wake-when-all-asleep
|
|
1480
|
+
// rely on that), so the resolver never saw it, and the message counted as
|
|
1481
|
+
// addressed to nobody here. It is either Cloud's mention list (mapped to
|
|
1482
|
+
// the local session name) or the orc's own bot id in the text. The orc is
|
|
1483
|
+
// taken out of the agent lists below: it is neither "elsewhere" nor a
|
|
1484
|
+
// dispatch target of the huddle.
|
|
1485
|
+
const orcSession = CREWLY_CONSTANTS.SESSIONS.ORCHESTRATOR_NAME;
|
|
1486
|
+
const orcBotId = this.deps.identities?.getInstalled(orcSession)?.botUserId;
|
|
1487
|
+
const orcAddressed = !handoffTo &&
|
|
1488
|
+
(!!message.mentionedAgentSessions?.some((sess) => localAgentSession(sess) === orcSession) ||
|
|
1489
|
+
(!!orcBotId && (message.text ?? '').includes(`<@${orcBotId}>`)));
|
|
1490
|
+
const agentsMentionedViaCloud = [...new Set([...(message.mentionedAgentSessions ?? []), ...namedElsewhere])].filter((sess) => localAgentSession(sess) !== orcSession);
|
|
1462
1491
|
const mentionedElsewhere = agentsMentionedViaCloud.filter((sess) => !isLocal(sess));
|
|
1492
|
+
// Nobody else here is addressed: the orchestrator's own pipeline takes it
|
|
1493
|
+
// (it replies in the same Slack thread). Returning null hands the message
|
|
1494
|
+
// back to the bridge, the way an unmapped channel message is. Members
|
|
1495
|
+
// @'d alongside the orc keep the room path (they answer in the thread).
|
|
1496
|
+
if (orcAddressed && resolved.mentions.length === 0 && declinedAll.length === 0 && mentionedElsewhere.length === 0) {
|
|
1497
|
+
const orcKey = `${message.channelId}:${message.ts}`;
|
|
1498
|
+
// The same message can arrive as two event types; hand it back once.
|
|
1499
|
+
if (this.orcHandedBack.has(orcKey))
|
|
1500
|
+
return { mapping, message: seen ?? {}, mentions: [], dispatch: null, duplicate: true };
|
|
1501
|
+
this.orcHandedBack.add(orcKey);
|
|
1502
|
+
if (this.orcHandedBack.size > SLACK_TEAM_CHANNEL_CONSTANTS.SEEN_INBOUND_MAX) {
|
|
1503
|
+
const oldest = this.orcHandedBack.values().next().value;
|
|
1504
|
+
if (oldest !== undefined)
|
|
1505
|
+
this.orcHandedBack.delete(oldest);
|
|
1506
|
+
}
|
|
1507
|
+
this.logger.info("Room message @'s this machine's orchestrator - handed to it", {
|
|
1508
|
+
slackChannel: `#${mapping.slackChannelName}`,
|
|
1509
|
+
ts: message.ts,
|
|
1510
|
+
});
|
|
1511
|
+
return null;
|
|
1512
|
+
}
|
|
1463
1513
|
const addressedElsewhereOnly = !handoffTo && resolved.mentions.length === 0 && mentionedElsewhere.length > 0;
|
|
1464
1514
|
// @-mentions of people: the owner asking a
|
|
1465
1515
|
// colleague is not a question for the agents (2026-10-01,
|
|
@@ -3015,6 +3065,53 @@ export class SlackTeamChannelService {
|
|
|
3015
3065
|
this.logger.info('Room message handed to an agent on another machine', { agentSession: hit.session, slackChannel: mapping.slackChannelName });
|
|
3016
3066
|
return { ok: true, agentSession: hit.session, displayName: hit.name, via: 'cloud' };
|
|
3017
3067
|
}
|
|
3068
|
+
/**
|
|
3069
|
+
* A room-join request from another machine (via Cloud): link the existing
|
|
3070
|
+
* Slack channel here with the given LOCAL agents as members, so they are in
|
|
3071
|
+
* the room and show up in this machine's heartbeat `rooms`.
|
|
3072
|
+
*
|
|
3073
|
+
* Sessions that are not agents of this machine are rejected and reported,
|
|
3074
|
+
* never silently dropped. A channel that is a team's channel is refused.
|
|
3075
|
+
*
|
|
3076
|
+
* @param input - Slack channel and the sessions Cloud addressed to this machine
|
|
3077
|
+
* @returns Which sessions joined and which were rejected
|
|
3078
|
+
*/
|
|
3079
|
+
async joinRoomFromCloud(input) {
|
|
3080
|
+
await this.load();
|
|
3081
|
+
const local = new Set();
|
|
3082
|
+
for (const team of await this.deps.storage.getTeams()) {
|
|
3083
|
+
for (const m of teamChannelMembers(team))
|
|
3084
|
+
local.add(m.sessionName);
|
|
3085
|
+
}
|
|
3086
|
+
const wanted = [...new Set((input.sessions ?? []).map((s) => localAgentSession(String(s))).filter(Boolean))];
|
|
3087
|
+
const joined = wanted.filter((s) => local.has(s));
|
|
3088
|
+
const rejected = wanted.filter((s) => !local.has(s));
|
|
3089
|
+
if (rejected.length > 0) {
|
|
3090
|
+
this.logger.warn('Room join: sessions are not agents of this machine', { slackChannel: input.slackChannelId, rejected });
|
|
3091
|
+
}
|
|
3092
|
+
if (joined.length === 0)
|
|
3093
|
+
return { ok: false, joined, rejected, error: 'no_local_members' };
|
|
3094
|
+
const existing = (this.store?.mappings ?? []).find((m) => m.slackChannelId === input.slackChannelId);
|
|
3095
|
+
if (existing && !isAdhocMapping(existing)) {
|
|
3096
|
+
return { ok: false, joined: [], rejected: wanted, error: 'channel_belongs_to_a_team' };
|
|
3097
|
+
}
|
|
3098
|
+
try {
|
|
3099
|
+
await this.ensureAgentChannel({
|
|
3100
|
+
name: input.slackChannelName,
|
|
3101
|
+
purpose: '',
|
|
3102
|
+
memberSessions: joined,
|
|
3103
|
+
existingChannelId: input.slackChannelId,
|
|
3104
|
+
applyPrefix: false,
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
catch (err) {
|
|
3108
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
3109
|
+
this.logger.warn('Room join failed', { slackChannel: input.slackChannelId, error });
|
|
3110
|
+
return { ok: false, joined: [], rejected: wanted, error };
|
|
3111
|
+
}
|
|
3112
|
+
this.logger.info('Joined a room created on another machine', { slackChannel: input.slackChannelId, name: input.slackChannelName, agents: joined });
|
|
3113
|
+
return { ok: true, joined, rejected };
|
|
3114
|
+
}
|
|
3018
3115
|
/**
|
|
3019
3116
|
* Every Slack channel this machine maps — team channels and ad-hoc rooms —
|
|
3020
3117
|
* with the local agents in it. What the delivery audit reads Slack for.
|