Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9c33eef
agent: add script healing capability
arrufat Jul 8, 2026
19a722a
agent: heal scripts that return empty data
arrufat Jul 8, 2026
70109b4
agent: detect and heal empty extract fields
arrufat Jul 8, 2026
8d1ef30
agent: add extract baselines for script healing
arrufat Jul 8, 2026
3cd1e76
refactor: unify extract field classification
arrufat Jul 8, 2026
dc768aa
feat(agent): use LLM verdict to judge empty script outputs
arrufat Jul 8, 2026
f1ba950
Merge branch 'main' into agent-self-heal
arrufat Jul 9, 2026
bd67911
agent: simplify tool calls and suspicion handling
arrufat Jul 9, 2026
7a4bbd1
fix(agent): filter hallucinated fields and refactor script judging
arrufat Jul 9, 2026
2e54b06
Merge branch 'main' into agent-self-heal
arrufat Jul 9, 2026
8b88b07
Merge branch 'main' into agent-self-heal
arrufat Jul 10, 2026
0584501
Merge branch 'main' into agent-self-heal
arrufat Jul 10, 2026
718d7e7
agent: update heal prompt to preserve comments
arrufat Jul 10, 2026
18d9855
Merge branch 'main' into agent-self-heal
arrufat Jul 16, 2026
f18dce6
agent: simplify script healing and verdict logic
arrufat Jul 16, 2026
2e79039
Merge branch 'main' into agent-self-heal
arrufat Jul 17, 2026
d6eb29a
Merge branch 'main' into agent-self-heal
arrufat Jul 20, 2026
f3f551d
agent: isolate baseline during heal and fix meta-turn cancel`
arrufat Jul 20, 2026
cec4ab4
Merge branch 'main' into agent-self-heal
arrufat Jul 21, 2026
83cbf91
agent: use trimEnd to help zig 0.16.0
arrufat Jul 22, 2026
9866c8f
agent: anchor verdict parsing with marker
arrufat Jul 22, 2026
7385367
mcp: add script replay and heal_commit tools
arrufat Jul 22, 2026
d89b9f8
Merge branch 'main' into agent-self-heal
arrufat Jul 22, 2026
257afa7
Merge branch 'main' into agent-self-heal
arrufat Jul 22, 2026
f4297be
deps: update zenai dependency
arrufat Jul 22, 2026
568b24e
Merge branch 'main' into agent-self-heal
arrufat Jul 22, 2026
690247c
Merge branch 'main' into agent-self-heal
arrufat Jul 23, 2026
dd774cb
Merge branch 'main' into agent-self-heal
arrufat Jul 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .antigravitycli/c9e31ac8-54b1-42f7-ad85-b50dcaa697ba.json
Binary file added Screenshot From 2026-07-03 21-36-34.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions hacker-news-karma.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const page = new Page();
await page.goto("https://news.ycombinator.com/login");
page.waitForSelector("input[name=\"acct\"]");
page.fill("input[name=\"acct\"]", "$LP_HN_USERNAME");
page.fill("input[name=\"pw\"]", "$LP_HN_PASSWORD");
page.press("input[name=\"pw\"]", "Enter");
await page.goto("https://news.ycombinator.com/user?id=$LP_HN_USERNAME");
const { karma } = page.extract({ karma: "#hnmain table table tr:nth-child(3) td:nth-child(2)" });
return { karma: parseInt(karma, 10) };
46 changes: 46 additions & 0 deletions hacker-news.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const page = new Page();
await page.goto("https://news.ycombinator.com");

const { stories } = page.extract({
stories: [{
selector: "tr.athing",
limit: 5,
fields: {
id: { selector: "", attr: "id" },
rank: ".rank",
title: ".titleline > a"
}
}]
});

const { scoreSpans } = page.extract({
scoreSpans: [{
selector: ".score",
fields: {
id: { selector: "", attr: "id" },
points: ""
}
}]
});

const { authors } = page.extract({
authors: [".hnuser"]
});

// .score id is "score_<storyId>"; .hnuser entries are parallel to .score entries
const scoreMap = {};
const authorMap = {};
scoreSpans.forEach((s, i) => {
const storyId = s.id.replace("score_", "");
scoreMap[storyId] = s.points;
authorMap[storyId] = authors[i] || null;
});

const results = stories.map(s => ({
rank: s.rank.replace(".", "").trim(),
title: s.title,
author: authorMap[s.id] || null,
score: scoreMap[s.id] || null
}));

return results;
5 changes: 5 additions & 0 deletions headers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
HTTP/1.1 200 OK
content-length: 6006
content-type: application/json
mcp-session-id: s1

Binary file added hn-agent-stories.zip
Binary file not shown.
Binary file added hn-agent-stories/hn-karma.mp4
Binary file not shown.
Binary file added hn-agent-stories/hn-scrape.mp4
Binary file not shown.
Binary file added hn-agent-stories/hn-story-karma.mp4
Binary file not shown.
Binary file added hn-agent-stories/hn-story-scrape.mp4
Binary file not shown.
1 change: 1 addition & 0 deletions hn-agent-stories/lightpanda
67 changes: 67 additions & 0 deletions hn-agent-stories/make-stories.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
#
# Build the two HN story videos. Each is: a 1s frozen lead-in frame, a step1
# still, sped-up/normal clips, a step2 still, then closing clips. Everything is
# normalized to 1514x1032 @ 60fps CFR so the pieces concatenate seamlessly.
#
# The frozen lead-in is extracted to a PNG first (input-seek lands on the frame
# displayed at that timestamp) because these are sparse VFR screen recordings:
# a sub-second trim window inside the filtergraph can contain zero frames.
#
set -euo pipefail

norm="scale=1514:1032,setsar=1,fps=60,format=yuv420p"

# ============================================================================
# Story 1 — from hn-scrape.mp4
# ============================================================================
ffmpeg -y -ss 16.5 -i hn-scrape.mp4 -frames:v 1 frame-scrape-16.5.png

ffmpeg -y \
-i hn-scrape.mp4 \
-loop 1 -t 3 -i step1.png \
-loop 1 -t 3 -i step2.png \
-loop 1 -t 1 -i frame-scrape-16.5.png \
-filter_complex "\
[3:v]${norm}[frz];\
[1:v]${norm}[img1];\
[2:v]${norm}[img2];\
[0:v]trim=16.5:20,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[a];\
[0:v]trim=20:72,setpts=(PTS-STARTPTS)/5,fps=60,scale=1514:1032,setsar=1,format=yuv420p[b];\
[0:v]trim=72:73,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[c];\
[0:v]trim=94:105,setpts=(PTS-STARTPTS)/5,fps=60,scale=1514:1032,setsar=1,format=yuv420p[d];\
[0:v]trim=105:112,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[e];\
[0:v]trim=119.5:126.5,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[f];\
[0:v]trim=start=134.5,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[g];\
[frz][img1][a][b][c][d][e][img2][f][g]concat=n=10:v=1[out]" \
-map "[out]" -fps_mode cfr -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p hn-story-scrape.mp4

# ============================================================================
# Story 2 — from hn-karma.mp4
# ============================================================================
ffmpeg -y -ss 18 -i hn-karma.mp4 -frames:v 1 frame-karma-18.png

ffmpeg -y \
-i hn-karma.mp4 \
-loop 1 -t 3 -i step1.png \
-loop 1 -t 3 -i step2.png \
-loop 1 -t 1 -i frame-karma-18.png \
-filter_complex "\
[3:v]${norm}[frz];\
[1:v]${norm}[img1];\
[2:v]${norm}[img2];\
[0:v]trim=18:27,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[a];\
[0:v]trim=27:57,setpts=(PTS-STARTPTS)/5,fps=60,scale=1514:1032,setsar=1,format=yuv420p[b];\
[0:v]trim=67:75,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[c];\
[0:v]trim=75:81,setpts=(PTS-STARTPTS)/5,fps=60,scale=1514:1032,setsar=1,format=yuv420p[d];\
[0:v]trim=100:105,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[e];\
[0:v]trim=105:114,setpts=(PTS-STARTPTS)/5,fps=60,scale=1514:1032,setsar=1,format=yuv420p[f];\
[0:v]trim=114:117,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[g];\
[0:v]trim=126:132,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[h];\
[0:v]trim=138:146,setpts=PTS-STARTPTS,fps=60,scale=1514:1032,setsar=1,format=yuv420p[i];\
[frz][img1][a][b][c][d][e][f][g][img2][h][i]concat=n=12:v=1[out]" \
-map "[out]" -fps_mode cfr -c:v libx264 -crf 20 -preset medium -pix_fmt yuv420p hn-story-karma.mp4

echo "done:"
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 hn-story-scrape.mp4
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 hn-story-karma.mp4
Binary file added hn-agent-stories/step1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hn-agent-stories/step2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions hn-comments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Summary of Reasoning:
* 1. Navigate to the Hacker News homepage to fetch the top 5 stories.
* 2. Extract story details along with their item IDs to construct individual comment page URLs.
* 3. Create independent Page objects for each story to load and extract their top 3 comments in parallel.
* 4. Merge the extracted comments with their respective stories and return the compiled results.
*/

// Navigate to the Hacker News homepage
const homePage = new Page();
await homePage.goto("https://news.ycombinator.com");

// Extract the top 5 stories and their item IDs
const { stories } = homePage.extract({
stories: [{
selector: ".athing",
limit: 5,
fields: {
id: { attr: "id" },
title: ".titleline a",
link: { selector: ".titleline a", attr: "href" }
}
}]
});

// Open comment pages for the top 5 stories in parallel
const commentPages = stories.map(() => new Page());
await Promise.all(
commentPages.map((page, i) => page.goto(`https://news.ycombinator.com/item?id=${stories[i].id}`))
);

