@dhf-openclaw/grix 0.4.13 → 0.4.15

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
@@ -3570,6 +3570,87 @@ async function deliverAibotPayload(params) {
3570
3570
  return { sent, firstMessageId };
3571
3571
  }
3572
3572
 
3573
+ // src/group-semantics.ts
3574
+ function normalizeEventType(value) {
3575
+ return String(value ?? "").trim().toLowerCase();
3576
+ }
3577
+ function normalizeMentionUserIds(value) {
3578
+ if (!Array.isArray(value)) {
3579
+ return [];
3580
+ }
3581
+ const deduped = /* @__PURE__ */ new Set();
3582
+ for (const entry of value) {
3583
+ const normalized = String(entry ?? "").trim();
3584
+ if (normalized) {
3585
+ deduped.add(normalized);
3586
+ }
3587
+ }
3588
+ return [...deduped];
3589
+ }
3590
+ function resolveGrixInboundSemantics(event) {
3591
+ const eventType = normalizeEventType(event.event_type);
3592
+ const isGroup = Number(event.session_type ?? 0) === 2 || eventType.startsWith("group_");
3593
+ const mentionUserIds = normalizeMentionUserIds(event.mention_user_ids);
3594
+ const hasAnyMention = mentionUserIds.length > 0;
3595
+ const wasMentioned = isGroup && eventType === "group_mention";
3596
+ const mentionsOther = isGroup && hasAnyMention && !wasMentioned;
3597
+ const mustReply = wasMentioned;
3598
+ const allowSilent = isGroup && !mustReply;
3599
+ const allowActions = !isGroup || mustReply;
3600
+ return {
3601
+ isGroup,
3602
+ eventType,
3603
+ wasMentioned,
3604
+ hasAnyMention,
3605
+ mentionsOther,
3606
+ mentionUserIds,
3607
+ mustReply,
3608
+ allowSilent,
3609
+ allowActions
3610
+ };
3611
+ }
3612
+ function buildGrixGroupSystemPrompt(semantics) {
3613
+ if (!semantics.isGroup) {
3614
+ return void 0;
3615
+ }
3616
+ if (semantics.wasMentioned) {
3617
+ return [
3618
+ "This group turn explicitly targeted you.",
3619
+ "You must reply or take the requested action when appropriate.",
3620
+ "Do not return NO_REPLY for this turn."
3621
+ ].join(" ");
3622
+ }
3623
+ if (semantics.mentionsOther) {
3624
+ return [
3625
+ "This group turn explicitly targeted someone else, not you.",
3626
+ "You may reply only if you add clear value.",
3627
+ "Otherwise return NO_REPLY.",
3628
+ "Do not take action unless the task is clearly yours."
3629
+ ].join(" ");
3630
+ }
3631
+ return [
3632
+ "This group turn is visible context, not an explicit mention for you.",
3633
+ "Reply only if it clearly helps the conversation.",
3634
+ "Otherwise return NO_REPLY.",
3635
+ "Do not take action unless the task is clearly yours."
3636
+ ].join(" ");
3637
+ }
3638
+ function resolveGrixMentionFallbackText() {
3639
+ return "I'm here.";
3640
+ }
3641
+ function resolveGrixDispatchResolution(params) {
3642
+ if (params.visibleOutputSent || params.eventResultReported) {
3643
+ return {
3644
+ shouldCompleteSilently: false,
3645
+ shouldSendMentionFallback: false
3646
+ };
3647
+ }
3648
+ return {
3649
+ shouldCompleteSilently: params.semantics.allowSilent,
3650
+ shouldSendMentionFallback: params.semantics.mustReply
3651
+ };
3652
+ }
3653
+
3573
3654
  // src/monitor.ts
3574
3655
  var activeMonitorClients = /* @__PURE__ */ new Map();
