Skip to content

Commit 8eccc65

Browse files
committed
Merge remote-tracking branch 'upstream/main' into doctor-detection-validation
2 parents a0eaef7 + a5a5df6 commit 8eccc65

13 files changed

Lines changed: 721 additions & 32 deletions

.github/workflows/mantis-discord-status-reactions.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,3 +573,44 @@ jobs:
573573
--artifact-url "$ARTIFACT_URL" \
574574
--run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
575575
--request-source "$REQUEST_SOURCE"
576+
577+
clear_issue_comment_reaction:
578+
name: Clear Mantis command reaction
579+
needs: [resolve_request, validate_refs, run_status_reactions]
580+
if: ${{ always() && github.event_name == 'issue_comment' && needs.resolve_request.outputs.request_source == 'issue_comment' }}
581+
runs-on: ubuntu-24.04
582+
permissions:
583+
issues: write
584+
steps:
585+
- name: Remove workflow eyes reaction
586+
uses: actions/github-script@v8
587+
with:
588+
script: |
589+
const { owner, repo } = context.repo;
590+
const commentId = context.payload.comment?.id;
591+
if (!commentId) {
592+
core.info("No issue comment id found; skipping reaction cleanup.");
593+
return;
594+
}
595+
596+
const reactions = await github.paginate(github.rest.reactions.listForIssueComment, {
597+
owner,
598+
repo,
599+
comment_id: commentId,
600+
per_page: 100,
601+
});
602+
const eyes = reactions.filter(
603+
(reaction) => reaction.content === "eyes" && reaction.user?.login === "github-actions[bot]",
604+
);
605+
for (const reaction of eyes) {
606+
await github.rest.reactions.deleteForIssueComment({
607+
owner,
608+
repo,
609+
comment_id: commentId,
610+
reaction_id: reaction.id,
611+
});
612+
core.info(`Removed eyes reaction ${reaction.id} from comment ${commentId}.`);
613+
}
614+
if (eyes.length === 0) {
615+
core.info(`No workflow eyes reaction found on comment ${commentId}.`);
616+
}

.github/workflows/mantis-discord-thread-attachment.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,3 +595,44 @@ jobs:
595595
run: |
596596
echo "Mantis comparison failed." >&2
597597
exit 1
598+
599+
clear_issue_comment_reaction:
600+
name: Clear Mantis command reaction
601+
needs: [resolve_request, validate_candidate, run_thread_attachment]
602+
if: ${{ always() && github.event_name == 'issue_comment' && needs.resolve_request.outputs.request_source == 'issue_comment' }}
603+
runs-on: ubuntu-24.04
604+
permissions:
605+
issues: write
606+
steps:
607+
- name: Remove workflow eyes reaction
608+
uses: actions/github-script@v8
609+
with:
610+
script: |
611+
const { owner, repo } = context.repo;
612+
const commentId = context.payload.comment?.id;
613+
if (!commentId) {
614+
core.info("No issue comment id found; skipping reaction cleanup.");
615+
return;
616+
}
617+
618+
const reactions = await github.paginate(github.rest.reactions.listForIssueComment, {
619+
owner,
620+
repo,
621+
comment_id: commentId,
622+
per_page: 100,
623+
});
624+
const eyes = reactions.filter(
625+
(reaction) => reaction.content === "eyes" && reaction.user?.login === "github-actions[bot]",
626+
);
627+
for (const reaction of eyes) {
628+
await github.rest.reactions.deleteForIssueComment({
629+
owner,
630+
repo,
631+
comment_id: commentId,
632+
reaction_id: reaction.id,
633+
});
634+
core.info(`Removed eyes reaction ${reaction.id} from comment ${commentId}.`);
635+
}
636+
if (eyes.length === 0) {
637+
core.info(`No workflow eyes reaction found on comment ${commentId}.`);
638+
}

