Skip to content

feat(agent): builder tools — triggers, cron, find_triggers (#4919)#4931

Draft
mmabrouk wants to merge 1 commit into
feat/agent-skills-4918-v2from
feat/agent-builder-tools-4919-v2
Draft

feat(agent): builder tools — triggers, cron, find_triggers (#4919)#4931
mmabrouk wants to merge 1 commit into
feat/agent-skills-4918-v2from
feat/agent-builder-tools-4919-v2

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Jun 29, 2026

Copy link
Copy Markdown
Member

Context

An agent building itself needs to set up cron schedules, subscribe to external events, and discover what triggers are available for a given use case. None of those operations existed in the platform op catalog or the API. Without them, the set-up-triggers skill (PR #4930) names tools the agent cannot call.

Design: docs/design/agent-workflows/projects/agent-builder-capabilities/

Changes

SDK — op_catalog.py: Seven new platform ops. Mutation ops default to approval; read ops default to allow.

Op Endpoint Default permission
find_triggers POST /api/triggers/discover allow
create_trigger / create_cron POST /api/triggers/subscriptions/ / schedules/ ask, approval
delete_trigger / delete_cron DELETE /api/triggers/subscriptions/{id} / schedules/{id} ask, approval
list_triggers / list_crons GET /api/triggers/subscriptions/ / schedules/ allow

Destinations (data.references, data.selector) are bound from run context ($ctx) and stripped from the model-facing schema. The model cannot route a trigger to an arbitrary target.

API — triggers router and service: New POST /triggers/discover endpoint. Accepts use_cases (list of keyword fragments), provider, and limit_alternatives. The service method discover_triggers tokenizes each use-case string, scores every catalog event by keyword overlap, and returns the top match per use case plus alternatives, connection state, and guidance.

New DTOs in dtos.py under the # Trigger discovery section: TriggerCapabilitiesResult, TriggerCapability, DiscoveredTriggerEvent, TriggerDiscoveryConnectionState, TriggerConnectionRequirement, TriggerConnectAffordance, TriggerDiscoveryGuidance.

TS runner (services/agent/):

  • protocol.tsDELETE added to DirectCallMethod union (required for delete ops that use paths like /triggers/{id}).
  • direct.tsdirectCallUrl now substitutes path parameters (e.g., {trigger_id}) from the assembled body params before the call.
  • relay.ts — Assembles the body before computing the URL so path param substitution has access to all params.

Scope / risk

find_triggers does keyword matching in-process against the Composio trigger catalog. It is not a semantic search. The quality of matches depends on overlap between the user's words and the event names/descriptions in the catalog. The _DISCOVERY_STOPWORDS set is a first-pass filter; it may need tuning.

The relay.ts change (assemble body before directCallUrl) is a correctness fix, but it is a behavior change to the relay path. Verify that path-param substitution does not incorrectly consume fields intended as request body keys.

The DELETE method addition in direct.ts opens delete calls to any direct-call op, not just trigger ops. Confirm the allowlist and path-param logic are correct before merging.

The new platform ops (create_trigger, delete_trigger, etc.) are model-facing. The input schemas, approval defaults, and context bindings are load-bearing for safety. These deserve a careful read.

Tests

  • SDK Python (op_catalog): 18/18 passed (Codex-reported).
  • API Python (triggers discovery): 6/6 passed (Codex-reported).
  • TS typecheck (tsc --noEmit): clean.
  • TS vitest (tool-direct.test.ts): 42/42 passed (Codex-reported).
  • ruff format/check: clean.

How to QA

Prerequisites: local dev stack with the triggers service running (EE edition with Composio configured).

Steps:

  1. Call POST /api/triggers/discover with {"use_cases": ["new GitHub pull request"]} and a valid project API key.
  2. Confirm the response includes a capabilities list with at least one entry whose event.integration is GITHUB or similar.
  3. Check the connections list reflects the current connection state for that integration.

Automated tests:

cd api && uv run python -m pytest oss/tests/pytest/unit/triggers/test_triggers_discovery.py -v
cd sdks/python && uv run python -m pytest oss/tests/pytest/unit/agents/platform/test_op_catalog.py -v
cd services/agent && npx vitest run tests/unit/tool-direct.test.ts

Edge cases:

  • Call POST /triggers/discover with use_cases: [""] (empty string). Expect a 422 validation error from the _require_use_cases validator.
  • Use a DELETE direct call op. Confirm the path parameter is substituted correctly and the body does not include the substituted field as a body key.
  • Call list_triggers / list_crons from an agent run and confirm the routing context binding ($ctx) fills the destination without any model-supplied value.

https://claude.ai/code/session_01GYo3UEfvsZpncagqb28Mbc

@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jun 29, 2026 11:22pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aa7ac47c-a927-4396-b7e3-783eb81cbd54

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a POST /triggers/discover endpoint backed by a use-case scoring and catalog-lookup service, introduces a set of trigger capability DTOs, extends the SDK platform op catalog with trigger/schedule/subscription operations, and broadens the GET/POST-only HTTP method constraint to include DELETE across the agent tool call stack with path-template parameter substitution.

Changes

Trigger Discovery Feature

Layer / File(s) Summary
Trigger discovery DTOs
api/oss/src/core/triggers/dtos.py, api/oss/src/apis/fastapi/triggers/models.py
Defines TriggerDiscoveryConnectionState, DiscoveredTriggerEvent, TriggerCapability, TriggerCapabilitiesResult, and related models; adds TriggerDiscoveryQuery request schema with field_validator and TriggerDiscoveryResponse alias.
Discovery scoring and orchestration
api/oss/src/core/triggers/service.py
Adds stopword-filtered tokenizer, term-based match scorer, and the full discover_triggers orchestration: candidate selection, event enrichment via get_event, connection state resolution (READY/NEEDS_INPUT/NEEDS_AUTH), and TriggerCapabilitiesResult assembly with guidance/pitfalls/notes.
POST /discover endpoint
api/oss/src/apis/fastapi/triggers/router.py
Registers POST /discover route, implements discover_triggers handler with Permission.VIEW_TRIGGERS check, and wires TriggerDiscoveryQuery/TriggerDiscoveryResponse.
Discovery unit tests
api/oss/tests/pytest/unit/triggers/test_triggers_discovery.py
Tests covering discover_triggers result shape, NEEDS_INPUT connection state path, route registration at /discover, request field forwarding, and TriggerDiscoveryQuery validation rejection cases.
SDK platform op catalog: trigger/schedule/subscription ops
sdks/python/agenta/sdk/agents/platform/op_catalog.py, sdks/python/oss/tests/pytest/unit/agents/platform/test_op_catalog.py
Extends PLATFORM_OPS with find_triggers, create_schedule, create_subscription, test_subscription, and lifecycle ops with JSON schemas, context_bindings, and permission defaults; tests assert method/path, approval gating, and self-target binding behavior.

DELETE Method Support in Agent Tool Stack

Layer / File(s) Summary
DELETE type extension
services/agent/src/protocol.ts, sdks/python/agenta/sdk/agents/tools/models.py
Extends method literal type from "GET" | "POST" to "GET" | "POST" | "DELETE" in ResolvedToolSpec.call and ToolCall.
directCallUrl path substitution and DELETE execution
services/agent/src/tools/direct.ts, services/agent/src/tools/relay.ts, services/agent/tests/unit/tool-direct.test.ts
Adds {param} placeholder substitution with dotted-path resolution and URL encoding in directCallUrl; extends callDirect method type; reorders body assembly before URL construction in relay; tests cover DELETE substitution, encoding, missing/non-scalar param rejection, and disallowed method.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.16% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main trigger-related agent-builder changes.
Description check ✅ Passed The description matches the PR scope and clearly explains the new API, SDK, and runner changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-builder-tools-4919-v2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mmabrouk mmabrouk added the needs-review Agent updated; awaiting Mahmoud's review label Jun 29, 2026
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

What to review on this PR:

  1. Platform op input schemas and context bindings (op_catalog.py). This is the load-bearing safety surface. For each new op (create_trigger, create_cron, delete_trigger, delete_cron), confirm that data.references and data.selector are bound from $ctx and stripped from the model-facing schema. The model must not be able to route a trigger to an arbitrary target.

  2. relay.ts body-before-URL ordering change. The PR assembles the request body before calling directCallUrl so path parameters are available for substitution. Confirm that assembling the body earlier does not change any observable behavior for existing non-DELETE ops (none of them use path params today, but the order change touches the general path).

  3. direct.ts DELETE allowlist. Adding DELETE to the DirectCallMethod union opens delete calls to any op that uses it. Confirm the path-param substitution logic extracts {id}-style params correctly and removes the substituted key from the body before it is sent as the request body.

  4. discover_triggers keyword scoring (service.py). The _score_trigger_match function is in-process keyword overlap against the Composio catalog. There are no tests for the scoring logic itself, only for the endpoint shape. Consider whether a low-confidence match (score of 1 on a common word) could return a wrong integration with high confidence to the model.

items = [str(v).strip() for v in value if str(v).strip()]
if not items:
raise ValueError("use_cases must contain at least one non-empty fragment")
return items

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 The AI agent says: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the discovery request schema strict.

provider accepts any string, and _require_use_cases() stringifies arbitrary list items (123, {}) instead of rejecting them. That turns malformed payloads into meaningless searches instead of a 422.

 class TriggerDiscoveryQuery(BaseModel):
     """Request body for ``POST /triggers/discover``."""

     use_cases: List[str]
-    provider: str = TriggerProviderKind.COMPOSIO.value
+    provider: TriggerProviderKind = TriggerProviderKind.COMPOSIO
     limit_alternatives: int = Field(default=3, ge=0)

     @field_validator("use_cases", mode="before")
     @classmethod
     def _require_use_cases(cls, value: Any) -> List[str]:
         if not isinstance(value, list):
             raise ValueError("use_cases must be a list of non-empty fragments")
-        items = [str(v).strip() for v in value if str(v).strip()]
+        if any(not isinstance(v, str) for v in value):
+            raise ValueError("use_cases must be a list of strings")
+        items = [v.strip() for v in value if v.strip()]
         if not items:
             raise ValueError("use_cases must contain at least one non-empty fragment")
         return items

return items


TriggerDiscoveryResponse = TriggerCapabilitiesResult

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 The AI agent says: 📄 Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Wrap /discover in a normal API response envelope.

Aliasing the response to the core DTO makes this endpoint the odd one out in the router: the rest return {count, ...} envelopes, but this one exposes the core result directly. Please define an explicit API response model here and wrap the service DTO at the route boundary. As per coding guidelines, "Define explicit request and response models in models.py, include count plus payload in response envelopes."

provider_key: str,
use_case: str,
limit_alternatives: int,
) -> List[Tuple[int, TriggerCatalogEvent, TriggerCatalogIntegration]]:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 The AI agent says: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use a named DTO for discovery matches instead of tuples.

Returning List[Tuple[int, TriggerCatalogEvent, TriggerCatalogIntegration]] from a method in core/**/service.py violates the core service contract and leaks positional tuple unpacking through the discovery flow. As per coding guidelines, "Do not return raw dicts or tuples from service methods or clients; define named DTOs in core/{domain}/dtos.py instead." and "Service methods must return typed DTOs (Pydantic BaseModel subclasses), not raw dicts, tuples, or Any."

)
)

