mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-09 16:35:27 +02:00
Merge bounded selection and verified cold CLI checks
Co-Authored-By: Paperclip <noreply@paperclip.ing> * codex/hermes-routines: fix(ui): bound manual agent selection to the switch transition
This commit is contained in:
commit
ab98588317
4 files changed
+34
-5
No files matched your search
@@ -250,7 +250,9 @@ async function waitForServer(
|
||||
output: { stdout: string[]; stderr: string[] },
|
||||
) {
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < 30_000) {
|
||||
// The source CLI runs doctor and imports the cold server graph. Keep this
|
||||
// health deadline inside the existing 60-second database/setup hook budget.
|
||||
while (Date.now() - startedAt < 45_000) {
|
||||
if (child.exitCode !== null) {
|
||||
throw new Error(
|
||||
`paperclipai run exited before healthcheck succeeded.\nstdout:\n${output.stdout.join("")}\nstderr:\n${output.stderr.join("")}`,
|
||||
|
||||
@@ -67,6 +67,12 @@ describe("buildImportTransferManifest", () => {
|
||||
});
|
||||
});
|
||||
|
||||
const CRC32_TABLE = Uint32Array.from({ length: 256 }, (_, value) => {
|
||||
let crc = value;
|
||||
for (let bit = 0; bit < 8; bit += 1) crc = (crc & 1) === 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
|
||||
return crc >>> 0;
|
||||
});
|
||||
|
||||
// Minimal single-entry DEFLATE zip, byte-compatible with the shared reader —
|
||||
// the stored-zip helper cannot model a small-compressed/large-inflated entry.
|
||||
function buildDeflateZip(entryPath: string, text: string): Uint8Array {
|
||||
@@ -75,8 +81,7 @@ function buildDeflateZip(entryPath: string, text: string): Uint8Array {
|
||||
const name = Buffer.from(entryPath, "utf8");
|
||||
let crc = 0xffffffff;
|
||||
for (const byte of raw) {
|
||||
crc ^= byte;
|
||||
for (let bit = 0; bit < 8; bit += 1) crc = (crc & 1) === 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
|
||||
crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ byte) & 0xff]!;
|
||||
}
|
||||
crc = (crc ^ 0xffffffff) >>> 0;
|
||||
const local = Buffer.alloc(30 + name.length);
|
||||
|
||||
@@ -1349,6 +1349,8 @@ test.describe("Exact failed chat run retry", () => {
|
||||
await expect(page.getByText(foreignTitle, { exact: true }).first()).toBeVisible();
|
||||
await expect.poll(() => page.evaluate(() => localStorage.getItem("paperclip.selectedCompanyId")))
|
||||
.toBe(other.companyId);
|
||||
const dismissAnnouncement = page.getByRole("button", { name: "Dismiss announcement", exact: true });
|
||||
if (await dismissAnnouncement.isVisible()) await dismissAnnouncement.click();
|
||||
await page.getByRole("button", { name: `Open ${foreignCompany.name} organization switcher`, exact: true }).click();
|
||||
const menuItems = await page.getByRole("menuitem").allTextContents();
|
||||
const scopedOption = page.getByRole("menuitem").filter({ hasText: scopedCompany.name });
|
||||
@@ -1362,6 +1364,7 @@ test.describe("Exact failed chat run retry", () => {
|
||||
afterSwitchSelectedCompany: await page.evaluate(() => localStorage.getItem("paperclip.selectedCompanyId")),
|
||||
}, null, 2));
|
||||
await expect(page).toHaveURL(new RegExp(`/${seed.prefix}/dashboard$`));
|
||||
if (await dismissAnnouncement.isVisible()) await dismissAnnouncement.click();
|
||||
await page.getByRole("link", { name: "Agents", exact: true }).first().click();
|
||||
await page.getByRole("link", { name: "Maya", exact: true }).first().click();
|
||||
await expect(page).toHaveURL(new RegExp(`/${seed.prefix}/agents/maya(?:/overview)?$`));
|
||||
@@ -1379,6 +1382,20 @@ test.describe("Exact failed chat run retry", () => {
|
||||
await expect.poll(() => page.evaluate(() => localStorage.getItem("paperclip.selectedCompanyId")))
|
||||
.toBe(seed.companyId);
|
||||
await page.screenshot({ path: testInfo.outputPath("cross-company-canonical-agent.png") });
|
||||
// A manual selection must not prevent a later history navigation from
|
||||
// following the authorized agent's company in the sidebar as well.
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL(new RegExp(`/${seed.prefix}/agents/all$`));
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL(new RegExp(`/${seed.prefix}/dashboard$`));
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL(new RegExp(`/${other.prefix}/agents/maya/overview$`));
|
||||
await expect(page.getByText(foreignTitle, { exact: true }).first()).toBeVisible();
|
||||
await expect.poll(() => page.evaluate(() => localStorage.getItem("paperclip.selectedCompanyId")))
|
||||
.toBe(other.companyId);
|
||||
await expect(page.getByRole("button", { name: `Open ${foreignCompany.name} organization switcher`, exact: true }))
|
||||
.toBeVisible();
|
||||
await page.screenshot({ path: testInfo.outputPath("cross-company-history-selection.png") });
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
|
||||
@@ -797,6 +797,7 @@ export function AgentDetail() {
|
||||
const [configSaving, setConfigSaving] = useState(false);
|
||||
const saveConfigActionRef = useRef<(() => void) | null>(null);
|
||||
const cancelConfigActionRef = useRef<(() => void) | null>(null);
|
||||
const previousSelectedCompanyId = useRef(selectedCompanyId);
|
||||
const { isMobile } = useSidebar();
|
||||
const routeAgentRef = agentId ?? "";
|
||||
const routeCompanyId = useMemo(() => {
|
||||
@@ -993,9 +994,13 @@ export function AgentDetail() {
|
||||
}, [agent, agentCompany, companyPrefix, routeAgentRef, canonicalAgentRef, urlRunId, urlTab, activeView, legacyAuditSection, navigate, queryClient]);
|
||||
|
||||
useEffect(() => {
|
||||
const manualSelectionChanged = selectionSource === "manual"
|
||||
&& previousSelectedCompanyId.current !== selectedCompanyId;
|
||||
previousSelectedCompanyId.current = selectedCompanyId;
|
||||
if (!agent?.companyId || agent.companyId === selectedCompanyId) return;
|
||||
// Let an explicit organization switch finish its navigation first.
|
||||
if (selectionSource === "manual") return;
|
||||
// Yield only for the selection change itself. Later agent links and history
|
||||
// navigation must follow the authorized company even if the source is manual.
|
||||
if (manualSelectionChanged) return;
|
||||
// The route owns selection until canonical navigation reaches this company.
|
||||
// Competing with Layout's old-prefix selection can cause an update loop.
|
||||
if (routeCompanyId && routeCompanyId !== agent.companyId) return;
|
||||
|
||||
Reference in new issue
Block a user