flue refactor and migrations

This commit is contained in:
-Puter
2026-07-29 00:45:46 +05:30
parent 601aca73c2
commit a907539810
6 changed files with 117 additions and 100 deletions

View File

@@ -1,5 +1,5 @@
import { defineConfig } from '@flue/cli/config';
import { defineConfig } from "@flue/cli/config";
export default defineConfig({
target: 'node',
target: "node",
});

View File

@@ -4,14 +4,15 @@
"private": true,
"type": "module",
"scripts": {
"build": "bun --env-file=../../.env flue build",
"build": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs build",
"start": "node --env-file=../../.env dist/server.mjs",
"check-types": "tsc --noEmit",
"dev": "bun --env-file=../../.env flue dev",
"dev:tailscale": "bun --env-file=../../.env flue dev",
"run": "bun --env-file=../../.env flue run",
"runner": "bun --env-file=../../.env src/runner.ts",
"run:zopu": "bun --env-file=../../.env flue run zopu",
"run:work-planner": "bun --env-file=../../.env flue run work-planner"
"dev": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs dev",
"dev:tailscale": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs dev",
"run": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs run",
"runner": "node --env-file=../../.env src/runner.ts",
"run:zopu": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs run zopu",
"run:work-planner": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs run work-planner"
},
"dependencies": {
"@agentos-software/git": "0.3.3",
@@ -30,6 +31,10 @@
"@code/config": "workspace:*",
"@flue/cli": "latest",
"@types/bun": "catalog:",
"@types/node": "catalog:",
"typescript": "catalog:"
},
"engines": {
"node": ">=22.18 <23 || >=23.6"
}
}

View File

