Skip to content

feat(foreman): knowledge search, Mastra-native automations & scheduler, Postgres-only storage, Zapier SDK 0.81, UI sweep - #21

Merged
hamchowderr merged 253 commits into
mainfrom
feat/foreman-platform-sweep
Jul 7, 2026
Merged

feat(foreman): knowledge search, Mastra-native automations & scheduler, Postgres-only storage, Zapier SDK 0.81, UI sweep#21
hamchowderr merged 253 commits into
mainfrom
feat/foreman-platform-sweep

Conversation

@hamchowderr

Copy link
Copy Markdown
Owner

Summary

A large batch of Foreman work accumulated on this branch (it began as the Zapier
deprecated-alias fix and grew). Recommend Squash and merge into one clean
commit on main. Merges cleanly — main has not diverged.

Highlights by theme

Knowledge layer (foreman-aqjx)

  • Documents as per-tenant Workspace files; save_document + KnowledgePanel viewer.
  • Semantic search in chat via native Workspace.search — one physical vector
    index per workspace (knowledge_<ws>) as the tenant boundary (the built-in
    search tool can't filter and BM25 ignores filters, so a shared index would
    leak). Vector-only; index shared docs on save; knowledge:reindex backfill.

Automations — durable + scheduled (foreman-bhb5, ufo3, zfnj, 6r9y)

  • Retired custom scheduling; rebuilt on Mastra Workflows + WorkflowScheduler
    (per-workspace cron). Daily digest: deterministic synthesis + opt-in LLM narrative.
  • Durable runs: human-approval approve/deny gates, cancel, retry detail, waiting
    state, event-level dedup, failed-trigger inbox.
  • Inbox intelligence: prioritization scoring + teammate-owned aggregation.

Storage (foreman-k7fc)

  • Removed DuckDB — pure Postgres backs every Mastra domain incl. observability.

Zapier SDK

  • Bumped SDK 0.81.0 / CLI 0.62.0; trigger-inbox name → key migration; handle
    remaining SDK error classes.

Web / UI

  • Dashboards → Apps rename + searchable picker; Cmd+K command palette;
    skeleton loaders; Documents sidebar + page + import; automation-runs pagination;
    avatars; row context menus; build/import progress bars.

Infra / docs / cleanup

  • Fallow dead-code sweep; rules/ conventions folder; CLAUDE.md audit; Mastra
    version-drift guard; Supabase CLI + Next bumps; db-types CI hardening;
    open-source prep (scrubbed machine-specific values).

Verification

Agents tsc + 405 tests green; web next build; Biome clean. Knowledge search
proven end-to-end vs local Postgres (per-tenant isolation).

Merge

Squash and merge — collapses the 251-commit branch history into one commit.

🤖 Generated with Claude Code

hamchowderr and others added 30 commits June 18, 2026 13:33
…al issues

Closed: kpxf (image upload), s3qd (onboarding/chat-500), aj6m (delete), fkoh
(color theme), shg7 (settings highlight), v9z3 (account menu). Added: mh2q +
321i (Mastra Agent Harness eval). Committing the jsonl so the Dolt re-import
doesn't revert the closes next session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mobile (<640px only, no desktop regression):
- channels + tool-discovery grids 3->2 cols; site-nav px-6->px-4 sm:px-6
- settings shell: nav rail stacks above content (flex-col md:flex-row), responsive padding
- dev-console w-[600px]->w-[min(100%,600px)]

Theme & toolbox contrast:
- toolbox chips opacity 0.35->0.6 + text/50->/70; checklist checks now solid accent
- stronger theme tinting + deeper default light neutrals (muted-fg, border)
- normalized pale presets (Lake View, Underground, Forest Whisper) + synced swatches
- enlarged picker swatch dots with contrast ring

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch app-catalog vector search and action-history RAG from OpenAI
text-embedding-3-small (1536d) to local @mastra/fastembed (bge-small, 384d),
matching the agent-memory embedder so local dev needs no OpenAI key.

NOTE: dimension change 1536->384. Envs with existing catalog_vectors /
action_history indexes must drop+recreate them before deploy (foreman-hcim).
Local was clean (tables did not exist). Verified: fastembed loads locally
(dims=384); agents vitest suite green (308 passed).

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

