feat: prepare issue workspaces from connected repositories

This commit is contained in:
-Puter
2026-07-24 02:19:35 +05:30
parent 7974bd5f3e
commit 7caad057db
12 changed files with 794 additions and 25 deletions

View File

@@ -14,9 +14,11 @@
"dependencies": {
"@code/backend": "workspace:*",
"@code/env": "workspace:*",
"@code/primitives": "workspace:*",
"@flue/runtime": "latest",
"@rivet-dev/agentos-core": "catalog:",
"convex": "catalog:",
"effect": "catalog:",
"hono": "4.12.30",
"valibot": "^1.4.2"
},

View File

@@ -14,15 +14,15 @@ export default defineAgent(({ env, id }) => {
const { AGENT_MODEL_NAME, AGENT_MODEL_PROVIDER } = parseAgentEnv(env);
return {
cwd: "/workspace",
cwd: "/workspace/repository",
description,
instructions: `You are the issue-scoped project manager and coding agent.
Start every run by calling lookup_issue_context, reading issue.md, project.md, business.md, design.md, agent.md, work.md, steps.md, artifacts.md, signals.md, agent-manager.md, context.md, and card.md from the AgentOS workspace. Call report_work_status with working before editing.
Start every run by calling lookup_issue_context, reading /workspace/control/issue.md, the canonical context files under /workspace/control/context, and the operational artifacts under /workspace/control/artifacts. Call report_work_status with working before editing.
Work only on the bound issue. Inspect existing files before changing them. If a missing product decision makes safe progress impossible, publish the current work.md and steps.md, report needs-input, then ask one focused question. Otherwise implement the complete issue, run the relevant command or scenario in AgentOS, and preserve command evidence.
Work only on the bound issue. The repository checkout is the current working directory; inspect existing source files before changing them. If a missing product decision makes safe progress impossible, publish the current work.md and steps.md, report needs-input, then ask one focused question. Otherwise implement the complete issue, run the relevant install, edit, test, or scenario command in AgentOS, and preserve command evidence.
Before finishing, update the local work.md, steps.md, artifacts.md, and context.md files and publish each changed canonical artifact with publish_project_artifact. Report completed only after verification. On an unrecoverable error, report failed with the exact blocker. Never claim a repository change or command result you did not observe.`,
Before finishing, update the operational files under /workspace/control/artifacts and publish each changed canonical artifact with publish_project_artifact. Report completed only after verification. On an unrecoverable error, report failed with the exact blocker. Never claim a repository change or command result you did not observe.`,
model: `${AGENT_MODEL_PROVIDER}/${AGENT_MODEL_NAME}`,
sandbox: agentOs(env),
tools: createProjectTools(id, env),

View File

@@ -1,9 +1,14 @@
import { api } from "@code/backend/convex/_generated/api";
import type { Id } from "@code/backend/convex/_generated/dataModel";
import { parseAgentEnv } from "@code/env/agent";
import {
decodeIssueWorkspaceResult,
makeIssueWorkspacePlan,
} from "@code/primitives/project-workspace";
import { createSandboxSessionEnv } from "@flue/runtime";
import type { FileStat, SandboxApi, SandboxFactory } from "@flue/runtime";
import { ConvexHttpClient } from "convex/browser";
import { Effect } from "effect";
import * as v from "valibot";
const execResultSchema = v.object({
@@ -218,15 +223,69 @@ export const agentOs = (
issueId,
token: env.FLUE_DB_TOKEN,
});
await sandbox.mkdir("/workspace", { recursive: true });
await Promise.all(
context.artifacts.map((artifact) =>
sandbox.writeFile(`/workspace/${artifact.path}`, artifact.content)
)
const plan = await Effect.runPromise(
makeIssueWorkspacePlan({
artifacts: context.artifacts.map((artifact) => ({
content: artifact.content,
path: artifact.path,
})),
branchName: context.run?.branchName,
checkoutPath: context.run?.checkoutPath,
contextFiles: context.contextDocuments.map((document) => ({
content: document.content,
kind: document.kind,
path: document.path,
})),
defaultBranch:
context.run?.baseBranch ?? context.source?.defaultBranch,
issueBody: context.issue.body,
issueId: String(context.issue._id),
issueNumber: context.issue.number,
issueTitle: context.issue.title,
sourceUrl: context.run?.sourceUrl ?? context.source?.url,
})
);
await sandbox.writeFile(
"/workspace/issue.md",
`# Issue ${context.issue.number}: ${context.issue.title}\n\n${context.issue.body}\n`
const [controlDirectory, checkoutOperation, ...stagingOperations] =
plan.operations;
if (!controlDirectory || controlDirectory._tag !== "Mkdir") {
throw new Error(
"Issue workspace plan must start with a control directory"
);
}
await sandbox.mkdir(controlDirectory.path, { recursive: true });
if (!checkoutOperation || checkoutOperation._tag !== "Exec") {
throw new Error("Issue workspace plan must include a checkout command");
}
const checkoutCommandResult = await sandbox.exec(
checkoutOperation.command,
{
cwd: checkoutOperation.cwd,
timeoutMs: checkoutOperation.timeoutMs,
}
);
await Promise.all(
stagingOperations
.filter((operation) => operation._tag === "Mkdir")
.map((operation) =>
sandbox.mkdir(operation.path, { recursive: true })
)
);
await Promise.all(
stagingOperations
.filter((operation) => operation._tag === "WriteFile")
.map((operation) =>
sandbox.writeFile(operation.path, operation.content)
)
);
await Effect.runPromise(
decodeIssueWorkspaceResult({
command: checkoutCommandResult,
plan,
})
);
return createSandboxSessionEnv(sandbox, "/workspace");
},