feat(foreman): knowledge search, Mastra-native automations & scheduler, Postgres-only storage, Zapier SDK 0.81, UI sweep - #21
Merged
Conversation
…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>
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Updates to Preview Branch (feat/foreman-platform-sweep) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
…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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 —mainhas not diverged.Highlights by theme
Knowledge layer (foreman-aqjx)
save_document+ KnowledgePanel viewer.Workspace.search— one physical vectorindex per workspace (
knowledge_<ws>) as the tenant boundary (the built-insearch tool can't filter and BM25 ignores filters, so a shared index would
leak). Vector-only; index shared docs on save;
knowledge:reindexbackfill.Automations — durable + scheduled (foreman-bhb5, ufo3, zfnj, 6r9y)
(per-workspace cron). Daily digest: deterministic synthesis + opt-in LLM narrative.
state, event-level dedup, failed-trigger inbox.
Storage (foreman-k7fc)
Zapier SDK
name → keymigration; handleremaining SDK error classes.
Web / UI
skeleton loaders; Documents sidebar + page + import; automation-runs pagination;
avatars; row context menus; build/import progress bars.
Infra / docs / cleanup
rules/conventions folder; CLAUDE.md audit; Mastraversion-drift guard; Supabase CLI + Next bumps; db-types CI hardening;
open-source prep (scrubbed machine-specific values).
Verification
Agents
tsc+ 405 tests green; webnext build; Biome clean. Knowledge searchproven 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