Run fixed Zopu worktrees with Pi
This commit is contained in:
19
bun.lock
19
bun.lock
@@ -64,7 +64,6 @@
|
|||||||
"name": "@code/agents",
|
"name": "@code/agents",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentos-software/codex-cli": "0.3.4",
|
|
||||||
"@agentos-software/git": "0.3.3",
|
"@agentos-software/git": "0.3.3",
|
||||||
"@code/backend": "workspace:*",
|
"@code/backend": "workspace:*",
|
||||||
"@code/env": "workspace:*",
|
"@code/env": "workspace:*",
|
||||||
@@ -155,8 +154,8 @@
|
|||||||
"name": "@code/primitives",
|
"name": "@code/primitives",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentos-software/codex": "0.0.0-agent-acp-codex-session-benchmark.fd745e7",
|
|
||||||
"@agentos-software/git": "0.3.3",
|
"@agentos-software/git": "0.3.3",
|
||||||
|
"@agentos-software/pi": "0.2.7",
|
||||||
"@rivet-dev/agentos": "0.2.14",
|
"@rivet-dev/agentos": "0.2.14",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -247,8 +246,6 @@
|
|||||||
|
|
||||||
"@agentos-software/claude-code": ["@agentos-software/claude-code@0.2.7", "", { "dependencies": { "@agentclientprotocol/sdk": "^0.16.1", "@anthropic-ai/claude-agent-sdk": "0.2.87", "zod": "^4.1.11" }, "bin": { "claude": "dist/claude-cli.mjs", "claude-sdk-acp": "dist/adapter.js" } }, "sha512-gXmqqOUWT98QvLkQXHn1UU8OOvqMO8BRSj+0PT99mOL6XNpBlPW6L065FJ9dC4IwJC5xeGzB1XFevjOdP7LwQg=="],
|
"@agentos-software/claude-code": ["@agentos-software/claude-code@0.2.7", "", { "dependencies": { "@agentclientprotocol/sdk": "^0.16.1", "@anthropic-ai/claude-agent-sdk": "0.2.87", "zod": "^4.1.11" }, "bin": { "claude": "dist/claude-cli.mjs", "claude-sdk-acp": "dist/adapter.js" } }, "sha512-gXmqqOUWT98QvLkQXHn1UU8OOvqMO8BRSj+0PT99mOL6XNpBlPW6L065FJ9dC4IwJC5xeGzB1XFevjOdP7LwQg=="],
|
||||||
|
|
||||||
"@agentos-software/codex": ["@agentos-software/codex@0.0.0-agent-acp-codex-session-benchmark.fd745e7", "", { "dependencies": { "@agentclientprotocol/sdk": "^0.16.1", "@agentos-software/codex-cli": "0.3.4" }, "bin": { "codex-acp": "dist/adapter.js" } }, "sha512-rA0PQHlxX9P9GdUQUZPHkansHg+C//khRJzT3YYOY4Y24RxzM7v40jcQPGxiKe/HDudtJ9SyuuVIJx1aLHFcRw=="],
|
|
||||||
|
|
||||||
"@agentos-software/codex-cli": ["@agentos-software/codex-cli@0.3.4", "", {}, "sha512-SAw3EOTa90dJLgEVVoE7JJIxHwticzdD9cEM9v00gpxhxC9vdLkeBva6rgWIUB9+qD1JEYBvUCyNkIpD2kT5YQ=="],
|
"@agentos-software/codex-cli": ["@agentos-software/codex-cli@0.3.4", "", {}, "sha512-SAw3EOTa90dJLgEVVoE7JJIxHwticzdD9cEM9v00gpxhxC9vdLkeBva6rgWIUB9+qD1JEYBvUCyNkIpD2kT5YQ=="],
|
||||||
|
|
||||||
"@agentos-software/common": ["@agentos-software/common@0.2.14", "", { "dependencies": { "@agentos-software/coreutils": "0.3.4", "@agentos-software/diffutils": "0.3.4", "@agentos-software/findutils": "0.3.4", "@agentos-software/gawk": "0.3.4", "@agentos-software/grep": "0.3.4", "@agentos-software/gzip": "0.3.4", "@agentos-software/sed": "0.3.4", "@agentos-software/tar": "0.3.5" } }, "sha512-ve/ks2MZtXFN9JK+kcFnSMGI1tqoUy36sTKWYGqApcgg/3JJwqnMs4JIKL5OXjgCtDuTvTjRySbQvEKxGkeeSQ=="],
|
"@agentos-software/common": ["@agentos-software/common@0.2.14", "", { "dependencies": { "@agentos-software/coreutils": "0.3.4", "@agentos-software/diffutils": "0.3.4", "@agentos-software/findutils": "0.3.4", "@agentos-software/gawk": "0.3.4", "@agentos-software/grep": "0.3.4", "@agentos-software/gzip": "0.3.4", "@agentos-software/sed": "0.3.4", "@agentos-software/tar": "0.3.5" } }, "sha512-ve/ks2MZtXFN9JK+kcFnSMGI1tqoUy36sTKWYGqApcgg/3JJwqnMs4JIKL5OXjgCtDuTvTjRySbQvEKxGkeeSQ=="],
|
||||||
@@ -1945,7 +1942,7 @@
|
|||||||
|
|
||||||
"dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="],
|
"dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="],
|
||||||
|
|
||||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
"data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
|
||||||
|
|
||||||
"dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="],
|
"dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="],
|
||||||
|
|
||||||
@@ -2929,7 +2926,7 @@
|
|||||||
|
|
||||||
"parse-png": ["parse-png@2.1.0", "", { "dependencies": { "pngjs": "^3.3.0" } }, "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ=="],
|
"parse-png": ["parse-png@2.1.0", "", { "dependencies": { "pngjs": "^3.3.0" } }, "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ=="],
|
||||||
|
|
||||||
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
"parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="],
|
||||||
|
|
||||||
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="],
|
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="],
|
||||||
|
|
||||||
@@ -3785,8 +3782,6 @@
|
|||||||
|
|
||||||
"cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
"cli-highlight/parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="],
|
|
||||||
|
|
||||||
"cli-highlight/yargs": ["yargs@16.2.2", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w=="],
|
"cli-highlight/yargs": ["yargs@16.2.2", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w=="],
|
||||||
|
|
||||||
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
@@ -3859,10 +3854,12 @@
|
|||||||
|
|
||||||
"gcp-metadata/google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
|
"gcp-metadata/google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="],
|
||||||
|
|
||||||
"get-uri/data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
|
|
||||||
|
|
||||||
"googleapis-common/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
"googleapis-common/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||||
|
|
||||||
|
"hast-util-from-html/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||||
|
|
||||||
|
"hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||||
|
|
||||||
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
|
|
||||||
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||||
@@ -4443,6 +4440,8 @@
|
|||||||
|
|
||||||
"@expo/package-manager/ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
"@expo/package-manager/ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||||
|
|
||||||
|
"@google/genai/google-auth-library/gaxios/node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||||
|
|
||||||
"@react-native/dev-middleware/serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"@react-native/dev-middleware/serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
|
|
||||||
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||||
|
|||||||
@@ -25,14 +25,7 @@ CONVEX_INSTANCE_NAME=zopu-production
|
|||||||
CONVEX_INSTANCE_SECRET=
|
CONVEX_INSTANCE_SECRET=
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 2. Self-hosted Git / Gitea — REQUIRED for issue lifecycle
|
# 2. Model gateway — REQUIRED
|
||||||
# The agent daemon clones repos and creates PRs through Gitea.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
GITEA_URL=https://git.openputer.com
|
|
||||||
GITEA_TOKEN=replace-with-gitea-api-token
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 3. Model gateway — REQUIRED
|
|
||||||
# All model calls route through this OpenAI-compatible endpoint.
|
# All model calls route through this OpenAI-compatible endpoint.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
AGENT_MODEL_PROVIDER=cheaptricks
|
AGENT_MODEL_PROVIDER=cheaptricks
|
||||||
@@ -44,17 +37,17 @@ AGENT_MODEL_CONTEXT_WINDOW=262000
|
|||||||
AGENT_MODEL_MAX_TOKENS=131072
|
AGENT_MODEL_MAX_TOKENS=131072
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 4. AgentOS / Rivet Engine — REQUIRED for the execution runner
|
# 3. AgentOS / Rivet Engine — REQUIRED for the execution runner
|
||||||
# The agent service and runner connect through the public engine endpoint.
|
# The runner creates isolated worktrees from ZOPU_SOURCE_REPOSITORY and
|
||||||
# RIVET_WORKSPACE_TOKEN authenticates every workspace actor connection.
|
# connects to AgentOS through the public engine endpoint.
|
||||||
# RIVET_ENVOY_VERSION must change for each runner deployment.
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
RIVET_ENDPOINT=https://default:@rivet.example.com
|
RIVET_ENDPOINT=https://default:@rivet.example.com
|
||||||
RIVET_PUBLIC_ENDPOINT=https://default@rivet.example.com
|
RIVET_PUBLIC_ENDPOINT=https://default@rivet.example.com
|
||||||
RIVET_ENVOY_VERSION=1
|
RIVET_ENVOY_VERSION=1
|
||||||
RIVET_WORKSPACE_TOKEN=replace-with-a-long-random-workspace-token
|
RIVET_WORKSPACE_TOKEN=replace-with-a-long-random-workspace-token
|
||||||
|
ZOPU_SOURCE_REPOSITORY=/opt/zopu-source
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 5. Zopu agent service (Flue)
|
# 4. Zopu agent service (Flue)
|
||||||
# FLUE_DB_TOKEN authenticates the Flue persistence adapter.
|
# FLUE_DB_TOKEN authenticates the Flue persistence adapter.
|
||||||
# zopu-agent.service pins the Flue Node server to port 3583.
|
# zopu-agent.service pins the Flue Node server to port 3583.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -62,7 +55,7 @@ FLUE_DB_TOKEN=replace-with-long-random-token
|
|||||||
AGENT_BACKEND_URL=https://zopu-agent.example.com
|
AGENT_BACKEND_URL=https://zopu-agent.example.com
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 6. Daemon identity
|
# 5. Daemon identity
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
DAEMON_ID=zopu-dedicated
|
DAEMON_ID=zopu-dedicated
|
||||||
DAEMON_NAME=Zopu-Dedicated-Server
|
DAEMON_NAME=Zopu-Dedicated-Server
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ FROM node:24-bookworm-slim
|
|||||||
COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun
|
COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends g++ make python3 \
|
&& apt-get install -y --no-install-recommends ca-certificates g++ git make python3 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ services:
|
|||||||
CONVEX_URL: ${CONVEX_URL}
|
CONVEX_URL: ${CONVEX_URL}
|
||||||
DAEMON_ID: zopu-agentos-runner
|
DAEMON_ID: zopu-agentos-runner
|
||||||
FLUE_DB_TOKEN: ${FLUE_DB_TOKEN}
|
FLUE_DB_TOKEN: ${FLUE_DB_TOKEN}
|
||||||
GITEA_TOKEN: ${GITEA_TOKEN}
|
|
||||||
GITEA_URL: ${GITEA_URL}
|
|
||||||
RIVET_ENDPOINT: ${RIVET_ENDPOINT}
|
RIVET_ENDPOINT: ${RIVET_ENDPOINT}
|
||||||
RIVET_PUBLIC_ENDPOINT: ${RIVET_PUBLIC_ENDPOINT}
|
RIVET_PUBLIC_ENDPOINT: ${RIVET_PUBLIC_ENDPOINT}
|
||||||
RIVET_ENVOY_VERSION: ${RIVET_ENVOY_VERSION}
|
RIVET_ENVOY_VERSION: ${RIVET_ENVOY_VERSION}
|
||||||
RIVET_WORKSPACE_TOKEN: ${RIVET_WORKSPACE_TOKEN}
|
RIVET_WORKSPACE_TOKEN: ${RIVET_WORKSPACE_TOKEN}
|
||||||
RIVET_POOL: default
|
RIVET_POOL: default
|
||||||
|
ZOPU_SOURCE_REPOSITORY: /opt/zopu-source
|
||||||
volumes:
|
volumes:
|
||||||
|
- ../..:/opt/zopu-source
|
||||||
- zopu-agentos-workspaces:/var/lib/zopu/workspaces
|
- zopu-agentos-workspaces:/var/lib/zopu/workspaces
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
"run:work-planner": "bun --env-file=../../.env flue run work-planner"
|
"run:work-planner": "bun --env-file=../../.env flue run work-planner"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentos-software/codex-cli": "0.3.4",
|
|
||||||
"@agentos-software/git": "0.3.3",
|
"@agentos-software/git": "0.3.3",
|
||||||
"@code/backend": "workspace:*",
|
"@code/backend": "workspace:*",
|
||||||
"@code/env": "workspace:*",
|
"@code/env": "workspace:*",
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { createClient } from "@rivet-dev/agentos/client";
|
|||||||
import { Effect } from "effect";
|
import { Effect } from "effect";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
codexSessionEnv,
|
makePiAgentOsConfig,
|
||||||
makeCodexAgentOsConfig,
|
makePiHomeFiles,
|
||||||
|
piSessionEnv,
|
||||||
} from "../../../primitives/src/agent-os";
|
} from "../../../primitives/src/agent-os";
|
||||||
import {
|
import {
|
||||||
decodeWorkAttemptExecutionInput,
|
decodeWorkAttemptExecutionInput,
|
||||||
@@ -18,7 +19,7 @@ import type {
|
|||||||
} from "../../../primitives/src/execution-runtime";
|
} from "../../../primitives/src/execution-runtime";
|
||||||
import { HostRepositoryWorkspace } from "./host-repository";
|
import { HostRepositoryWorkspace } from "./host-repository";
|
||||||
|
|
||||||
const codexConfig = makeCodexAgentOsConfig();
|
const piConfig = makePiAgentOsConfig();
|
||||||
const workspace = agentOS<undefined, { token: string }>({
|
const workspace = agentOS<undefined, { token: string }>({
|
||||||
onBeforeConnect: (_context, params) => {
|
onBeforeConnect: (_context, params) => {
|
||||||
if (params.token !== process.env.RIVET_WORKSPACE_TOKEN) {
|
if (params.token !== process.env.RIVET_WORKSPACE_TOKEN) {
|
||||||
@@ -28,8 +29,8 @@ const workspace = agentOS<undefined, { token: string }>({
|
|||||||
options: {
|
options: {
|
||||||
actionTimeout: 10 * 60 * 1000,
|
actionTimeout: 10 * 60 * 1000,
|
||||||
},
|
},
|
||||||
permissions: codexConfig.permissions,
|
permissions: piConfig.permissions,
|
||||||
software: codexConfig.software,
|
software: piConfig.software,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const runtimeRegistry = setup({ use: { workspace } });
|
export const runtimeRegistry = setup({ use: { workspace } });
|
||||||
@@ -94,24 +95,61 @@ export const executeAgentOsAttempt = async (
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
const hostRepository = new HostRepositoryWorkspace();
|
const hostRepository = new HostRepositoryWorkspace();
|
||||||
const prepared = await hostRepository.prepare(input);
|
const prepared = await hostRepository.prepare({
|
||||||
|
attemptId: input.attemptId,
|
||||||
|
piHomeFiles: makePiHomeFiles({
|
||||||
|
api: env.AGENT_MODEL_API,
|
||||||
|
apiKeyEnvironmentVariable: "AGENT_MODEL_API_KEY",
|
||||||
|
baseUrl: env.AGENT_MODEL_BASE_URL,
|
||||||
|
contextWindow: env.AGENT_MODEL_CONTEXT_WINDOW,
|
||||||
|
maxTokens: env.AGENT_MODEL_MAX_TOKENS,
|
||||||
|
model: env.AGENT_MODEL_NAME,
|
||||||
|
provider: env.AGENT_MODEL_PROVIDER,
|
||||||
|
}),
|
||||||
|
});
|
||||||
events.push(
|
events.push(
|
||||||
event(
|
event(
|
||||||
1,
|
1,
|
||||||
prepared.cloned ? "repository.cloning" : "runtime.preparing",
|
"runtime.preparing",
|
||||||
prepared.cloned
|
prepared.created
|
||||||
? "Project repository cloned on the execution host"
|
? "Isolated Zopu worktree created on the execution host"
|
||||||
: "Host repository checkout reused"
|
: "Isolated Zopu worktree recreated"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
await vm.mountFs({
|
await Promise.all([
|
||||||
path: "/workspace/repository",
|
vm.mountFs({
|
||||||
plugin: createHostDirBackend({
|
path: "/workspace/repository",
|
||||||
hostPath: prepared.checkoutPath,
|
plugin: createHostDirBackend({
|
||||||
|
hostPath: prepared.checkoutPath,
|
||||||
|
readOnly: false,
|
||||||
|
}),
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
}),
|
}),
|
||||||
readOnly: false,
|
vm.mountFs({
|
||||||
});
|
path: `${prepared.sourceRepositoryPath}/.git`,
|
||||||
|
plugin: createHostDirBackend({
|
||||||
|
hostPath: `${prepared.sourceRepositoryPath}/.git`,
|
||||||
|
readOnly: false,
|
||||||
|
}),
|
||||||
|
readOnly: false,
|
||||||
|
}),
|
||||||
|
vm.mountFs({
|
||||||
|
path: "/home/zopu",
|
||||||
|
plugin: createHostDirBackend({
|
||||||
|
hostPath: prepared.piHomePath,
|
||||||
|
readOnly: false,
|
||||||
|
}),
|
||||||
|
readOnly: false,
|
||||||
|
}),
|
||||||
|
vm.mountFs({
|
||||||
|
path: "/opt/zopu-tools",
|
||||||
|
plugin: createHostDirBackend({
|
||||||
|
hostPath: prepared.toolsPath,
|
||||||
|
readOnly: true,
|
||||||
|
}),
|
||||||
|
readOnly: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
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", {
|
||||||
@@ -119,22 +157,19 @@ export const executeAgentOsAttempt = async (
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const sessionId = `codex-${input.attemptId}`;
|
const sessionId = `pi-${input.attemptId}`;
|
||||||
await vm.openSession({
|
await vm.openSession({
|
||||||
|
additionalDirectories: [`${prepared.sourceRepositoryPath}/.git`],
|
||||||
additionalInstructions:
|
additionalInstructions:
|
||||||
"Work only inside /workspace/repository. Do not reveal credentials. Make the requested change and run focused verification. Do not push or open a pull request.",
|
"Work only inside /workspace/repository. This is an isolated worktree of the Zopu product repository. Read AGENTS.md and the relevant product specifications before changing code. Never reveal credentials, modify the read-only base checkout, push, or open a pull request. Implement the requested change and run focused verification.",
|
||||||
agent: "codex",
|
agent: "pi",
|
||||||
cwd: "/workspace/repository",
|
cwd: "/workspace/repository",
|
||||||
env: codexSessionEnv({
|
env: piSessionEnv(env.AGENT_MODEL_API_KEY),
|
||||||
apiKey: env.AGENT_MODEL_API_KEY,
|
|
||||||
baseUrl: env.AGENT_MODEL_BASE_URL,
|
|
||||||
model: env.AGENT_MODEL_NAME,
|
|
||||||
}),
|
|
||||||
permissionPolicy: "allow_all",
|
permissionPolicy: "allow_all",
|
||||||
sessionId,
|
sessionId,
|
||||||
});
|
});
|
||||||
events.push(
|
events.push(
|
||||||
event(3, "harness.started", "Codex implementation session started")
|
event(3, "harness.started", "Pi implementation session started")
|
||||||
);
|
);
|
||||||
const promptResult = await vm.prompt({
|
const promptResult = await vm.prompt({
|
||||||
content: [{ text: input.prompt, type: "text" }],
|
content: [{ text: input.prompt, type: "text" }],
|
||||||
@@ -145,14 +180,14 @@ export const executeAgentOsAttempt = async (
|
|||||||
const reason =
|
const reason =
|
||||||
promptResult.stopReason === "cancelled" ? "Cancelled" : "HarnessFailed";
|
promptResult.stopReason === "cancelled" ? "Cancelled" : "HarnessFailed";
|
||||||
throw executionError(
|
throw executionError(
|
||||||
`Codex stopped with ${promptResult.stopReason}`,
|
`Pi stopped with ${promptResult.stopReason}`,
|
||||||
reason,
|
reason,
|
||||||
promptResult.stopReason === "max_tokens" ||
|
promptResult.stopReason === "max_tokens" ||
|
||||||
promptResult.stopReason === "max_turn_requests"
|
promptResult.stopReason === "max_turn_requests"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
events.push(
|
events.push(
|
||||||
event(4, "harness.progress", "Codex implementation turn completed", {
|
event(4, "harness.progress", "Pi implementation turn completed", {
|
||||||
stopReason: promptResult.stopReason,
|
stopReason: promptResult.stopReason,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -184,8 +219,8 @@ export const executeAgentOsAttempt = async (
|
|||||||
events,
|
events,
|
||||||
summary:
|
summary:
|
||||||
changedFiles.length > 0
|
changedFiles.length > 0
|
||||||
? `Codex changed ${changedFiles.length} file(s)`
|
? `Pi changed ${changedFiles.length} file(s)`
|
||||||
: "Codex completed without repository changes",
|
: "Pi completed without repository changes",
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw classifyRuntimeFailure(error);
|
throw classifyRuntimeFailure(error);
|
||||||
@@ -205,5 +240,5 @@ export const cancelAgentOsAttempt = async (
|
|||||||
.getOrCreate([workspaceKey], {
|
.getOrCreate([workspaceKey], {
|
||||||
params: { token: env.RIVET_WORKSPACE_TOKEN },
|
params: { token: env.RIVET_WORKSPACE_TOKEN },
|
||||||
})
|
})
|
||||||
.cancelPrompt({ sessionId: `codex-${attemptId}` });
|
.cancelPrompt({ sessionId: `pi-${attemptId}` });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,21 +1,30 @@
|
|||||||
|
import { execFileSync, spawn } from "node:child_process";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import { mkdir } from "node:fs/promises";
|
import { once } from "node:events";
|
||||||
|
import {
|
||||||
|
access,
|
||||||
|
chmod,
|
||||||
|
copyFile,
|
||||||
|
mkdir,
|
||||||
|
rm,
|
||||||
|
writeFile,
|
||||||
|
} from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import type { RepositoryExecutionAuth } from "../../../primitives/src/execution-runtime";
|
import type { PiHomeFiles } from "../../../primitives/src/agent-os";
|
||||||
|
|
||||||
interface PrepareRepositoryInput {
|
interface PrepareRepositoryInput {
|
||||||
auth: Pick<RepositoryExecutionAuth, "credential" | "provider" | "username">;
|
attemptId: string;
|
||||||
baseBranch: string;
|
piHomeFiles: PiHomeFiles;
|
||||||
repositoryUrl: string;
|
|
||||||
runId: string;
|
|
||||||
workspaceKey: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PreparedRepository {
|
interface PreparedRepository {
|
||||||
baseRevision: string;
|
baseRevision: string;
|
||||||
checkoutPath: string;
|
checkoutPath: string;
|
||||||
cloned: boolean;
|
created: boolean;
|
||||||
|
piHomePath: string;
|
||||||
|
sourceRepositoryPath: string;
|
||||||
|
toolsPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CollectRepositoryInput {
|
interface CollectRepositoryInput {
|
||||||
@@ -30,30 +39,19 @@ interface CollectedRepository {
|
|||||||
diff: string;
|
diff: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GitResult {
|
interface ProcessResult {
|
||||||
exitCode: number;
|
exitCode: number;
|
||||||
stderr: string;
|
stderr: string;
|
||||||
stdout: string;
|
stdout: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const requireSuccess = (result: GitResult, operation: string): string => {
|
const requireSuccess = (result: ProcessResult, operation: string): string => {
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
throw new Error(`${operation} failed: ${result.stderr || result.stdout}`);
|
throw new Error(`${operation} failed: ${result.stderr || result.stdout}`);
|
||||||
}
|
}
|
||||||
return result.stdout.trim();
|
return result.stdout.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
const authEnvironment = (
|
|
||||||
auth: PrepareRepositoryInput["auth"]
|
|
||||||
): Record<string, string> => ({
|
|
||||||
GIT_CONFIG_COUNT: "1",
|
|
||||||
GIT_CONFIG_KEY_0: "http.extraHeader",
|
|
||||||
GIT_CONFIG_VALUE_0: `Authorization: Basic ${Buffer.from(
|
|
||||||
`${auth.username ?? (auth.provider === "github" ? "x-access-token" : "git")}:${auth.credential}`
|
|
||||||
).toString("base64")}`,
|
|
||||||
GIT_TERMINAL_PROMPT: "0",
|
|
||||||
});
|
|
||||||
|
|
||||||
const changedFilePath = (line: string): string => {
|
const changedFilePath = (line: string): string => {
|
||||||
const filePath = line.slice(3).trim();
|
const filePath = line.slice(3).trim();
|
||||||
const renameSeparator = " -> ";
|
const renameSeparator = " -> ";
|
||||||
@@ -63,99 +61,138 @@ const changedFilePath = (line: string): string => {
|
|||||||
: filePath.slice(renameIndex + renameSeparator.length);
|
: filePath.slice(renameIndex + renameSeparator.length);
|
||||||
};
|
};
|
||||||
|
|
||||||
const runGit = async (
|
const runProcess = async (
|
||||||
cwd: string,
|
command: string,
|
||||||
args: readonly string[],
|
args: readonly string[],
|
||||||
|
cwd: string,
|
||||||
env: Record<string, string> = {}
|
env: Record<string, string> = {}
|
||||||
): Promise<GitResult> => {
|
): Promise<ProcessResult> => {
|
||||||
const environment = Object.fromEntries(
|
const environment = Object.fromEntries(
|
||||||
Object.entries(process.env).filter(
|
Object.entries(process.env).filter(
|
||||||
(entry): entry is [string, string] => entry[1] !== undefined
|
(entry): entry is [string, string] => entry[1] !== undefined
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const child = Bun.spawn(["git", ...args], {
|
const child = spawn(command, args, {
|
||||||
cwd,
|
cwd,
|
||||||
env: { ...environment, ...env },
|
env: { ...environment, ...env },
|
||||||
stderr: "pipe",
|
|
||||||
stdout: "pipe",
|
|
||||||
});
|
});
|
||||||
const [exitCode, stderr, stdout] = await Promise.all([
|
const stderr: Uint8Array[] = [];
|
||||||
child.exited,
|
const stdout: Uint8Array[] = [];
|
||||||
new Response(child.stderr).text(),
|
child.stderr.on("data", (chunk: Uint8Array) => stderr.push(chunk));
|
||||||
new Response(child.stdout).text(),
|
child.stdout.on("data", (chunk: Uint8Array) => stdout.push(chunk));
|
||||||
]);
|
const [exitCode] = await once(child, "close");
|
||||||
return { exitCode, stderr, stdout };
|
return {
|
||||||
|
exitCode: typeof exitCode === "number" ? exitCode : 1,
|
||||||
|
stderr: Buffer.concat(stderr).toString(),
|
||||||
|
stdout: Buffer.concat(stdout).toString(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const runGit = (cwd: string, args: readonly string[]) =>
|
||||||
|
runProcess("git", args, cwd);
|
||||||
|
|
||||||
|
const pathExists = async (target: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await access(target);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export class HostRepositoryWorkspace {
|
export class HostRepositoryWorkspace {
|
||||||
readonly #root: string;
|
readonly #root: string;
|
||||||
|
readonly #sourceRepositoryPath: string;
|
||||||
|
readonly #installDependencies: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
root = process.env.AGENT_WORKSPACE_ROOT ?? "/var/lib/zopu/workspaces"
|
root = process.env.AGENT_WORKSPACE_ROOT ?? "/var/lib/zopu/workspaces",
|
||||||
|
sourceRepositoryPath = process.env.ZOPU_SOURCE_REPOSITORY ??
|
||||||
|
"/opt/zopu-source",
|
||||||
|
installDependencies = true
|
||||||
) {
|
) {
|
||||||
this.#root = root;
|
this.#root = root;
|
||||||
|
this.#sourceRepositoryPath = sourceRepositoryPath;
|
||||||
|
this.#installDependencies = installDependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepare(input: PrepareRepositoryInput): Promise<PreparedRepository> {
|
async prepare(input: PrepareRepositoryInput): Promise<PreparedRepository> {
|
||||||
const checkoutPath = path.join(
|
if (!(await pathExists(path.join(this.#sourceRepositoryPath, ".git")))) {
|
||||||
this.#root,
|
throw new Error(
|
||||||
createHash("sha256")
|
`Fixed Zopu source repository is unavailable at ${this.#sourceRepositoryPath}`
|
||||||
.update(input.workspaceKey)
|
|
||||||
.digest("hex")
|
|
||||||
.slice(0, 32),
|
|
||||||
"repository"
|
|
||||||
);
|
|
||||||
await mkdir(path.dirname(checkoutPath), { recursive: true });
|
|
||||||
const cloned = !(await Bun.file(
|
|
||||||
path.join(checkoutPath, ".git", "HEAD")
|
|
||||||
).exists());
|
|
||||||
const authEnv = authEnvironment(input.auth);
|
|
||||||
|
|
||||||
if (cloned) {
|
|
||||||
requireSuccess(
|
|
||||||
await runGit(
|
|
||||||
this.#root,
|
|
||||||
["clone", input.repositoryUrl, checkoutPath],
|
|
||||||
authEnv
|
|
||||||
),
|
|
||||||
"Repository clone"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
requireSuccess(
|
|
||||||
await runGit(checkoutPath, [
|
|
||||||
"remote",
|
|
||||||
"set-url",
|
|
||||||
"origin",
|
|
||||||
input.repositoryUrl,
|
|
||||||
]),
|
|
||||||
"Repository remote update"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const identity = createHash("sha256")
|
||||||
|
.update(input.attemptId)
|
||||||
|
.digest("hex")
|
||||||
|
.slice(0, 24);
|
||||||
|
const workspacePath = path.join(this.#root, identity);
|
||||||
|
const checkoutPath = path.join(workspacePath, "repository");
|
||||||
|
const toolsPath = path.join(workspacePath, "tools");
|
||||||
|
const piHomePath = path.join(workspacePath, "home");
|
||||||
|
const branch = `zopu/attempt-${identity}`;
|
||||||
|
const created = !(await pathExists(path.join(checkoutPath, ".git")));
|
||||||
|
|
||||||
|
await mkdir(workspacePath, { recursive: true });
|
||||||
|
if (!created) {
|
||||||
|
requireSuccess(
|
||||||
|
await runGit(this.#sourceRepositoryPath, [
|
||||||
|
"worktree",
|
||||||
|
"remove",
|
||||||
|
"--force",
|
||||||
|
checkoutPath,
|
||||||
|
]),
|
||||||
|
"Existing worktree removal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await rm(checkoutPath, { force: true, recursive: true });
|
||||||
requireSuccess(
|
requireSuccess(
|
||||||
await runGit(
|
await runGit(this.#sourceRepositoryPath, [
|
||||||
checkoutPath,
|
"worktree",
|
||||||
["fetch", "origin", input.baseBranch],
|
"add",
|
||||||
authEnv
|
|
||||||
),
|
|
||||||
"Repository fetch"
|
|
||||||
);
|
|
||||||
requireSuccess(
|
|
||||||
await runGit(checkoutPath, [
|
|
||||||
"checkout",
|
|
||||||
"-B",
|
"-B",
|
||||||
`zopu/${input.runId}`,
|
branch,
|
||||||
`origin/${input.baseBranch}`,
|
checkoutPath,
|
||||||
|
"HEAD",
|
||||||
]),
|
]),
|
||||||
"Repository checkout"
|
"Zopu worktree creation"
|
||||||
);
|
);
|
||||||
requireSuccess(
|
|
||||||
await runGit(checkoutPath, ["reset", "--hard", "HEAD"]),
|
const sourceEnvPath = path.join(this.#sourceRepositoryPath, ".env");
|
||||||
"Repository reset"
|
if (await pathExists(sourceEnvPath)) {
|
||||||
|
await copyFile(sourceEnvPath, path.join(checkoutPath, ".env"));
|
||||||
|
}
|
||||||
|
if (this.#installDependencies) {
|
||||||
|
requireSuccess(
|
||||||
|
await runProcess(
|
||||||
|
"bun",
|
||||||
|
["install", "--frozen-lockfile"],
|
||||||
|
checkoutPath,
|
||||||
|
{ CI: "1" }
|
||||||
|
),
|
||||||
|
"Workspace dependency installation"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
const piAgentPath = path.join(piHomePath, ".pi", "agent");
|
||||||
|
await mkdir(piAgentPath, { recursive: true });
|
||||||
|
await writeFile(
|
||||||
|
path.join(piAgentPath, "models.json"),
|
||||||
|
input.piHomeFiles.models
|
||||||
);
|
);
|
||||||
requireSuccess(
|
await writeFile(
|
||||||
await runGit(checkoutPath, ["clean", "-fdx"]),
|
path.join(piAgentPath, "settings.json"),
|
||||||
"Repository clean"
|
input.piHomeFiles.settings
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -164,7 +201,10 @@ export class HostRepositoryWorkspace {
|
|||||||
"Base revision lookup"
|
"Base revision lookup"
|
||||||
),
|
),
|
||||||
checkoutPath,
|
checkoutPath,
|
||||||
cloned,
|
created,
|
||||||
|
piHomePath,
|
||||||
|
sourceRepositoryPath: this.#sourceRepositoryPath,
|
||||||
|
toolsPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,8 +242,8 @@ export class HostRepositoryWorkspace {
|
|||||||
"Candidate tree creation"
|
"Candidate tree creation"
|
||||||
);
|
);
|
||||||
candidateRevision = requireSuccess(
|
candidateRevision = requireSuccess(
|
||||||
await runGit(
|
await runProcess(
|
||||||
input.checkoutPath,
|
"git",
|
||||||
[
|
[
|
||||||
"commit-tree",
|
"commit-tree",
|
||||||
tree,
|
tree,
|
||||||
@@ -212,6 +252,7 @@ export class HostRepositoryWorkspace {
|
|||||||
"-m",
|
"-m",
|
||||||
`Zopu candidate for ${input.attemptId}`,
|
`Zopu candidate for ${input.attemptId}`,
|
||||||
],
|
],
|
||||||
|
input.checkoutPath,
|
||||||
{
|
{
|
||||||
GIT_AUTHOR_EMAIL: "agent@zopu.dev",
|
GIT_AUTHOR_EMAIL: "agent@zopu.dev",
|
||||||
GIT_AUTHOR_NAME: "Zopu Agent",
|
GIT_AUTHOR_NAME: "Zopu Agent",
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export const cancelAttempt = internalAction({
|
|||||||
handler: async (_ctx, args) => {
|
handler: async (_ctx, args) => {
|
||||||
// The workspace key remains the URL path segment (workspace identity),
|
// The workspace key remains the URL path segment (workspace identity),
|
||||||
// while the body carries the attemptId so the runtime can target the
|
// while the body carries the attemptId so the runtime can target the
|
||||||
// codex session `codex-${attemptId}` for cancellation.
|
// matching Pi ACP session for cancellation.
|
||||||
await fetch(
|
await fetch(
|
||||||
`${backendUrl()}/internal/work-attempts/${encodeURIComponent(args.workspaceKey)}/cancel`,
|
`${backendUrl()}/internal/work-attempts/${encodeURIComponent(args.workspaceKey)}/cancel`,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ export const completeAttempt = internalMutation({
|
|||||||
kind: "diff",
|
kind: "diff",
|
||||||
metadataJson: JSON.stringify({ changedFiles: args.result.changedFiles }),
|
metadataJson: JSON.stringify({ changedFiles: args.result.changedFiles }),
|
||||||
organizationId: work.organizationId,
|
organizationId: work.organizationId,
|
||||||
producer: "agentos-codex",
|
producer: "agentos-pi",
|
||||||
projectId: work.projectId,
|
projectId: work.projectId,
|
||||||
provenanceJson: JSON.stringify({
|
provenanceJson: JSON.stringify({
|
||||||
baseRevision: args.result.baseRevision,
|
baseRevision: args.result.baseRevision,
|
||||||
|
|||||||
1
packages/env/src/agent.ts
vendored
1
packages/env/src/agent.ts
vendored
@@ -17,6 +17,7 @@ const agentEnvSchema = z.object({
|
|||||||
RIVET_ENDPOINT: z.url(),
|
RIVET_ENDPOINT: z.url(),
|
||||||
RIVET_PUBLIC_ENDPOINT: z.url().optional(),
|
RIVET_PUBLIC_ENDPOINT: z.url().optional(),
|
||||||
RIVET_WORKSPACE_TOKEN: z.string().min(32),
|
RIVET_WORKSPACE_TOKEN: z.string().min(32),
|
||||||
|
ZOPU_SOURCE_REPOSITORY: z.string().min(1).default("/opt/zopu-source"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AgentEnv = z.infer<typeof agentEnvSchema>;
|
export type AgentEnv = z.infer<typeof agentEnvSchema>;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"test:watch": "vitest"
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentos-software/codex": "0.0.0-agent-acp-codex-session-benchmark.fd745e7",
|
"@agentos-software/pi": "0.2.7",
|
||||||
"@agentos-software/git": "0.3.3",
|
"@agentos-software/git": "0.3.3",
|
||||||
"@rivet-dev/agentos": "0.2.14",
|
"@rivet-dev/agentos": "0.2.14",
|
||||||
"effect": "catalog:"
|
"effect": "catalog:"
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
|
||||||
|
|
||||||
import { codexSessionEnv, makeCodexAgentOsConfig } from "./agent-os";
|
|
||||||
|
|
||||||
describe("Codex agent-os config", () => {
|
|
||||||
it("always includes the codex + git software bundle", () => {
|
|
||||||
const config = makeCodexAgentOsConfig();
|
|
||||||
expect(config.software).toHaveLength(2);
|
|
||||||
expect(config.software?.[0]).toBeTypeOf("object");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows repository execution capabilities", () => {
|
|
||||||
const config = makeCodexAgentOsConfig();
|
|
||||||
expect(config.permissions?.network).toBe("allow");
|
|
||||||
expect(config.permissions?.fs).toBe("allow");
|
|
||||||
expect(config.permissions?.childProcess).toBe("allow");
|
|
||||||
expect(config.permissions?.env).toBe("allow");
|
|
||||||
expect(config.permissions?.process).toBe("allow");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("builds model-provider session env", () => {
|
|
||||||
const env = codexSessionEnv(
|
|
||||||
{
|
|
||||||
apiKey: "sk-test",
|
|
||||||
baseUrl: "https://ai.example.com/v1",
|
|
||||||
model: "glm-5.2",
|
|
||||||
},
|
|
||||||
{ CODEX_MODEL: "glm-5.2" }
|
|
||||||
);
|
|
||||||
expect(env.OPENAI_API_KEY).toBe("sk-test");
|
|
||||||
expect(env.OPENAI_BASE_URL).toBe("https://ai.example.com/v1");
|
|
||||||
expect(env.CODEX_MODEL).toBe("glm-5.2");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable max-classes-per-file -- the AgentOS service and its tagged error form one adapter contract. */
|
/* eslint-disable max-classes-per-file -- the AgentOS service and its tagged error form one adapter contract. */
|
||||||
import codex from "@agentos-software/codex";
|
import pi from "@agentos-software/pi";
|
||||||
import { agentOS as createAgentOsActor } from "@rivet-dev/agentos";
|
import { agentOS as createAgentOsActor } from "@rivet-dev/agentos";
|
||||||
import type { AgentOSConfigInput } from "@rivet-dev/agentos";
|
import type { AgentOSConfigInput } from "@rivet-dev/agentos";
|
||||||
import { Context, Effect, Layer, Schema } from "effect";
|
import { Context, Effect, Layer, Schema } from "effect";
|
||||||
@@ -73,55 +73,86 @@ export const createAgentOsActorEffect = Effect.fn("createAgentOsActorEffect")(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Codex harness configuration
|
// Pi harness configuration
|
||||||
//
|
//
|
||||||
// The canonical execution registry runs exactly one AgentOS actor for the
|
// Slice 5 intentionally runs one harness against the server's fixed Zopu
|
||||||
// puter/zopu-code repo, booted with the Codex CLI harness + git. The model
|
// checkout. Pi speaks ACP natively and reads its model gateway from a mounted
|
||||||
// provider is injected as VM env (OpenAI-compatible base URL + key) so Codex
|
// HOME, avoiding a second executable adapter or credentials path.
|
||||||
// authenticates against the configured gateway without a second credentials
|
|
||||||
// path. This is a pure config builder; the RivetKit registry/server that hosts
|
|
||||||
// the actor lives in the agents package.
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Software bundle for a Codex-capable VM: the Codex harness plus git. */
|
/** Software bundle for a Pi-capable VM: the Pi ACP harness plus git. */
|
||||||
export const codexSoftware = [...codex, getExecutableGitSoftware()] as const;
|
export const piSoftware = [pi, getExecutableGitSoftware()] as const;
|
||||||
|
|
||||||
/** Model-provider env that Codex reads inside the VM. Pass this to the session's
|
export interface PiModelConfig {
|
||||||
* `env` when opening a Codex session (`agent: "codex"`). */
|
readonly api: "openai-completions";
|
||||||
export interface CodexModelEnv {
|
readonly apiKeyEnvironmentVariable: string;
|
||||||
readonly apiKey: string;
|
|
||||||
readonly baseUrl: string;
|
readonly baseUrl: string;
|
||||||
|
readonly contextWindow: number;
|
||||||
|
readonly maxTokens: number;
|
||||||
readonly model: string;
|
readonly model: string;
|
||||||
|
readonly provider: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Builds the VM env record Codex reads to authenticate against the model gateway. */
|
export interface PiHomeFiles {
|
||||||
export const codexSessionEnv = (
|
readonly models: string;
|
||||||
model: CodexModelEnv,
|
readonly settings: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds the two files Pi reads from ~/.pi/agent. */
|
||||||
|
export const makePiHomeFiles = (model: PiModelConfig): PiHomeFiles => ({
|
||||||
|
models: JSON.stringify(
|
||||||
|
{
|
||||||
|
providers: {
|
||||||
|
[model.provider]: {
|
||||||
|
api: model.api,
|
||||||
|
apiKey: model.apiKeyEnvironmentVariable,
|
||||||
|
authHeader: true,
|
||||||
|
baseUrl: model.baseUrl,
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
contextWindow: model.contextWindow,
|
||||||
|
id: model.model,
|
||||||
|
maxTokens: model.maxTokens,
|
||||||
|
name: model.model,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
settings: JSON.stringify(
|
||||||
|
{
|
||||||
|
defaultModel: model.model,
|
||||||
|
defaultProvider: model.provider,
|
||||||
|
quietStartup: true,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Environment supplied to the Pi ACP session. */
|
||||||
|
export const piSessionEnv = (
|
||||||
|
apiKey: string,
|
||||||
extra: Readonly<Record<string, string>> = {}
|
extra: Readonly<Record<string, string>> = {}
|
||||||
): Record<string, string> => ({
|
): Record<string, string> => ({
|
||||||
CODEX_HOME: "/tmp/codex-home",
|
AGENT_MODEL_API_KEY: apiKey,
|
||||||
CODEX_MODEL: model.model,
|
HOME: "/home/zopu",
|
||||||
HOME: "/tmp",
|
PATH: "/opt/zopu-tools/bin:/usr/local/bin:/usr/bin:/bin",
|
||||||
OPENAI_API_KEY: model.apiKey,
|
|
||||||
OPENAI_BASE_URL: model.baseUrl,
|
|
||||||
...extra,
|
...extra,
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface CodexAgentOsConfigInput {
|
export interface PiAgentOsConfigInput {
|
||||||
/** Extra software merged after the Codex+git bundle. */
|
/** Extra software merged after the Pi+git bundle. */
|
||||||
readonly software?: NonNullable<AgentOSConfigInput<undefined>["software"]>;
|
readonly software?: NonNullable<AgentOSConfigInput<undefined>["software"]>;
|
||||||
/** VM permission overrides merged over the execution policy. */
|
/** VM permission overrides merged over the execution policy. */
|
||||||
readonly permissions?: AgentOSConfigInput<undefined>["permissions"];
|
readonly permissions?: AgentOSConfigInput<undefined>["permissions"];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const makePiAgentOsConfig = (
|
||||||
* are always present; software the caller passes is appended, never replacing
|
input: PiAgentOsConfigInput = {}
|
||||||
* the harness or git. Filesystem, process execution, environment variables, and
|
|
||||||
* outbound network access are enabled for repository and model-provider work.
|
|
||||||
* Model-provider env is supplied per session via `codexSessionEnv`.
|
|
||||||
*/
|
|
||||||
export const makeCodexAgentOsConfig = (
|
|
||||||
input: CodexAgentOsConfigInput = {}
|
|
||||||
): AgentOSConfigInput<undefined> => ({
|
): AgentOSConfigInput<undefined> => ({
|
||||||
permissions: {
|
permissions: {
|
||||||
childProcess: "allow",
|
childProcess: "allow",
|
||||||
@@ -131,5 +162,5 @@ export const makeCodexAgentOsConfig = (
|
|||||||
process: "allow",
|
process: "allow",
|
||||||
...input.permissions,
|
...input.permissions,
|
||||||
},
|
},
|
||||||
software: [...codexSoftware, ...(input.software ?? [])],
|
software: [...piSoftware, ...(input.software ?? [])],
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user