flue refactor and migrations
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { defineConfig } from '@flue/cli/config';
|
import { defineConfig } from "@flue/cli/config";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
target: 'node',
|
target: "node",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,14 +4,15 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"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",
|
"check-types": "tsc --noEmit",
|
||||||
"dev": "bun --env-file=../../.env flue dev",
|
"dev": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs dev",
|
||||||
"dev:tailscale": "bun --env-file=../../.env flue dev",
|
"dev:tailscale": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs dev",
|
||||||
"run": "bun --env-file=../../.env flue run",
|
"run": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs run",
|
||||||
"runner": "bun --env-file=../../.env src/runner.ts",
|
"runner": "node --env-file=../../.env src/runner.ts",
|
||||||
"run:zopu": "bun --env-file=../../.env flue run zopu",
|
"run:zopu": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs run zopu",
|
||||||
"run:work-planner": "bun --env-file=../../.env flue run work-planner"
|
"run:work-planner": "node --env-file=../../.env node_modules/@flue/cli/bin/flue.mjs run work-planner"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentos-software/git": "0.3.3",
|
"@agentos-software/git": "0.3.3",
|
||||||
@@ -30,6 +31,10 @@
|
|||||||
"@code/config": "workspace:*",
|
"@code/config": "workspace:*",
|
||||||
"@flue/cli": "latest",
|
"@flue/cli": "latest",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
|
"@types/node": "catalog:",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.18 <23 || >=23.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,9 @@
|
|||||||
import { parseAgentEnv } from "@code/env/agent";
|
import { parseAgentEnv } from "@code/env/agent";
|
||||||
import { defineAgent } from "@flue/runtime";
|
import { defineAgent } from "@flue/runtime";
|
||||||
|
|
||||||
|
import INSTRUCTIONS from "../prompts/zopu-instructions.md" with { type: "markdown" };
|
||||||
import { createSliceOneTools } from "../tools/slice-one";
|
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 {
|
export {
|
||||||
convexAgentRoute as attachments,
|
convexAgentRoute as attachments,
|
||||||
convexAgentRoute as route,
|
convexAgentRoute as route,
|
||||||
|
|||||||
45
packages/agents/src/prompts/zopu-instructions.md
Normal file
45
packages/agents/src/prompts/zopu-instructions.md
Normal 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.
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
import { parseAgentEnv } from "@code/env/agent";
|
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 { agentOS, setup } from "@rivet-dev/agentos";
|
||||||
import { createHostDirBackend } from "@rivet-dev/agentos-core";
|
import { createHostDirBackend } from "@rivet-dev/agentos-core";
|
||||||
import { createClient } from "@rivet-dev/agentos/client";
|
import { createClient } from "@rivet-dev/agentos/client";
|
||||||
import { Effect } from "effect";
|
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";
|
import { HostRepositoryWorkspace } from "./host-repository";
|
||||||
|
|
||||||
const piConfig = makePiAgentOsConfig();
|
const piConfig = makePiAgentOsConfig();
|
||||||
@@ -116,40 +114,53 @@ export const executeAgentOsAttempt = async (
|
|||||||
: "Isolated Zopu worktree recreated"
|
: "Isolated Zopu worktree recreated"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
await Promise.all([
|
const mounts = [
|
||||||
vm.mountFs({
|
{
|
||||||
|
hostPath: prepared.checkoutPath,
|
||||||
path: "/workspace/repository",
|
path: "/workspace/repository",
|
||||||
plugin: createHostDirBackend({
|
|
||||||
hostPath: prepared.checkoutPath,
|
|
||||||
readOnly: false,
|
|
||||||
}),
|
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
}),
|
},
|
||||||
vm.mountFs({
|
{
|
||||||
path: `${prepared.sourceRepositoryPath}/.git`,
|
hostPath: prepared.sourceRepositoryPath,
|
||||||
plugin: createHostDirBackend({
|
path: prepared.sourceRepositoryPath,
|
||||||
hostPath: `${prepared.sourceRepositoryPath}/.git`,
|
|
||||||
readOnly: false,
|
|
||||||
}),
|
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
}),
|
},
|
||||||
vm.mountFs({
|
{
|
||||||
|
hostPath: prepared.piHomePath,
|
||||||
path: "/home/zopu",
|
path: "/home/zopu",
|
||||||
plugin: createHostDirBackend({
|
|
||||||
hostPath: prepared.piHomePath,
|
|
||||||
readOnly: false,
|
|
||||||
}),
|
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
}),
|
},
|
||||||
vm.mountFs({
|
{
|
||||||
|
hostPath: prepared.toolsPath,
|
||||||
path: "/opt/zopu-tools",
|
path: "/opt/zopu-tools",
|
||||||
plugin: createHostDirBackend({
|
|
||||||
hostPath: prepared.toolsPath,
|
|
||||||
readOnly: true,
|
|
||||||
}),
|
|
||||||
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;
|
const { baseRevision } = prepared;
|
||||||
events.push(
|
events.push(
|
||||||
event(2, "repository.ready", "Repository checkout is ready", {
|
event(2, "repository.ready", "Repository checkout is ready", {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from "node:fs/promises";
|
} from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import type { PiHomeFiles } from "../../../primitives/src/agent-os";
|
import type { PiHomeFiles } from "@code/primitives";
|
||||||
|
|
||||||
interface PrepareRepositoryInput {
|
interface PrepareRepositoryInput {
|
||||||
attemptId: string;
|
attemptId: string;
|
||||||
@@ -159,6 +159,10 @@ export class HostRepositoryWorkspace {
|
|||||||
"Zopu worktree creation"
|
"Zopu worktree creation"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const bunExecutable =
|
||||||
|
process.env.BUN_EXECUTABLE ??
|
||||||
|
execFileSync("which", ["bun"], { encoding: "utf-8" }).trim();
|
||||||
|
|
||||||
const sourceEnvPath = path.join(this.#sourceRepositoryPath, ".env");
|
const sourceEnvPath = path.join(this.#sourceRepositoryPath, ".env");
|
||||||
if (await pathExists(sourceEnvPath)) {
|
if (await pathExists(sourceEnvPath)) {
|
||||||
await copyFile(sourceEnvPath, path.join(checkoutPath, ".env"));
|
await copyFile(sourceEnvPath, path.join(checkoutPath, ".env"));
|
||||||
@@ -166,7 +170,7 @@ export class HostRepositoryWorkspace {
|
|||||||
if (this.#installDependencies) {
|
if (this.#installDependencies) {
|
||||||
requireSuccess(
|
requireSuccess(
|
||||||
await runProcess(
|
await runProcess(
|
||||||
"bun",
|
bunExecutable,
|
||||||
["install", "--frozen-lockfile"],
|
["install", "--frozen-lockfile"],
|
||||||
checkoutPath,
|
checkoutPath,
|
||||||
{ CI: "1" }
|
{ CI: "1" }
|
||||||
@@ -177,9 +181,6 @@ export class HostRepositoryWorkspace {
|
|||||||
|
|
||||||
const toolsBinPath = path.join(toolsPath, "bin");
|
const toolsBinPath = path.join(toolsPath, "bin");
|
||||||
await mkdir(toolsBinPath, { recursive: true });
|
await mkdir(toolsBinPath, { recursive: true });
|
||||||
const bunExecutable = execFileSync("which", ["bun"], {
|
|
||||||
encoding: "utf-8",
|
|
||||||
}).trim();
|
|
||||||
const bunPath = path.join(toolsBinPath, "bun");
|
const bunPath = path.join(toolsBinPath, "bun");
|
||||||
await copyFile(bunExecutable, bunPath);
|
await copyFile(bunExecutable, bunPath);
|
||||||
await chmod(bunPath, 0o755);
|
await chmod(bunPath, 0o755);
|
||||||
|
|||||||
Reference in New Issue
Block a user