mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 19:35:13 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Paperclip uses adapters to connect agents and model providers to its control plane > - The sandbox login panel supports displayed-code login for selected adapters > - Grok users need the same login path and a private credential home for later runs > - This pull request adds Grok support to the shared device-login path and preserves the existing Codex path > - The benefit is one secure login flow for both adapters with company-scoped credential storage ## Linked Issues or Issue Description **Agent or provider** Grok Local needs displayed-code login support in the sandbox login panel. **Why this adapter is useful** This change lets users sign in to Grok from the sandbox login panel. It also gives later Grok runs access to the stored credential. **How the agent is invoked** The Grok local adapter uses its login command through the shared displayed-code login flow. Later runs receive the managed home through `GROK_HOME`. **Additional context** The change uses adapter-scoped login lifecycle handling. It stores the credential in a company-scoped directory with mode `0700`, and it stores the credential file with mode `0600`. ## What Changed - Rename the shared device-login modules to adapter-neutral names. - Scope the shared login lifecycle to a closed adapter set. - Return the device-login URL that the provider prints. - Add the Grok prompt parser, login command, capability, and login panel entry. - Store the Grok credential in a private, company-scoped home directory. - Pass `GROK_HOME` to later Grok runs. - Add tests for the Grok adapter, the Daytona sandbox provider, the server login path, and the user interface. ## Verification - Run `pnpm vitest run packages/adapters/grok-local/src/server/adapter-auth-promotion.test.ts`. - Run the Grok adapter package suite. - Run the Daytona sandbox provider suite. - Run the server device-login suites. - Run the user interface suite. - Confirm the full CI suite passes. ## Risks The change extends shared login lifecycle code to another adapter. A regression could affect Codex login. The credential path uses explicit `chmod` calls to keep the directory at mode `0700` and the file at mode `0600`. ## Model Used OpenAI Codex, GPT-5. The runtime used tool calls and code review support. The runtime did not provide a context-window value. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
78 lines
2.8 KiB
TypeScript
78 lines
2.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { AdapterLoginCapability, ServerAdapterModule } from "@paperclipai/adapter-utils";
|
|
import { requireServerAdapter } from "../adapters/registry.js";
|
|
import { buildAdapterCapabilities } from "./adapters.js";
|
|
|
|
// The adapter listing projects the safe scalar login fields to the client. The
|
|
// projection carries the panel mode, the sandbox transport, and the timeout
|
|
// policy. It never carries a function member or a secret. An adapter with no
|
|
// login capability projects no `login` object.
|
|
|
|
function makeAdapter(overrides: Partial<ServerAdapterModule> = {}): ServerAdapterModule {
|
|
return {
|
|
type: "vendor_local",
|
|
execute: async () => {
|
|
throw new Error("not used");
|
|
},
|
|
testEnvironment: async () => {
|
|
throw new Error("not used");
|
|
},
|
|
...overrides,
|
|
} as ServerAdapterModule;
|
|
}
|
|
|
|
const displayedCodeLogin: AdapterLoginCapability = {
|
|
panelMode: "displayed_code",
|
|
timeoutPolicy: "caller_bounded",
|
|
getCommand: () => "vendor login",
|
|
parsePrompt: () => null,
|
|
};
|
|
|
|
describe("buildAdapterCapabilities login projection", () => {
|
|
it("projects the safe scalar login fields", () => {
|
|
const caps = buildAdapterCapabilities(makeAdapter({ loginCapability: displayedCodeLogin }));
|
|
expect(caps.login).toEqual({
|
|
panelMode: "displayed_code",
|
|
timeoutPolicy: "caller_bounded",
|
|
});
|
|
});
|
|
|
|
it("omits the login object when the adapter declares no capability", () => {
|
|
const caps = buildAdapterCapabilities(makeAdapter());
|
|
expect(caps.login).toBeUndefined();
|
|
});
|
|
|
|
it("never projects the function members or a completion claim", () => {
|
|
const caps = buildAdapterCapabilities(
|
|
makeAdapter({
|
|
loginCapability: {
|
|
panelMode: "submitted_browser_code",
|
|
timeoutPolicy: "fixed",
|
|
getCommand: () => "vendor setup-token",
|
|
parsePrompt: () => null,
|
|
captureCredential: () => null,
|
|
completionClaim: "storedSessionId",
|
|
},
|
|
}),
|
|
);
|
|
expect(caps.login).toEqual({
|
|
panelMode: "submitted_browser_code",
|
|
timeoutPolicy: "fixed",
|
|
});
|
|
expect(caps.login).not.toHaveProperty("getCommand");
|
|
expect(caps.login).not.toHaveProperty("parsePrompt");
|
|
expect(caps.login).not.toHaveProperty("captureCredential");
|
|
expect(caps.login).not.toHaveProperty("completionClaim");
|
|
});
|
|
|
|
it("projects panelMode and timeoutPolicy for the registered grok_local adapter, with no function member", () => {
|
|
const caps = buildAdapterCapabilities(requireServerAdapter("grok_local"));
|
|
expect(caps.login).toEqual({
|
|
panelMode: "displayed_code",
|
|
timeoutPolicy: "caller_bounded",
|
|
});
|
|
expect(caps.login).not.toHaveProperty("getCommand");
|
|
expect(caps.login).not.toHaveProperty("parsePrompt");
|
|
});
|
|
});
|