Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d4de1eb
chore(deps): bump @mastra/* to 1.42.0-alpha.3 alpha set — unlocks Sig…
hamchowderr Jun 11, 2026
b57e81e
fix(build): legacy-peer-deps for the uncoordinated @mastra alpha set …
hamchowderr Jun 11, 2026
db63a26
feat(workflows): un-stub poll trigger as a Foreman-native poll driver…
hamchowderr Jun 12, 2026
edac304
refactor(signals): host the poll trigger as an agent-mounted Mastra S…
hamchowderr Jun 12, 2026
fae9ca7
feat(signals): channel triggers as an agent-hosted SignalProvider wit…
hamchowderr Jun 12, 2026
f825d4d
fix(cron): atomic same-minute claim prevents double-fire on restart (…
hamchowderr Jun 12, 2026
4c5da4c
fix(workflows): never leave a run stuck in 'running' (foreman-2afc, p…
hamchowderr Jun 12, 2026
c039220
feat(workflows): persist run failure + trigger context in run history…
hamchowderr Jun 12, 2026
87efca5
feat(agent): opt read-only Zapier tools into Mastra background execut…
hamchowderr Jun 12, 2026
0a20828
fix(agent): background search_history, not the Zapier tools — live-ve…
hamchowderr Jun 12, 2026
177c281
fix(signals): connect the trigger providers in the worker/webhook pro…
hamchowderr Jun 12, 2026
278246a
test(live): end-to-end poll driver against real Postgres (foreman-ueep)
hamchowderr Jun 12, 2026
37e324d
fix(zapier): resolve app keys via the catalog, not string-munging (fo…
hamchowderr Jun 12, 2026
ada01c9
feat(web): surface run fired_by + error reason, fix failed-status badge
hamchowderr Jun 12, 2026
c942a92
feat(channels): dedupe channel triggers on real platform message ids …
hamchowderr Jun 12, 2026
3e8a5ec
fix(web): use npm ci for Vercel install to match the green CI path (f…
hamchowderr Jun 12, 2026
a5bb049
fix(web): clean stale workspace node_modules before Vercel install (f…
hamchowderr Jun 12, 2026
9745953
fix(web): dedupe react to a single version so Vercel resolves it (for…
hamchowderr Jun 12, 2026
7ded42a
fix(web): dedupe react via web caret pin, keep lockfile in sync (fore…
hamchowderr Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
legacy-peer-deps=true
1,125 changes: 545 additions & 580 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"esbuild": ">=0.25.0",
"zod": "4.4.3",
"postcss": "^8.5.10",
"@mastra/core": "1.41.0"
"@mastra/core": "1.42.0-alpha.3"
},
"devDependencies": {
"@biomejs/biome": "^2.4.12",
Expand Down
1 change: 1 addition & 0 deletions packages/agents/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
legacy-peer-deps=true
28 changes: 14 additions & 14 deletions packages/agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,17 @@
"@chat-adapter/telegram": "^4.28.1",
"@chat-adapter/whatsapp": "^4.28.1",
"@hono/node-server": "^1.19.14",
"@mastra/ai-sdk": "^1.4.4",
"@mastra/core": "^1.41.0",
"@mastra/duckdb": "^1.4.1",
"@mastra/editor": "^0.11.1",
"@mastra/evals": "^1.2.4",
"@mastra/loggers": "^1.1.2",
"@mastra/memory": "^1.20.2",
"@mastra/observability": "^1.14.1",
"@mastra/pg": "^1.12.1",
"@mastra/rag": "^2.2.1",
"@mastra/voice-openai": "^0.12.2",
"@mastra/ai-sdk": "1.4.5-alpha.0",
"@mastra/core": "1.42.0-alpha.3",
"@mastra/duckdb": "1.4.1-alpha.0",
"@mastra/editor": "0.11.2-alpha.0",
"@mastra/evals": "1.2.4-alpha.0",
"@mastra/loggers": "1.1.2-alpha.0",
"@mastra/memory": "1.20.2-alpha.0",
"@mastra/observability": "1.14.1-alpha.1",
"@mastra/pg": "1.13.0-alpha.0",
"@mastra/rag": "2.2.1-alpha.0",
"@mastra/voice-openai": "0.12.2-alpha.0",
"@supabase/supabase-js": "^2.105.4",
"@zapier/zapier-sdk": "^0.69.3",
"ai": "^6.0.177",
Expand All @@ -80,14 +80,14 @@
"zod": "^4.3.6"
},
"optionalDependencies": {
"@mastra/deployer-cloudflare": "^1.1.42",
"@mastra/deployer-vercel": "^1.1.36"
"@mastra/deployer-cloudflare": "1.1.43-alpha.0",
"@mastra/deployer-vercel": "1.1.37-alpha.0"
},
"devDependencies": {
"@copilotkit/aimock": "^1.14.3",
"@types/node": "^20",
"@zapier/zapier-sdk-cli": "^0.54.3",
"mastra": "^1.12.2",
"mastra": "1.12.3-alpha.3",
"typescript": "^5",
"vitest": "^4.1.4",
"zlib-sync": "^0.1.10"
Expand Down
27 changes: 21 additions & 6 deletions packages/agents/src/cron-driver-server.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
/**
* Standalone entrypoint for the cron driver. Run with:
* Standalone entrypoint for the trigger drivers. Run with:
* node --env-file=.env.local src/cron-driver-server.ts
*
* Keeps a long-lived process that ticks once per minute and fires due
* `workflow_trigger` rows. Only run a single instance — there is no
* distributed lock.
* `workflow_trigger` rows: the cron driver (schedule matches) and the poll
* driver (Zapier read actions returning new records). Only run a single
* instance — there is no distributed lock.
*/
import { getMastra } from "./mastra";
import { zapierPollProvider } from "./mastra/signals/zapier-poll-provider";
import { startCronDriver } from "./workflows/cron-driver";
import { startPollDriver } from "./workflows/poll-driver";