@@ -1,54 +1,9 @@
import { parseAgentEnv } from "@code/env/agent";
import { defineAgent } from "@flue/runtime";
import INSTRUCTIONS from "../prompts/zopu-instructions.md" with { type: "markdown" };
import { createSliceOneTools } from "../tools/slice-one";
const INSTRUCTIONS = `You are Zopu for product Slice 1 and the Work planning handoff.
## Your role
The application stores each user message as exact evidence before you process it. You may interpret that evidence, but never supply, rewrite, or invent source text.
## Work routing loop
When a user sends a message, follow this decision flow:
1. **Assess actionability.** Does the message contain a concrete problem, request, blocker, opportunity, or decision that warrants a work unit? Greetings, questions about the system, casual conversation, and exploration do NOT create work. If the message is casual conversation, respond naturally and do nothing else.
Direct questions, casual conversation, and image-reading requests must be answered immediately without calling any tools.
2. **Identify project context.** Call list_projects. If there is one project, use it. If there are several and the request is ambiguous, ask one focused question.
3. **Create a Signal (only when actionable).** When the message is actionable:
a. Call list_signal_evidence to see the exact admitted user messages.
b. Select the message IDs that compose the problem statement.
c. Call create_signal with a structured problem statement (title, summary, desiredOutcome, constraints). The problem statement must faithfully represent the user's own intent. Do not invent scope they did not mention.
d. Include the projectId when the project is known.
4. **Route the Signal.** After creating the Signal:
a. Call list_proposed_work for the project.
b. If the Signal clearly describes the same desired outcome as existing Work, call attach_signal_to_work.
c. Otherwise call create_work_from_signal.
e. If genuinely uncertain whether to attach or create, ask one focused question.
5. **Explain the outcome.** Tell the user clearly what happened:
- "Captured [Signal title] and linked it to [Work title]."
- "Captured [Signal title] and proposed [Work title]."
- Keep the response brief; the product renders the durable Work card separately.
## Rules
- Never supply or rewrite the raw source message text. The control plane copies it server-side.
- You receive and can see images attached to the current user message. This model supports image input. Never claim images are unavailable, omitted, or unsupported. If a message has attached images, inspect them before responding.
- Never create Work from casual chat.
- Ask at most one focused clarification when genuinely ambiguous.
- Preserve project and organization scope at all times.
- Repeated delivery of the same message must not create duplicate Signals or attachments. The backend is idempotent.
- Never claim you created a Signal until the tool call returns successfully.
- Do not start implementation, planning, sandboxes, Git, verification, or delivery. Those are explicitly outside Slice 1.
- After creating proposed Work, the system may invoke the private work-planner. Never claim that a Definition, Design, approval, or implementation exists unless Convex reports it.
- Proposed Work is the only Work status you directly create.`;
export {
convexAgentRoute as attachments,
convexAgentRoute as route,

View File

@@ -0,0 +1,45 @@
You are Zopu for product Slice 1 and the Work planning handoff.
## Your role
The application stores each user message as exact evidence before you process it. You may interpret that evidence, but never supply, rewrite, or invent source text.
## Work routing loop
When a user sends a message, follow this decision flow:
1. **Assess actionability.** Does the message contain a concrete problem, request, blocker, opportunity, or decision that warrants a work unit? Greetings, questions about the system, casual conversation, and exploration do NOT create work. If the message is casual conversation, respond naturally and do nothing else.
Direct questions, casual conversation, and image-reading requests must be answered immediately without calling any tools.
2. **Identify project context.** Call list_projects. If there is one project, use it. If there are several and the request is ambiguous, ask one focused question.
3. **Create a Signal (only when actionable).** When the message is actionable:
a. Call list_signal_evidence to see the exact admitted user messages.
b. Select the message IDs that compose the problem statement.
c. Call create_signal with a structured problem statement (title, summary, desiredOutcome, constraints). The problem statement must faithfully represent the user's own intent. Do not invent scope they did not mention.
d. Include the projectId when the project is known.
4. **Route the Signal.** After creating the Signal:
a. Call list_proposed_work for the project.
b. If the Signal clearly describes the same desired outcome as existing Work, call attach_signal_to_work.
c. Otherwise call create_work_from_signal.
e. If genuinely uncertain whether to attach or create, ask one focused question.
5. **Explain the outcome.** Tell the user clearly what happened:
- "Captured [Signal title] and linked it to [Work title]."
- "Captured [Signal title] and proposed [Work title]."
- Keep the response brief; the product renders the durable Work card separately.
## Rules
- Never supply or rewrite the raw source message text. The control plane copies it server-side.
- You receive and can see images attached to the current user message. This model supports image input. Never claim images are unavailable, omitted, or unsupported. If a message has attached images, inspect them before responding.
- Never create Work from casual chat.
- Ask at most one focused clarification when genuinely ambiguous.
- Preserve project and organization scope at all times.
- Repeated delivery of the same message must not create duplicate Signals or attachments. The backend is idempotent.
- Never claim you created a Signal until the tool call returns successfully.
- Do not start implementation, planning, sandboxes, Git, verification, or delivery. Those are explicitly outside Slice 1.
- After creating proposed Work, the system may invoke the private work-planner. Never claim that a Definition, Design, approval, or implementation exists unless Convex reports it.
- Proposed Work is the only Work status you directly create.

View File

@@ -1,22 +1,20 @@
import { parseAgentEnv } from "@code/env/agent";
import {
makePiAgentOsConfig,
makePiHomeFiles,
decodeWorkAttemptExecutionInput,
WorkAttemptExecutionError,
piSessionEnv,
} from "@code/primitives";
import type {
ExecutionEvent,
WorkAttemptExecutionResult,
} from "@code/primitives";
import { agentOS, setup } from "@rivet-dev/agentos";
import { createHostDirBackend } from "@rivet-dev/agentos-core";
import { createClient } from "@rivet-dev/agentos/client";
import { Effect } from "effect";
import {
makePiAgentOsConfig,
makePiHomeFiles,
piSessionEnv,
} from "../../../primitives/src/agent-os";
import {
decodeWorkAttemptExecutionInput,
WorkAttemptExecutionError,
} from "../../../primitives/src/execution-runtime";
import type {
ExecutionEvent,
WorkAttemptExecutionResult,
} from "../../../primitives/src/execution-runtime";
import { HostRepositoryWorkspace } from "./host-repository";
const piConfig = makePiAgentOsConfig();
@@ -116,40 +114,53 @@ export const executeAgentOsAttempt = async (
: "Isolated Zopu worktree recreated"
)
);
await Promise.all([
vm.mountFs({
const mounts = [
{
hostPath: prepared.checkoutPath,
path: "/workspace/repository",
plugin: createHostDirBackend({
hostPath: prepared.checkoutPath,
readOnly: false,
}),
readOnly: false,
}),
vm.mountFs({
path: `${prepared.sourceRepositoryPath}/.git`,
plugin: createHostDirBackend({
hostPath: `${prepared.sourceRepositoryPath}/.git`,
readOnly: false,
}),
},
{
hostPath: prepared.sourceRepositoryPath,
path: prepared.sourceRepositoryPath,
readOnly: false,
}),
vm.mountFs({
},
{
hostPath: prepared.piHomePath,
path: "/home/zopu",
plugin: createHostDirBackend({
hostPath: prepared.piHomePath,
readOnly: false,
}),
readOnly: false,
}),
vm.mountFs({
},
{
hostPath: prepared.toolsPath,
path: "/opt/zopu-tools",
plugin: createHostDirBackend({
hostPath: prepared.toolsPath,
readOnly: true,
}),
readOnly: true,
}),
]);
},
] as const;
const mountNext = async (index: number): Promise<void> => {
const mount = mounts[index];
if (!mount) {
return;
}
try {
await vm.mountFs({
path: mount.path,
plugin: createHostDirBackend({
hostPath: mount.hostPath,
readOnly: mount.readOnly,
}),
readOnly: mount.readOnly,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw executionError(
`Failed to mount ${mount.hostPath} at ${mount.path}: ${message}`,
"HarnessFailed",
false
);
}
await mountNext(index + 1);
};
await mountNext(0);
const { baseRevision } = prepared;
events.push(
event(2, "repository.ready", "Repository checkout is ready", {

View File

@@ -11,7 +11,7 @@ import {
} from "node:fs/promises";
import path from "node:path";
import type { PiHomeFiles } from "../../../primitives/src/agent-os";
import type { PiHomeFiles } from "@code/primitives";
interface PrepareRepositoryInput {
attemptId: string;
@@ -159,6 +159,10 @@ export class HostRepositoryWorkspace {
"Zopu worktree creation"
);
const bunExecutable =
process.env.BUN_EXECUTABLE ??
execFileSync("which", ["bun"], { encoding: "utf-8" }).trim();
const sourceEnvPath = path.join(this.#sourceRepositoryPath, ".env");
if (await pathExists(sourceEnvPath)) {
await copyFile(sourceEnvPath, path.join(checkoutPath, ".env"));
@@ -166,7 +170,7 @@ export class HostRepositoryWorkspace {
if (this.#installDependencies) {
requireSuccess(
await runProcess(
"bun",
bunExecutable,
["install", "--frozen-lockfile"],
checkoutPath,
{ CI: "1" }
@@ -177,9 +181,6 @@ export class HostRepositoryWorkspace {
const toolsBinPath = path.join(toolsPath, "bin");
await mkdir(toolsBinPath, { recursive: true });
const bunExecutable = execFileSync("which", ["bun"], {
encoding: "utf-8",
}).trim();
const bunPath = path.join(toolsBinPath, "bun");
await copyFile(bunExecutable, bunPath);
await chmod(bunPath, 0o755);