Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions granola/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Granola Agent
==================

[![Launch Agent](https://agentrelay.com/launch-agent.svg)](https://agentrelay.com/cloud/deploy?agent=granola)

A proactive agent that responds when a new meeting recording is created, if it is a prospect
creates a Linear issue with what the prospect wanted and then opens up a PR implementing
what the prospect wanted in Github.
2 changes: 2 additions & 0 deletions hn-monitor/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Hacker News Monitor
==================

[![Launch Agent](https://agentrelay.com/launch-agent.svg)](https://agentrelay.com/cloud/deploy?agent=hn-monitor)

A proactive agent that scans hacker news posts for relevant topics that we decide
and posts to Slack a summary of them.

24 changes: 15 additions & 9 deletions hn-monitor/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,22 @@ export default handler(async (ctx, event) => {
await saveSeen(ctx, [...seen, ...fresh.map((s) => s.id)].slice(-200));
});

/** Top ~30 front-page stories via the public HN Algolia API. */
/** Top ~30 front-page stories via the public HN Algolia API. Returns [] on
* any network/parse failure so a transient outage doesn't crash the run. */
async function fetchFrontPage(): Promise<Story[]> {
const res = await fetch('https://hn.algolia.com/api/v1/search?tags=front_page&hitsPerPage=30');
const data = (await res.json()) as { hits: Array<{ objectID: string; title: string; url: string | null; points: number }> };
return data.hits.map((h) => ({
id: Number(h.objectID),
title: h.title,
url: h.url ?? `https://news.ycombinator.com/item?id=${h.objectID}`,
points: h.points
}));
try {
const res = await fetch('https://hn.algolia.com/api/v1/search?tags=front_page&hitsPerPage=30');
if (!res.ok) return [];
const data = (await res.json()) as { hits: Array<{ objectID: string; title: string; url: string | null; points: number }> };
return data.hits.map((h) => ({
id: Number(h.objectID),
title: h.title,
url: h.url ?? `https://news.ycombinator.com/item?id=${h.objectID}`,
points: h.points
}));
} catch {
return [];
}
Comment on lines +46 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Swallowing all network and parsing errors silently makes troubleshooting and debugging extremely difficult when the API is down or its response structure changes. Logging the errors to console.error provides visibility into failures without crashing the run.

Suggested change
if (!res.ok) return [];
const data = (await res.json()) as { hits: Array<{ objectID: string; title: string; url: string | null; points: number }> };
return data.hits.map((h) => ({
id: Number(h.objectID),
title: h.title,
url: h.url ?? `https://news.ycombinator.com/item?id=${h.objectID}`,
points: h.points
}));
} catch {
return [];
}
if (!res.ok) {
console.error(`HN API returned status ${res.status}`);
return [];
}
const data = (await res.json()) as { hits: Array<{ objectID: string; title: string; url: string | null; points: number }> };
return data.hits.map((h) => ({
id: Number(h.objectID),
title: h.title,
url: h.url ?? `https://news.ycombinator.com/item?id=${h.objectID}`,
points: h.points
}));
} catch (error) {
console.error('Error fetching HN front page:', error);
return [];
}

}

async function summarize(ctx: WorkforceCtx, stories: Story[]): Promise<string> {
Expand Down
2 changes: 2 additions & 0 deletions linear/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Linear Agent
==================

[![Launch Agent](https://agentrelay.com/launch-agent.svg)](https://agentrelay.com/cloud/deploy?agent=linear)

A proactive agent that when an issue on Linear with a certain label is opened up,
opes up a PR with the implementation. Also when commented in the Linear issue
will open up a PR.
2 changes: 2 additions & 0 deletions review/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Review Agent
==================

[![Launch Agent](https://agentrelay.com/launch-agent.svg)](https://agentrelay.com/cloud/deploy?agent=review)

A proactive agent that when a PR is opened up posts a multi agent review. If
the review finds items that needs to be changed it proactively fixes the issues both
from its own review but also other bot reviews. If there are failing CI checks
Expand Down
2 changes: 2 additions & 0 deletions spotify-releases/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Spotify Releases
==================

[![Launch Agent](https://agentrelay.com/launch-agent.svg)](https://agentrelay.com/cloud/deploy?agent=spotify-releases)

A proactive agent that checks for new releases from your favorite artists
and sends you a DM about them.
14 changes: 8 additions & 6 deletions spotify-releases/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ export default handler(async (ctx, event) => {
if (!token) throw new Error('SPOTIFY_TOKEN is required');

const since = await loadLastCheck(ctx);
const releases: Release[] = [];
for (const artist of await followedArtists(token)) {
for (const r of await latestReleases(token, artist)) {
if (r.date > since) releases.push(r);
}
}
const artists = await followedArtists(token);

// Fetch every artist's releases in parallel; one failing artist shouldn't
// sink the whole check.
const perArtist = await Promise.allSettled(
artists.map((artist) => latestReleases(token, artist))
);
const releases = perArtist.flatMap((r) => (r.status === 'fulfilled' ? r.value : [])).filter((rel) => rel.date > since);
Comment on lines +30 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Executing up to 50 concurrent requests to the Spotify API in parallel via Promise.allSettled can easily trigger rate limits (HTTP 429) or socket exhaustion. Since this is a background cron job, running the requests sequentially with a try/catch block is safer, avoids rate limiting, and still ensures that one failing artist does not sink the entire run.

  // Fetch every artist's releases sequentially with try/catch to prevent rate limiting
  // and ensure one failing artist doesn't sink the whole check.
  const releases: Release[] = [];
  for (const artist of artists) {
    try {
      const artistReleases = await latestReleases(token, artist);
      releases.push(...artistReleases.filter((rel) => rel.date > since));
    } catch {
      // Skip failing artist
    }
  }


if (releases.length > 0) await ctx.slack.dm(user, render(releases));
await saveLastCheck(ctx, today());
Expand Down
2 changes: 2 additions & 0 deletions vendor-monitor/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Vendor Monitor
==================

[![Launch Agent](https://agentrelay.com/launch-agent.svg)](https://agentrelay.com/cloud/deploy?agent=vendor-monitor)

A proactive agent that checks for changes in vendors you use in your stack and
sends the team a Slack about it.
13 changes: 9 additions & 4 deletions vendor-monitor/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ export default handler(async (ctx, event) => {
await saveVersions(ctx, { ...lastSeen, ...current });
});

/** Latest published version from the npm registry. */
/** Latest published version from the npm registry. Returns undefined on any
* failure so one bad/unreachable package doesn't abort the whole sweep. */
async function latestVersion(pkg: string): Promise<string | undefined> {
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg)}/latest`);
if (!res.ok) return undefined;
return ((await res.json()) as { version?: string }).version;
try {
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg)}/latest`);
if (!res.ok) return undefined;
return ((await res.json()) as { version?: string }).version;
} catch {
return undefined;
}
Comment on lines +45 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Swallowing all network and parsing errors silently makes troubleshooting and debugging extremely difficult when the API is down or its response structure changes. Logging the errors to console.error provides visibility into failures without crashing the run.

  } catch (error) {
    console.error(`Error fetching latest version for ${pkg}:`, error);
    return undefined;
  }

}

// ── tiny helpers ────────────────────────────────────────────────────────────
Expand Down