Skip to content

chore(deps): Zapier SDK 0.91, Mastra off alpha, AI SDK v7, Node 24 — audit 25 → 8 - #22

Merged
hamchowderr merged 267 commits into
mainfrom
chore/dep-refresh
Jul 28, 2026
Merged

chore(deps): Zapier SDK 0.91, Mastra off alpha, AI SDK v7, Node 24 — audit 25 → 8#22
hamchowderr merged 267 commits into
mainfrom
chore/dep-refresh

Conversation

@hamchowderr

Copy link
Copy Markdown
Owner

Brings Foreman's dependency stack current and clears every reachable security finding. npm audit 25 → 8; production dependencies now 3 findings, all low — zero moderate, zero high, zero critical.

What's here

commit change
6931f97 Zapier SDK 0.85 → 0.91, CLI → 0.67.x, migrate two breaking APIs
7fabbec next 16.2.12; sharp + adm-zip overrides
8867c30 rimraf ^6 override — clears the googleapis branch
1c8045d Mastra off the alpha track → stable 1.53.0
0482649 Vercel AI SDK v6 → v7
a6b80c8 Node aligned on 24 across all five places; zod 4.4.3
79d5605 drop unused @hono/node-server, pin the safe major

Notable

Mastra off alpha. Foreman adopted the alpha in #17 for SignalProviders. That code has since been removed — zero references remain — so the alpha track was being carried for a capability that no longer exists. Every @mastra/* package moved together per the procedure in CLAUDE.md. Zero typecheck errors across 11 minors.

AI SDK v7. ai is not a Mastra dependency, and @mastra/core@1.53.0 already bundles v7 provider bindings, so only our declarations were behind. stepCountIs moved to a local helper (lib/stop-conditions.ts) because stopWhen must satisfy Mastra's StopCondition, not the AI SDK's — those diverged in v7. Type-level only; Mastra still invokes it with { steps }.

Node 24. Foreman previously declared no Node version anywhere, so "which @types/node?" had no correct answer. Now Dockerfile.agents, CI, engines.node, and @types/node all agree on 24. Deliberately not 26 — types must not exceed the runtime, and the deployed container was verified running Node 22 before this change.

@opentelemetry/api is now declared. lib/providers/cost.ts imported it while it resolved only by accident through a v6 transitive. Latent packaging bug the v7 tree exposed.

Verified locally

  • typecheck: 0 errors
  • 405 tests pass
  • mastra build and next build both succeed
  • biome exit 0, check-dep-uniqueness passes
  • authed HTTP routes exercised against a running server: /api/agents 200; /capabilities, /conversations, /automations, /workspaces, /documents, /api-keys all 200 with a valid token and 401 without
  • live chat streaming round trip against AIMock produced a correct v7 UI-message stream (startstart-steptext-starttext-delta ×9 → text-endfinish-stepfinish[DONE])

Worth extra scrutiny in CI

Everything above ran on Windows against a local stack. Three things CI checks that local runs can't:

  1. db-types — Linux Docker, where the documented --db-url introspection failure lives.
  2. webnext build in a clean environment.
  3. All six jobs now run on Node 24 for the first time.

Known remaining (not fixable here)

The 8 remaining findings are two upstream advisories with no available fix:

  • brace-expansion ≤5.0.7 — 5 rows, dev-only, via mastra > serve-handler > minimatch@3.1.5. serve-handler@6.1.7 is the latest release and pins minimatch exactly; serve@14.2.6 pins serve-handler exactly. Overriding breaks the build (5.0.8 changed its export shape). Reported upstream: chore(deps): update security updates [security] (major) mastra-ai/mastra#19265.
  • @ai-sdk/provider-utils ≤3.0.97 — 3 rows, from @mastra/core bundling a legacy AI-SDK v5 compat shim.