const stop = startCronDriver();
// Build the Mastra instance here so the foreman agent is constructed in THIS
// worker process — that connects the SignalProviders (poll/channel) and gives
// them the notification storage notify() needs. Without it, runDuePolls() fires
// workflows fine but notifyOwner() is a silent no-op (no connected agent). The
// provider has no pollInterval, so constructing the agent here doesn't start a
// second poll loop. (foreman notify-wiring)
getMastra();
console.log(`[trigger-driver] poll provider connected: ${zapierPollProvider.isConnected}`);

const stopCron = startCronDriver();
const stopPoll = startPollDriver();

const shutdown = (sig: string) => {
console.log(`[cron-driver] received ${sig}, shutting down`);
stop();
console.log(`[trigger-driver] received ${sig}, shutting down`);
stopCron();
stopPoll();
process.exit(0);
};
process.on("SIGINT", () => shutdown("SIGINT"));
Expand Down
2 changes: 2 additions & 0 deletions packages/agents/src/discord/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export async function getDiscordBot() {
channel: "discord",
text: message.text,
from: message.author.userId,
dedupeKey: message.id,
room: thread.channelId,
});
if (fired > 0) return;
Expand All @@ -144,6 +145,7 @@ export async function getDiscordBot() {
channel: "discord",
text: message.text,
from: message.author.userId,
dedupeKey: message.id,
room: thread.id,
});
if (fired > 0) return;
Expand Down
2 changes: 2 additions & 0 deletions packages/agents/src/gchat/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export async function getGoogleChatBot() {
channel: "gchat",
text: message.text,
from: message.author.userId,
dedupeKey: message.id,
room: thread.channelId,
});
if (fired > 0) return;
Expand All @@ -88,6 +89,7 @@ export async function getGoogleChatBot() {
channel: "gchat",
text: message.text,
from: message.author.userId,
dedupeKey: message.id,
room: thread.id,
});
if (fired > 0) return;
Expand Down
2 changes: 2 additions & 0 deletions packages/agents/src/github/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export async function getGitHubBot() {
channel: "github",
text: message.text,
from: message.author.userId,
dedupeKey: message.id,
room: thread.channelId,
});
if (fired > 0) return;
Expand All @@ -91,6 +92,7 @@ export async function getGitHubBot() {
channel: "github",
text: message.text,
from: message.author.userId,
dedupeKey: message.id,
room: thread.id,
});
if (fired > 0) return;
Expand Down
22 changes: 22 additions & 0 deletions packages/agents/src/lib/background.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Gated background-execution config for agent tools (foreman-7am4).
*
* Returns `{ background: { enabled: true } }` when FOREMAN_BACKGROUND_TOOLS=1,
* else `{}` — spread into a tool's createTool() config to opt it into Mastra
* background execution.
*
* Apply ONLY to simple-schema custom tools. Live verification showed that
* backgrounding the generated Zapier SDK tools hangs `mastra dev`: enabling
* background makes Mastra inject a `_background` field into the tool's input
* schema, and Studio's `toJSONSchema` introspection over the Zapier tools'
* (pathologically complex) zod schemas then loops forever — the same failure the
* lazy-init-zapier regression test guards. `mastra build` is unaffected (no
* Studio introspection), but the dev playground is. Simple schemas (e.g.
* search_history's 3 fields) inject + introspect fine.
*
* Read at call time so tests can toggle the flag. OFF by default — this changes
* live tool dispatch and is alpha-unverified end-to-end, so it ships inert.
*/
export function backgroundIfEnabled(): { background?: { enabled: true } } {
return process.env.FOREMAN_BACKGROUND_TOOLS === "1" ? { background: { enabled: true } } : {};
}
1 change: 1 addition & 0 deletions packages/agents/src/lib/catalog/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { resolveAppSlug } from "./resolve";
export { seedCatalog } from "./seed";
export { ensureCatalogIndex, indexAppCatalog, searchAppCatalog } from "./vector";
37 changes: 37 additions & 0 deletions packages/agents/src/lib/catalog/resolve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getSupabase } from "../db";