Adds scripts/reindex-embeddings.ts (npm run embeddings:reindex) to support the
fastembed switch on envs that already have catalog_vectors/action_history at
1536d. Drops the stale indexes (guarded by --yes; dry-run by default) so the app
recreates them at the code's current EMBEDDING_DIMENSION on next use; then
`npm run catalog:embed` repopulates the catalog. Verified dry-run locally.

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

Phase 1 foundation for data dashboards (foreman-gnr0):
- Migration: append-only app_data_snapshot (text ids/user_id, JSON-as-text, REVOKE anon), index (user_id, app_key, refreshed_at DESC).
- Regenerated database.types.ts.
- lib/dashboards/snapshot.ts: saveSnapshot (append-only insert), getLatestSnapshot, getSnapshotHistory(since). Matches getSupabase()/text-JSON conventions.
Remaining P1: wire snapshot-save into zapier-poll-provider refresh + read endpoint + tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional config.snapshot to the Zapier poll trigger. When set, pollOne persists the full current record set to app_data_snapshot each cycle (append-only history), additive + best-effort so it never blocks per-record workflow firing. Reads need no approval. Agents test suite green (308 passed).

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

GET /dashboards/snapshots/:appKey returns the latest app_data_snapshot for
the caller; ?history=true&since=&limit= returns the newest-first series for
trend charts. Thin route delegating to lib/dashboards/snapshot; registered in
routes/index.ts (dynamic mount — no prefix list). Adds 8 mocked-tier unit
tests for saveSnapshot / getLatestSnapshot / getSnapshotHistory.

Closes foreman-gnr0 (Dashboards Phase 1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mocked unit tests proved the lib's logic but never touched the running stack.
Add two tiers that do:

- tests/live/dashboards-snapshot.test.ts (3 tests, npm run test:live) — real
  save→read round-trip against the migrated app_data_snapshot table: JSON-as-text
  survives the Postgres round-trip, append-only history orders newest-first, the
  since filter works. Auto-skips if Supabase is unreachable.
- tests/integration/api-routes.test.ts (+6) — real HTTP through the Hono app +
  auth middleware: 401 no-auth, 200 latest, 404 none, 200 history, 400 bad
  since/limit. Targets from("app_data_snapshot") by name since the Bearer auth
  path consumes a from() call (ensureUserExists) before the handler.

Full suite 322 pass; live tier 3 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
hamchowderr and others added 22 commits July 4, 2026 23:49
…dfm)

Replace the hand-rolled <kbd> in the sidebar Search item with the restored
ui/kbd <Kbd> component (keeping the ml-auto / collapse-hide layout classes).
It was the only ad-hoc shortcut hint in the app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the restored Pagination primitive into the automation runs table
(10/page, newest-first): Previous / "Page X of Y" / Next, shown only when
runs exceed a page and reset when switching automations. Page index is
clamped so live run updates that shrink the list can't strand an empty page.
No separate action-history table exists in the web UI — the runs list is the
history surface, so it's the single paginated target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the restored ContextMenu primitive onto sidebar chat rows, mirroring the
existing "…" dropdown actions (Rename, Share → Private/Public, Archive, Delete)
so they're reachable via right-click. The dropdown stays as the hover/click
affordance. Document-row half is split out: documents have no delete/rename in
the data layer, so that row context menu (Open + Copy path) ships in the same
change set and the missing APIs are tracked in foreman-vfbc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n-97mv, t09r)

- 97mv: the live preview build feed now renders a real Progress bar mapping the
  streamed stages (design→build→fix→ready) to 25/60/75/100%, shown only on the
  newest step so bars don't stack. Documents file-import shows a Progress bar
  that eases toward ~90% while the (single-request) import is in flight and
  snaps to 100% on success.
- t09r: document rows get a right-click ContextMenu (Open + Copy path — the only
  actions the documents data layer supports today).