chat-adapter-imessage 0.1.1 → 1.1.0 was attempted and reverted: it took audit from 9 → 21 by pulling spectrum-ts@1.18.0 → @photon-ai/otel@0.1.2 and 12 vulnerable @opentelemetry/* packages.

🤖 Generated with Claude Code

hamchowderr and others added 30 commits June 19, 2026 11:38
A working vertical slice of Phase 2, verified live in the browser:
- DashboardRenderer (client): KPI cards + recharts bar chart + react-data-grid
  table, composed from a constrained DashboardSpec ({title, blocks:[kpi|chart|
  table]}). Pure/no-code-exec → safe to render in-app and (later) publicly.
- /dashboards authed server page: fetches latest snapshot via the proxy, derives
  a default spec from the records, renders. Suspense layout for Next 16
  cacheComponents (cookies()/searchParams must resolve under a boundary).
- /api/dashboards/[...path] token-forwarding proxy + typed dashboards-client.
- Add react-is@^19.2.0 — recharts's required peer dep, previously missing
  (recharts could not run at all; chart.tsx was never exercised).

Renderer fixes found via live browser verification: ChartContainer aspect-video
vs explicit-height conflict (collapsed plot) → aspect-auto h-[280px]; recharts
bar-animation-stuck-at-0 on ResponsiveContainer re-measure → isAnimationActive
=false; react-data-grid columns/rows clipped → explicit rowHeight/headerRowHeight
+ defaultColumnOptions minWidth + deterministic blockSize.

Part of foreman-xl8m (Dashboards Phase 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Type a prompt in chat → agent pulls the latest snapshot → builds + validates a
dashboard spec → stores an artifact → renders it inline in the conversation.

- artifact table (kind='dashboard', spec JSONB, snapshot_id ref, visibility,
  version) — artifact-shaped foundation; reports/mini-sites can follow.
- lib/dashboards/spec.ts: zod DashboardSpec (the safety boundary) + buildDefaultSpec.
- lib/dashboards/artifact.ts: saveArtifact (zod-validated) + getArtifactWithData
  (resolves records from the referenced snapshot); snapshot.ts getSnapshotById.
- create_dashboard tool (registered on foreman): reads latest snapshot, builds
  spec, persists, returns spec+records for inline render. No outputSchema —
  open spec/records would emit additionalProperties:object (Anthropic-rejected);
  the execute return still flows to the UI via part.output.
- routes/dashboards GET /artifacts/:id; web getArtifact client + /dashboards/[id].
- chat message.tsx: tool-create_dashboard branch renders <DashboardRenderer>
  inline + 'Open full' link.

Standalone /dashboards page + renderer verified live (hubspot + stripe). The
in-chat path is wired but currently blocked by a PRE-EXISTING repo-wide bug:
8 tools (run-action, get-input-fields-schema, run_workflow, attach_trigger,
table writers, ...) emit additionalProperties:object in their input schemas,
which Anthropic rejects — breaking ALL foreman tool-calling. Tracked separately.

Part of foreman-xl8m. 322 agents tests green, Biome clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (foreman-2aj0)

PARTIAL fix — does NOT fully unblock tool-calling yet. Adds a build-time walker
that strips object-valued additionalProperties from tool input schemas (applied
in getForemanTools). Confirmed it changes the Anthropic rejection from
'tools.9: additionalProperties: object is not supported' to
'tools.8: additionalProperties must be explicitly set to false' — i.e. the
schema layer is the right place, but Anthropic (via AI SDK v6) requires
additionalProperties:false on EVERY tool-input object, regardless of per-tool
strict (SDK tools are non-strict yet still rejected).

UNRESOLVED: that requirement conflicts with dynamic-input tools (run-action.inputs
etc.) where the model must pass arbitrary per-app keys; setting false would break
them. Real fix = retype dynamic inputs as a parsed JSON string, or a Mastra/AI-SDK
version fix — verified against a live run-action. See file header + foreman-2aj0.
…for dynamic inputs

Anthropic's tool-use API (AI SDK v6) requires additionalProperties:false on
every object in a tool's input schema. Several always-loaded foreman tools have
genuinely dynamic open-map inputs (z.record): run-action(inputs),
get/list-input-field-*, create/update-table-records(records[].data),
create-table-fields, run_workflow(inputs), attach_trigger(poll.inputs). Setting
false there would forbid the per-app keys the model must pass; omitting it is
rejected outright — so every tool-calling turn 400'd (run-action is core, so ALL
turns broke). Not caught by CI (no real provider keys).

Rewrite the sanitizer into a single centralized transform applied to the foreman
tool map: open-map nodes become JSON strings ({type:string}) in the model-facing
schema; fixed objects get additionalProperties:false; execute is wrapped to
JSON.parse those strings back into the exact objects the handler/SDK expect
(top-level + nested-in-array via a "[]" path marker). The revive is a no-op on
non-string values, so it is idempotent and safe for any shared tool object.

Verified keylessly + deterministically (no model/Zapier needed): unit tests
assert (a) no object in ANY real generated SDK tool schema carries an
object-valued/absent additionalProperties after sanitize, (b) run-action's
inputs is rewritten to a string, (c) the execute round-trip reconstructs the
original object. This regression guard runs in CI, which previously missed it.
Live end-to-end run-action against a real Zapier connection remains the
deploy-time confidence step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oards branch

# Conflicts:
#	.beads/export-state.json
#	.beads/issues.jsonl
#	packages/agents/src/mastra/agents/foreman.ts
…dashboards branch (one trunk)

# Conflicts:
#	.beads/export-state.json
#	.beads/issues.jsonl
…idebar

- Theme presets set --sidebar-accent to their saturated --primary, so every
  sidebar nav button rendered as a solid color block (looked permanently
  hovered/active). Point --sidebar-accent / -foreground at the preset's own
  neutral --accent / --accent-foreground (scoped to preset blocks only; base
  theme untouched). Verified in light and dark.
- Remove the destructive "Delete all" item from the sidebar nav (kept the
  /purge slash command). Dropped now-unused imports/state.

foreman-sinf, foreman-v2i1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Model selector derived its logo from model.id ("foreman" → models.dev/
  logos/foreman.svg, 404) instead of model.provider ("anthropic"). That broke
  the trigger icon ("✦ foreman") AND caused a React hydration mismatch in the
  composer footer (server <img> vs client Mic). Key the logo off provider.
- Surface the real model name ("Claude Sonnet 4.6") in the selector instead of
  the lowercase agent id; id stays "foreman" so server routing is unaffected.
- Composer: more defined/rounded container (rounded-3xl, border/60, focus
  border) — the faint border/30 read as no outline.

foreman-82vf, foreman-hkfg
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hat outline

- Add Dashboards nav item (the feature route /dashboards was unlinked).
- SidebarFooter pb-3 -> pb-5 so the user/theme menu isn't jammed against the
  bottom edge.
- Outer chat panel: border/40 -> border/60 and rounded-tl-2xl for a more
  defined "outside" outline (the user meant the panel, not just the composer).

foreman-sinf, foreman-hkfg
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion, F mark)

- Multi-model: offer Claude Sonnet 4.6 / Opus 4.6 / Haiku 4.5 (all Anthropic so
  prompt+tool cache-control stays valid). The composer sends the chosen model id;
  the /chat/:agentId handler validates it against an allowlist and passes it to
  agent.stream({ model }), falling back to the agent default otherwise.
- fix(dashboards): /dashboards was missing from CUSTOM_ROUTE_PREFIXES, so the
  agent server served Studio HTML for /dashboards/* → "Unexpected token '<'" on
  the dashboards page. Add the prefix so the custom route is dispatched.
- fix(hydration): MicButton feature-detected SpeechRecognition during render
  (null on server, button on client) → toolbar order mismatch → React hydration
  error (the "1 Issue" badge + red composer outline). Gate on a post-mount flag.
- Sidebar: replace the generic chat-bubble icon with the Foreman "F" brand mark.

foreman-82vf
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the generic chat-bubble / placeholder mark above "New chat" with the
official Zapier logo (public/zapier.svg). Per the user's request.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Swap the sidebar brand mark to the Zapier asterisk (the user's reference
  image), replacing the earlier rounded-square wordmark.
- Model selector: items rendered as distinct bordered cards with a gap between
  them (were flush/stacked), each showing name + description + capability icons;
  wider popover; selected item gets a subtle accent highlight.

foreman-82vf
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tatic default

onFinishCostLogger logged primary(AGENT_MODELS[agent]) — always Foreman's
default (sonnet) — so per-request model overrides were invisible and pricing was
wrong for them. Read the real model from the AI SDK finish event
(response.modelId), re-attaching the "anthropic/" prefix to match MODEL_PRICING,
and fall back to the static default when absent.

Confirms the multi-model selector end-to-end: choosing Haiku now logs
anthropic/claude-haiku-4-5-20251001 (verified live).

foreman-82vf
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…irline)

The chat panel used border-border/60 + rounded-tl-2xl, which rendered as a faint
off-color hairline that curved into a half-baked rounded corner at the top —
read as "ugly / not how it usually looks." Replace with a crisp 1px line in the
proper --sidebar-border color, running straight top-to-bottom (the standard
sidebar↔content divider). Drops the awkward top border/corner.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The conversation (header + messages + composer) is now a rounded, bordered card
that floats on the sidebar/canvas background with a gap on all sides, instead of
being flush against the sidebar with a divider. Header moved inside the card,
de-chromed (border-b separator instead of bg-sidebar). Subtle variant — 1px
border + gap, no shadow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ctive, Settings in user menu

- cbzv: shadow-sm on the floating chat card so it reads as a surface
- hkfg: conversation title on the left, visibility control pushed right
  (title plumbed /api/messages -> use-active-chat -> ChatHeader)
- sinf: drop New-chat resting border (was reading as 'selected'), unify
  nav heights (h-8), more header->nav breathing room
- 9m7h: move Settings out of sidebar nav into the bottom-left user menu
- fix cursor-pointer typo in document.tsx (was 'cursor pointer')

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dynamicModels was hardcoded to undefined (no /api/models endpoint), making
the truthy ternary branch dead code that TS narrowed to 'never' — broke
'next build' (and CI) since 44a9fb8. Removed the dead branch + declaration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…an-to5b)

Audit found settings/workflows/onboarding + the chat artifact/document
subsystem hand-rolled buttons/inputs/alerts/badges/empty-states/skeletons
with inline styles instead of the 55 vendored ui/ primitives. Swept them
onto the design system (verified: biome + tsc + next build all green).

- workflows: native confirm() -> AlertDialog (detach/delete); Switch, Button,
  Input, Alert, Badge, Empty (foreman-d6v9, partial wyhs/zo1e/33w6/jrab)
- chat: tool-approval buttons -> Button (foreman-l7nj); message error divs ->
  Alert; version-footer/artifact/document/multimodal -> Button; mic title ->
  Tooltip; slash menu -> cmdk Command; SuggestionDialog -> Dialog; history
  rename -> Input; skeletons -> Skeleton (l7nj/sx4f/ltnn/jrab/wyhs)
- settings: shared CopyButton; Input/Field, Button, Alert (+ surfaced silent
  failures), Badge, Empty (dwga + wyhs/zo1e/33w6/jrab)
- onboarding: Alert, Button, Collapsible, aria-pressed
- auth: ui/Field + shared destructive Alert across all four forms (wltd)
- dashboards+editor: shared BrandHeader (Zapier mark), Alert, Empty, Checkbox,
  Badge (rqsy + wyhs/zo1e/33w6/jrab)
- landing: aria-pressed on demo selectors (faml)
- new: components/copy-button.tsx, components/brand-header.tsx

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapsed rail applies p-2! to the icon button (32->16px content box); the
mark's max-width:100% capped width at 16 while h-5 kept height 20 -> 16x20
squish. Render the collapsed mark at size-4 (clean 16x16, matches nav glyphs);
expanded stays 20x20. Verified live: collapsed 15.99x15.99, centered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…' box (foreman-nwpf)

The landing nav and footer showed a legacy 'F'-in-accent-box Foreman mark.
Replaced both with the Zapier asterisk (/zapier.svg), matching the sidebar
and dashboards branding. Foreman wordmark + Alpha badge kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eset (foreman-mcla)

Landing built brand elements on accent/text-accent/variant=accent; theme
presets repurpose --accent as a light neutral (brand moves to --primary), so
signed-in users with a preset saw washed-out CTAs/badges/headings. Added
.landing-brand-lock re-pinning --primary/--accent/--ring to orange on the
landing root. Verified: Connect CTA + 'Drive the one' heading now vivid orange
under sunset-glow (oklab L 0.66 vs the prior pale 0.84).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Empty-state heading is now 'Good morning/afternoon/evening, <First>' (late-night
-> 'Welcome back'), with 'What can I help with?' as subtext. First name read
client-side from supabase user_metadata (full_name/name/given_name/first_name),
graceful fallback to the time-only greeting when no name is set. Computed in a
mount effect to avoid SSR/timezone hydration mismatch. Verified live: 'Good
morning' at 9am local.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New Profile settings section (page + form + nav entry) to set a display name,
stored in Supabase user_metadata.full_name via supabase.auth.updateUser. Feeds
the chat greeting (foreman-1k9u) so the time-of-day greeting can show the user's
first name. Built on ui/Field/Input/Button/Alert/Skeleton primitives. Verified:
page renders at /settings/profile with the input, Save, and Profile nav link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reman-r36j)

agents: toAISdkStream now attaches token usage via messageMetadata (both the
main + approval stream paths) so it rides the AI SDK v6 finish part.
web: useChat exposes it as message.metadata.usage; new ContextUsage gauge (ring
+ percent, hover shows input/output/reasoning/cache breakdown) in the composer
toolbar, built on the vendored hover-card primitive (no tokenlens dep; window
size from models config — Claude 4.x = 200k).

Verified live on haiku: finish part carried usage {inputTokens:31867,
outputTokens:5, totalTokens:31872, cacheCreationInputTokens:30589}. Web build +
biome green; agent bundles + streams the metadata.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an archive flag so users can hide a conversation from the sidebar
history without deleting it (and its Mastra thread).

- migration: conversation.archived_at (nullable) + partial index on the
  active-rows-by-recency sidebar query; regenerated db types
- agent route: GET /conversations?archived=true filters; default list
  excludes archived. PATCH /:id now toggles {archived} as well as title
- web: /api/history passes ?archived through and surfaces archivedAt
- sidebar: Archive/Unarchive action in the chat item menu (optimistic,
  with Undo toast), and a collapsed "Archived" section that loads on
  demand and stays reachable even when all chats are archived

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server-side, debounced sidebar search. One pass merges conversation
title matches with message-content matches (scoped to the user via
mastra_messages.resourceId), de-duped by conversation, newest first,
each with a short snippet around the match.

- agent route: GET /conversations/search?q= (registered before /:id so
  it isn't shadowed); escapes LIKE wildcards; caps results
- web: /api/history/search proxy + a search box atop the sidebar that
  replaces the history/archived view with results while a query is active

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hamchowderr and others added 22 commits July 5, 2026 11:35
…ions (foreman-ufo3.1)

Foreman-side cron layer inside the inbox worker (the removed cron-driver's job,
minus a second process). A scheduled automation carries its schedule in the same
`trigger` json as event automations, under a `schedule` key, so it's naturally
excluded from the event path (which requires app+action).

- lib/automations/schedule.ts (pure): ScheduleSpec = interval{everyMinutes} |
  daily{atHourUtc,atMinuteUtc?}; scheduleOf(trigger) validates + extracts;
  isScheduleDue(spec,lastRunAtMs,nowMs) (interval elapsed / daily boundary);
  isDigestTrigger() for the ufo3.2 routing. Dependency-free — no cron parser;
  full cron expressions are a follow-up.
- store: listActiveScheduledAutomations() + getLastRunAt() (last-fire derived
  from automation_run — no new column/migration).
- worker: runDueSchedules(now) pass, wired into the tick between the inbox cycle
  and reconcile. A due automation's durable is fired via triggerAutomation and
  recorded as "started"; reconcile advances it like any event run. Digest
  automations are recognized but not fired (synthesis lands in ufo3.2). One
  owner's failure doesn't stop the others. Single-instance worker ⇒ no lock.

Tests: schedule.test.ts (16 — scheduleOf validation + interval/daily due checks)
+ runDueSchedules worker cases (fires due, skips not-due, skips digest, continues
past a fire failure). Full agents suite 402 pass; tsc + biome clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…box (foreman-ufo3.2)

A digest is a scheduled automation (trigger {schedule, digest:true}) that, when
due, synthesizes the workspace's recent runs into a prioritized summary instead
of firing a durable. No new table: the digest is stored as the `output` of a
`finished` automation_run, tagged with kind="automation_digest", and the inbox
finds the latest via a JSONB filter — reuses all existing run infra.

- lib/automations/digest.ts (pure): buildDigest(runs, start, end) → totals by
  status + prioritized lists (failures → waiting-on-approval → retrying, each
  most-recent-first) + a headline. Deterministic, credit-free; error text pulled
  from a {message} or a DurableRunDetail. An LLM narrative can wrap it later.
- worker.runDueSchedules: a due digest routes to runDigestForAutomation — gathers
  store.listRecentRunsForWorkspace (excluding its own runs, names via
  getAutomationsByIds), builds the digest, records it finished.
- store: listRecentRunsForWorkspace + getLatestDigest (kind-discriminator lookup).
- service.getWorkspaceInbox now also returns the workspace's latest digest.
- web /inbox renders a Daily digest card (headline + failures/waiting) at the top.

Verify: full agents suite 408 pass (digest.test.ts 6 pure cases + worker digest
routing + service surfacing); agents+web tsc + biome clean. digest-query-probe.ts
confirms the getLatestDigest JSONB filter is accepted by live PostgREST.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ompt (foreman-ufo3.3)

Makes the ufo3 scheduling/digest engine user-facing and drops the stale prompt
claim that recurring automation is unavailable.

- create_automation tool: adds a `schedule` (interval{everyMinutes} |
  daily{atHourUtc,atMinuteUtc?}) and `digest` flag; `source` is now optional
  (a digest has no durable). Description guides event vs schedule vs digest.
- service.provisionAutomation: a schedule stores {schedule} (wins over an event
  trigger) in the trigger json; a digest (schedule+digest) skips the Zapier
  deploy entirely and persists with a unique sentinel workflow id — no
  nullable-column migration, the shared automation schema stays uniform.
- prompt-template: replaces the "scheduled/event automation is not available yet"
  block (event already worked!) with accurate guidance — build recurring runs and
  daily digests via create_automation, never by handing off to Zapier.
- web: automations list + detail show a schedule chip ("Daily 09:00 UTC") and a
  Digest badge; the trigger-inbox tab explains a scheduled/digest automation
  instead of wrongly showing "not armed yet".

Verify: full agents suite 410 pass (provisionAutomation digest = no deploy +
sentinel id + {schedule,digest} trigger; scheduled durable = deploys + stores
schedule). agents+web tsc + biome clean. digest-e2e-probe.ts proves the whole
ufo3 acceptance against live local Postgres: provision digest → runDueSchedules
fires it → synthesizes "2 runs · 1 failed · 1 ok" → getLatestDigest surfaces it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-ufo3)

Wraps the deterministic digest with a short prose summary from a small model —
the "future add" flagged in ufo3.2. Opt-in and fails soft, so the deterministic
digest is never affected by the LLM.

- digest.ts (pure): AutomationDigest.narrative (null default); DIGEST_NARRATOR_
  INSTRUCTIONS + buildDigestNarrativePrompt (compact JSON view of the structured
  digest — deterministic, unit-tested).
- digest-narrator.ts: narrateDigest runs a one-shot Mastra Agent whose model is
  FOREMAN_DIGEST_MODEL (provider/model, e.g. openai/gpt-4o-mini) or the fast tier;
  resolution + keys go through Mastra's gateway. Off unless FOREMAN_DIGEST_
  NARRATIVE=true; any failure (no key, rate limit, empty output) degrades to null.
  The generator is injectable for tests.
- worker.runDigestForAutomation sets digest.narrative before recording.
- web /inbox DigestCard renders the narrative above the structured headline.

Verify: full agents suite 417 pass (buildDigestNarrativePrompt + narrator enable/
fail-soft/empty cases, all with an injected generator — no provider hit in CI);
agents+web tsc + biome clean. digest-narrator-probe.ts, run live on
openai/gpt-4o-mini, produced: "The 'Nightly Sheets→Slack sync' automation failed
due to a connection refused error. There is one invoice approval pending. Two
other automations finished successfully."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s, retire custom (foreman-bhb5)

Replace the hand-rolled setInterval scheduler + custom digest orchestration with
Mastra's native primitives. Per user directive — stop building custom scaffolding.

Scheduling → Mastra WorkflowScheduler (native, in our pinned 1.42.0-alpha.3):
- daily-digest workflow (createWorkflow, evented): gather runs → synthesize the
  deterministic digest (reuses pure buildDigest) → narrate → store as a finished
  automation_run. run-automation workflow fires a scheduled durable.
- lib/automations/schedules.ts: register/unregister a per-workspace imperative
  Mastra schedule (schedulesStore.createSchedule) targeting a workflow with
  {workspaceId, automationId} inputData, cron + timezone. provisionAutomation
  registers on create; removeAutomation deletes. CAS on mastra_schedules makes it
  multi-instance safe — no single-instance limitation.
- SchedulesPG (via the composite's Postgres default) auto-provisions its tables —
  no migration. scheduler:{enabled:true} + startWorkers() in the inbox worker.
- Schedules are cron strings now (Mastra's unit), not the bespoke interval/daily
  spec. create_automation + the prompt take a cron; the web shows a cron label.

Narrator → a first-class registry agent (AGENT_MODELS.digest, env DIGEST_MODEL,
fast default). Deletes the bespoke FOREMAN_DIGEST_MODEL / FOREMAN_DIGEST_NARRATIVE
env flags — narration is a workflow step, fail-soft to null.

Deleted: lib/automations/schedule.ts (scheduleOf/isScheduleDue), worker
runDueSchedules + runDigestForAutomation, store.listActiveScheduledAutomations +
getLastRunAt, and their tests.

Verify: agents tsc + web tsc clean; 399 agents tests pass; changed files biome
clean. scheduler-poc.ts proved the scheduler fires a cron workflow against local
Postgres; digest-workflow-e2e-probe.ts proved the full native chain (imperative
schedule → daily-digest workflow → synthesize "2 runs · 1 failed · 1 ok" → store →
getLatestDigest); digest-narrator-probe.ts proved the registry narrator on
openai/gpt-4o-mini.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ncl. observability

Foreman is Supabase/Postgres-only; DuckDB was carrying only the observability
domain on the belief that Postgres/LibSQL couldn't. That's stale — @mastra/pg
(1.13.0-alpha.0) ships ObservabilityPG (mastra_ai_spans / scorers / …) and
PostgresStore is itself a composite over all domains, so the MastraCompositeStore
wrapper + DuckDB override are unnecessary.

- mastra/index.ts: storage is now a plain PostgresStore; dropped the composite
  wrapper, the DuckDB observability override, and the path/fileURLToPath/
  MastraCompositeStore/DuckDBStore imports. Fixed the "(DuckDB)" logging comment.
- Removed @mastra/duckdb from packages/agents deps + the root overrides pin;
  npm install dropped 4 packages, check-dep-uniqueness still passes (core/
  deployer/server pins intact). Deleted the local data/mastra.duckdb files.

Verify: agents+web tsc clean; 399 agents tests pass. Probed the live local DB — a
plain PostgresStore serves getStore('observability') → ObservabilityPG and init()
creates the observability tables; the REAL getMastra() boots with _ObservabilityPG
+ the schedules store present (no DuckDB).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x.1)

Agent can semantically search a workspace's shared documents/ in chat via
Mastra's native Workspace.search — no second RAG pipeline. Each workspace gets
its OWN physical vector index (knowledge_<ws>): the built-in search tool lets the
LLM pick the mode but not the filter, and BM25 ignores filters, so a shared
multi-tenant index would leak — a per-workspace index makes the index itself the
tenant boundary.

- foremanWorkspace: static instance -> per-request async resolver
  (buildForemanWorkspace); vector-only search (bm25:false), mastra_workspace_index
  disabled (index server-side).
- lib/knowledge/vector.ts: PgVector singleton + fastembed BatchEmbedder (384-d,
  lazy index create) + knowledgeIndexName.
- indexSharedDoc/buildTenantKnowledgeWorkspace in workspace.ts; save_document
  indexes shared docs (best-effort). Personal _private/ NOT indexed (follow-up).
- 3 fs call sites (documents route, save_document, share) -> resolveWorkspaceFilesystem.
- knowledge:reindex backfill script; prompt tweak (semantic, not keyword).

Verified: agents tsc, 405 tests (+6, 0 regressions), biome; agent wiring probe
(canVector true, index tool disabled); E2E isolation probe PROVEN vs local Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lint

First full CI run on this branch surfaced two biome failures: (1) biome floated ^2.4.12 -> 2.5.2 whose stricter rules/schema-version check flagged the config, and (2) biome tried to parse .fallowrc.json (Fallow's JSONC config) as strict JSON -> 20 parse errors. Pin biome to 2.4.16 (matches biome.json $schema; consistent with how @mastra/* is pinned) and exclude the lockfile + .fallowrc.json from the lint globs (biome shouldn't lint machine-generated or third-party tool files).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Mastra alpha bump adds mastra_favorites / mastra_notifications / mastra_tool_provider_connections, which Mastra provisions at RUNTIME (not via supabase migrations). The committed types included them (generated on a machine where the agent server had run), but CI's migrations-only DB can't reproduce them, so db:types:check diffed. Regenerated from a clean migrations-only DB (supabase db reset) so the committed types match what CI produces. tsc stays green (no app code references those internal tables).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- App count 9,000+ -> 10,000+ (matches app copy); tool count 28 -> 26 (9 approval + 17 read-only); read-only 16 -> 17.
- Embeddings: OpenAI text-embedding-3-small -> fastembed (local ONNX, no OpenAI embeddings in code).
- Storage: Postgres + DuckDB -> pure Postgres (PostgresStore, incl. observability); dropped @mastra/duckdb.
- Routes/pages: /workflows -> /automations (+ /apps, /documents); removed dead approval-card component ref.
- Agent-framework row completed (ai-sdk, editor, loggers, rag added).
- Split Documents from Automations; removed unbuilt 'shared team brain' roadmap item.
- Channels credited to Vercel Chat SDK (github.com/vercel/chat); new Experimental access section w/ apply link (next-gen-zaps.zapier.app).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…move personal info

- README: deploy-anywhere matrix (local/Vercel/Mastra Cloud/VPS), harness
  reframe, plain-language sweep, purpose note, contact -> hello@; separate
  Documents & Automations; channels -> Vercel Chat SDK; experimental-access note.
- Durable docs: historical banners; "early-access allowlist — apply; not GA"
  framing across auth-model / capability-map / spike / engineer-questions.
- Personal-info scrub: remove Infisical project-id flag entirely (not redacted);
  swap personal emails/example identities for neutral placeholders; delete
  unreferenced tests/sdk/inspect-output.txt dump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le pins

Runtime @zapier/zapier-sdk ^0.81.0->^0.85.0 and @zapier/zapier-sdk-cli ^0.62.0->^0.66.2. Registry surface unchanged (78->78, zero methods added/removed; only getConnection/getAuthentication/fetch param cleanup) so no new tools to classify. Verified: surface-sweep blind-spots 44->44, sdk-tools test 13/13, typecheck clean, full mocked suite 405 passed.

Durable sandbox pins (deps.ts AGED_DURABLE_DEPS), picked by publish-date >=24h: sdk 0.79.0->0.84.4 (0.85.0 too young for the sandbox), durable 0.6.1->0.9.1, zod 4.4.3 unchanged. Re-proven via a live run-durable smoke test (output {ping:{ok:true}}).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ansi-to-react ^6.2.6 pulled linkify-it@3.0.3, flagged HIGH for quadratic-complexity ReDoS (used in ai-elements/terminal.tsx to render ANSI output). 6.1.6 drops that transitive; the only linkify-it left is markdown-it's 5.0.2 (not in the vulnerable <=5.0.0 range). Default-export API unchanged. npm audit 9->7 advisories, both HIGH ReDoS cleared. Follow-up foreman-jhzj: replace the abandoned lib with a maintained ANSI renderer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onts fetch)

next build failed fetching Geist/Geist Mono from Google Fonts at build time (next/font/google), which breaks any network-restricted build. Switched to Vercel's geist package (next/font/local, bundled woff2) — no network at build. Also wired --font-sans/--font-mono to var(--font-geist-sans/mono), which were previously dead: globals.css referenced the literal "Geist" family only, so the bundled webfont never applied. Verified: next build exit 0, tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regenerated package-lock.json from scratch. An ordinary `npm install` was a
no-op: the lockfile's `packages/agents` entry declared ^0.91.0 while its tree
entries still pinned 0.85.0/0.66.2, so npm trusted the lock and reported "up to
date". `npm dedupe` and `npm install -w <pkg> <pkg>@<ver>` both failed (the
latter silently reverted package.json). Only a full
`rm -rf node_modules packages/*/node_modules package-lock.json && npm install`
worked. The SDK is now hoisted to the root with no nested copy.

Breaking API migrations:

- FunctionRegistryEntry.inputParameters was removed in favor of `positional`
  (an ordered list of input KEYS, not objects carrying `.name`). Every registry
  entry now also has an `inputSchema`, so the old `else if (fn.inputParameters)`
  branch became unreachable. Positional spreading moved inside the inputSchema
  branch — `sdk.fetch` is `(...args) => canonicalValue(pack(args))`, so passing
  the canonical bag would bind the whole object to the first positional key and
  silently break the fetch tool.

- SDK 0.88.0 deprecated the millisecond duration params:
  maxNetworkRetryDelayMs -> maxNetworkRetryDelaySeconds,
  runAction timeoutMs -> timeoutSeconds.

Verified: typecheck clean, 405 tests pass, biome clean, check-dep-uniqueness
passes, `npm run sdk:check` reports both packages up to date.

Note: the workflow-draft lifecycle added in 0.89-0.91 lives on the
/experimental surface only — getRegistry({package:"mcp"}) returns 56 functions
and contains zero Workflow/Draft/Durable methods, so those never become agent
tools and need no APPROVAL_REQUIRED/READ_ONLY classification. Auditing that
invariant did surface a pre-existing gap: 16 trigger-inbox methods are
unclassified (9 of them writes generating as no-approval tools) — filed as
foreman-eadn.

Also carries an uncommitted-in-tree @biomejs/biome 2.4.16->2.5.4 bump that
predates this change; the regenerated lockfile bakes it in, so separating them
would leave package.json and the lock inconsistent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…errides)

npm audit: 25 -> 17 findings; high 12(under-reported)/21(true) -> 14.

Root-cause analysis found only a handful of advisories driving everything:

- next 16.2.10 -> 16.2.12 clears NINE advisories (4 high, 5 moderate),
  including middleware/proxy bypass in App Router, SSRF in Server Actions on
  custom servers, SSRF via rewrites, and unauthenticated disclosure of internal
  Server Function endpoints. This is the only internet-facing item in the set.

- sharp override ^0.35.3 (was 0.34.5; advisory <0.35.0, inherited libvips
  CVE-2026-33327/33328/35590/35591). Both consumers pin ^0.34.5, so an override
  is required. Clears 4 findings: sharp, next-via-sharp,
  @photon-ai/advanced-imessage-kit, chat-adapter-imessage.

- adm-zip override ^0.6.0 (was 0.5.18; crafted ZIP triggers 4GB allocation).
  onnxruntime-node pins ^0.5.16, so an override is required. Clears 3 findings:
  adm-zip, onnxruntime-node, @mastra/fastembed.

Deliberately NOT fixed, with reasons:

- The 14 remaining highs are ONE cascade: brace-expansion <=5.0.7 flowing
  through minimatch -> glob -> rimraf/archiver-utils/readdir-glob/serve-handler
  -> gaxios/archiver/serve -> gcp-metadata/googleapis-common/zip-stream. Patched
  only in brace-expansion 5.0.8, which is NOT drop-in: require() there returns
  {EXPANSION_MAX, EXPANSION_MAX_LENGTH, expand} instead of a callable, so
  minimatch 3.x/5.x (`const expand = require(...)`; `expand(...)`) would throw.
  minimatch@10 has the same problem (returns an object, not a function), so
  forcing the chain would break glob/rimraf/archiver/serve — the mastra CLI
  build and dev path. All of it is build tooling fed by our own glob patterns,
  not attacker input. Correct fix is upstream: mastra bumping its own deps.

- @hono/node-server 1.19.17 (moderate, path traversal in serve-static on Windows
  via encoded %5C). Foreman never calls serveStatic — verified, zero matches in
  packages/agents/src — so the vulnerable code path is unreachable. Bumping to
  2.x is a major on the agent server's HTTP adapter; not folded into a patch pass.

NEVER run `npm audit fix --force` here: npm proposes downgrading mastra
1.12.3-alpha.3 -> 0.18.9, @mastra/fastembed 1.1.2 -> 1.0.1, and next -> 14.2.35,
which would destroy the pinned alpha track.

Note: overrides did not apply on an incremental `npm install` (next's nested
postcss 8.4.31 escaped the ^8.5.10 override and tripped check-dep-uniqueness).
A full lockfile regen was required for them to take effect.

Verified: typecheck clean, 405 agents tests pass, `next build` succeeds,
biome check exits 0, check-dep-uniqueness passes, sharp loads its native
binding (libvips 8.18.3).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… 13)

Fourth and last safely-fixable branch. npm audit 17 -> 13; high 14 -> 10.

The gaxios/rimraf/googleapis chain does NOT come from the mastra CLI — it enters
via @chat-adapter/gchat > @googleapis/chat > googleapis-common > gaxios >
rimraf@5.0.10 > glob@10 > minimatch > brace-expansion. Overriding rimraf to
^6.1.3 moves it onto glob ^13 > minimatch ^10 (patched), clearing four findings:
rimraf, gaxios, gcp-metadata, googleapis-common.

Safe because rimraf 4+ already exposes named exports; 5.0.10 and 6.1.3 have the
same object shape (rimraf, sync, native, manual, posix, windows, ...), so
gaxios's call site is unaffected. Engine floor is "20 || >=22" — CI runs 22,
local 24.

Also evaluated and REJECTED archiver ^8, which would have cleared six more
(archiver, archiver-utils, readdir-glob, zip-stream, glob, and possibly
minimatch/brace-expansion): require("archiver") in v8 returns an OBJECT, not a
callable, and mastra 1.12.3-alpha.3 calls it as archiver("zip"). mastra 1.20.x
adapted its call site and moved to archiver ^8; our pinned alpha has not, so
forcing it would break `mastra build`.

The 10 remaining highs are now entirely the mastra CLI's two glob branches:
  mastra > archiver > {archiver-utils > glob, readdir-glob} > minimatch > brace-expansion
  mastra > serve > serve-handler > minimatch@3.1.5 > brace-expansion
serve-handler@6.1.7 is the LATEST release and still pins minimatch 3.1.5
exactly, and serve@14.2.6 (also latest) pins serve-handler 6.1.7 exactly — that
branch has no upstream fix at all today. The archiver branch unblocks when the
@mastra/* pins move to a line built on archiver ^8.

Verified: typecheck clean, 405 tests pass, `mastra build` succeeds (deploy
bundle intact), `next build` succeeds (106 static pages), biome exit 0,
check-dep-uniqueness passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ha.3 -> 1.53.0)

Foreman adopted the @mastra alpha in PR #17 to get SignalProviders
(zapierPollProvider / channelTriggerProvider on `foreman signals:[]`). That code
has since been removed — there are ZERO references to any signal primitive left
in packages/agents/src. So the alpha track was being carried for a capability
that no longer exists, at the cost of exact-pinning 16 packages and eating the
transitive-drift hazard that check-dep-uniqueness.mjs exists to catch.

Every @mastra/* package now has a stable release. Moved the whole set together,
per the procedure in CLAUDE.md (all pins at once, then a clean lockfile regen):

  @mastra/core          1.42.0-alpha.3 -> 1.53.0
  @mastra/deployer      1.42.0-alpha.3 -> 1.53.0
  @mastra/server        1.42.0-alpha.3 -> 1.53.0
  @mastra/ai-sdk        1.4.5-alpha.0  -> 1.6.3
  @mastra/editor        0.11.2-alpha.0 -> 0.13.8
  @mastra/evals         1.2.4-alpha.0  -> 1.6.0
  @mastra/fastembed     1.1.2          -> 1.2.0
  @mastra/loggers       1.1.2-alpha.0  -> 1.2.0
  @mastra/memory        1.20.2-alpha.0 -> 1.23.1
  @mastra/observability 1.14.1-alpha.1 -> 1.16.2
  @mastra/pg            1.13.0-alpha.0 -> 1.17.1
  @mastra/rag           2.2.1-alpha.0  -> 2.4.2
  @mastra/voice-openai  0.12.2-alpha.0 -> 0.13.0
  @mastra/deployer-*    1.1.4x-alpha.0 -> 1.2.10
  mastra (CLI)          1.12.3-alpha.3 -> 1.20.2

Security side effect: npm audit 13 -> 9, high 10 -> 5. The CLI at 1.20.2 uses
archiver ^8, which drops archiver-utils entirely and moves readdir-glob to
minimatch ^10 — clearing archiver, archiver-utils, readdir-glob, zip-stream and
glob without any override. We could not force archiver ^8 under the old CLI
because require("archiver") in v8 returns an object, not a callable, and
1.12.3-alpha.3 calls archiver("zip").

The 5 remaining highs are one branch with no upstream fix at any version:
mastra > serve@14.2.6 > serve-handler@6.1.7 > minimatch@3.1.5 > brace-expansion.
Both serve and serve-handler are at their LATEST release and pin their child
exactly. Build tooling only, fed patterns from our own config.

Verified — including the failure mode that hid last time (agents work in-process
while every authed HTTP route 500s):
  - typecheck: 0 errors across 11 minors
  - 405 tests pass
  - `mastra build` succeeds (deploy bundle intact)
  - `next build` succeeds
  - booted .mastra/output and probed live routes: /api/agents -> 200,
    /capabilities|/conversations|/automations -> 401 (correct auth rejection,
    NOT 500). core/deployer/server all report 1.53.0.
  - check-dep-uniqueness passes, biome exit 0

Not covered: a valid-token round trip, which needs the local Supabase stack up.
The 401-vs-500 distinction is the regression signal and it is clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ct 4.0.41)

Foreman was a full major behind: npm `latest` for `ai` is the 7.x line while we
declared ^6.0.177 (agents) / ^6.0.168 (web).

Mastra was never the blocker. `ai` is not a @mastra/* dependency at all — it is
Foreman's own direct dep — and @mastra/core 1.53.0 already bundles v7 provider
bindings alongside v5/v6 via npm aliases (@ai-sdk/provider-utils-v7 -> 5.0.11,
@ai-sdk/provider-v7 -> 4.0.3). Only our declarations were behind.

  packages/agents  ai              ^6.0.177  -> ^7.0.37  (resolves 7.0.38)
  packages/web     ai              ^6.0.168  -> ^7.0.37
  packages/web     @ai-sdk/react   ^3.0.118  -> ^4.0.40  (resolves 4.0.41)

Two things the upgrade surfaced:

1. `stepCountIs` moved to a local helper (lib/stop-conditions.ts). `stopWhen` is
   a MASTRA agent option, so what we pass must satisfy Mastra's `StopCondition`,
   not the AI SDK's — and those diverged in v7. `ai`'s `stepCountIs` now returns
   `StopCondition<any, any>` (its StepResult gained callId, stepNumber, model,
   toolsContext, …) while Mastra 1.53 still declares `StopCondition<any>`, which
   broke every agent.generate()/stream() call across 11 files. The behaviour is
   a one-line step count and identical either way, so we own it rather than
   coupling the channel bots to the AI SDK's major. Purely a type-level fix —
   Mastra still invokes the condition with `{ steps }` exactly as before.

2. `@opentelemetry/api` is now declared. lib/providers/cost.ts imports it for
   the `foreman.llm.cost.usd` counter but Foreman never declared it — it was
   resolving by accident through a transitive that the v6 tree happened to pull
   in. The v7 tree does not, so it vanished and typecheck caught it. This was a
   latent packaging bug, not something v7 broke.

Verified — including a REAL streaming round trip, which is the part types cannot
catch (Mastra emits, the AI SDK wraps, @ai-sdk/react consumes; all three must
agree on the wire format):
  - typecheck: 0 errors
  - 405 tests pass
  - `next build` and `mastra build` both succeed
  - biome exit 0, check-dep-uniqueness passes
  - POST /chat/foreman against AIMock (ANTHROPIC_BASE_URL redirect, no API spend)
    emitted a correct v7 UI-message stream: start -> start-step -> text-start ->
    9x text-delta -> text-end -> finish-step -> finish (with usage metadata) ->
    [DONE]

Audit is unchanged at 9 findings, as expected: the three low @ai-sdk items come
from @mastra/core bundling a legacy v5 compat shim (@ai-sdk/ui-utils-v5 ->
@ai-sdk/provider-utils@2.2.8), which Mastra ships regardless of what we declare.

Known, not introduced here: the toxicity scorer logs "Structured output
validation failed" under AIMock. There is no scorer fixture in
tests/fixtures/aimock, so the mock returns canned prose where the scorer expects
JSON. Not verified against a live provider.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Node target — previously Foreman never declared one at all (no engines field
anywhere), so "which @types/node?" had no correct answer. It does now:

  Dockerfile.agents      node:22-slim -> node:24-slim   (BOTH stages)
  .github/workflows      node-version 22 -> 24          (all 6 jobs)
  engines.node           (absent) -> ">=24"             (root + agents)
  @types/node            ^20/^20/^22.8.6 -> ^24         (root/agents/web)

The three workspaces disagreed with each other (root ^20, agents ^20, web
^22.8.6), which mattered more than being behind.

Deliberately NOT @types/node 26. Types describe the RUNTIME API surface: compile
against 26 while running an older Node and TypeScript accepts calls to APIs that
do not exist -> clean typecheck, production crash. Verified this was a live risk,
not a hypothetical — the deployed container (Coolify app foreman-agents, build
pack `dockerfile` reading /Dockerfile.agents) reports **node v22.22.2**. Shipping
^24 types + engines ">=24" against that would have meant EBADENGINE on every
deploy and types promising APIs the container lacks. 24 is current LTS and matches
the local runtime; 26 is the Current line, not LTS — wrong under a service.

zod 4.3.6 -> 4.4.3, moved in lockstep across the root override and both
workspaces. It MUST stay single-version: check-dep-uniqueness.mjs enforces it and
a zod v3/v4 split once hung `mastra dev` for an afternoon. Confirmed one resolved
copy. (A "zod@0.1.20" in `npm ls` output is a false positive — it is
@composio/json-schema-to-zod.)

chat-adapter-imessage was bumped 0.1.1 -> 1.1.0 during this work and REVERTED.
1.1.0 took npm audit from 9 -> 21 findings (moderate 1 -> 13) by pulling
spectrum-ts@1.18.0 -> @photon-ai/otel@0.1.2 -> 12 vulnerable @opentelemetry/*
packages, rooted in @opentelemetry/core <2.8.0 (unbounded memory allocation in
W3C Baggage propagation). Those pins are wildly stale — spectrum-ts is at 12.4.0
and @photon-ai/otel at 3.3.0. npm's own recommended fix was to revert, and
@photon-ai/otel is flagged at range `*` with no forward fix. The upside that
motivated the bump (1.1.0 drops @photon-ai/advanced-imessage-kit and its sharp
chain) was already neutralised by our existing sharp ^0.35.3 override, so the
trade was: gain nothing, add 12 moderates.

Audit unchanged at 9 (3 low / 1 moderate / 5 high / 0 critical); production-only
is 4 with ZERO high.

Verified: typecheck 0 errors, 405 tests pass, `next build` succeeds, `mastra
build` succeeds, biome exit 0, check-dep-uniqueness passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Clears the last remaining moderate. npm audit 9 -> 8; production-only 4 -> 3
(all low), zero moderate, zero high in production.

@foreman/agents declared @hono/node-server ^1.19.14, which resolved 1.19.17 —
inside the GHSA-frvp-7c67-39w9 range (<2.0.5, path traversal in `serve-static`
on Windows via an encoded backslash). Foreman never imported it: zero references
across packages/agents/src, scripts and tests, the only match being a code
comment in webhook-server.ts. So the vulnerable copy existed solely because we
declared a package we do not use, pinned to the old major.

The safe 2.0.12 was already in the tree twice, via @modelcontextprotocol/sdk
(through @mastra/core) and @hono/node-ws (through @mastra/deployer).

Removed the unused declaration AND added a root override to ^2.0.12. The
override is required, not belt-and-braces: @hono/node-ws@1.3.1 declares
@hono/node-server ^1.19.11, so dropping our declaration alone would let npm
resolve a fresh vulnerable 1.x underneath it. With the override the tree now
has exactly ONE copy at 2.0.12.

Forcing @hono/node-ws past its declared range is safe here on the evidence: it
was already resolving to 2.0.12 before this change (npm flagged it `invalid`
against ^1.19.11 but deduped it there anyway), and that is the tree on which
this branch's authed HTTP routes and live chat streaming were verified working.

Verified: typecheck 0 errors, 405 tests pass, `mastra build` succeeds,
`next build` succeeds, biome exit 0, check-dep-uniqueness passes.

Remaining 8 findings are two upstream-owned advisories with no available fix:
brace-expansion <=5.0.7 (5 rows, dev-only, via mastra > serve-handler >
minimatch@3.1.5) and @ai-sdk/provider-utils <=3.0.97 (3 rows, via @mastra/core's
bundled AI-SDK v5 compat shim).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
foreman Ready Ready Preview, Comment Jul 28, 2026 7:44am

@supabase

supabase Bot commented Jul 28, 2026

Copy link
Copy Markdown

Updates to Preview Branch (chore/dep-refresh) ↗︎

Deployments Status Updated
Database Tue, 28 Jul 2026 07:42:52 UTC
Services Tue, 28 Jul 2026 07:42:52 UTC
APIs Tue, 28 Jul 2026 07:42:52 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Tue, 28 Jul 2026 07:42:53 UTC
Migrations Tue, 28 Jul 2026 07:42:54 UTC
Seeding Tue, 28 Jul 2026 07:42:54 UTC
Edge Functions Tue, 28 Jul 2026 07:42:54 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

Resolves the conflicts blocking PR #22. They were almost entirely a squash-merge
artifact: main has exactly ONE commit since the merge-base (74cafe9, the squash
of #21), and this branch carries that same work as individual commits, so git
saw "both sides changed" on 16 files that are largely the same content.

Resolution was NOT a blanket take-ours — main held two genuine improvements that
never came back to the branch after the squash:

  CLAUDE.md                              -> took main's
  packages/agents/scripts/qdna-preview-probe.ts -> took main's
    Both add `--projectId e56e0da5-...` to the documented `infisical run`
    command. The branch still had the older projectId-less form.

Everything else took the branch's side, because it is strictly newer work that
post-dates the squash:

  .github/workflows/test.yml   Node 22 -> 24 across all 6 jobs
  package.json                 @mastra/* stable pins, sharp/adm-zip/rimraf/hono
                               overrides, zod 4.4.3, @types/node ^24, engines
  packages/agents/package.json SDK 0.91, mastra CLI 1.20.2, ai ^7, otel/api
  packages/web/package.json     next 16.2.12, ai ^7, @ai-sdk/react ^4
  src/mastra/index.ts          stepCountIs from lib/stop-conditions (AI SDK v7)
  src/mastra/agents/foreman.ts same
  src/lib/durable/deps.ts      newer aged pins (sdk 0.84.4 / durable 0.9.1)
  .gitignore                   beads ignore lines added by 8295001
  docs/zapier-*.md             early-access-allowlist clarifications, and the
                               anonymised contact address from 7a5a673

`.beads/issues.jsonl` was a modify/delete: main still tracks it, 8295001
deliberately untracked it as disposable. Kept deleted.

The lockfile needed no regeneration — all three package.json files resolved to
the branch's exact state, and `npm install` produced no diff.

Verified on the merged tree: typecheck 0 errors, 405 tests pass, `mastra build`
succeeds, `next build` succeeds, biome exit 0, check-dep-uniqueness passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hamchowderr
hamchowderr merged commit c5df497 into main Jul 28, 2026
9 checks passed
@hamchowderr
hamchowderr deleted the chore/dep-refresh branch July 28, 2026 08:45
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