// Extract the top 3 comments from each story page and combine the results
const results = commentPages.map((page, i) => {
const { comments } = page.extract({
comments: [{
selector: ".comment",
limit: 3,
fields: {
text: ".commtext"
}
}]
});

return {
title: stories[i].title,
link: stories[i].link,
discussionLink: `https://news.ycombinator.com/item?id=${stories[i].id}`,
comments: comments.map(c => c.text ? c.text.trim() : "")
};
});

return results;
46 changes: 46 additions & 0 deletions hn-gemini.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const mainPage = new Page();
await mainPage.goto("https://news.ycombinator.com");

const { stories } = mainPage.extract({
stories: [
{
selector: ".athing",
limit: 5,
fields: {
id: { selector: "", attr: "id" },
title: ".titleline > a",
url: { selector: ".titleline > a", attr: "href" },
},
},
],
});

const detailPages = stories.map(() => new Page());
await Promise.all(
detailPages.map((p, i) =>
p.goto(`https://news.ycombinator.com/item?id=${stories[i].id}`),
),
);

const results = stories.map((story, i) => {
const p = detailPages[i];
const { comments } = p.extract({
comments: [
{
selector: ".comtr",
limit: 3,
fields: {
user: ".hnuser",
text: ".commtext",
},
},
],
});
return {
title: story.title,
url: story.url,
comments: comments,
};
});