3575
3656
  function registerActiveMonitor(accountId, client) {
@@ -3847,8 +3928,10 @@ async function processEvent(params) {
3847
3928
  const quotedMessageId = normalizeNumericMessageId(event.quoted_message_id);
3848
3929
  const bodyForAgent = buildBodyWithQuotedReplyId(rawBody, quotedMessageId);
3849
3930
  const senderId = toStringId2(event.sender_id);
3850
- const isGroup = Number(event.session_type ?? 0) === 2 || String(event.event_type ?? "").startsWith("group_");
3931
+ const semantics = resolveGrixInboundSemantics(event);
3932
+ const isGroup = semantics.isGroup;
3851
3933
  const chatType = isGroup ? "group" : "direct";
3934
+ const groupSystemPrompt = buildGrixGroupSystemPrompt(semantics);
3852
3935
  const createdAt = toTimestampMs(event.created_at);
3853
3936
  const baseLogContext = buildEventLogContext({
3854
3937
  eventId,
@@ -3884,7 +3967,7 @@ async function processEvent(params) {
3884
3967
  return;
3885
3968
  }
3886
3969
  runtime2.log(
3887
- `[grix:${account.accountId}] inbound event ${baseLogContext} chatType=${chatType} bodyLen=${rawBody.length} quotedMessageId=${quotedMessageId || "-"}`
3970
+ `[grix:${account.accountId}] inbound event ${baseLogContext} chatType=${chatType} eventType=${semantics.eventType || "-"} wasMentioned=${semantics.wasMentioned ? "true" : "false"} mentionsOther=${semantics.mentionsOther ? "true" : "false"} bodyLen=${rawBody.length} quotedMessageId=${quotedMessageId || "-"}`
3888
3971
  );
3889
3972
  let inboundEventAccepted = false;
3890
3973
  const commandOutcome = await handleExecApprovalCommand({
@@ -3994,6 +4077,7 @@ async function processEvent(params) {
3994
4077
  SessionKey: route.sessionKey,
3995
4078
  AccountId: route.accountId,
3996
4079
  ChatType: chatType,
4080
+ GroupSystemPrompt: groupSystemPrompt,
3997
4081
  ConversationLabel: fromLabel,
3998
4082
  SenderName: senderId || void 0,
3999
4083
  SenderId: senderId || void 0,
@@ -4004,6 +4088,7 @@ async function processEvent(params) {
4004
4088
  // This field carries the inbound quoted message id from end user (event.quoted_message_id).
4005
4089
  // It is not the outbound reply anchor used when plugin sends replies back to Aibot.
4006
4090
  ReplyToMessageSid: quotedMessageId,
4091
+ WasMentioned: isGroup ? semantics.wasMentioned : void 0,
4007
4092
  OriginatingChannel: "grix",
4008
4093
  OriginatingTo: to
4009
4094
  });
@@ -4357,6 +4442,49 @@ async function processEvent(params) {
4357
4442
  markVisibleOutputSent();
4358
4443
  }
4359
4444
  }
4445
+ const dispatchResolution = resolveGrixDispatchResolution({
4446
+ semantics,
4447
+ visibleOutputSent,
4448
+ eventResultReported
4449
+ });
4450
+ if (dispatchResolution.shouldCompleteSilently) {
4451
+ runtime2.log(
4452
+ `[grix:${account.accountId}] group dispatch completed silently ${baseLogContext} attempt=${attemptLabel} wasMentioned=${semantics.wasMentioned ? "true" : "false"}`
4453
+ );
4454
+ reportEventResult("responded");
4455
+ }
4456
+ if (dispatchResolution.shouldSendMentionFallback) {
4457
+ outboundCounter++;
4458
+ const stableClientMsgId = `reply_${messageSid}_${outboundCounter}`;
4459
+ runtime2.log(
4460
+ `[grix:${account.accountId}] explicit mention fallback reply ${buildEventLogContext({
4461
+ eventId,
4462
+ sessionId,
4463
+ messageSid,
4464
+ clientMsgId: stableClientMsgId,
4465
+ outboundCounter
4466
+ })}`
4467
+ );
4468
+ const didSendFallback = await deliverAibotMessage({
4469
+ payload: {
4470
+ text: resolveGrixMentionFallbackText()
4471
+ },
4472
+ client,
4473
+ account,
4474
+ sessionId,
4475
+ abortSignal: runAbortController.signal,
4476
+ eventId,
4477
+ quotedMessageId: outboundQuotedMessageId,
4478
+ runtime: runtime2,
4479
+ statusSink,
4480
+ stableClientMsgId,
4481
+ tableMode
4482
+ });
4483
+ attemptHasOutbound = attemptHasOutbound || didSendFallback;
4484
+ if (didSendFallback) {
4485
+ markVisibleOutputSent();
4486
+ }
4487
+ }
4360
4488
  break;
4361
4489
  }
4362
4490
  if (!visibleOutputSent && !eventResultReported) {
@@ -4579,6 +4707,26 @@ function applySetupAccountConfig(params) {
4579
4707
  };
4580
4708
  }
4581
4709
 
4710
+ // src/group-adapter.ts
4711
+ function resolveGrixGroupRequireMention() {
4712
+ return false;
4713
+ }
4714
+ function resolveGrixGroupIntroHint() {
4715
+ return [
4716
+ "All Grix group messages are visible to you.",
4717
+ "If WasMentioned is true, you are the primary addressee and should respond.",
4718
+ "If WasMentioned is false, reply only when you add clear value; otherwise use NO_REPLY."
4719
+ ].join(" ");
4720
+ }
4721
+
4722
+ // src/group-tool-policy.ts
4723
+ var GROUP_DEFAULT_DENY = ["message"];
4724
+ function resolveGrixGroupToolPolicy() {
4725
+ return {
4726
+ deny: [...GROUP_DEFAULT_DENY]
4727
+ };
4728
+ }
4729
+
4582
4730
  // src/channel.ts
4583
4731
  var meta = {
4584
4732
  id: "grix",
@@ -4795,6 +4943,11 @@ var aibotPlugin = {
4795
4943
  hasRepliedRef
4796
4944
  })
4797
4945
  },
4946
+ groups: {
4947
+ resolveRequireMention: () => resolveGrixGroupRequireMention(),
4948
+ resolveGroupIntroHint: () => resolveGrixGroupIntroHint(),
4949
+ resolveToolPolicy: () => resolveGrixGroupToolPolicy()
4950
+ },
4798
4951
  agentPrompt: {
4799
4952
  messageToolHints: () => [
4800
4953
  "- Grix `action=unsend` is a silent cleanup action: unsend the target `messageId`, unsend the recall command message when applicable, then end with `NO_REPLY` and do not send any confirmation text. Omit `sessionId`/`to` only when targeting the current Grix chat."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhf-openclaw/grix",
3
- "version": "0.4.13",
3
+ "version": "0.4.15",
4
4
  "description": "Unified Grix OpenClaw plugin with channel transport, typed admin tools, and operator CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -77,10 +77,13 @@ Required input:
77
77
 
78
78
  Guardrails:
79
79
 
80
- 1. This action is only for the current Agent leaving its own group membership.
81
- 2. Never translate a request to remove other members into `leave`; use `remove_members` for that.
82
- 3. Do not send `memberId`, `memberIds`, or `memberTypes` with this action.
83
- 4. This action does not require scope and should not be described as a scope-grant workflow.
80
+ 1. Only the current Agent owner can request this action.
81
+ 2. If the requester is not the current Agent owner, reject the request directly and do not call `grix_group`.
82
+ 3. This action is only for the current Agent leaving its own group membership.
83
+ 4. Never translate a request to remove other members into `leave`; use `remove_members` for that.
84
+ 5. Do not send `memberId`, `memberIds`, or `memberTypes` with this action.
85
+ 6. This action does not require scope and should not be described as a scope-grant workflow.
86
+ 7. Execute `leave` silently: do not send any pre-leave or farewell message to the group before leaving.
84
87
 
85
88
  ### remove_members
86
89
 
@@ -154,6 +157,7 @@ Required input:
154
157
  2. Include key identifiers (`session_id`, member count, mute state) when successful.
155
158
  3. Include exact remediation when failed.
156
159
  4. Never hide scope or auth errors behind generic wording.
160
+ 5. For `leave`, report result to the requester only; do not post extra messages into the group session.
157
161
 
158
162
  ## References
159
163