return sorted(matches, key=lambda item: item[0], reverse=True)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 The AI agent says: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Drop zero-score candidates before returning matches.

When _candidate_integrations() / _candidate_events() fall back to unfiltered catalog pages, this helper still appends score-0 events. discover_triggers() then surfaces an arbitrary event instead of hitting its no-match path.

             for event in events:
                 key = (event.integration or integration.key, event.key)
                 if key in seen:
                     continue
+                score = _score_trigger_match(
+                    use_case=use_case,
+                    event=event,
+                    integration=integration,
+                )
+                if score <= 0:
+                    continue
                 seen.add(key)
-                matches.append(
-                    (
-                        _score_trigger_match(
-                            use_case=use_case,
-                            event=event,
-                            integration=integration,
-                        ),
-                        event,
-                        integration,
-                    )
-                )
+                matches.append((score, event, integration))

}
},
"required": ["id"],
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 The AI agent says: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Close the no-arg and id-only schemas.

_EMPTY_INPUT_SCHEMA and _TRIGGER_ID_INPUT_SCHEMA currently leave additionalProperties open, so list_connections plus the POST pause_* / resume_* ops expose arbitrary extra body fields to the backend. That makes these tools depend on server-side internals instead of the cataloged request shape.

-_EMPTY_INPUT_SCHEMA: Dict[str, Any] = {"type": "object", "properties": {}}
+_EMPTY_INPUT_SCHEMA: Dict[str, Any] = {
+    "type": "object",
+    "properties": {},
+    "additionalProperties": False,
+}
 _TRIGGER_ID_INPUT_SCHEMA: Dict[str, Any] = {
     "type": "object",
     "properties": {
         "id": {
             "type": "string",
             "description": "The schedule or subscription id returned by the list tools.",
         }
     },
     "required": ["id"],
+    "additionalProperties": False,
 }

