Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
a487545
scripts: capture dsynth failure evidence bundles
tuxillo Jan 10, 2026
68404a0
docs: add UI option for the agentic proof of concept
tuxillo Jan 10, 2026
3631bdf
docs: add implementation plan
tuxillo Jan 10, 2026
6b6545d
scripts: complete phase 1
tuxillo Jan 10, 2026
d170e43
scripts: complete phase 2
tuxillo Jan 10, 2026
6c01236
scripts: complete phase 3
tuxillo Jan 11, 2026
7ba5b9e
scripts: complete phase 4
tuxillo Jan 11, 2026
54a3c4a
scripts: complete phase 5
tuxillo Jan 11, 2026
17a48c1
scripts: complete phase 6 (state-server)
tuxillo Jan 11, 2026
4905be4
scripts: add Phase 9 Bootstrap UI dashboard
tuxillo Jan 11, 2026
d4bb5a4
scripts: add Bundles to UI navbar
tuxillo Jan 11, 2026
c3ed911
scripts: complete phase 7
tuxillo Jan 11, 2026
0ca9250
scripts: phase 8 end-to-end automation
tuxillo Jan 12, 2026
cbaba2d
config: add whitespace preservation rule to dports-patch agent
tuxillo Jan 12, 2026
d25c941
scripts/apply-patch: delete existing branch before creating
tuxillo Jan 12, 2026
daebb86
scripts/agent-queue-runner: improve diff extraction from agent response
tuxillo Jan 12, 2026
cb678f7
config: add explicit diff format requirements to dports-patch agent
tuxillo Jan 12, 2026
9c709df
feat(patch-agent): output file content blocks instead of diffs
tuxillo Jan 12, 2026
21dab55
fix(patch-agent): clarify raw content vs diff format in prompt
tuxillo Jan 12, 2026
ab16afe
feat(observability): add activity log and runner status to state-serv…
tuxillo Jan 13, 2026
95bf0db
fix(observability): prevent event flooding, fix tab reset, improve jo…
tuxillo Jan 13, 2026
f8c2e64
fix(runner): make apply-patch flags configurable
tuxillo Jan 13, 2026
880f2a9
fix(runner): write apply context at runs root
tuxillo Jan 13, 2026
94faa46
fix(hooks): honor apply context without ai-fix branch
tuxillo Jan 13, 2026
fc8179a
fix(hooks): use apply context even without DeltaPorts dir
tuxillo Jan 13, 2026
4d82796
fix(runner): propagate iteration to patch jobs
tuxillo Jan 13, 2026
55335c8
feat(observability): add user context gating and retriage
tuxillo Jan 13, 2026
2eba718
fix(patch): enforce unified diff validation before apply
tuxillo Jan 13, 2026
387b212
feat(blobstore): add artifact-store daemon and db-only bundles
tuxillo Jan 13, 2026
00ec2aa
fix(hooks): snapshot DeltaPorts port files for patch base
tuxillo Jan 14, 2026
a6a1926
fix(agents): require unified diff output for dports-patch
tuxillo Jan 14, 2026
58b3592
fix(state-server): handle trailing slashes safely
tuxillo Jan 14, 2026
df039b0
config: replace json agents with MD ones
tuxillo Jan 14, 2026
42ca20e
feat(agentic-workspace): add worker and docs
tuxillo Jan 14, 2026
26ed7b0
feat(agentic-workspace): adapt runner to workspace patch flow
tuxillo Jan 15, 2026
1964d44
fix(runner): accept JSON section headings
tuxillo Jan 15, 2026
1d530ad
fix(tools): forward workspace env over SSH
tuxillo Jan 17, 2026
6942f2b
fix(agent): use dports tool names in prompt
tuxillo Jan 17, 2026
9f72355
fix(agent): require tool calls
tuxillo Jan 17, 2026
d084b4c
fix(agent): force tool calls before output
tuxillo Jan 17, 2026
b344995
fix(agent): require tool call first
tuxillo Jan 17, 2026
76fde33
fix(tools): return text payloads for opencode
tuxillo Jan 17, 2026
a632897
fix(worker): keep WRKDIR under workspace
tuxillo Jan 17, 2026
c5776de
fix(worker): auto-stash dirty DeltaPorts before checkout
tuxillo Jan 17, 2026
3a7096f
fix(tools): auto-encode raw content for put-file
tuxillo Jan 17, 2026
cef79a2
fix(worker): import datetime for auto-stash
tuxillo Jan 17, 2026
b056725
fix(worker): enforce origin-only changes
tuxillo Jan 17, 2026
23ea8d1
fix(runner): surface opencode JSON errors
tuxillo Jan 17, 2026
8c22fd2
fix(agent): require absolute workspace paths
tuxillo Jan 17, 2026
e27ce1b
fix(agent): disable local file tools
tuxillo Jan 17, 2026
f712c3a
feat(tools): add remote grep
tuxillo Jan 17, 2026
45e6c6e
fix(agent): avoid HTML-escaped grep patterns
tuxillo Jan 17, 2026
1496b20
fix(grep): base64-encode patterns
tuxillo Jan 17, 2026
73aafe9
fix(ui): add missing SSE event types to EVENT_BADGES
tuxillo Jan 18, 2026
cbe79dc
fix(agent): enforce tool-based patch generation in dports-patch agent
tuxillo Jan 18, 2026
60a084f
docs(testing): update E2E guide with correct agentic workspace paths
tuxillo Jan 18, 2026
d4bc7be
docs: add dportsv3 merge plan
tuxillo May 14, 2026
71e2614
feat(dev-env): add exec subcommand for non-interactive use
tuxillo May 15, 2026
8eab9ae
docs(consolidation): expand phase 1 implementation detail
tuxillo May 15, 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ For a disposable DragonFly chroot development shell, use
- **scripts/**
Shell scripts to generate the final DPorts repository, as well as a copy of the Tinderbox hooks.

Also includes `scripts/dsynth-hooks/` which provides dsynth hook scripts to capture a bounded, high-signal “evidence bundle” (distilled errors + port context) when a port fails to build.

- **ports/**
Contains subdirectories corresponding to Ports categories (e.g., `audio`, `editors`, `devel`, etc.).

Expand Down
62 changes: 62 additions & 0 deletions config/opencode/agent/dports-patch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
description: Applies DeltaPorts workspace fixes and rebuilds
mode: subagent
model: opencode/gpt-5-nano
tools:
write: false
edit: false
bash: false
read: false
glob: false
grep: false
webfetch: false
task: false
---
# DeltaPorts Patch Agent (Workspace Flow)

You operate on the shared DragonFlyBSD workspace using the custom `dports_*` tools. Do NOT output unified diffs or FILE blocks. All edits must be performed via the tools. Never write patch file contents directly; always generate `dragonfly/patch-*` files via `dports_dports_genpatch` and `dports_dports_install_patches` (one patch per source file).

You MUST begin by calling `dports_dports_workspace_verify()` before writing any text. Your first response must be a tool call (no prose). If you do not call tools, your response is invalid.

Always use absolute paths under `/build/synth/agentic-workspace` when calling `dports_dports_get_file` or `dports_dports_put_file`. Do NOT use relative paths.
For searching across files, use `dports_dports_grep` (not local grep).
When passing patterns, do NOT use HTML-escaped sequences like `>` or `<`; use raw operators like `->`.
Do NOT use the `bash` tool; it runs on the opencode host and cannot access the VM workspace.

## Required Workflow

You MUST call the tools below in order. If a tool fails, stop and report the failure.

Do NOT write placeholder content to workspace files. Only write real patch content.

1. `dports_dports_workspace_verify()` to validate workspace + FPORTS pin.
2. `dports_dports_checkout_branch(origin)` (creates `ai-work/<origin_sanitized>` if missing).
3. `dports_dports_materialize_closure(origin)` to regenerate `DPorts/<origin>` + MASTERDIR closure.
4. `dports_dports_extract(origin)` and record `WRKSRC`/`WRKDIR` if needed.
5. Apply changes using tools:
- Source patches: `dports_dports_dupe`, `dports_dports_get_file`, `dports_dports_put_file`, `dports_dports_genpatch`, `dports_dports_install_patches`.
- Skeleton diffs: edit `DPorts/<origin>` files and emit `dports_dports_emit_diff`.
- Overlay-only changes: edit `DeltaPorts/ports/<origin>` directly.
6. `dports_dports_commit(origin, message)` and capture the commit hash. Use a single-line message.
7. `dports_dports_materialize_closure(origin)` again if needed.
8. `dports_dports_dsynth_build(origin, profile)` using the profile from workspace config.

If any tool fails, stop and report the failure in `## Rebuild Status` and `## Rebuild Proof (JSON)` with `rebuild_ok=false`.

## Required Output Format

Return ONLY the sections below, in this order:

- `## Patch Log`
- `## Rebuild Status`
- `## Patch Plan (JSON)` with a ```json block
- `## Rebuild Proof (JSON)` with a ```json block

### Patch Plan (JSON) keys
Include: `origin`, `summary`, `steps`, `files`, `tools_used`, `commit_message`.

### Rebuild Proof (JSON) keys
Include: `origin`, `rebuild_ok`, `dsynth_profile`, `deltaports_branch`, `deltaports_head`, `fports_ref`, `fports_head`, `build_command`, `timestamp_utc`.

## Optional: Snippet Requests
If more context is needed, add a `## Snippet Requests` section using the documented request grammar.
40 changes: 40 additions & 0 deletions config/opencode/agent/dports-triage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
description: Triages dsynth build failures for DragonFlyBSD DeltaPorts
mode: subagent
model: opencode/gpt-5-nano
tools:
write: false
edit: false
bash: false
read: false
glob: false
grep: false
webfetch: false
task: false
---
# DeltaPorts Build Failure Triage Agent

You triage DragonFlyBSD dsynth build failures using ONLY the provided evidence.

## Output (exact headings)

## Classification
One of: compile-error, configure-error, patch-error, plist-error, missing-dep, fetch-error, unknown

## Platform
One of: dragonfly-specific, freebsd-upstream, generic

## Root Cause
1-3 sentences.

## Evidence
- Quote exact log lines from errors.txt that support the root cause.

## Suggested Fix
Concrete DeltaPorts-style fix plan.

## Confidence
One of: high, medium, low

## Notes
Optional.
257 changes: 257 additions & 0 deletions config/opencode/tool/dports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import { tool } from "@opencode-ai/plugin";

const DEFAULT_WORKER_PATH = "/build/synth/DeltaPorts/scripts/agentic-worker";

function requireEnv(name: string) {
const value = process.env[name];
if (!value) {
throw new Error(`Missing ${name} for dports tools`);
}
return value;
}

function getWorkerPath() {
return process.env.DP_WORKER_PATH ?? DEFAULT_WORKER_PATH;
}

function sshArgs() {
const host = requireEnv("DP_SSH_HOST");
const port = process.env.DP_SSH_PORT ?? "22";
const key = requireEnv("DP_SSH_KEY");
return [
"-i",
key,
"-p",
port,
"-o",
"StrictHostKeyChecking=no",
"-o",
"UserKnownHostsFile=/dev/null",
host,
];
}

function envArgs() {
const envs = [
"DP_WORKSPACE_BASE",
"DP_WORKSPACE_CONFIG",
"DP_FPORTS_DIR",
"DP_DELTAPORTS_DIR",
"DP_DPORTS_DIR",
];
const args: string[] = [];
for (const key of envs) {
const value = process.env[key];
if (!value) {
continue;
}
args.push(`${key}=${value}`);
}
return args;
}

async function runWorker(args: string[]) {
const cmd = ["ssh", ...sshArgs(), "--", "env", ...envArgs(), getWorkerPath(), ...args];
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
const stdout = await new Response(proc.stdout).text();
const stderr = await new Response(proc.stderr).text();
const exitCode = await proc.exited;

if (exitCode !== 0 && !stdout.trim()) {
throw new Error(stderr.trim() || `Worker failed with exit code ${exitCode}`);
}

let payload;
try {
payload = JSON.parse(stdout);
} catch (err) {
throw new Error(`Failed to parse worker output: ${stdout}\n${stderr}`);
}

if (!payload.ok) {
throw new Error(payload.error || "Worker returned error");
}

return JSON.stringify(payload.result);
}

export const dports_workspace_verify = tool({
description: "Verify workspace config and pinned FPORTS ref",
args: {},
async execute() {
return await runWorker(["workspace-verify"]);
},
});

export const dports_checkout_branch = tool({
description: "Checkout or create per-origin fix branch",
args: {
origin: tool.schema.string().describe("Port origin (category/port)"),
},
async execute(args) {
return await runWorker(["checkout-branch", "--origin", args.origin]);
},
});

function normalizeCommitMessage(message: string) {
return message.replace(/[\r\n]+/g, " ").trim();
}

export const dports_commit = tool({
description: "Commit current DeltaPorts changes",
args: {
origin: tool.schema.string().describe("Port origin (category/port)"),
message: tool.schema.string().describe("Commit message"),
},
async execute(args) {
const message = normalizeCommitMessage(args.message);
return await runWorker(["commit", "--origin", args.origin, "--message", message]);
},
});

export const dports_materialize_closure = tool({
description: "Materialize port + MASTERDIR closure into staged DPorts",
args: {
origin: tool.schema.string().describe("Port origin (category/port)"),
},
async execute(args) {
return await runWorker(["materialize-closure", "--origin", args.origin]);
},
});

export const dports_extract = tool({
description: "Run make extract and return WRKSRC",
args: {
origin: tool.schema.string().describe("Port origin (category/port)"),
},
async execute(args) {
return await runWorker(["extract", "--origin", args.origin]);
},
});

export const dports_get_file = tool({
description: "Read a file from the workspace (base64)",
args: {
path: tool.schema.string().describe("Absolute path inside workspace"),
},
async execute(args) {
return await runWorker(["get-file", "--path", args.path]);
},
});

function normalizeContent(content: string) {
const compact = content.replace(/\s+/g, "");
if (compact.length % 4 === 0 && /^[A-Za-z0-9+/=]+$/.test(compact)) {
try {
const buf = Buffer.from(compact, "base64");
if (buf.toString("base64") === compact) {
return compact;
}
} catch {
// fall through to raw encoding
}
}
return Buffer.from(content, "utf8").toString("base64");
}

export const dports_put_file = tool({
description: "Write a file to the workspace (raw text or base64)",
args: {
path: tool.schema.string().describe("Absolute path inside workspace"),
content: tool.schema.string().describe("Raw text or base64-encoded content"),
expectedSha256: tool.schema.string().optional().describe("Optional sha256 for optimistic lock"),
},
async execute(args) {
const encoded = normalizeContent(args.content);
const workerArgs = ["put-file", "--path", args.path, "--content", encoded];
if (args.expectedSha256) {
workerArgs.push("--expected-sha256", args.expectedSha256);
}
return await runWorker(workerArgs);
},
});

export const dports_dupe = tool({
description: "Run dupe on a WRKSRC file",
args: {
path: tool.schema.string().describe("Absolute file path inside workspace"),
},
async execute(args) {
return await runWorker(["dupe", "--path", args.path]);
},
});

export const dports_genpatch = tool({
description: "Run genpatch on a WRKSRC file",
args: {
path: tool.schema.string().describe("Absolute file path inside workspace"),
},
async execute(args) {
return await runWorker(["genpatch", "--path", args.path]);
},
});

export const dports_install_patches = tool({
description: "Install patch-* files into DeltaPorts dragonfly/",
args: {
origin: tool.schema.string().describe("Port origin (category/port)"),
patches: tool.schema.array(tool.schema.string()).optional().describe("Optional patch filenames"),
},
async execute(args) {
const workerArgs = ["install-patches", "--origin", args.origin];
if (args.patches) {
for (const patch of args.patches) {
workerArgs.push("--patch", patch);
}
}
return await runWorker(workerArgs);
},
});

export const dports_emit_diff = tool({
description: "Emit diffs/*.diff for a port skeleton file",
args: {
origin: tool.schema.string().describe("Port origin (category/port)"),
relpath: tool.schema.string().describe("Relative path under port dir (e.g. Makefile)"),
},
async execute(args) {
return await runWorker(["emit-diff", "--origin", args.origin, "--relpath", args.relpath]);
},
});

export const dports_grep = tool({
description: "Search files in workspace using ripgrep",
args: {
pattern: tool.schema.string().describe("Regex pattern to search"),
path: tool.schema.string().describe("Absolute path inside workspace"),
include: tool.schema.string().optional().describe("Glob pattern, e.g. *.c"),
maxBytes: tool.schema.number().optional().describe("Max bytes of output (default 8192)"),
},
async execute(args) {
const encoded = Buffer.from(args.pattern, "utf8").toString("base64");
const workerArgs = ["grep", "--pattern-base64", encoded, "--path", args.path];
if (args.include) {
workerArgs.push("--include", args.include);
}
if (args.maxBytes !== undefined) {
workerArgs.push("--max-bytes", String(args.maxBytes));
}
return await runWorker(workerArgs);
},
});

export const dports_dsynth_build = tool({
description: "Run dsynth just-build for an origin",
args: {
origin: tool.schema.string().describe("Port origin (category/port)"),
profile: tool.schema.string().optional().describe("dsynth profile name"),
},
async execute(args) {
const workerArgs = ["dsynth-build", "--origin", args.origin];
if (args.profile) {
workerArgs.push("--profile", args.profile);
}
return await runWorker(workerArgs);
},
});

Loading
Loading