npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pushary/mastra

v0.2.0

Published

Human-in-the-loop for Mastra: a blocking createTool that asks a real human on their phone, plus a durable suspend/resume workflow step.

Readme

@pushary/mastra

CI npm license

Full walkthrough: Human-in-the-loop for Mastra. Reaching your own end-users on their phones is the Pushary Partner plan.

Human-in-the-loop for Mastra. Ask a real human to approve, and get the answer on their phone. Two seams, pick by wait length:

  • A blocking createTool for an approval that resolves in well under a minute.
  • A durable workflow step (suspend/resume) for long waits. Mastra persists the snapshot, so the wait holds no idle compute and survives a restart.

Requires the Pushary Partner plan and @mastra/core v1.

Install

npm i @pushary/mastra @mastra/core zod

Set PUSHARY_API_KEY (get it in your dashboard).

Connect a phone once

import { connect } from '@pushary/mastra'
const { universalLink } = await connect({ apiKey: process.env.PUSHARY_API_KEY! }, user.id)

Blocking tool

import { createPusharyAskTool } from '@pushary/mastra'
import { Agent } from '@mastra/core/agent'

const askHuman = createPusharyAskTool({ apiKey: process.env.PUSHARY_API_KEY! }, { externalId: user.id })

const agent = new Agent({
  name: 'Support',
  instructions: 'Call ask-human before any refund.',
  model,
  tools: { askHuman },
})

The tool blocks until the person answers and returns { approved, value, status }, fail-closed. externalId is bound in code, never taken from model input.

Gating a tool the model cannot skip

createPusharyAskTool is a tool the model chooses to call. That is right for "go ask someone about this", and wrong for "this must not happen without a yes", because a model that does not want to be interrupted can decline to call it.

Mastra's own gate splits in two: requireApproval decides whether a human is needed, and the run then suspends. Nothing asks anyone. resolvePusharyApprovals asks the person and resumes or declines:

import { createTool } from '@mastra/core/tools'
import { pusharyRequireApproval, resolvePusharyApprovals } from '@pushary/mastra'

const issueRefund = createTool({
  id: 'issue-refund',
  description: 'Refund an order',
  inputSchema: z.object({ amount: z.number() }),
  outputSchema: z.object({ ok: z.boolean() }),
  requireApproval: pusharyRequireApproval(),
  execute: async ({ amount }) => ({ ok: await chargeBack(amount) }),
})

const output = await agent.generate('Refund order 1234', { requireToolApproval: true })
if (output.finishReason === 'suspended') {
  await resolvePusharyApprovals({ externalId: user.id }, { agent })
}

With no runs passed it lists the agent's own suspended runs, so a background worker can drain approvals for a whole thread with the same call. Scope it with threadId or resourceId, or hand in runs you already have.

Each decision is keyed on runId plus toolCallId, so running this twice against the same suspended run resolves to the same decision rather than paging twice. A tool that suspended for its own resume data (rather than for approval) is left alone.

Fail-closed: a denial, an expiry, or nobody answering all decline, with the reason handed to the model. For a multi-tenant product, resolve the end-user per call:

resolvePusharyApprovals({ externalId: (pending) => ownerOf(pending.runId) }, { agent })

Durable step

import { pusharyApprovalStep } from '@pushary/mastra'
import { createWorkflow } from '@mastra/core/workflows'
import { z } from 'zod'

const approval = pusharyApprovalStep(
  { apiKey: process.env.PUSHARY_API_KEY! },
  { callbackUrl: `${process.env.PUBLIC_URL}/api/pushary/callback` },
)

export const refund = createWorkflow({
  id: 'refund',
  inputSchema: z.object({ question: z.string(), externalId: z.string() }),
  outputSchema: z.object({ approved: z.boolean(), value: z.string() }),
})
  .then(approval)
  .commit()

Run it, and it suspends at the approval step. Resume from the callback route:

import { resolvePusharyCallback } from '@pushary/mastra'

// POST /api/pushary/callback
export async function POST(req: Request) {
  const raw = await req.text()
  const cb = resolvePusharyCallback(raw, req.headers.get('x-pushary-signature'), process.env.PUSHARY_WEBHOOK_SECRET!)
  if (!cb) return new Response('bad signature', { status: 401 })
  const runId = await lookupRun(cb.correlationId) // your own correlationId -> runId map
  const run = await refund.createRun({ runId })
  await run.resume({ step: approval, resumeData: { answer: cb.answer } })
  return new Response('ok')
}

API

  • connect(config, externalId) — enroll an end-user's phone.
  • createPusharyAskTool(config, { externalId }) — a Mastra createTool that blocks on a human.
  • pusharyRequireApproval() — a requireApproval predicate that routes every call to a human.
  • resolvePusharyApprovals(config, { agent, runs?, threadId?, resourceId? }) — ask about each suspended tool call, then approve or decline it.
  • pusharyApprovalStep(config, { callbackUrl }) — a durable createStep with suspend/resume.
  • createPusharyGate(config) — the raw fail-closed gate, for anything the helpers above do not cover.
  • resolvePusharyCallback(raw, signature, secret) — verify + parse a callback into { correlationId, answer, approved, ... }.
  • askExternalUser, createDurableDecision, describeAnswer, isAffirmative, deterministicKey, SIGNATURE_HEADER.

Example

A runnable example is in examples/.

License

MIT