@mmabrouk

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
services/agent/tests/unit/tool-direct.test.ts (1)

382-421: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add one relay-level test for path-param stripping.

These cases lock down URL construction, but they will not catch the new POST regression because executeRelayedTool() is the layer that still forwards the original body. A single test that inspects the fetch payload after {id} substitution would cover the contract this PR is trying to add.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 02c0305c-383f-4ef7-9b3e-ddefa6a78701

📥 Commits

Reviewing files that changed from the base of the PR and between 420f61c and 2f3b95e.

⛔ Files ignored due to path filters (1)
  • services/agent/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • api/oss/src/apis/fastapi/triggers/models.py
  • api/oss/src/apis/fastapi/triggers/router.py
  • api/oss/src/core/triggers/dtos.py
  • api/oss/src/core/triggers/service.py
  • api/oss/tests/pytest/unit/triggers/test_triggers_discovery.py
  • sdks/python/agenta/sdk/agents/platform/op_catalog.py
  • sdks/python/agenta/sdk/agents/tools/models.py
  • sdks/python/oss/tests/pytest/unit/agents/platform/test_op_catalog.py
  • services/agent/src/protocol.ts
  • services/agent/src/tools/direct.ts
  • services/agent/src/tools/relay.ts
  • services/agent/tests/unit/tool-direct.test.ts

