@mbandeira/reach 0.1.0

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.
@@ -0,0 +1,22 @@
1
+ import type { FormConfig, FormAdapters } from './types.js';
2
+ /**
3
+ * Creates a Mastra tool that runs a multi-turn form with the user.
4
+ *
5
+ * The tool drives one question per turn via Mastra's suspend()/resume()
6
+ * mechanism. autoResumeSuspendedTools carries the conversation forward
7
+ * automatically on every subsequent user message — no extra orchestration
8
+ * needed. Answer persistence, LLM parsing, and delivery are all provided
9
+ * by the caller via `adapters`.
10
+ */
11
+ export declare function createForm<TAnswers extends Record<string, unknown>>(config: FormConfig<TAnswers>, adapters: FormAdapters): import("@mastra/core/tools").Tool<{}, {
12
+ message: string;
13
+ }, {
14
+ message: string;
15
+ }, {
16
+ userMessage: string;
17
+ }, import("@mastra/core/tools").ToolExecutionContext<{
18
+ message: string;
19
+ }, {
20
+ userMessage: string;
21
+ }, unknown>, string, unknown>;
22
+ //# sourceMappingURL=create-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-form.d.ts","sourceRoot":"","sources":["../../src/forms/create-form.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAU1D;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjE,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC5B,QAAQ,EAAE,YAAY;;;;;;;;;;8BAyDvB"}
@@ -0,0 +1,63 @@
1
+ import { createTool } from '@mastra/core/tools';
2
+ import { z } from 'zod';
3
+ /**
4
+ * Creates a Mastra tool that runs a multi-turn form with the user.
5
+ *
6
+ * The tool drives one question per turn via Mastra's suspend()/resume()
7
+ * mechanism. autoResumeSuspendedTools carries the conversation forward
8
+ * automatically on every subsequent user message — no extra orchestration
9
+ * needed. Answer persistence, LLM parsing, and delivery are all provided
10
+ * by the caller via `adapters`.
11
+ */
12
+ export function createForm(config, adapters) {
13
+ return createTool({
14
+ id: config.id,
15
+ description: config.description,
16
+ inputSchema: z.object({}),
17
+ outputSchema: z.object({ message: z.string() }),
18
+ suspendSchema: z.object({ message: z.string() }),
19
+ resumeSchema: z.object({ userMessage: z.string() }),
20
+ execute: async (_input, context) => {
21
+ const ctx = context;
22
+ const { resumeData, suspend } = ctx.agent ?? {};
23
+ const userId = adapters.getUserId(context);
24
+ if (!userId)
25
+ throw new Error(`[reach/form:${config.id}] userId is required in execution context`);
26
+ if (!resumeData) {
27
+ await adapters.clearAnswers(userId, config.id);
28
+ }
29
+ const answers = await adapters.getAnswers(userId, config.id);
30
+ const pending = config.fields.find(f => answers[f.id] === undefined) ?? null;
31
+ if (resumeData?.userMessage && pending) {
32
+ const parsed = await adapters.parseAnswer({
33
+ field: pending,
34
+ rawAnswer: resumeData.userMessage,
35
+ instructions: config.instructions,
36
+ toolContext: context,
37
+ });
38
+ if (!parsed.accepted) {
39
+ if (pending.optional) {
40
+ answers[pending.id] = '__skipped__';
41
+ await adapters.saveAnswer(userId, config.id, pending.id, '__skipped__');
42
+ }
43
+ else {
44
+ return suspend({ message: parsed.reply });
45
+ }
46
+ }
47
+ else {
48
+ answers[pending.id] = parsed.value;
49
+ await adapters.saveAnswer(userId, config.id, pending.id, parsed.value);
50
+ }
51
+ }
52
+ const next = config.fields.find(f => answers[f.id] === undefined);
53
+ if (next) {
54
+ return suspend({ message: next.question });
55
+ }
56
+ const result = await config.onComplete(answers, {
57
+ userId,
58
+ mastra: ctx.mastra,
59
+ });
60
+ return { message: result.message };
61
+ },
62
+ });
63
+ }
@@ -0,0 +1,3 @@
1
+ export { createForm } from './create-form.js';
2
+ export type { FormField, FormConfig, FormAdapters, FormRunContext, ParsedAnswer } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1 @@
1
+ export { createForm } from './create-form.js';
@@ -0,0 +1,50 @@
1
+ export interface FormField {
2
+ id: string;
3
+ question: string;
4
+ /** Extra guidance for the LLM when interpreting this field's answer. */
5
+ interpret?: string;
6
+ optional?: boolean;
7
+ }
8
+ export interface FormRunContext {
9
+ userId: string;
10
+ mastra: unknown;
11
+ }
12
+ export interface ParsedAnswer {
13
+ value: unknown;
14
+ accepted: boolean;
15
+ /** Acknowledgment (if accepted) or a follow-up clarification (if not). */
16
+ reply: string;
17
+ }
18
+ export interface FormAdapters {
19
+ /** Extract the current user id from the raw Mastra tool execution context. */
20
+ getUserId: (context: unknown) => string | undefined;
21
+ /** Load all saved answers for this user + form. */
22
+ getAnswers: (userId: string, formId: string) => Promise<Record<string, unknown>>;
23
+ /** Persist a single field answer. */
24
+ saveAnswer: (userId: string, formId: string, fieldId: string, value: unknown) => Promise<void>;
25
+ /** Wipe all saved answers when the form starts fresh. */
26
+ clearAnswers: (userId: string, formId: string) => Promise<void>;
27
+ /** LLM-based answer validation. Fully owned by the caller — use whatever
28
+ * model/provider the application already has configured.
29
+ * `toolContext` is the raw Mastra tool execution context, forwarded as-is
30
+ * so the adapter can access `mastra`, `requestContext`, etc. */
31
+ parseAnswer: (params: {
32
+ field: FormField;
33
+ rawAnswer: string;
34
+ instructions: string;
35
+ toolContext: unknown;
36
+ }) => Promise<ParsedAnswer>;
37
+ }
38
+ export interface FormConfig<TAnswers extends Record<string, unknown> = Record<string, unknown>> {
39
+ id: string;
40
+ /** Shown to the LLM as the tool description — keep it action-oriented. */
41
+ description: string;
42
+ /** System-level instructions injected into every LLM call within this form. */
43
+ instructions: string;
44
+ fields: FormField[];
45
+ /** Called once all fields are collected. Return the closing message to deliver. */
46
+ onComplete: (answers: TAnswers, ctx: FormRunContext) => Promise<{
47
+ message: string;
48
+ }>;
49
+ }
50
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/forms/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,OAAO,CAAA;IACjB,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAA;IAEnD,mDAAmD;IACnD,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAEhF,qCAAqC;IACrC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAE9F,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAE/D;;;qEAGiE;IACjE,WAAW,EAAE,CAAC,MAAM,EAAE;QACpB,KAAK,EAAE,SAAS,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,OAAO,CAAA;KACrB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CAC5B;AAED,MAAM,WAAW,UAAU,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5F,EAAE,EAAE,MAAM,CAAA;IACV,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAA;IACnB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,mFAAmF;IACnF,UAAU,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACrF"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export { createForm } from './forms/index.js';
2
+ export type { FormField, FormConfig, FormAdapters, FormRunContext, ParsedAnswer } from './forms/index.js';
3
+ export { dispatch, createOutreachWorkflow } from './outreach/index.js';
4
+ export type { Outreach, SimpleOutreach, ToolOutreach, OutreachAdapters, DispatchResult } from './outreach/index.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEzG,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACtE,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createForm } from './forms/index.js';
2
+ export { dispatch, createOutreachWorkflow } from './outreach/index.js';
@@ -0,0 +1,14 @@
1
+ import type { Outreach, OutreachAdapters, DispatchResult } from './types.js';
2
+ /**
3
+ * Dispatches an outreach to a user.
4
+ *
5
+ * Warm path: generates or runs the outreach content and delivers it directly.
6
+ * Cold path: for tool outreaches, primes the tool in the agent's thread state
7
+ * so autoResumeSuspendedTools picks it up when the user replies to the
8
+ * template. For simple outreaches, cold dispatches are dropped unless a
9
+ * `sendTemplate` adapter is provided.
10
+ */
11
+ export declare function dispatch(mastra: unknown, userId: string, outreach: Outreach, adapters: OutreachAdapters, opts?: {
12
+ payload?: Record<string, unknown>;
13
+ }): Promise<DispatchResult>;
14
+ //# sourceMappingURL=dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/outreach/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE5E;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC3C,OAAO,CAAC,cAAc,CAAC,CA6BzB"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Dispatches an outreach to a user.
3
+ *
4
+ * Warm path: generates or runs the outreach content and delivers it directly.
5
+ * Cold path: for tool outreaches, primes the tool in the agent's thread state
6
+ * so autoResumeSuspendedTools picks it up when the user replies to the
7
+ * template. For simple outreaches, cold dispatches are dropped unless a
8
+ * `sendTemplate` adapter is provided.
9
+ */
10
+ export async function dispatch(mastra, userId, outreach, adapters, opts) {
11
+ const warm = await adapters.isWarm(userId);
12
+ if (warm) {
13
+ let text;
14
+ if (outreach.type === 'simple') {
15
+ const prompt = typeof outreach.prompt === 'function'
16
+ ? outreach.prompt(opts?.payload)
17
+ : outreach.prompt;
18
+ text = await adapters.generateMessage(prompt, userId, opts?.payload);
19
+ }
20
+ else {
21
+ text = await adapters.runTool(mastra, userId, outreach.toolId);
22
+ }
23
+ await adapters.sendMessage(userId, text);
24
+ return { delivered: true, warm: true };
25
+ }
26
+ if (outreach.type === 'tool') {
27
+ // Prime the tool in thread state — result discarded, only the suspension
28
+ // matters so autoResumeSuspendedTools can pick it up on the user's reply.
29
+ await adapters.runTool(mastra, userId, outreach.toolId);
30
+ }
31
+ if (adapters.sendTemplate) {
32
+ await adapters.sendTemplate(userId, outreach.slug);
33
+ return { delivered: true, warm: false };
34
+ }
35
+ return { delivered: false, warm: false };
36
+ }
@@ -0,0 +1,4 @@
1
+ export { dispatch } from './dispatch.js';
2
+ export { createOutreachWorkflow } from './workflow.js';
3
+ export type { Outreach, SimpleOutreach, ToolOutreach, OutreachAdapters, DispatchResult } from './types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/outreach/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { dispatch } from './dispatch.js';
2
+ export { createOutreachWorkflow } from './workflow.js';
@@ -0,0 +1,35 @@
1
+ /** A one-shot LLM-generated message — no conversation, no suspend/resume. */
2
+ export interface SimpleOutreach {
3
+ slug: string;
4
+ type: 'simple';
5
+ /** Static prompt string or a factory receiving the dispatch payload. */
6
+ prompt: string | ((payload?: Record<string, unknown>) => string);
7
+ }
8
+ /** Triggers a Mastra tool on the user's thread via toolChoice-forced agent.stream(). */
9
+ export interface ToolOutreach {
10
+ slug: string;
11
+ type: 'tool';
12
+ /** Must match the id of a tool registered on the agent. */
13
+ toolId: string;
14
+ }
15
+ export type Outreach = SimpleOutreach | ToolOutreach;
16
+ export interface DispatchResult {
17
+ delivered: boolean;
18
+ warm: boolean;
19
+ }
20
+ export interface OutreachAdapters {
21
+ /** True if the user has sent a message recently enough for direct delivery.
22
+ * The definition of "recently" (e.g. WhatsApp 24h window) is owned by the caller. */
23
+ isWarm: (userId: string) => Promise<boolean>;
24
+ /** Deliver a message directly to the user (warm path). */
25
+ sendMessage: (userId: string, text: string) => Promise<void>;
26
+ /** Send a template to re-open the messaging window (cold path).
27
+ * When absent, cold dispatches for simple outreaches are silently dropped. */
28
+ sendTemplate?: (userId: string, slug: string) => Promise<void>;
29
+ /** Generate text for a SimpleOutreach. Fully owned by the caller. */
30
+ generateMessage: (prompt: string, userId: string, payload?: Record<string, unknown>) => Promise<string>;
31
+ /** Force the agent to call a tool on the user's thread and return the first
32
+ * outbound message. Used both to deliver (warm) and to prime thread state (cold). */
33
+ runTool: (mastra: unknown, userId: string, toolId: string) => Promise<string>;
34
+ }
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/outreach/types.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,QAAQ,CAAA;IACd,wEAAwE;IACxE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,CAAA;CACjE;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,YAAY,CAAA;AAEpD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,OAAO,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B;0FACsF;IACtF,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAE5C,0DAA0D;IAC1D,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAE5D;mFAC+E;IAC/E,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAE9D,qEAAqE;IACrE,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAEvG;0FACsF;IACtF,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC9E"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import type { Outreach, OutreachAdapters } from './types.js';
2
+ /**
3
+ * Creates a Mastra workflow that dispatches a named outreach for a user.
4
+ *
5
+ * This is the required adapter for `mastra.schedules.create({ workflowId })`
6
+ * since scheduled agent runs don't support toolChoice. The workflow receives
7
+ * `{ userId, slug }` as input, resolves the outreach from the registry, and
8
+ * calls `dispatch`. One workflow covers all outreach types — no separate
9
+ * workflow per protocol or message type.
10
+ *
11
+ * Interrupted runs (e.g. container restart mid-dispatch) are NOT auto-resumed
12
+ * on boot — the schedule fires again on the next cron tick regardless.
13
+ */
14
+ export declare function createOutreachWorkflow(outreaches: Record<string, Outreach>, adapters: OutreachAdapters): import("@mastra/core/workflows").Workflow<import("@mastra/core/workflows").DefaultEngineType, import("@mastra/core/workflows").Step<string, unknown, unknown, unknown, unknown, unknown, any, unknown>[], "outreach-dispatch", unknown, {
15
+ userId: string;
16
+ slug: string;
17
+ }, {
18
+ delivered: boolean;
19
+ warm: boolean;
20
+ }, {
21
+ delivered: boolean;
22
+ warm: boolean;
23
+ }, unknown>;
24
+ //# sourceMappingURL=workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/outreach/workflow.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAO5D;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EACpC,QAAQ,EAAE,gBAAgB;;;;;;;;;YAsB3B"}
@@ -0,0 +1,40 @@
1
+ import { createStep, createWorkflow } from '@mastra/core/workflows';
2
+ import { z } from 'zod';
3
+ import { dispatch } from './dispatch.js';
4
+ const resultSchema = z.object({
5
+ delivered: z.boolean(),
6
+ warm: z.boolean(),
7
+ });
8
+ /**
9
+ * Creates a Mastra workflow that dispatches a named outreach for a user.
10
+ *
11
+ * This is the required adapter for `mastra.schedules.create({ workflowId })`
12
+ * since scheduled agent runs don't support toolChoice. The workflow receives
13
+ * `{ userId, slug }` as input, resolves the outreach from the registry, and
14
+ * calls `dispatch`. One workflow covers all outreach types — no separate
15
+ * workflow per protocol or message type.
16
+ *
17
+ * Interrupted runs (e.g. container restart mid-dispatch) are NOT auto-resumed
18
+ * on boot — the schedule fires again on the next cron tick regardless.
19
+ */
20
+ export function createOutreachWorkflow(outreaches, adapters) {
21
+ const dispatchStep = createStep({
22
+ id: 'dispatch',
23
+ inputSchema: z.object({ userId: z.string(), slug: z.string() }),
24
+ outputSchema: resultSchema,
25
+ execute: async ({ inputData, mastra }) => {
26
+ const outreach = outreaches[inputData.slug];
27
+ if (!outreach)
28
+ return { delivered: false, warm: false };
29
+ return dispatch(mastra, inputData.userId, outreach, adapters);
30
+ },
31
+ });
32
+ const workflow = createWorkflow({
33
+ id: 'outreach-dispatch',
34
+ inputSchema: z.object({ userId: z.string(), slug: z.string() }),
35
+ outputSchema: resultSchema,
36
+ options: { autoRestartActiveRuns: false },
37
+ }).then(dispatchStep);
38
+ workflow.commit();
39
+ return workflow;
40
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@mbandeira/reach",
3
+ "version": "0.1.0",
4
+ "description": "Ergonomic outreach and forms primitives for Mastra agents",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/mb4ndeira/reach.git"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest"
26
+ },
27
+ "peerDependencies": {
28
+ "@mastra/core": ">=1.50.0",
29
+ "zod": ">=3.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@mastra/core": "^1.67.0",
33
+ "@types/node": "^22.20.4",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^4.1.10",
36
+ "zod": "^3.25.76"
37
+ }
38
+ }