Both build-verified (next build + tsc + biome green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jc12: getDurableRunStatus now extracts a compact DurableRunDetail from
getDurableRun.execution (summary.last_error + the retrying/failed ops).
Reconcile flips a mid-retry run to a new non-terminal 'retrying' status
with that detail in error, so it no longer looks like a stalled 'started';
recovered runs return to 'started' and the detail clears.

dwf8: the inbox cycle now reflects the subscription state on the automation
— an inbox that goes initialization_failure marks the automation
'trigger_failed' (self-heals to active on recovery), and the /automations
UI renders both trigger-failure states red with an explanatory banner.

foreman-jc12, foreman-dwf8

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b8k2: the worker now honors Zapier's possible_duplicate_data flag, not just
the message-id claim. Off by default (the flag is a heuristic, so skipping
risks dropping a real event); FOREMAN_SKIP_POSSIBLE_DUPLICATES=true drops
flagged events before they claim/fire. The inbox view already badges these,
so operators get visibility before opting into the skip.

foreman-b8k2

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
y4kc: adds cancelDurableRun to the durable layer, store.getRun, and
service.cancelRunForUser (workspace-scoped; already-terminal runs no-op; a
run whose durable hasn't linked is cancelled locally). New route
POST /automations/runs/:runId/cancel and a web cancelRun action. The
/automations Runs tab shows a Cancel (X) button on in-flight rows; a new
'cancelled' terminal status is added to the vocabulary and reconcile now
treats a cancelled durable as terminal so it won't flip back to 'started'.

foreman-y4kc

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rm8z (read-only half): getDurableRun detail now detects a run paused on a
ctx.createCallback approval gate (execution.status = waiting) and surfaces
the gate(s) — name, callback_token, payload_schema, expires_at. Reconcile
flips such a run to a new non-terminal 'waiting' status; the /automations
Runs tab badges it and labels the expanded detail 'Waiting for approval'.

The resume/approve POST is split to foreman-zfnj: the callback URL is minted
inside the durable and never exposed by getDurableRun, and the SDK has no
resumeDurableRun method — so it can't be wired without a durable-reports-URL
convention or a Zapier resume endpoint (and durable auth is scope-walled).

foreman-rm8z (surfacing), foreman-zfnj (resume, blocked)

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

qdna: the shadcn SKILL.md told the builder to skill_read a references/ dir
that doesn't exist (only SKILL.md is present) — reworded to lean on the
inline import map instead, so a skill_read on a missing path can't misfire.
Adds scripts/qdna-preview-probe.ts: a headless harness that builds the
preview-builder, generates from a sample brief, and reports whether the
'skill' tool fired + whether the output type-checks (the live 'TS-error
drop' verification). The wiring loads + reaches the model cleanly; the live
run is blocked on the Anthropic account being out of credits.

foreman-qdna

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live-probed the preview-builder with a small OpenAI model (Anthropic account
out of credits): the shadcn 'skill' tool fires on every build (wiring
verified), and the preview path type-checks after the self-heal loop.

Two real SKILL.md defects the probe surfaced + fixed: (1) the Allowed-imports
block modeled a bare 'import * as React' the model copied unused (fails tsc
under noUnusedLocals) — reworded around React 19's automatic JSX runtime;
(2) added an explicit 'imports must match usage' self-check + pitfall, the #1
compile failure class. The probe now mirrors preview_app's self-heal loop so
it verifies the product's real convergence, not just first-try output.

foreman-qdna

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

Re-probed the durable endpoints on SDK 0.81.0 (client-creds): 0/18 walled
(was 14 scope-walled + 4 early-access-gated on 0.79.0). Verified Foreman's
Phase-3 pipeline live end-to-end (durable-smoke: runDurable/deploy+trigger/
inbox all pass, self-cleaning) and proved the human-approval callback loop:
a ctx.createCallback run parks in execution.status=waiting; getDurableRun
exposes callback_token+payload_schema but NOT the URL (opaque id != token,
not reconstructable) — the durable must self-report it; a plain unauthenticated
POST{json} to that URL returns 200 and resumes the run to finished.

Refreshes the capability-map durable-wall note (superseding the 0.79.0
history) and prepends the narrowed, evidence-backed questions for Zapier
(GA/stability, native callback-URL exposure, callback-URL security model,
payload_schema enforcement). Unblocks zfnj (resume is buildable today).

New scripts: durable-callback-probe, durable-callback-resume-probe,
durable-cleanup-probes; durable-smoke now client-creds-aware.

foreman-e1ob, foreman-zfnj

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

Correction: the 0.81.0 durable re-probe tested CLIENT-CREDENTIALS only. The
June 403 was proven under BOTH client-creds and a real per-user PKCE user-JWT,
and Foreman's production durable auth is the per-user PKCE token (external
scope) — which durable's userJwt scheme wanted at 'internal'. So 'the wall is
open' is confirmed only for client-creds; the production per-user path is NOT
yet re-verified. Adds the caveat to the capability map, sharpens the lead
Zapier question to the credential-type/scope issue, and updates
durable-pkce-probe.ts to the current durable contract (runs Foreman's real
production path with a per-user token). Follow-up: foreman-eln1.

foreman-e1ob, foreman-eln1

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

Ran durable-pkce-probe.ts with a real per-user PKCE user-JWT (Foreman's
production auth): scope granted = external (unchanged from June), runDurable
ACCEPTED, run finished with correct output. So durable is open on BOTH app
client-credentials AND the per-user external-scope PKCE path — both were 403
in June. The tell: Zapier still grants only external scope; durable's userJwt
scheme now ACCEPTS external where it required internal.

Corrects the client-creds-only caveat across the capability map, the auth
model (table + guidance now say durable is AVAILABLE), and the engineer
questions (Q1 narrowed to GA/permanence, since credential coverage is
answered). Probe now writes the authorize URL to a scratch file (gitignored)
for headless hand-off.

foreman-e1ob, foreman-eln1

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wires the human-in-the-loop resume the callback probes proved:
- lib/durable: resolveCallbackUrl (reads the URL the durable self-reports via
  a step — getDurableRun never exposes it) + postCallback; humanApprovalGate
  source helper emits the self-report convention.
- service.respondToCallbackForUser: approve = resolve URL server-side + POST
  the payload; deny(cancel) = cancelDurableRun. The bearer URL is resolved
  fresh per request and never persisted or sent to the client.
- route POST /automations/runs/:runId/callback; web respondToCallback action.
- /automations Runs tab: Approve/Deny on a 'waiting' run.
- rules/zapier-sdk.md documents the self-report convention.

Verified live end-to-end (scripts/durable-approval-probe.ts): humanApprovalGate
durable → waiting → resolveCallbackUrl → postCallback(200) → resumed→finished
with the decision in the output. 382 agents tests, tsc (agents+web), biome green.

foreman-zfnj

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

getWorkspaceInbox now ranks entries by a pure importance/urgency score
(scoreInboxEntry) and aggregates teammate-owned automations across the
workspace. Each trigger inbox is read with its OWNER's experimental SDK
(reading a teammate's inbox with the requester's SDK 403s); one SDK is
resolved per distinct owner and cached, and an un-connected owner or a
per-inbox failure degrades to an empty entry.

Scoring signals (all from fields Zapier/Foreman expose): trigger_failed
status, paused inbox, errored messages, redelivered (stuck) messages, stale
pending backlog age; possible-duplicate is surfaced but not scored up. Each
signal is capped so one noisy inbox can't dominate the ranking.

Web /inbox surfaces a "Needs attention" list (high/medium entries with their
reasons), priority pills on subscription cards, and a "teammate" chip for
automations owned by another workspace member. Entries arrive pre-ranked.

Tests: inbox-priority.test.ts (10 pure-scorer cases incl. per-signal caps) +
getWorkspaceInbox service cases (owner attribution, per-owner SDK dedup,
priority ranking, un-connected-owner degrade). Agents+web tsc + biome clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…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>
@vercel

vercel Bot commented Jul 6, 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 6, 2026 4:31am

@supabase

supabase Bot commented Jul 6, 2026

Copy link
Copy Markdown

Updates to Preview Branch (feat/foreman-platform-sweep) ↗︎

Deployments Status Updated
Database Mon, 06 Jul 2026 04:30:12 UTC
Services Mon, 06 Jul 2026 04:30:12 UTC
APIs Mon, 06 Jul 2026 04:30:12 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 Mon, 06 Jul 2026 04:30:12 UTC
Migrations Mon, 06 Jul 2026 04:30:13 UTC
Seeding Mon, 06 Jul 2026 04:30:13 UTC
Edge Functions Mon, 06 Jul 2026 04:30:14 UTC

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

hamchowderr and others added 2 commits July 5, 2026 21:26
…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>
@hamchowderr
hamchowderr merged commit 74cafe9 into main Jul 7, 2026
9 checks passed
hamchowderr added a commit that referenced this pull request Jul 28, 2026
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>
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