mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-05 07:54:38 +00:00
feat(providers): add Hoplite as a built-in provider
Bundle a `hoplite-provider` plugin that registers the `hoplite` provider through the plugin SDK's ACP shim over `hoplite acp`, with the official Hoplite mark as its icon. A narrow transformer turns Hoplite's `_hoplite/run_phase` provisioning notifications into one in-place "Hoplite workspace" timeline row per run. Add Hoplite to the ACP provider catalog next to Devin, with the same mark, so daemons without the bundled plugin can still install it over ACP. The catalog hides the entry wherever the built-in is registered. Document setup, project selection (`HOPLITE_PROJECT_ID`), and limits, and add a real-provider daemon E2E that runs when `HOPLITE_E2E_CWD` points at a repository linked to a Hoplite project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c062280429
commit
62a2ff4b38
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -41.5 168 168" fill="none">
|
||||
<path d="M165.642 84.8565H158.118C157.544 84.8565 157.078 84.3907 157.078 83.8162V80.7201C157.078 80.3359 156.866 79.983 156.527 79.8022L143.081 72.6307C142.37 72.252 142.341 71.2449 143.028 70.8253L150.104 66.5006C150.53 66.2408 151.079 66.3189 151.415 66.6869L166.41 83.115C167.02 83.7827 166.546 84.8565 165.642 84.8565Z" fill="currentColor"/>
|
||||
<path d="M138.319 59.226L127.839 58.9642H53.804C53.6196 58.9642 53.4385 59.0132 53.2793 59.1063L20.2869 78.3834L14.7776 81.3261C14.439 81.507 14.2275 81.8597 14.2275 82.2436V83.808C14.2275 84.3857 13.7568 84.8528 13.1791 84.8482L2.28806 84.7626C1.39507 84.7556 0.925459 83.7007 1.51785 83.0324L25.9364 55.4869C25.986 55.4309 26.0295 55.3697 26.066 55.3043L29.36 49.4108C29.8762 48.4872 28.8444 47.4724 27.9295 48.0038L19.6157 52.8328C13.2683 56.5196 5.13866 54.4694 1.29881 48.2133L0.153672 46.3476C0.0531892 46.1839 0 45.9956 0 45.8035V25.9642C0 24.8289 1.55934 24.5146 1.99917 25.5612L6.37353 35.97C8.79413 41.7298 15.7102 44.0714 21.1321 40.9668L32.2092 34.624C32.5305 34.44 32.7298 34.0991 32.7325 33.7289L32.7605 29.8871C32.7663 29.0864 31.9032 28.5796 31.2069 28.9748L18.5022 36.1854C15.8352 37.699 12.4443 36.5885 11.1894 33.7905L0.0910985 9.04488C0.031046 8.91098 4.11494e-06 8.76589 1.20745e-05 8.61915L0.000419757 1.10282C0.000449406 0.556184 0.44359 0.113067 0.990222 0.113067C9.75588 0.113067 18.4875 1.20395 26.9837 3.36057L38.7291 6.34193C39.3865 6.5088 40.0252 6.01197 40.0252 5.33369V0.872775C40.0252 0.4532 40.3653 0.113067 40.7849 0.113067C48.1954 0.113067 55.4203 2.43158 61.447 6.74371L74.9799 16.4265C75.1564 16.5529 75.3681 16.6208 75.5852 16.6208H88.3801C88.9546 16.6208 89.4203 16.1551 89.4203 15.5806V13.9863C89.4203 13.4118 88.9546 12.9461 88.3801 12.9461H75.7621C74.7167 12.9461 74.3245 11.577 75.2114 11.0235L83.2179 6.02657C89.5285 2.08806 96.818 0 104.257 0C111.21 0 118.042 1.82471 124.07 5.29182L126.474 6.67485C127.148 7.06209 127.174 8.02376 126.524 8.44791L116.021 15.2954C115.726 15.4874 115.548 15.8152 115.548 16.1668V19.6916C115.548 20.0588 115.355 20.3989 115.039 20.5863L111.542 22.6598C110.613 23.2109 109.564 22.1556 110.12 21.2295L110.584 20.4579C111 19.7646 110.501 18.8821 109.692 18.8821H105.243C104.131 18.8821 103.795 20.393 104.803 20.8645L108.528 22.6076C109.275 22.9573 109.337 23.996 108.637 24.4324L101.859 28.6585C101.694 28.7615 101.503 28.8161 101.309 28.8161H98.2915C97.7251 28.8161 97.266 29.2752 97.266 29.8416C97.266 30.408 97.7251 30.8672 98.2915 30.8672H103.861C104.048 30.8672 104.232 30.8168 104.392 30.7213L146.474 5.72225C151.426 2.78084 157.761 3.71042 161.658 7.95031C161.737 8.03645 161.781 8.1492 161.781 8.26621V11.3862C161.781 12.2892 162.853 12.7633 163.521 12.1558L166.26 9.66601C166.928 9.05855 168 9.53265 168 10.4357V44.8083C168 45.3828 167.534 45.8485 166.96 45.8485H162.27C161.96 45.8485 161.667 45.7106 161.469 45.4723L151.582 33.5514C151.176 33.0612 150.428 33.0481 150.005 33.5237L141.76 42.7837C141.337 43.2597 141.437 44.0013 141.971 44.3481L158.788 55.2561C159.436 55.6767 159.416 56.6325 158.75 57.0249L132.549 72.4643C131.877 72.8607 131.024 72.4024 130.983 71.6228L130.61 64.5475C130.579 63.9524 131.053 63.4526 131.649 63.4526H135.878C136.133 63.4526 136.379 63.3591 136.569 63.1899L138.984 61.0433C139.69 60.4163 139.263 59.2495 138.319 59.226Z" fill="currentColor"/>
|
||||
<path d="M75.1697 62.1866H60.8352C60.6502 62.1866 60.4686 62.2359 60.3091 62.3295L43.8303 71.9906C43.1099 72.4129 43.1558 73.4693 43.9101 73.8275L58.7045 80.854C59.0673 81.0263 59.2984 81.3921 59.2984 81.7937V83.8162C59.2984 84.3907 59.7641 84.8565 60.3386 84.8565H69.0808C69.9832 84.8565 70.4575 83.7862 69.8515 83.1176L61.5235 73.9306C61.0809 73.4423 61.1982 72.6706 61.766 72.3359L75.698 64.1229C76.6105 63.585 76.229 62.1866 75.1697 62.1866Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,7 @@
|
||||
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
||||
import { createHopliteProvider } from "./server/provider.js";
|
||||
|
||||
export default function contribute(server: PluginServerContext) {
|
||||
server.registerProvider(createHopliteProvider());
|
||||
return () => {};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "id": "hoplite-provider", "requirements": { "paseo": ">=0.11.0" } }
|
||||
@@ -0,0 +1,14 @@
|
||||
import { runAcpProvider } from "@getpaseo/plugin/server/acp";
|
||||
import type { ProviderRegistration } from "@getpaseo/plugin/server/provider";
|
||||
import { runPhaseTransformer } from "./run-phase.js";
|
||||
|
||||
export function createHopliteProvider(): ProviderRegistration {
|
||||
return runAcpProvider({
|
||||
id: "hoplite",
|
||||
label: "Hoplite",
|
||||
description: "Hoplite's coding agent through the local `hoplite acp` bridge",
|
||||
icon: "icon.svg",
|
||||
command: ["hoplite", "acp"],
|
||||
transformers: [runPhaseTransformer],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runPhaseTransformer } from "./run-phase.js";
|
||||
|
||||
const context = { sessionId: "session-1" };
|
||||
|
||||
// Frames captured from `hoplite acp` 3.0.0.
|
||||
function runPhase(phase: string | null, label: string | null) {
|
||||
return {
|
||||
method: "_hoplite/run_phase",
|
||||
params: {
|
||||
sessionId: "thr_c75d39f7d7624256af918eeee2f4941b",
|
||||
runId: "run_eb213b97bd3147afb2927fbc27f87180",
|
||||
phase,
|
||||
label,
|
||||
startedAt: phase === null ? null : "2026-10-04T17:51:46.311Z",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("runPhaseTransformer", () => {
|
||||
it("shows a provisioning phase as one running workspace item per run", () => {
|
||||
const update = runPhaseTransformer.notification?.(
|
||||
runPhase("fetching_repository", "Cloning repository"),
|
||||
context,
|
||||
);
|
||||
|
||||
expect(update).toEqual({
|
||||
type: "timeline",
|
||||
item: {
|
||||
type: "tool_call",
|
||||
id: "hoplite-run-phase:run_eb213b97bd3147afb2927fbc27f87180",
|
||||
callId: "hoplite-run-phase:run_eb213b97bd3147afb2927fbc27f87180",
|
||||
name: "Hoplite workspace",
|
||||
detail: { type: "plain_text", label: "Cloning repository", icon: "wrench" },
|
||||
status: "running",
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("completes the same item when the run leaves provisioning", () => {
|
||||
const update = runPhaseTransformer.notification?.(runPhase(null, null), context);
|
||||
|
||||
expect(update).toMatchObject({
|
||||
type: "timeline",
|
||||
item: {
|
||||
id: "hoplite-run-phase:run_eb213b97bd3147afb2927fbc27f87180",
|
||||
status: "completed",
|
||||
detail: { label: "Workspace ready" },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the phase name when Hoplite sends no label", () => {
|
||||
const update = runPhaseTransformer.notification?.(
|
||||
runPhase("configuring_workspace", null),
|
||||
context,
|
||||
);
|
||||
|
||||
expect(update).toMatchObject({ item: { detail: { label: "configuring_workspace" } } });
|
||||
});
|
||||
|
||||
it("ignores other vendor notifications and malformed phases", () => {
|
||||
expect(
|
||||
runPhaseTransformer.notification?.({ method: "_hoplite/other", params: {} }, context),
|
||||
).toBeNull();
|
||||
expect(
|
||||
runPhaseTransformer.notification?.(
|
||||
{ method: "_hoplite/run_phase", params: { phase: "x" } },
|
||||
context,
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { AcpTransformer } from "@getpaseo/plugin/server/acp";
|
||||
import { z } from "zod";
|
||||
|
||||
// Hoplite provisions a cloud workspace before the first agent output of a run and reports each
|
||||
// step as `_hoplite/run_phase`. A null phase ends provisioning for that run.
|
||||
const runPhaseSchema = z.object({
|
||||
runId: z.string().min(1),
|
||||
phase: z.string().nullable(),
|
||||
label: z.string().nullable(),
|
||||
});
|
||||
|
||||
const RUN_PHASE_METHOD = "_hoplite/run_phase";
|
||||
|
||||
export const runPhaseTransformer: AcpTransformer = {
|
||||
notification(notification) {
|
||||
if (notification.method !== RUN_PHASE_METHOD) return null;
|
||||
const params = runPhaseSchema.safeParse(notification.params);
|
||||
if (!params.success) return null;
|
||||
const { runId, phase, label } = params.data;
|
||||
const id = `hoplite-run-phase:${runId}`;
|
||||
const running = phase !== null;
|
||||
return {
|
||||
type: "timeline",
|
||||
item: {
|
||||
type: "tool_call",
|
||||
id,
|
||||
callId: id,
|
||||
name: "Hoplite workspace",
|
||||
detail: {
|
||||
type: "plain_text",
|
||||
label: running ? (label ?? phase) : "Workspace ready",
|
||||
icon: "wrench",
|
||||
},
|
||||
status: running ? "running" : "completed",
|
||||
error: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user