/**
* Resolve any Zapier app identifier to its canonical slug, using the
* `app_catalog` table (seeded from `sdk.listApps`) as the source of truth.
*
* The catalog stores BOTH the Zapier `app_key` (e.g. "GitHubCLIAPI") and the
* display `slug` (e.g. "github"), so it knows the correct mapping. This replaces
* the string-munging `normalizeAppKey`, which can't reliably derive a slug from
* a CamelCase implementation name: "GitHubCLIAPI" → "git-hub" is wrong (the real
* slug is "github"), while "GoogleSheetsV2CLIAPI" → "google-sheets" needs the
* dash. You can't tell those apart from the string alone — only the catalog can.
* (foreman-c8fo)
*
* Accepts either form (app_key or slug, case-insensitive) and returns the slug.
* If the app isn't in the catalog (e.g. an unseeded local DB), the key is
* returned UNCHANGED — the Zapier SDK accepts both raw app_keys and slugs, so
* passing the original through is safe; only the mangled form ever broke.
*/
export async function resolveAppSlug(key: string): Promise<string> {
if (!key) return key;
// App keys/slugs are simple identifiers; guard the PostgREST `or` filter.
if (!/^[\w.-]+$/.test(key)) return key;

try {
const { data } = await getSupabase()
.from("app_catalog")
.select("slug")
.or(`app_key.ilike.${key},slug.ilike.${key}`)
.limit(1)
.maybeSingle();
if (data?.slug) return data.slug;
} catch {
// catalog unavailable / not seeded — fall through to pass-through
}
return key;
}
9 changes: 9 additions & 0 deletions packages/agents/src/lib/db/database.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3429,25 +3429,34 @@ export type Database = {
Row: {
completed_at: string | null;
created_at: string;
error_message: string | null;
fired_by: string | null;
id: string;
inputs: string;
status: string;
trigger_id: string | null;
workflow_id: string;
};
Insert: {
completed_at?: string | null;
created_at: string;
error_message?: string | null;
fired_by?: string | null;
id: string;
inputs: string;
status: string;
trigger_id?: string | null;
workflow_id: string;
};
Update: {
completed_at?: string | null;
created_at?: string;
error_message?: string | null;
fired_by?: string | null;
id?: string;
inputs?: string;
status?: string;
trigger_id?: string | null;
workflow_id?: string;
};
Relationships: [
Expand Down
Loading
Loading