return results;
44 changes: 44 additions & 0 deletions hn-pandascript-skill-refactor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Navigate to Hacker News homepage
const mainPage = new Page();
await mainPage.goto("https://news.ycombinator.com/");

// Extract the top 5 stories and their unique item IDs
const { stories } = mainPage.extract({
stories: [{
selector: "tr.athing",
limit: 5,
fields: {
id: { selector: "", attr: "id" },
title: ".titleline > a",
link: { selector: ".titleline > a", attr: "href" }
}
}]
});

// Create parallel page instances for each of the top 5 stories
const commentPages = stories.map(() => new Page());

// Navigate to all 5 comment pages concurrently
await Promise.all(commentPages.map((p, i) => p.goto(`https://news.ycombinator.com/item?id=${stories[i].id}`)));

// Extract the top 3 comments from each story's comment page
const results = commentPages.map((p, i) => {
const { comments } = p.extract({
comments: [{
selector: "tr.comtr",
limit: 3,
fields: {
author: "a.hnuser",
text: ".commtext"
}
}]
});
return {
title: stories[i].title,
link: stories[i].link,
comments: comments
};
});

// Return the aggregated list of stories with their comments
return results;
39 changes: 39 additions & 0 deletions hn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const page = new Page();
await page.goto("https://news.ycombinator.com");

const { stories } = page.extract({
stories: [{
selector: ".athing",
limit: 5,
fields: {
rank: ".rank",
title: ".titleline > a",
url: { selector: ".titleline > a", attr: "href" },
id: { selector: "", attr: "id" }
}
}]
});

const results = [];

for (const story of stories) {
await page.goto(`https://news.ycombinator.com/item?id=${story.id}`);
const { comments } = page.extract({
comments: [{
selector: ".comtr",
limit: 3,
fields: {
user: ".hnuser",
text: ".commtext"
}
}]
});
results.push({
rank: story.rank,
title: story.title,
url: story.url,
comments
});
}

return results;
1 change: 1 addition & 0 deletions mcp.pid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26444
1 change: 1 addition & 0 deletions src/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ const Commands = cli.Builder(.{
.{ .name = "system_prompt", .type = ?[:0]const u8 },
.{ .name = "task", .type = ?[]const u8 },
.{ .name = "save", .type = ?[]const u8 },
.{ .name = "heal", .type = bool },
.{ .name = "attach", .short = 'a', .type = []const u8, .multiple = true },
.{ .name = "verbosity", .type = ?AgentVerbosity },
.{ .name = "effort", .type = ?Effort },
Expand Down
Loading
Loading