Comment thread api/oss/src/core/triggers/service.py Outdated
Comment thread api/oss/tests/pytest/unit/triggers/test_triggers_discovery.py
Comment thread sdks/python/agenta/sdk/agents/platform/op_catalog.py
Comment thread services/agent/src/tools/direct.ts

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the tests here are pretty thin for a complicated logic. please update with much more use cases

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 The AI agent says: Expanded the suite substantially in cc2e131. Added direct coverage for the scoring/evidence helpers (_discovery_terms stopword/dedup/short-token handling, _match_signal exact-phrase vs distinct-term counting, zero-score cases, _has_primary_evidence), for _discover_events_for_use_case (score-descending ranking and the zero-score drop), and for discover_triggers end to end (weak single-term -> no-match, no-events -> no-match, mixed match/no-match across multiple use cases, and slug-collision increment in the connection-state path). 22 tests pass.

min_length=1, description="Model-facing description (SDK-owned)."
)
method: Literal["GET", "POST"]
method: Literal["GET", "POST", "DELETE"]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason we dont want to include put?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 The AI agent says: Intentionally excluded for now. No cataloged op targets a PUT endpoint: the edit endpoints (PUT /triggers/subscriptions/{id}, PUT /triggers/schedules/{id}, the workflow edits) are not exposed as platform ops, lifecycle is POST .../stop/.../start, and deletes are DELETE. The whole direct-call path is GET/POST/DELETE only too — DIRECT_CALL_METHODS in services/agent/src/tools/direct.ts, the callDirect method type, and the body-sent-only-on-POST branch — so widening this Literal alone would be incoherent. When we catalog an edit op we will add PUT to the Literal and the runner allowlist + body handling together. Leaving as-is.

@mmabrouk mmabrouk removed the needs-review Agent updated; awaiting Mahmoud's review label Jun 29, 2026
@mmabrouk mmabrouk force-pushed the feat/agent-skills-4918-v2 branch from 4b8d038 to 851f2e0 Compare June 29, 2026 19:50
@mmabrouk mmabrouk force-pushed the feat/agent-builder-tools-4919-v2 branch from 19a2f07 to adf0f9e Compare June 29, 2026 19:50
mmabrouk added a commit that referenced this pull request Jun 29, 2026
…+ strip path params