.github/workflows/mantis-telegram-desktop-proof.yml

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Mantis Telegram Desktop Proof
33
on:
44
issue_comment:
55
types: [created]
6-
pull_request_target:
6+
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned Mantis label trigger; trusted base workflow validates refs before checkout/use
77
types: [labeled]
88
workflow_dispatch:
99
inputs:
@@ -120,6 +120,7 @@ jobs:
120120
publish_run_id: ${{ steps.resolve.outputs.publish_run_id }}
121121
pr_number: ${{ steps.resolve.outputs.pr_number }}
122122
request_source: ${{ steps.resolve.outputs.request_source }}
123+
should_run: ${{ steps.resolve.outputs.should_run }}
123124
steps:
124125
- name: Resolve refs and target PR
125126
id: resolve
@@ -145,24 +146,52 @@ jobs:
145146
return;
146147
}
147148
148-
const { owner, repo } = context.repo;
149-
const { data: pr } = await github.rest.pulls.get({
150-
owner,
151-
repo,
152-
pull_number: Number(prNumber),
153-
});
154149
const body =
155150
eventName === "workflow_dispatch"
156151
? inputs.instructions || ""
157152
: eventName === "issue_comment"
158153
? context.payload.comment?.body || ""
159154
: "";
155+
if (eventName === "issue_comment") {
156+
const normalized = body.toLowerCase();
157+
const requestedDesktopProof =
158+
(normalized.includes("@openclaw-mantis") || normalized.includes("/openclaw-mantis")) &&
159+
(normalized.includes("desktop proof") ||
160+
normalized.includes("desktop-proof") ||
161+
normalized.includes("telegram desktop") ||
162+
normalized.includes("native telegram") ||
163+
normalized.includes("visible proof") ||
164+
normalized.includes("visible-proof") ||
165+
normalized.includes("telegram-visible-proof"));
166+
if (!requestedDesktopProof) {
167+
core.notice("Comment mentioned Mantis but did not request Telegram desktop proof.");
168+
setOutput("should_run", "false");
169+
setOutput("baseline_ref", "");
170+
setOutput("candidate_ref", "");
171+
setOutput("pr_number", "");
172+
setOutput("instructions", "");
173+
setOutput("crabbox_provider", "");
174+
setOutput("lease_id", "");
175+
setOutput("publish_artifact_name", "");
176+
setOutput("publish_run_id", "");
177+
setOutput("request_source", "unsupported_issue_comment");
178+
return;
179+
}
180+
}
181+
182+
const { owner, repo } = context.repo;
183+
const { data: pr } = await github.rest.pulls.get({
184+
owner,
185+
repo,
186+
pull_number: Number(prNumber),
187+
});
160188
const provider = inputs.crabbox_provider || "aws";
161189
if (!["aws", "hetzner"].includes(provider)) {
162190
core.setFailed(`Unsupported Crabbox provider for Mantis Telegram desktop proof: ${provider}`);
163191
return;
164192
}
165193
194+
setOutput("should_run", "true");
166195
setOutput("baseline_ref", pr.base.sha);
167196
setOutput("candidate_ref", pr.head.sha);
168197
setOutput("pr_number", String(pr.number));
@@ -185,7 +214,7 @@ jobs:
185214
validate_refs:
186215
name: Validate selected refs
187216
needs: resolve_request
188-
if: needs.resolve_request.outputs.publish_artifact_name == ''
217+
if: needs.resolve_request.outputs.should_run == 'true' && needs.resolve_request.outputs.publish_artifact_name == ''
189218
runs-on: ubuntu-24.04
190219
outputs:
191220
baseline_revision: ${{ steps.validate.outputs.baseline_revision }}
@@ -264,7 +293,7 @@ jobs:
264293
run_telegram_desktop_proof:
265294
name: Run agentic native Telegram proof
266295
needs: [resolve_request, validate_refs]
267-
if: needs.resolve_request.outputs.publish_artifact_name == ''
296+
if: needs.resolve_request.outputs.should_run == 'true' && needs.resolve_request.outputs.publish_artifact_name == ''
268297
runs-on: blacksmith-16vcpu-ubuntu-2404
269298
timeout-minutes: 360
270299
environment: qa-live-shared
@@ -513,7 +542,7 @@ jobs:
513542
publish_existing_telegram_desktop_proof:
514543
name: Publish existing native Telegram proof
515544
needs: resolve_request
516-
if: needs.resolve_request.outputs.publish_artifact_name != ''
545+
if: needs.resolve_request.outputs.should_run == 'true' && needs.resolve_request.outputs.publish_artifact_name != ''
517546
runs-on: ubuntu-24.04
518547
environment: qa-live-shared
519548
steps:
@@ -598,3 +627,44 @@ jobs:
598627
--artifact-url "$PUBLISH_ARTIFACT_URL" \
599628
--run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${PUBLISH_RUN_ID}" \
600629
--request-source "$REQUEST_SOURCE"
630+
631+
clear_issue_comment_reaction:
632+
name: Clear Mantis command reaction
633+
needs: [resolve_request, validate_refs, run_telegram_desktop_proof]
634+
if: ${{ always() && github.event_name == 'issue_comment' && needs.resolve_request.outputs.request_source == 'issue_comment' }}
635+
runs-on: ubuntu-24.04
636+
permissions:
637+
issues: write
638+
steps:
639+
- name: Remove workflow eyes reaction
640+
uses: actions/github-script@v8
641+
with:
642+
script: |
643+
const { owner, repo } = context.repo;
644+
const commentId = context.payload.comment?.id;
645+
if (!commentId) {
646+
core.info("No issue comment id found; skipping reaction cleanup.");
647+
return;
648+
}
649+
650+
const reactions = await github.paginate(github.rest.reactions.listForIssueComment, {
651+
owner,
652+
repo,
653+
comment_id: commentId,
654+
per_page: 100,
655+
});
656+
const eyes = reactions.filter(
657+
(reaction) => reaction.content === "eyes" && reaction.user?.login === "github-actions[bot]",
658+
);
659+
for (const reaction of eyes) {
660+
await github.rest.reactions.deleteForIssueComment({
661+
owner,
662+
repo,
663+
comment_id: commentId,
664+
reaction_id: reaction.id,
665+
});
666+
core.info(`Removed eyes reaction ${reaction.id} from comment ${commentId}.`);
667+
}
668+
if (eyes.length === 0) {
669+
core.info(`No workflow eyes reaction found on comment ${commentId}.`);
670+
}

.github/workflows/mantis-telegram-live.yml

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,18 @@ jobs:
139139
}
140140
141141
const normalized = body.toLowerCase();
142+
const requestedDesktopProof =
143+
normalized.includes("desktop proof") ||
144+
normalized.includes("desktop-proof") ||
145+
normalized.includes("telegram desktop") ||
146+
normalized.includes("native telegram") ||
147+
normalized.includes("visible proof") ||
148+
normalized.includes("visible-proof") ||
149+
normalized.includes("telegram-visible-proof");
142150
const requested =
143151
(normalized.includes("@openclaw-mantis") || normalized.includes("/openclaw-mantis")) &&
144-
normalized.includes("telegram");
152+
normalized.includes("telegram") &&
153+
!requestedDesktopProof;
145154
if (!requested) {
146155
core.notice("Comment mentioned Mantis but did not request Telegram live QA.");
147156
setOutput("should_run", "false");
@@ -531,3 +540,44 @@ jobs:
531540
run: |
532541
echo "Mantis Telegram live failed: comparison=${COMPARISON_STATUS:-unset} telegram_exit=${TELEGRAM_EXIT:-unset}." >&2
533542
exit 1
543+
544+
clear_issue_comment_reaction:
545+
name: Clear Mantis command reaction
546+
needs: [resolve_request, validate_ref, run_telegram_live]
547+
if: ${{ always() && github.event_name == 'issue_comment' && needs.resolve_request.outputs.request_source == 'issue_comment' }}
548+
runs-on: ubuntu-24.04
549+
permissions:
550+
issues: write
551+
steps:
552+
- name: Remove workflow eyes reaction
553+
uses: actions/github-script@v8
554+
with:
555+
script: |
556+
const { owner, repo } = context.repo;
557+
const commentId = context.payload.comment?.id;
558+
if (!commentId) {
559+
core.info("No issue comment id found; skipping reaction cleanup.");
560+
return;
561+
}
562+
563+
const reactions = await github.paginate(github.rest.reactions.listForIssueComment, {
564+
owner,
565+
repo,
566+
comment_id: commentId,
567+
per_page: 100,
568+
});
569+
const eyes = reactions.filter(
570+
(reaction) => reaction.content === "eyes" && reaction.user?.login === "github-actions[bot]",
571+
);
572+
for (const reaction of eyes) {
573+
await github.rest.reactions.deleteForIssueComment({
574+
owner,
575+
repo,
576+
comment_id: commentId,
577+
reaction_id: reaction.id,
578+
});
579+
core.info(`Removed eyes reaction ${reaction.id} from comment ${commentId}.`);
580+
}
581+
if (eyes.length === 0) {
582+
core.info(`No workflow eyes reaction found on comment ${commentId}.`);
583+
}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai
2727

2828
### Fixes
2929

30+
- Feishu: return bound subagent delivery origins from session thread setup so Feishu subagent completions route back to the same DM or topic. (#83190) Thanks @100menotu001.
3031
- CLI/update: tailor post-update Gateway recovery hints by platform, showing systemd, LaunchAgent, Scheduled Task, or generic service-manager guidance instead of macOS-only recovery text. (#83096) Thanks @rubencu.
3132
- Plugins: apply a default 15-second timeout to legacy `before_agent_start` hooks so hung plugin handlers no longer block agent startup. Fixes #48534. (#83136) Thanks @therahul-yo.
3233
- Feishu: refresh inbound session delivery context for DM, group, and broadcast turns so later replies do not inherit stale WebChat routing. Fixes #78274.

extensions/feishu/src/subagent-hooks.test.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,15 @@ describe("feishu subagent hook handlers", () => {
5454
{},
5555
);
5656

57-
expect(result).toEqual({ status: "ok", threadBindingReady: true });
57+
expect(result).toEqual({
58+
status: "ok",
59+
threadBindingReady: true,
60+
deliveryOrigin: {
61+
channel: "feishu",
62+
accountId: "work",
63+
to: "user:ou_sender_1",
64+
},
65+
});
5866

5967
const deliveryTargetHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
6068
await expect(
@@ -141,7 +149,16 @@ describe("feishu subagent hook handlers", () => {
141149
{},
142150
);
143151

144-
expect(result).toEqual({ status: "ok", threadBindingReady: true });
152+
expect(result).toEqual({
153+
status: "ok",
154+
threadBindingReady: true,
155+
deliveryOrigin: {
156+
channel: "feishu",
157+
accountId: "work",
158+
to: "chat:oc_group_chat",
159+
threadId: "om_topic_root",
160+
},
161+
});
145162
await expect(
146163
deliveryHandler(
147164
{
@@ -204,7 +221,16 @@ describe("feishu subagent hook handlers", () => {
204221
},
205222
);
206223

207-
expect(reboundResult).toEqual({ status: "ok", threadBindingReady: true });
224+
expect(reboundResult).toEqual({
225+
status: "ok",
226+
threadBindingReady: true,
227+
deliveryOrigin: {
228+
channel: "feishu",
229+
accountId: "work",
230+
to: "chat:oc_group_chat",
231+
threadId: "om_topic_root",
232+
},
233+
});
208234
const childBindings = manager.listBySessionKey("agent:main:subagent:sender-child");
209235
expect(childBindings).toHaveLength(1);
210236
expect(childBindings[0]?.conversationId).toBe(

extensions/feishu/src/subagent-hooks.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,16 @@ type FeishuSubagentEndedEvent = {
270270
};
271271

272272
type FeishuSubagentSpawningResult =
273-
| { status: "ok"; threadBindingReady?: boolean }
273+
| {
274+
status: "ok";
275+
threadBindingReady?: boolean;
276+
deliveryOrigin?: {
277+
channel: "feishu";
278+
accountId?: string;
279+
to?: string;
280+
threadId?: string | number;
281+
};
282+
}
274283
| { status: "error"; error: string }
275284
| undefined;
276285

@@ -347,6 +356,13 @@ export async function handleFeishuSubagentSpawning(
347356
return {
348357
status: "ok" as const,
349358
threadBindingReady: true,
359+
deliveryOrigin: resolveFeishuDeliveryOrigin({
360+
conversationId: binding.conversationId,
361+
parentConversationId: binding.parentConversationId,
362+
accountId: binding.accountId,
363+
deliveryTo: binding.deliveryTo,
364+
deliveryThreadId: binding.deliveryThreadId,
365+
}),
350366
};
351367
} catch (err) {
352368
return {

0 commit comments

Comments
 (0)