mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-02 16:58:52 +01:00
Merge qualified backup expiration parent into GitHub connection work
Co-Authored-By: Paperclip <noreply@paperclip.ing> * commit 'aae32f2f493848199f6cb8c0a9996af3ee0061b1': (30 commits) Scope status-card refresh tasks to their claim revision test(chat): retain completion identity before link metadata loads test(chat): locate completion through its rendered task link fix(chat): recover committed native Agent Chat replies (#18465) perf(heartbeat): drive retry promotion from durable deadlines (#18387) fix(ui): make the Coordinator composer footer opaque (#18455) fix(testops): verify unexpanded result job runs (#18462) test(server): observe restored Retry before draining unrelated work test(runner): own listener cleanup across setup failures test(runner): buffer upgrade frames until the wire subscribes fix(runner): preserve OpenCode written-answer defaults (#18456) fix(native): fence retries when replaying an audited Stop (#18457) test: await async outcomes and drive saved deadlines deterministically (#18416) fix(ui): retain startup autosave through session recovery (#18412) perf(server): recover independent native replies concurrently (#18421) perf(chat): scope task questions and batch inbox queue checks (#18422) fix(native): recover controllers across webserver replacement (#18343) fix(ui): restore task document authoring and add persistence journey (#18356) fix(tools): stabilize tied profile entry order (#18427) fix(adapter-utils): tolerate entries removed during workspace snapshots (#18426) ...
This commit is contained in:
commit
f522ab07e2
484 files changed
+33834
-1620
No files matched your search
@@ -42,7 +42,7 @@ export function agentHarnessType(adapterType: string, config: Record<string, unk
|
||||
if (config.provider === "opencode") return "opencode_local";
|
||||
if (config.provider === "acpx") {
|
||||
const agent = config.acpxAgent ?? "claude";
|
||||
return ({ codex: "codex_local", claude: "claude_local", grok: "grok_local", cursor: "cursor", pi: "pi_local", antigravity: "antigravity_runtime" } as Record<string, string>)[String(agent)] ?? `acpx:${String(agent)}`;
|
||||
return ({ codex: "codex_local", claude: "claude_local", grok: "grok_local", cursor: "cursor", pi: "pi_local", kimi: "kimi_local", antigravity: "antigravity_runtime" } as Record<string, string>)[String(agent)] ?? `acpx:${String(agent)}`;
|
||||
}
|
||||
return String(config.provider);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,26 @@ import {
|
||||
googleWorkspaceGrantedScopes, googleWorkspaceToolTarget, isGoogleWorkspaceToolGranted,
|
||||
googleWorkspaceServiceGranted, isGoogleWorkspaceScopeGrant,
|
||||
googleWorkspaceSelectedScopes, googleWorkspaceEffectiveScopes, GOOGLE_WORKSPACE_DEFAULT_SELECTION,
|
||||
googleWorkspaceDraftRequestedScopes,
|
||||
} from "./google-workspace-connectors.js";
|
||||
|
||||
const scope = (value: string) => `https://www.googleapis.com/auth/${value}`;
|
||||
|
||||
describe("combined Google Workspace consent", () => {
|
||||
it("restores draft intent only for its exact identity and current service scopes", () => {
|
||||
const binding = { companyId: "company", connectionId: "connection", actorType: "user", actorId: "owner",
|
||||
grantKind: "user" as const, subjectId: "owner" };
|
||||
const draft = { version: 1, ...binding, requestedScopes: [scope("gmail.readonly")] };
|
||||
expect(googleWorkspaceDraftRequestedScopes(draft, binding)).toEqual([scope("gmail.readonly")]);
|
||||
for (const field of ["companyId", "connectionId", "actorType", "actorId", "grantKind", "subjectId"] as const) {
|
||||
expect(googleWorkspaceDraftRequestedScopes({ ...draft, [field]: "different" }, binding)).toBeNull();
|
||||
}
|
||||
for (const requestedScopes of [[], [scope("chat.messages.readonly")], [scope("contacts.readonly")], [scope("gmail.send")], "gmail.readonly"]) {
|
||||
expect(googleWorkspaceDraftRequestedScopes({ ...draft, requestedScopes }, binding)).toBeNull();
|
||||
}
|
||||
expect(googleWorkspaceDraftRequestedScopes(undefined, binding)).toBeNull();
|
||||
});
|
||||
|
||||
it("defaults to Gmail, combined Drive, and Calendar write scopes without Chat or People", () => {
|
||||
const selected = googleWorkspaceSelectedScopes(GOOGLE_WORKSPACE_DEFAULT_SELECTION);
|
||||
expect(selected).toHaveLength(9);
|
||||
|
||||
@@ -48,6 +48,31 @@ export function googleWorkspaceSelectionForScopes(scopes: readonly string[]): Go
|
||||
return selection;
|
||||
}
|
||||
|
||||
export interface GoogleWorkspaceDraftBinding {
|
||||
companyId: string;
|
||||
connectionId: string;
|
||||
actorType: string | null;
|
||||
actorId: string | null;
|
||||
grantKind: "user" | "agent" | "organization";
|
||||
subjectId: string | null;
|
||||
}
|
||||
|
||||
export function googleWorkspaceDraftRequestedScopes(
|
||||
value: unknown,
|
||||
binding: GoogleWorkspaceDraftBinding,
|
||||
): string[] | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
||||
const draft = value as Record<string, unknown>;
|
||||
if (!binding.actorType || !binding.actorId || draft.version !== 1
|
||||
|| Object.entries(binding).some(([key, expected]) => draft[key] !== expected)) return null;
|
||||
const scopes = draft.requestedScopes;
|
||||
const allowed = new Set(GOOGLE_WORKSPACE_SERVICE_GROUPS.flatMap(group =>
|
||||
[...group.read, ...group.write].flatMap(profile => GOOGLE_WORKSPACE_CONNECTOR_PROFILES[profile].scopes)));
|
||||
if (!Array.isArray(scopes) || scopes.length === 0 || scopes.length > allowed.size
|
||||
|| scopes.some(scope => typeof scope !== "string" || !allowed.has(scope))) return null;
|
||||
return [...new Set(scopes)].sort();
|
||||
}
|
||||
|
||||
const auth = (scope: string) => `https://www.googleapis.com/auth/${scope}`;
|
||||
|
||||
const SERVICE_PROFILES: Readonly<Record<Exclude<GoogleWorkspaceConnectorProfileId, "workspace.all">, {
|
||||
|
||||
@@ -1993,6 +1993,8 @@ export {
|
||||
issueCommentMetadataSectionSchema,
|
||||
issueCommentMetadataSchema,
|
||||
addIssueCommentSchema,
|
||||
sendChatMessageSchema,
|
||||
markIssueReadSchema,
|
||||
issueThreadInteractionStatusSchema,
|
||||
issueThreadInteractionKindSchema,
|
||||
issueThreadInteractionContinuationPolicySchema,
|
||||
|
||||
@@ -1178,6 +1178,12 @@ export interface IssueCommentMetadataSection {
|
||||
|
||||
export interface IssueCommentMetadata {
|
||||
version: 1;
|
||||
chatMessage?: {
|
||||
sourceTaskId: string;
|
||||
sourceTaskIdentifier: string;
|
||||
sourceRunId: string;
|
||||
contentHash: string;
|
||||
};
|
||||
/** Producer-classified diagnostics; task interruption UI owns system repair. */
|
||||
recoveryResponsibility?: "system" | "user";
|
||||
/** Inbound channel attribution; never an authorization input. */
|
||||
|
||||
@@ -460,6 +460,8 @@ export {
|
||||
issueCommentMetadataSectionSchema,
|
||||
issueCommentMetadataSchema,
|
||||
addIssueCommentSchema,
|
||||
sendChatMessageSchema,
|
||||
markIssueReadSchema,
|
||||
issueThreadInteractionStatusSchema,
|
||||
issueThreadInteractionKindSchema,
|
||||
issueThreadInteractionCanonicalResolverPolicySchema,
|
||||
|
||||
@@ -1101,6 +1101,12 @@ export const issueCommentMetadataSectionSchema = z
|
||||
export const issueCommentMetadataSchema = z
|
||||
.object({
|
||||
version: z.literal(1),
|
||||
chatMessage: z.object({
|
||||
sourceTaskId: z.uuid(),
|
||||
sourceTaskIdentifier: z.string().min(1).max(80),
|
||||
sourceRunId: z.uuid(),
|
||||
contentHash: z.string().regex(/^[a-f0-9]{64}$/),
|
||||
}).strict().optional(),
|
||||
recoveryResponsibility: z.enum(["system", "user"]).optional(),
|
||||
sourceChannel: z.enum(CHAT_PROVIDERS).optional(),
|
||||
sourceRunId: z.string().guid().nullable().optional(),
|
||||
@@ -1125,6 +1131,13 @@ export const issueCommentMetadataSchema = z
|
||||
|
||||
export type IssueCommentMetadata = z.infer<typeof issueCommentMetadataSchema>;
|
||||
|
||||
export const sendChatMessageSchema = z.object({
|
||||
body: multilineTextSchema.pipe(z.string().trim().min(1).max(20000)),
|
||||
idempotencyKey: z.string().trim().min(1).max(240),
|
||||
}).strict();
|
||||
|
||||
export const markIssueReadSchema = z.object({ throughCommentId: z.uuid().optional() }).strict();
|
||||
|
||||
export const addIssueCommentSchema = z.object({
|
||||
clientRequestId: z.string().uuid().optional(),
|
||||
body: multilineTextSchema.pipe(z.string().min(1)),
|
||||
|
||||
Reference in new issue
Block a user