Address PR #4931 review:
- triggers discovery: gate primary match on stronger evidence (exact phrase or
  >=2 distinct terms) and drop zero-score candidates so weak/ambiguous use cases
  fall to the no-match path instead of surfacing an arbitrary integration.
- op_catalog: set additionalProperties:false on the create-schedule/subscription
  schemas (and their data objects) so the model cannot retarget via references/selector.
- direct-call relay: strip substituted path params out of the POST body.
- tests: substantially expand trigger-discovery coverage; assert ValidationError.

Claude-Session: https://claude.ai/code/session_01LUTGjUDzSX1tiJwqnqACtj
@mmabrouk mmabrouk force-pushed the feat/agent-skills-4918-v2 branch from 851f2e0 to 2340380 Compare June 29, 2026 20:20
mmabrouk added a commit that referenced this pull request Jun 29, 2026
…+ strip path params

Address PR #4931 review:
- triggers discovery: gate primary match on stronger evidence (exact phrase or
  >=2 distinct terms) and drop zero-score candidates so weak/ambiguous use cases
  fall to the no-match path instead of surfacing an arbitrary integration.
- op_catalog: set additionalProperties:false on the create-schedule/subscription
  schemas (and their data objects) so the model cannot retarget via references/selector.
- direct-call relay: strip substituted path params out of the POST body.
- tests: substantially expand trigger-discovery coverage; assert ValidationError.

Claude-Session: https://claude.ai/code/session_01LUTGjUDzSX1tiJwqnqACtj
@mmabrouk mmabrouk force-pushed the feat/agent-builder-tools-4919-v2 branch from cc2e131 to c0ccd67 Compare June 29, 2026 20:20
@mmabrouk mmabrouk force-pushed the feat/agent-skills-4918-v2 branch from 2340380 to 505fb1b Compare June 29, 2026 20:49
mmabrouk added a commit that referenced this pull request Jun 29, 2026
…+ strip path params

Address PR #4931 review:
- triggers discovery: gate primary match on stronger evidence (exact phrase or
  >=2 distinct terms) and drop zero-score candidates so weak/ambiguous use cases
  fall to the no-match path instead of surfacing an arbitrary integration.
- op_catalog: set additionalProperties:false on the create-schedule/subscription
  schemas (and their data objects) so the model cannot retarget via references/selector.
- direct-call relay: strip substituted path params out of the POST body.
- tests: substantially expand trigger-discovery coverage; assert ValidationError.

Claude-Session: https://claude.ai/code/session_01LUTGjUDzSX1tiJwqnqACtj
@mmabrouk mmabrouk force-pushed the feat/agent-builder-tools-4919-v2 branch from c0ccd67 to 34f4351 Compare June 29, 2026 20:49
@mmabrouk mmabrouk force-pushed the feat/agent-skills-4918-v2 branch from 505fb1b to b7c6154 Compare June 29, 2026 23:19
mmabrouk added a commit that referenced this pull request Jun 29, 2026
…+ strip path params

Address PR #4931 review:
- triggers discovery: gate primary match on stronger evidence (exact phrase or
  >=2 distinct terms) and drop zero-score candidates so weak/ambiguous use cases
  fall to the no-match path instead of surfacing an arbitrary integration.
- op_catalog: set additionalProperties:false on the create-schedule/subscription
  schemas (and their data objects) so the model cannot retarget via references/selector.
- direct-call relay: strip substituted path params out of the POST body.
- tests: substantially expand trigger-discovery coverage; assert ValidationError.

Claude-Session: https://claude.ai/code/session_01LUTGjUDzSX1tiJwqnqACtj
@mmabrouk mmabrouk force-pushed the feat/agent-builder-tools-4919-v2 branch from 34f4351 to d4b566e Compare June 29, 2026 23:19
@mmabrouk mmabrouk force-pushed the feat/agent-skills-4918-v2 branch from b7c6154 to 9a02b8a Compare June 29, 2026 23:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant