Skip to content

New Components - loopquest#21304

Open
TomPhillipsLabs wants to merge 10 commits into
PipedreamHQ:masterfrom
TomPhillipsLabs:add-loopquest-components
Open

New Components - loopquest#21304
TomPhillipsLabs wants to merge 10 commits into
PipedreamHQ:masterfrom
TomPhillipsLabs:add-loopquest-components

Conversation

@TomPhillipsLabs

@TomPhillipsLabs TomPhillipsLabs commented Jul 1, 2026

Copy link
Copy Markdown

LoopQuest

Adds components for LoopQuest — a human-in-the-loop review layer for AI/automation output. Send an item to a person to review, gate a downstream action until it's approved, or monitor quality in the background.

Components

  • Create Review Task (action) — POST /api/v1/tasks. Pick a review game (swiper, versus, sorter, detective, fixer, redact, grounding) and a mode (gate blocks until approval, monitor reviews out-of-band), with an optional timeout + on-timeout fallback.
  • New Verdict (source) — a webhook source. Registers its endpoint with LoopQuest (POST /api/v1/hooks) on deploy and unsubscribes on teardown; emits each resolved verdict deduped by task_id.
  • Get Task Status (action) — GET /api/v1/tasks/{id}.

Auth

API key (api_key), sent as Authorization: Bearer. Connection test hits GET /api/v1/me. Optional base_url for self-hosted deployments.

Notes

Summary by CodeRabbit

  • New Features
    • Added LoopQuest actions to create review tasks and check task status.
    • Added a new LoopQuest source to receive verdict updates and emit corresponding events.
    • Introduced LoopQuest app support for creating tasks, fetching task status, and managing verdict webhook subscriptions.
  • Bug Fixes
    • Improved verdict handling by validating incoming task data and acknowledging requests immediately.
    • Enhanced task summaries by including external identifiers when available.

@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
pipedream-docs-redirect-do-not-edit Ignored Ignored Jul 1, 2026 9:13pm

Request Review

@pipedream-component-development

Copy link
Copy Markdown
Collaborator

Thank you so much for submitting this! We've added it to our backlog to review, and our team has been notified.

@pipedream-component-development

Copy link
Copy Markdown
Collaborator

Thanks for submitting this PR! When we review PRs, we follow the Pipedream component guidelines. If you're not familiar, here's a quick checklist:

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ce21d828-98b0-4320-ad23-f04c21b64673

📥 Commits

Reviewing files that changed from the base of the PR and between a0f73d8 and 75760e7.

📒 Files selected for processing (4)
  • components/loopquest/actions/create-review-task/create-review-task.mjs
  • components/loopquest/actions/get-task-status/get-task-status.mjs
  • components/loopquest/loopquest.app.mjs
  • components/loopquest/sources/new-verdict/new-verdict.mjs

📝 Walkthrough

Walkthrough

Adds a new LoopQuest Pipedream component with an app definition, task body builder, two actions, a verdict webhook source, and package metadata.

Changes

LoopQuest component

Layer / File(s) Summary
App definition, HTTP helpers, and task body builder
components/loopquest/loopquest.app.mjs, components/loopquest/common/body.mjs
Defines propDefinitions, base URL and header helpers, task creation and lookup methods, webhook subscription methods, and the request body builder used for task creation.
Create review task and get task status actions
components/loopquest/actions/create-review-task/create-review-task.mjs, components/loopquest/actions/get-task-status/get-task-status.mjs
Adds actions to create a review task via createTask and fetch task status via getTask, each exporting a $summary.
New-verdict webhook source
components/loopquest/sources/new-verdict/new-verdict.mjs
Adds a source that subscribes to verdict webhooks on activate, unsubscribes on deactivate, acknowledges HTTP deliveries, and emits derived verdict events.
Package metadata
components/loopquest/package.json
Adds package identity, ESM module config, and dependency on @pipedream/platform.

Estimated code review effort: 2 (Simple) | ~15 minutes

Suggested reviewers: michelle0927

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too generic and doesn't clearly describe the new LoopQuest app and components added. Make the title specific to the change, e.g. mention the new LoopQuest app and its review task/source components.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description is mostly complete and covers summary, auth, versioning, and new-app notes, though the checklist items aren't explicitly checked off.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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

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.

@TomPhillipsLabs

Copy link
Copy Markdown
Author

Auth details for the loopquest app

LoopQuest is a key-based app (not OAuth). Everything you need to register it:

Custom fields (slugs must match — the components read this.$auth.api_key / this.$auth.base_url):

Slug Label Type Required Secret Default
api_key API Key string yes yes
base_url Base URL string no no https://loopquest.tomphillips.uk

Usage: sent on every request as Authorization: Bearer {{api_key}}.

Connection test / verification:

GET {{base_url}}/api/v1/me
Authorization: Bearer {{api_key}}
→ 200 { "org_id": "...", "workspace": "..." }   (valid key)
→ 401                                            (invalid key)

Test key for validation (a dedicated, disposable review workspace — please feel free to use it to test all three components; I'll delete it after the review):

lq_H5_KpmUXndqXoM2hb1d1S8SLRrWfQDcV

Docs: https://loopquest.tomphillips.uk/docs · OpenAPI: https://loopquest.tomphillips.uk/openapi.json · Website: https://loopquest.tomphillips.uk

Happy to provide a logo asset in any size you need. Thanks!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/loopquest/actions/create-review-task/create-review-task.mjs`:
- Line 6: The action description text in create-review-task.mjs uses the wrong
documentation link label; update the description string in the createReviewTask
metadata so the markdown link text reads “[See the documentation]” instead of
“[See the docs]”, while keeping the same URL and surrounding sentence unchanged.
- Around line 3-45: The Create Review Task action is missing the required
annotations metadata on its exported object. Update the default export in
create-review-task.mjs to include an annotations object with readOnlyHint set to
false, destructiveHint set to false, and openWorldHint set to true, keeping the
rest of the action definition unchanged.

In `@components/loopquest/actions/get-task-status/get-task-status.mjs`:
- Line 6: The action description in get-task-status.mjs uses the wrong
documentation link phrasing. Update the description string in the LoopQuest task
status action to match the shared convention used by create-review-task.mjs,
replacing the end text with "[See the documentation]" while keeping the rest of
the description unchanged.
- Around line 3-18: The Get Task Status action is missing the required
annotations metadata. Update the action object in get-task-status.mjs to add an
annotations object on the exported definition, matching the pattern used by
create-review-task, and set readOnlyHint to true, destructiveHint to false, and
openWorldHint to true. Keep the change alongside the existing
loopquest-get-task-status action config so the hints are declared on the action
itself.

In `@components/loopquest/common/body.mjs`:
- Around line 13-18: In body.mjs, the manual truthiness checks around optional
request fields are unnecessary because axios drops undefined values
automatically. Update the body-building logic in the body assembly block to
assign p.source, p.externalId, p.callbackUrl, p.timeoutSeconds, p.onTimeout, and
p.reviewsRequired directly to body.source, body.external_id, body.callback_url,
body.timeout_seconds, body.on_timeout, and body.reviews_required without
wrapping them in if statements.

In `@components/loopquest/loopquest.app.mjs`:
- Line 9: Add missing description fields to the propDefinitions in
loopquest.app.mjs for title, reviewsRequired, and taskId. Update the
propDefinition entries near the existing labels so each includes a concise
description, and make taskId’s description explicitly tell users it is the id
returned by createTask and used by get-task-status. Use the existing
propDefinition objects and related symbols like createTask and get-task-status
to place the guidance correctly.
- Around line 73-87: The public API methods in loopquest.app.mjs use mixed
calling conventions, with createTask/getTask accepting destructured option
objects while subscribeVerdicts/unsubscribeVerdicts use positional arguments.
Update subscribeVerdicts and unsubscribeVerdicts to match the same destructured
signature style as the other methods, and adjust their internal references to
use the named fields consistently.
- Around line 57-87: The public methods createTask, getTask, subscribeVerdicts,
and unsubscribeVerdicts are duplicating raw axios request setup instead of using
the shared request helper. Refactor these methods in loopquest.app.mjs to
delegate to the existing private _makeRequest() (or add one if needed) so
headers, base URL, and axios options are centralized. Keep the method-specific
pieces only in the call site by passing the HTTP method, path, and payload
through the helper. Ensure the helper is the single place that constructs the
axios($, ...) call.

In `@components/loopquest/sources/new-verdict/new-verdict.mjs`:
- Around line 34-44: The event dedupe key in the new-verdict handler is too
coarse because `this.$emit` uses only `body.task_id`, which can collapse
distinct LoopQuest deliveries like escalation and final resolution into one.
Update the `new-verdict` flow so the `id` is unique per delivery by combining
`body.task_id` with the computed `verdict`/state, or by using a
delivery-specific payload field if available, while keeping it stable for the
same event.
- Around line 18-25: Direct `this.db.set`/`this.db.get` usage in `activate()`
and `deactivate()` violates the state access pattern for `$.service.db`. Add
private helper methods on the same service (for example `_setHookId()` and
`_getHookId()`) and move the `hookId` read/write logic there, then update
`activate()`/`deactivate()` to call those helpers instead of touching `db`
directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 88a94710-cad3-40ff-bda8-e0ef7af4bf63

📥 Commits

Reviewing files that changed from the base of the PR and between bb5d8e3 and a0f73d8.

📒 Files selected for processing (6)
  • components/loopquest/actions/create-review-task/create-review-task.mjs
  • components/loopquest/actions/get-task-status/get-task-status.mjs
  • components/loopquest/common/body.mjs
  • components/loopquest/loopquest.app.mjs
  • components/loopquest/package.json
  • components/loopquest/sources/new-verdict/new-verdict.mjs

Comment thread components/loopquest/actions/create-review-task/create-review-task.mjs Outdated
Comment thread components/loopquest/actions/get-task-status/get-task-status.mjs
Comment thread components/loopquest/actions/get-task-status/get-task-status.mjs Outdated
Comment on lines +13 to +18
if (p.source) body.source = p.source;
if (p.externalId) body.external_id = p.externalId;
if (p.callbackUrl) body.callback_url = p.callbackUrl;
if (p.timeoutSeconds) body.timeout_seconds = p.timeoutSeconds;
if (p.onTimeout) body.on_timeout = p.onTimeout;
if (p.reviewsRequired) body.reviews_required = p.reviewsRequired;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Manual truthiness guards are unnecessary — rely on automatic undefined stripping.

Since these values are sent via @pipedream/platform's axios as a JSON data payload, undefined fields are dropped automatically during serialization. The if (p.x) body.x = ... guards can be replaced with direct unconditional assignment, per the guideline that optional props may be passed directly into request bodies without truthiness checks.

♻️ Proposed simplification
-  if (p.source) body.source = p.source;
-  if (p.externalId) body.external_id = p.externalId;
-  if (p.callbackUrl) body.callback_url = p.callbackUrl;
-  if (p.timeoutSeconds) body.timeout_seconds = p.timeoutSeconds;
-  if (p.onTimeout) body.on_timeout = p.onTimeout;
-  if (p.reviewsRequired) body.reviews_required = p.reviewsRequired;
+  body.source = p.source;
+  body.external_id = p.externalId;
+  body.callback_url = p.callbackUrl;
+  body.timeout_seconds = p.timeoutSeconds;
+  body.on_timeout = p.onTimeout;
+  body.reviews_required = p.reviewsRequired;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (p.source) body.source = p.source;
if (p.externalId) body.external_id = p.externalId;
if (p.callbackUrl) body.callback_url = p.callbackUrl;
if (p.timeoutSeconds) body.timeout_seconds = p.timeoutSeconds;
if (p.onTimeout) body.on_timeout = p.onTimeout;
if (p.reviewsRequired) body.reviews_required = p.reviewsRequired;
body.source = p.source;
body.external_id = p.externalId;
body.callback_url = p.callbackUrl;
body.timeout_seconds = p.timeoutSeconds;
body.on_timeout = p.onTimeout;
body.reviews_required = p.reviewsRequired;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/loopquest/common/body.mjs` around lines 13 - 18, In body.mjs, the
manual truthiness checks around optional request fields are unnecessary because
axios drops undefined values automatically. Update the body-building logic in
the body assembly block to assign p.source, p.externalId, p.callbackUrl,
p.timeoutSeconds, p.onTimeout, and p.reviewsRequired directly to body.source,
body.external_id, body.callback_url, body.timeout_seconds, body.on_timeout, and
body.reviews_required without wrapping them in if statements.

Source: Coding guidelines

Comment thread components/loopquest/loopquest.app.mjs Outdated
Comment thread components/loopquest/loopquest.app.mjs Outdated
Comment thread components/loopquest/loopquest.app.mjs Outdated
Comment on lines +18 to +25
async activate() {
const { id } = await this.loopquest.subscribeVerdicts(this, this.http.endpoint);
this.db.set("hookId", id);
},
async deactivate() {
const hookId = this.db.get("hookId");
if (hookId) await this.loopquest.unsubscribeVerdicts(this, hookId);
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Encapsulate db access in private helper methods.

this.db.set/this.db.get are called directly inside activate()/deactivate(). Per path instructions, state stored in $.service.db should go through private helper methods rather than being called directly from event handlers.

♻️ Proposed refactor
+  methods: {
+    _getHookId() {
+      return this.db.get("hookId");
+    },
+    _setHookId(id) {
+      this.db.set("hookId", id);
+    },
+  },
   hooks: {
     async activate() {
       const { id } = await this.loopquest.subscribeVerdicts(this, this.http.endpoint);
-      this.db.set("hookId", id);
+      this._setHookId(id);
     },
     async deactivate() {
-      const hookId = this.db.get("hookId");
+      const hookId = this._getHookId();
       if (hookId) await this.loopquest.unsubscribeVerdicts(this, hookId);
     },
   },

As per path instructions, "State stored in $.service.db should be accessed through private methods (_getX(), _setX()), not called directly from run() or event handlers."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async activate() {
const { id } = await this.loopquest.subscribeVerdicts(this, this.http.endpoint);
this.db.set("hookId", id);
},
async deactivate() {
const hookId = this.db.get("hookId");
if (hookId) await this.loopquest.unsubscribeVerdicts(this, hookId);
},
methods: {
_getHookId() {
return this.db.get("hookId");
},
_setHookId(id) {
this.db.set("hookId", id);
},
},
async activate() {
const { id } = await this.loopquest.subscribeVerdicts(this, this.http.endpoint);
this._setHookId(id);
},
async deactivate() {
const hookId = this._getHookId();
if (hookId) await this.loopquest.unsubscribeVerdicts(this, hookId);
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/loopquest/sources/new-verdict/new-verdict.mjs` around lines 18 -
25, Direct `this.db.set`/`this.db.get` usage in `activate()` and `deactivate()`
violates the state access pattern for `$.service.db`. Add private helper methods
on the same service (for example `_setHookId()` and `_getHookId()`) and move the
`hookId` read/write logic there, then update `activate()`/`deactivate()` to call
those helpers instead of touching `db` directly.

Source: Path instructions

Comment thread components/loopquest/sources/new-verdict/new-verdict.mjs
@TomPhillipsLabs

Copy link
Copy Markdown
Author

Thanks @coderabbitai — addressed 8 of the 10 in the latest commits:

  • Docs link label[See the documentation] on all three components.
  • annotations added to both actions (Create: readOnlyHint:false; Get Status: readOnlyHint:true; both destructiveHint:false, openWorldHint:true).
  • Prop descriptions added for title, reviewsRequired, taskId (taskId notes it's returned by Create Review Task, used by Get Task Status).
  • Shared _makeRequest()createTask/getTask/subscribeVerdicts/unsubscribeVerdicts now all delegate to it (single axios call site).
  • Consistent signaturessubscribeVerdicts/unsubscribeVerdicts now use the destructured { $, ... } style like the others; updated the source's calls.
  • Dedupe key$emit id is now ${task_id}:${state} so distinct states can't collapse.

Skipped 2, with reasoning:

  • body.mjs truthiness guards — kept intentionally. buildTaskBody is a pure, unit-tested helper whose tests assert unset keys are absent from the object; the guards keep it independently testable rather than relying on axios's undefined-stripping side-effect.
  • db helper methods — direct this.db.get/set on $.service.db is the pattern shown throughout the Pipedream component docs; wrapping two calls in private helpers adds indirection without a functional benefit.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@TomPhillipsLabs Thanks for the detailed rundown and for working through the checklist so thoroughly!

The 8 fixes all sound solid — the shared _makeRequest() consolidation and the ${task_id}:${state} dedupe key in particular are nice improvements for correctness and maintainability.

On the 2 you skipped, both justifications are reasonable and I won't push further:

  • body.mjs truthiness guards: Fair point — keeping the guards explicit makes the function's contract (only present keys are truthy/set) easy to unit test without relying on downstream axios behavior to strip undefined values. That's a legitimate design choice for a pure helper.
  • db helper methods: Agreed, direct this.db.get/this.db.set calls are the standard pattern shown in Pipedream's own component examples, so adding wrapper methods here would just be extra indirection for two call sites.

Nice work getting this PR into shape. Let me know if you'd like another pass once the auth details are wired up on the platform side, or if there's anything else you'd like me to look at.

🐇✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

User submitted Submitted by a user

Projects

Status: Ready for PR Review

Development

Successfully merging this pull request may close these issues.

4 participants