Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 1 | import type { ActionsApi } from "./actions"; |
| 2 | import type { BillingAdminApi, BillingApi } from "./billing"; | |
| 3 | import type { DeploymentsApi } from "./deployments"; | |
| 4 | import type { ProjectsApi } from "./projects"; | |
| 5 | import type { EventsApi } from "./events"; | |
| 6 | import type { IdentityAdminApi, IdentityApi } from "./identity"; | |
| 7 | import type { IntegrationsApi } from "./integrations"; | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 8 | import type { PackagesApi } from "./packages"; |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 9 | import type { WebhooksApi } from "./webhooks"; |
| 10 | import type { ReposApi } from "./repos"; | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 11 | import type { PullDetail, WorkApi } from "./work"; |
| 12 | import type { Result } from "./result"; | |
| Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next. | 13 | import type { RunnerComputerApi } from "./runner"; |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 14 | import type { RunnersApi } from "./runners"; |
| 15 | ||
| 16 | /** A service binding, as far as these clients need it. */ | |
| 17 | export type ServiceBinding = { | |
| 18 | fetch(input: string, init?: RequestInit): Promise<Response>; | |
| 19 | }; | |
| 20 | ||
| 21 | /** | |
| 22 | * Calls a method on a service that speaks the JSON protocol used by the | |
| 23 | * Rust services: `POST /rpc/<method>` with the arguments as the body. | |
| 24 | */ | |
| 25 | async function rpc<T>( | |
| 26 | service: ServiceBinding, | |
| 27 | method: string, | |
| 28 | args: object, | |
| 29 | ): Promise<T> { | |
| 30 | // The hostname is ignored; a service binding always reaches its service. | |
| 31 | const response = await service.fetch(`https://service/rpc/${method}`, { | |
| 32 | method: "POST", | |
| 33 | headers: { "content-type": "application/json" }, | |
| 34 | body: JSON.stringify(args), | |
| 35 | }); | |
| 36 | if (!response.ok) { | |
| 37 | throw new Error(`${method} failed with status ${response.status}`); | |
| 38 | } | |
| 39 | return (await response.json()) as T; | |
| 40 | } | |
| 41 | ||
| 42 | export function identityClient(service: ServiceBinding): IdentityApi { | |
| 43 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 44 | return { | |
| Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm | 45 | register: (username, email, password, inviteCode, client, emailProof) => |
| 46 | call("register", { | |
| 47 | username, | |
| 48 | email, | |
| 49 | password, | |
| 50 | invite_code: inviteCode ?? null, | |
| 51 | email_proof: emailProof ?? null, | |
| 52 | client: client ?? null, | |
| 53 | }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 54 | signIn: (username, password, client) => call("sign_in", { username, password, client: client ?? null }), |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 55 | twoFactorSignIn: (challenge, code, client) => call("two_factor_sign_in", { challenge, code, client: client ?? null }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 56 | signOut: (sessionToken) => call("sign_out", { sessionToken }), |
| 57 | resendVerification: (user) => call("resend_verification", { user }), | |
| 58 | verifyEmail: (token) => call("verify_email", { token }), | |
| 59 | requestPasswordReset: (email, client) => call("request_password_reset", { email, client: client ?? null }), | |
| 60 | resetPassword: (token, password) => | |
| 61 | call("reset_password", { token, password }), | |
| 62 | deviceStart: (clientName) => call("device_start", { clientName }), | |
| 63 | deviceLookup: (userCode) => call("device_lookup", { userCode }), | |
| 64 | deviceResolve: (userCode, user, approve) => | |
| 65 | call("device_resolve", { userCode, user, approve }), | |
| 66 | deviceClaim: (deviceCode) => call("device_claim", { deviceCode }), | |
| 67 | oauthAuthorize: (user, approval) => call("oauth_authorize", { user, ...approval }), | |
| 68 | oauthExchange: (code, codeVerifier, clientId, redirectUri) => | |
| 69 | call("oauth_exchange", { code, codeVerifier, clientId, redirectUri }), | |
| 70 | oauthRefresh: (refreshToken, clientId) => | |
| 71 | call("oauth_refresh", { refreshToken, clientId }), | |
| 72 | listOAuthGrants: (user) => call("list_oauth_grants", { user }), | |
| 73 | revokeOAuthGrant: (user, id) => call("revoke_oauth_grant", { user, id }), | |
| 74 | updateOAuthGrant: (user, id, grant) => | |
| 75 | call("update_oauth_grant", { user, id, scopes: grant.scopes }), | |
| 76 | createWorkspace: (user, slug, name) => call("create_workspace", { user, slug, name }), | |
| 77 | getWorkspace: (slug) => call("get_workspace", { slug }), | |
| Merge branch 'worktree-agent-a2013627e5ea4ab13' | 78 | workspaceResidency: (slug) => call("workspace_residency", { slug }), |
| 79 | setWorkspaceResidency: (actor, slug, residency) => call("set_workspace_residency", { actor, slug, residency }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 80 | listMembers: (slug, viewer) => call("list_members", { slug, viewer }), |
| 81 | addMember: (actor, slug, username) => call("add_member", { actor, slug, username }), | |
| 82 | removeMember: (actor, slug, username) => | |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 83 | call("remove_member", { actor, slug, username, surface: "web" }), |
| 84 | updateMember: (actor, slug, username, change) => | |
| 85 | call("update_member", { actor, slug, username, role: change.role ?? null, org_roles: change.org_roles ?? null, surface: "web" }), | |
| 86 | transferOwnership: (actor, slug, username) => call("transfer_ownership", { actor, slug, username, surface: "web" }), | |
| 87 | leaveWorkspace: (user, slug) => call("leave_workspace", { user, slug, surface: "web" }), | |
| 88 | setMemberPrivileges: (actor, slug, change) => call("set_member_privileges", { actor, slug, privileges: change, surface: "web" }), | |
| 89 | setTwoFactorRequirement: (actor, slug, required) => | |
| 90 | call("set_two_factor_requirement", { actor, slug, required, surface: "web" }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 91 | updateWorkspace: (actor, slug, details) => |
| 92 | call("update_workspace", { actor, slug, ...details }), | |
| 93 | renameWorkspace: (actor, slug, newSlug) => call("rename_workspace", { actor, slug, newSlug }), | |
| 94 | checkWorkspaceRename: (actor, slug, newSlug) => | |
| 95 | call("check_workspace_rename", { actor, slug, newSlug }), | |
| 96 | resolveSlug: (slug) => call("resolve_slug", { slug }), | |
| 97 | deleteWorkspace: (actor, slug, confirm) => call("delete_workspace", { actor, slug, confirm }), | |
| 98 | checkWorkspaceDeletion: (actor, slug) => call("check_workspace_deletion", { actor, slug }), | |
| 99 | setWorkspaceAvatar: (actor, slug, image) => call("set_workspace_avatar", { actor, slug, image }), | |
| 100 | setUserAvatar: (user, image) => call("set_user_avatar", { user, image }), | |
| 101 | listWorkspaceTokens: (slug, viewer) => call("list_workspace_tokens", { slug, viewer }), | |
| 102 | removeWorkspaceToken: (actor, slug, id) => | |
| 103 | call("remove_workspace_token", { actor, slug, id }), | |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 104 | createToken: (actor, input) => |
| 105 | call("create_token", { | |
| 106 | actor, | |
| 107 | owner: input.owner ?? null, | |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 108 | name: input.name, |
| 109 | description: input.description ?? null, | |
| 110 | ttl_seconds: input.ttlSeconds, | |
| 111 | workspace: input.workspace, | |
| 112 | repository_selection: input.repositorySelection, | |
| 113 | repositories: input.repositories, | |
| 114 | permissions: input.permissions, | |
| Merge main into Artifacts Phase 2 | 115 | website: input.website ?? false, |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 116 | }), |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 117 | updateToken: (actor, id, change, owner) => |
| 118 | call("update_token", { | |
| 119 | actor, | |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 120 | id, |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 121 | owner: owner ?? null, |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 122 | name: change.name ?? null, |
| 123 | description: change.description ?? null, | |
| 124 | repository_selection: change.repositorySelection ?? null, | |
| 125 | repositories: change.repositories ?? null, | |
| 126 | permissions: change.permissions ?? null, | |
| Merge main into Artifacts Phase 2 | 127 | website: change.website ?? null, |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 128 | }), |
| 129 | getTokenPolicy: (slug, viewer) => call("get_token_policy", { slug, viewer }), | |
| 130 | setTokenPolicy: (actor, slug, change) => | |
| 131 | call("set_token_policy", { | |
| 132 | actor, | |
| 133 | slug, | |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 134 | allow_tokens_for_all_workspaces: change.allowTokensForAllWorkspaces ?? null, |
| 135 | allow_tokens_for_this_workspace: change.allowTokensForThisWorkspace ?? null, | |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 136 | require_approval: change.requireApproval ?? null, |
| 137 | max_lifetime_days: change.maxLifetimeDays ?? null, | |
| 138 | forbid_no_expiry: change.forbidNoExpiry ?? null, | |
| 139 | surface: "web", | |
| 140 | }), | |
| 141 | listMemberTokens: (actor, slug, filter = {}) => | |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 142 | call("list_member_tokens", { actor, slug, status: filter.status ?? null }), |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 143 | reviewTokenRequest: (actor, slug, id, approve, reason) => |
| 144 | call("review_token_request", { actor, slug, id, approve, reason: reason ?? null, surface: "web" }), | |
| 145 | revokeMemberToken: (actor, slug, id, reason) => | |
| 146 | call("revoke_member_token", { actor, slug, id, reason: reason ?? null, surface: "web" }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 147 | userForSession: (sessionToken) => call("user_for_session", { sessionToken }), |
| 148 | registration: () => call("registration", {}), | |
| 149 | listInvites: (user) => call("list_invites", { user }), | |
| 150 | createInvite: (user, options = {}) => | |
| Merge two kinds of invite, kept apart: an invite to g1t (Settings, invite-only only, no workspace unless asked) and an invitation to a workspace (its People page) | 151 | call("create_invite", { user, email: options.email ?? null, workspace: options.workspace ?? null, join: options.join ?? null, join_role: options.join ? (options.joinRole ?? null) : null }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 152 | revokeInvite: (user, id) => call("revoke_invite", { user, id }), |
| 153 | checkInvite: (code, client, options = {}) => | |
| 154 | call("check_invite", { | |
| 155 | code, | |
| 156 | client: client ?? null, | |
| 157 | viewer: options.viewer ?? null, | |
| 158 | any_status: options.anyStatus ?? false, | |
| Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm | 159 | email_proof: options.emailProof ?? null, |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 160 | }), |
| 161 | acceptInvite: (user, code) => call("accept_invite", { user, code }), | |
| Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040) | 162 | inviteMember: (actor, slug, who) => |
| Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now. | 163 | call("invite_member", { actor, slug, email: who.email ?? "", username: who.username ?? null, role: who.role ?? null, message: who.message ?? null }), |
| Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040) | 164 | listInvitations: (user) => call("list_invitations", { user }), |
| 165 | acceptInvitation: (user, id) => call("accept_invitation", { user, id, surface: "web" }), | |
| 166 | declineInvitation: (user, id) => call("decline_invitation", { user, id, surface: "web" }), | |
| 167 | findPeople: (query, limit) => call("find_people", { query, limit: limit ?? null }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 168 | workspaceInvites: (slug, viewer) => call("workspace_invites", { slug, viewer }), |
| 169 | revokeWorkspaceInvite: (actor, slug, id) => call("revoke_workspace_invite", { actor, slug, id }), | |
| Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now. | 170 | resendWorkspaceInvite: (actor, slug, id) => call("resend_workspace_invite", { actor, slug, id }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 171 | requestAccess: (email, about, client) => call("request_access", { email, about, client: client ?? null }), |
| 172 | userForGitCredentials: (username, secret) => | |
| 173 | call("user_for_git_credentials", { username, secret }), | |
| 174 | userForAccessToken: (token) => call("user_for_access_token", { token }), | |
| 175 | userForSshKey: (fingerprint) => call("user_for_ssh_key", { fingerprint }), | |
| 176 | userByUsername: (username) => call("user_by_username", { username }), | |
| 177 | usernames: (ids) => call("usernames", { ids }), | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 178 | usersForAudience: (ids) => call("users_for_audience", { ids }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 179 | profile: (username) => call("profile", { username }), |
| 180 | updateProfile: (actor, fields) => call("update_profile", { actor, ...fields }), | |
| 181 | profileWorkspaces: (username, viewer, publicIn) => | |
| 182 | call("profile_workspaces", { username, viewer, public: publicIn }), | |
| The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how. | 183 | dockPins: (user, workspace) => call("dock_pins", { user, workspace }), |
| 184 | setDockPins: (user, workspace, apps) => call("set_dock_pins", { user, workspace, apps }), | |
| Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules. | 185 | soundSettings: (user) => call("sound_settings", { user }), |
| 186 | setSoundSettings: (user, change) => call("set_sound_settings", { user, change }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 187 | listSshKeys: (user) => call("list_ssh_keys", { user }), |
| 188 | addSshKey: (user, title, publicKey) => | |
| 189 | call("add_ssh_key", { user, title, publicKey }), | |
| 190 | removeSshKey: (user, id) => call("remove_ssh_key", { user, id }), | |
| 191 | listAccessTokens: (user) => call("list_access_tokens", { user }), | |
| 192 | createAccessToken: (user, name, ttlSeconds, grant) => | |
| 193 | call("create_access_token", { | |
| 194 | user, | |
| 195 | name, | |
| 196 | ttlSeconds, | |
| 197 | scopes: grant?.scopes ?? null, | |
| 198 | listed: grant?.listed ?? false, | |
| 199 | }), | |
| 200 | createAgentToken: (onBehalfOf, scope, ttlSeconds) => | |
| 201 | call("create_agent_token", { onBehalfOf, scope, ttlSeconds }), | |
| 202 | removeAccessToken: (user, id) => call("remove_access_token", { user, id }), | |
| 203 | createRunCredential: (input) => call("create_run_credential", input), | |
| 204 | bindRunCredentials: (tokenHashes, runId) => call("bind_run_credentials", { tokenHashes, runId }), | |
| 205 | revokeRunCredentials: (target) => call("revoke_run_credentials", target), | |
| 206 | // Who has access to a repository; see access.ts. | |
| 207 | repoAccess: (owner, name, viewer) => call("repo_access", { viewer, path: { namespace: owner, name } }), | |
| 208 | addCollaborator: (actor, owner, name, invitee, role) => | |
| 209 | call("add_collaborator", { actor, path: { namespace: owner, name }, invitee, role }), | |
| 210 | setCollaboratorRole: (actor, owner, name, username, role) => | |
| 211 | call("set_collaborator_role", { actor, path: { namespace: owner, name }, username, role }), | |
| 212 | removeCollaborator: (actor, owner, name, username) => | |
| 213 | call("remove_collaborator", { actor, path: { namespace: owner, name }, username }), | |
| 214 | collaboratorPermission: (viewer, owner, name, username) => | |
| 215 | call("collaborator_permission", { viewer, path: { namespace: owner, name }, username }), | |
| 216 | myRepoInvitations: (user) => call("my_repo_invitations", { user }), | |
| 217 | respondRepoInvitation: (user, id, accept) => call("respond_repo_invitation", { user, id, accept }), | |
| 218 | revokeRepoInvitation: (actor, owner, name, id) => | |
| 219 | call("revoke_repo_invitation", { actor, path: { namespace: owner, name }, id }), | |
| 220 | setBasePermission: (actor, slug, base) => call("set_base_permission", { actor, slug, base_permission: base }), | |
| 221 | outsideCollaborators: (viewer, slug) => call("outside_collaborators", { viewer, slug }), | |
| Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca | 222 | // A repository's deploy keys; see deploy-keys.ts. |
| 223 | listDeployKeys: (viewer, owner, name) => call("list_deploy_keys", { viewer, path: { namespace: owner, name } }), | |
| 224 | addDeployKey: (actor, owner, name, key) => | |
| 225 | call("add_deploy_key", { actor, path: { namespace: owner, name }, title: key.title, key: key.key, read_only: key.readOnly }), | |
| 226 | removeDeployKey: (actor, owner, name, id) => call("remove_deploy_key", { actor, path: { namespace: owner, name }, id }), | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 227 | // Teams; see teams.ts. |
| 228 | listTeams: (viewer, workspace, query) => call("list_teams", { viewer, workspace, query: query ?? null }), | |
| 229 | getTeam: (viewer, workspace, team) => call("get_team", { viewer, workspace, team }), | |
| 230 | createTeam: (actor, workspace, team) => call("create_team", { actor, workspace, ...team }), | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 231 | setTeamCreation: (actor, slug, setting) => call("set_team_creation", { actor, slug, team_creation: setting }), |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 232 | updateTeam: (actor, workspace, team, changes) => call("update_team", { actor, workspace, team, ...changes }), |
| 233 | deleteTeam: (actor, workspace, team) => call("delete_team", { actor, workspace, team }), | |
| 234 | teamMembers: (viewer, workspace, team, includeChildTeams) => | |
| 235 | call("team_members", { viewer, workspace, team, include_child_teams: includeChildTeams ?? false }), | |
| 236 | setTeamMember: (actor, workspace, team, username, role) => | |
| 237 | call("set_team_member", { actor, workspace, team, username, role }), | |
| 238 | removeTeamMember: (actor, workspace, team, username) => | |
| 239 | call("remove_team_member", { actor, workspace, team, username }), | |
| 240 | childTeams: (viewer, workspace, team) => call("child_teams", { viewer, workspace, team }), | |
| 241 | teamRepos: (viewer, workspace, team) => call("team_repos", { viewer, workspace, team }), | |
| 242 | setTeamRepo: (actor, workspace, team, owner, name, role) => | |
| 243 | call("set_team_repo", { actor, workspace, team, repo: { namespace: owner, name }, role }), | |
| 244 | removeTeamRepo: (actor, workspace, team, owner, name) => | |
| 245 | call("remove_team_repo", { actor, workspace, team, repo: { namespace: owner, name } }), | |
| 246 | userTeams: (viewer, workspace, username) => call("user_teams", { viewer, workspace, username }), | |
| 247 | teamMemberships: (viewer, workspace) => call("team_memberships", { viewer, workspace }), | |
| People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how. | 248 | teamAgents: (viewer, workspace, team) => call("team_agents", { viewer, workspace, team }), |
| 249 | setTeamAgent: (actor, workspace, team, agentId) => call("set_team_agent", { actor, workspace, team, agent_id: agentId }), | |
| 250 | removeTeamAgent: (actor, workspace, team, agentId) => call("remove_team_agent", { actor, workspace, team, agent_id: agentId }), | |
| An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how. | 251 | agentTeams: (workspace, agentId) => call("agent_teams", { workspace, agent_id: agentId }), |
| 252 | teamAgentIndex: (workspace) => call("team_agent_index", { workspace }), | |
| 253 | adoptAgentTeams: (workspaceId, agents) => call("adopt_agent_teams", { workspace_id: workspaceId, agents }), | |
| People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how. | 254 | // People; see people.ts. |
| 255 | peopleDirectory: (viewer, workspace) => call("people_directory", { viewer, workspace }), | |
| 256 | setMemberProfile: (actor, workspace, username, changes) => call("set_member_profile", { actor, workspace, username, ...changes }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 257 | }; |
| 258 | } | |
| 259 | ||
| 260 | /** | |
| 261 | * The slug a renamed workspace has now, for a `workspace.renamed` handler: | |
| 262 | * asked of identity by the workspace's id, so that renames delivered twice | |
| 263 | * or out of order converge. Falls back to the event's `to`. | |
| 264 | */ | |
| 265 | export async function currentWorkspaceSlug( | |
| 266 | identity: ServiceBinding, | |
| 267 | renamed: { workspaceId: string; to: string }, | |
| 268 | ): Promise<string> { | |
| 269 | const names = await identityClient(identity).usernames([renamed.workspaceId]); | |
| 270 | return names[renamed.workspaceId] ?? renamed.to; | |
| 271 | } | |
| 272 | ||
| 273 | /** The slugs whose rows move to `current`: the two a rename names, less `current`. */ | |
| 274 | export function staleSlugs(renamed: { from: string; to: string }, current: string): string[] { | |
| 275 | return [...new Set([renamed.from, renamed.to])].filter((slug) => slug !== current); | |
| 276 | } | |
| 277 | ||
| 278 | /** | |
| 279 | * Where a transferred repository is now, as `namespace/name`, for a | |
| 280 | * `repo.transferred` handler: asked of repos by id, so transfers delivered | |
| 281 | * twice or out of order converge. Falls back to the event's destination. | |
| 282 | */ | |
| 283 | export async function currentRepoPath( | |
| 284 | repos: ServiceBinding, | |
| 285 | transferred: { repoId: string; name: string; to: string }, | |
| 286 | ): Promise<string> { | |
| 287 | const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: transferred.repoId }); | |
| 288 | return path ? `${path.namespace}/${path.name}` : `${transferred.to}/${transferred.name}`; | |
| 289 | } | |
| 290 | ||
| 291 | /** The paths whose rows move to `current`: the two a transfer names, less `current`. */ | |
| 292 | export function stalePaths(transferred: { name: string; from: string; to: string }, current: string): string[] { | |
| 293 | return [...new Set([transferred.from, transferred.to].map((ns) => `${ns}/${transferred.name}`))].filter( | |
| 294 | (path) => path !== current, | |
| 295 | ); | |
| 296 | } | |
| 297 | ||
| 298 | /** | |
| 299 | * A repository's path change, from `repo.transferred` or `repo.renamed`, | |
| 300 | * read the same way: the two paths (`namespace/name`) the event names, old | |
| 301 | * then new. Null for any other event. | |
| 302 | */ | |
| 303 | export type RepoMove = { repoId: string; paths: [string, string] }; | |
| 304 | ||
| 305 | export function repoMove(event: { type: string; data: unknown }): RepoMove | null { | |
| 306 | const data = event.data as Record<string, string>; | |
| 307 | if (event.type === "repo.transferred") { | |
| 308 | return { repoId: data.repoId!, paths: [`${data.from}/${data.name}`, `${data.to}/${data.name}`] }; | |
| 309 | } | |
| 310 | if (event.type === "repo.renamed") { | |
| 311 | return { repoId: data.repoId!, paths: [`${data.namespace}/${data.from}`, `${data.namespace}/${data.to}`] }; | |
| 312 | } | |
| 313 | return null; | |
| 314 | } | |
| 315 | ||
| 316 | /** | |
| 317 | * Where a moved repository is now, as `namespace/name`: asked of repos by | |
| 318 | * id, so moves delivered twice or out of order converge. Falls back to the | |
| 319 | * event's new path. | |
| 320 | */ | |
| 321 | export async function currentMovedPath(repos: ServiceBinding, move: RepoMove): Promise<string> { | |
| 322 | const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: move.repoId }); | |
| 323 | return path ? `${path.namespace}/${path.name}` : move.paths[1]; | |
| 324 | } | |
| 325 | ||
| 326 | /** The paths whose rows move to `current`: the two a move names, less `current`. */ | |
| 327 | export function staleMovedPaths(move: RepoMove, current: string): string[] { | |
| 328 | return [...new Set(move.paths)].filter((path) => path !== current); | |
| 329 | } | |
| 330 | ||
| 331 | /** Staff-only identity. Only sudo binds to it; see `IdentityAdminApi`. */ | |
| 332 | export function identityAdminClient(service: ServiceBinding): IdentityAdminApi { | |
| 333 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 334 | return { | |
| 335 | workspaces: (query) => call("admin_workspaces", { query: query ?? null }), | |
| 336 | workspace: (slug) => call("admin_workspace", { slug }), | |
| 337 | waitlist: (query, status) => call("admin_waitlist", { query: query ?? null, status: status ?? null }), | |
| 338 | waitlistPending: () => call("admin_waitlist_pending", {}), | |
| 339 | decideWaitlist: (id, approve, staff, note) => call("admin_decide_waitlist", { id, approve, staff, note: note ?? null }), | |
| 340 | invites: (query) => call("admin_invites", { query: query ?? null }), | |
| 341 | revokeInvite: (id, staff) => call("admin_revoke_invite", { id, staff }), | |
| 342 | mintInvite: (email, staff) => call("admin_mint_invite", { email, staff }), | |
| 343 | grantInvites: (target, name, amount, note, staff) => | |
| 344 | call("admin_grant_invites", { target, name, amount, note, staff }), | |
| 345 | inviteTree: (username) => call("admin_invite_tree", { username }), | |
| 346 | workspaceInvites: (slug) => call("admin_workspace_invites", { slug }), | |
| Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038) | 347 | sharedInvites: () => call("admin_shared_invites", {}), |
| 348 | createSharedInvite: (link, staff) => | |
| 349 | call("admin_create_shared_invite", { | |
| 350 | label: link.label, | |
| 351 | max_uses: link.maxUses, | |
| 352 | expires_on: link.expiresOn, | |
| 353 | domains: link.domains, | |
| 354 | staff, | |
| 355 | }), | |
| 356 | revokeSharedInvite: (id, staff) => call("admin_revoke_shared_invite", { id, staff }), | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 357 | deletedWorkspaces: () => call("admin_deleted_workspaces", {}), |
| 358 | restoreWorkspace: (workspaceId, staff) => call("admin_restore_workspace", { workspaceId, staff }), | |
| 359 | purgeWorkspace: (workspaceId, staff, confirm) => call("admin_purge_workspace", { workspaceId, staff, confirm }), | |
| Merge branch 'worktree-agent-a8385d293d42c913a' | 360 | aliases: () => call("admin_aliases", {}), |
| 361 | setAlias: (alias, workspace, note, staff) => call("admin_set_alias", { alias, workspace, note, staff }), | |
| 362 | removeAlias: (alias, reason, staff) => call("admin_remove_alias", { alias, reason, staff }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 363 | }; |
| 364 | } | |
| 365 | ||
| 366 | export function reposClient(service: ServiceBinding): ReposApi { | |
| 367 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 368 | return { | |
| 369 | get: (path, viewer) => call("get", { path, viewer }), | |
| 370 | getById: (id, viewer) => call("get_by_id", { id, viewer }), | |
| 371 | readable: (ids, viewer) => call("readable", { ids, viewer }), | |
| 372 | publicNamespaces: (ownerId) => call("public_namespaces", { ownerId }), | |
| 373 | list: (viewer, options = {}) => call("list", { viewer, ...options }), | |
| 374 | create: (owner, input) => call("create", { owner, ...input }), | |
| 375 | update: (actor, path, changes) => call("update", { actor, path, ...changes }), | |
| 376 | transfer: (actor, path, to) => call("transfer", { actor, path, to }), | |
| 377 | delete: (actor, path, confirm) => call("delete", { actor, path, confirm }), | |
| 378 | deleted: (viewer, namespace) => call("deleted", { viewer, namespace }), | |
| 379 | restore: (actor, path) => call("restore", { actor, path }), | |
| 380 | purge: (actor, path, confirm) => call("purge", { actor, path, confirm }), | |
| 381 | rename: (actor, path, name) => call("rename", { actor, path, name }), | |
| 382 | archive: (actor, path, archived) => call("archive", { actor, path, archived }), | |
| 383 | setVisibility: (actor, path, isPrivate, confirm) => call("set_visibility", { actor, path, isPrivate, confirm }), | |
| 384 | setDefaultBranch: (actor, path, branch) => call("set_default_branch", { actor, path, branch }), | |
| 385 | renameBranch: (actor, path, from, to) => call("rename_branch", { actor, path, from, to }), | |
| The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days. | 386 | createBranch: (actor, path, name, from = "") => call("create_branch", { actor, path, name, from }), |
| 387 | removeBranch: (actor, path, branch, head) => call("remove_branch", { actor, path, branch, head }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 388 | resolveBranch: (repoId, branch) => call("resolve_branch", { repoId, branch }), |
| 389 | statusById: (id) => call("status_by_id", { id }), | |
| Merge branch 'worktree-agent-a2013627e5ea4ab13' | 390 | storageOptions: () => call("storage_options", {}), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 391 | resolvePath: (path) => call("resolve_path", { path }), |
| 392 | tree: (path, viewer, ref, treePath) => | |
| 393 | call("tree", { path, viewer, ref, treePath }), | |
| 394 | blob: (path, viewer, ref, filePath) => | |
| 395 | call("blob", { path, viewer, ref, filePath }), | |
| 396 | log: (path, viewer, ref, limit) => call("log", { path, viewer, ref, limit }), | |
| 397 | blame: (path, viewer, ref, filePath) => call("blame", { path, viewer, ref, filePath }), | |
| 398 | forkForPull: (sourceId, pullId, actor) => | |
| 399 | call("fork_for_pull", { sourceId, pullId, actor }), | |
| 400 | gitAccess: (path, viewer, service) => | |
| 401 | call("git_access", { path, viewer, service }), | |
| 402 | branches: (path, viewer) => call("branches", { path, viewer }), | |
| Branches and Tags pages, each file's last commit, and the branch menu on files | 403 | lastCommits: (path, viewer, ref, treePath) => call("last_commits", { path, viewer, ref, treePath }), |
| Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 25 | 404 | branchDrift: (path, viewer, base, heads) => call("branch_drift", { path, viewer, base, heads }), |
| Branches and Tags pages, each file's last commit, and the branch menu on files | 405 | tags: (path, viewer) => call("tags", { path, viewer }), |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 406 | about: (path, viewer) => call("about", { path, viewer }), |
| 407 | languages: (path, viewer) => call("languages", { path, viewer }), | |
| 408 | contributors: (path, viewer) => call("contributors", { path, viewer }), | |
| 409 | license: (path, viewer) => call("license", { path, viewer }), | |
| 410 | stars: (path, viewer) => call("stars", { path, viewer }), | |
| 411 | star: (actor, path, starred) => call("star", { path, actor, starred }), | |
| 412 | stargazers: (path, viewer, page) => call("stargazers", { path, viewer, page: page ?? null }), | |
| 413 | starred: (username, viewer) => call("starred", { username, viewer }), | |
| 414 | releases: (path, viewer) => call("releases", { path, viewer }), | |
| 415 | release: (path, viewer, which) => call("release", { path, viewer, id: which.id ?? null, tag: which.tag ?? null, latest: which.latest ?? false }), | |
| 416 | createRelease: (actor, path, release) => call("create_release", { path, actor, ...release }), | |
| 417 | updateRelease: (actor, path, id, change) => call("update_release", { path, actor, id, ...change }), | |
| 418 | deleteRelease: (actor, path, id) => call("delete_release", { path, actor, id }), | |
| Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's. | 419 | listFiles: (repoId, ref, limit, options) => call("list_files", { repoId, ref, skipDirs: [], limit, links: options?.links ?? false }), |
| 420 | openArchiveToken: (path, token) => call("open_archive_token", { path, token }), | |
| Download ZIP from the Code button; a slimmer lifecycle panel; agent steps say what was done, not sandbox paths | 421 | rawBlobs: (repoId, hashes, maxBytes) => call("raw_blobs", { repoId, hashes, maxBytes }), |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 422 | rawFile: (repoId, ref, path, maxBytes) => call("raw_file", { repoId, ref, path, maxBytes }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 423 | commitFile: (repo, actor, file) => call("commit_file", { repo, actor, ...file }), |
| 424 | land: (sourceId, actor, branch) => call("land", { sourceId, actor, branch }), | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 425 | compare: (repoId, viewer, base, head, baseBranch) => call("compare", { repoId, viewer, base, head, baseBranch }), |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 426 | claimBackups: (limit, maxRunning) => call("claim_backups", { limit, maxRunning }), |
| 427 | // The sandbox's own calls are snake_case (they come through the API). | |
| 428 | failBackup: (jobId, token, error) => call("backup_fail", { job_id: jobId, token, error }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 429 | }; |
| 430 | } | |
| 431 | ||
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 432 | /** |
| 433 | * `PullDetail`'s own fields that the work service writes in snake_case: | |
| 434 | * `g1t_contracts::work::PullDetail` has no camelCase renaming, though what | |
| 435 | * it holds (`Pull`, `CheckRun` and the rest) does. Each with its name here. | |
| 436 | */ | |
| 437 | const PULL_DETAIL_FIELDS = [ | |
| 438 | ["review_pending", "reviewPending"], | |
| 439 | ["earlier_checks", "earlierChecks"], | |
| 440 | ["required_checks", "requiredChecks"], | |
| 441 | ["code_owners", "codeOwners"], | |
| 442 | ] as const; | |
| 443 | ||
| 444 | /** | |
| 445 | * A pull request's detail as the work service sent it, with its own fields | |
| 446 | * in the camelCase this package uses: read at the edge, by `workClient`'s | |
| 447 | * `getPull`. Either spelling is taken, so a service that already sends | |
| 448 | * camelCase reads the same. | |
| 449 | */ | |
| 450 | export function pullDetailFromWire(raw: Record<string, unknown>): PullDetail { | |
| 451 | const detail: Record<string, unknown> = { ...raw }; | |
| 452 | for (const [snake, camel] of PULL_DETAIL_FIELDS) { | |
| 453 | if (snake in detail) { | |
| 454 | if (!(camel in detail)) detail[camel] = detail[snake]; | |
| 455 | delete detail[snake]; | |
| 456 | } | |
| 457 | } | |
| 458 | if (typeof detail.reviewPending !== "boolean") detail.reviewPending = false; | |
| 459 | return detail as PullDetail; | |
| 460 | } | |
| 461 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 462 | export function workClient(service: ServiceBinding): WorkApi { |
| 463 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 464 | return { | |
| 465 | openIssue: (actor, repo, input) => call("open_issue", { actor, repo, ...input }), | |
| 466 | delegateIssue: (actor, repo, input) => call("delegate_issue", { actor, repo, ...input }), | |
| 467 | listIssues: (repo, viewer, filter = {}) => call("list_issues", { repo, viewer, ...filter }), | |
| 468 | getIssue: (repo, number, viewer) => call("get_issue", { repo, number, viewer }), | |
| 469 | updateIssue: (actor, repo, number, input) => | |
| 470 | call("update_issue", { actor, repo, number, ...input }), | |
| 471 | closeIssue: (actor, repo, number, reason) => | |
| 472 | call("close_issue", { actor, repo, number, reason }), | |
| 473 | reopenIssue: (actor, repo, number) => call("reopen_issue", { actor, repo, number }), | |
| 474 | listLabels: (repo, viewer) => call("list_labels", { repo, viewer }), | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 475 | saveLabel: (actor, repo, label) => call("save_label", { actor, repo, ...label }), |
| 476 | deleteLabel: (actor, repo, name) => call("delete_label", { actor, repo, name }), | |
| 477 | addDefaultLabels: (actor, repo) => call("add_default_labels", { actor, repo }), | |
| 478 | setLabels: (actor, repo, number, labels, change = "set") => | |
| 479 | call("set_labels", { actor, repo, number, labels, change }), | |
| 480 | listMilestones: (repo, viewer, state) => call("list_milestones", { repo, viewer, state }), | |
| 481 | getMilestone: (repo, number, viewer) => call("get_milestone", { repo, number, viewer }), | |
| 482 | saveMilestone: (actor, repo, milestone) => call("save_milestone", { actor, repo, ...milestone }), | |
| 483 | deleteMilestone: (actor, repo, number) => call("delete_milestone", { actor, repo, number }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 484 | counts: (repo, viewer) => call("counts", { repo, viewer }), |
| 485 | addComment: (actor, repo, number, comment) => | |
| 486 | call("add_comment", { actor, repo, number, ...comment }), | |
| Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts | 487 | editComment: (actor, repo, commentId, body) => call("edit_comment", { actor, repo, commentId, body }), |
| 488 | deleteComment: (actor, repo, commentId) => call("delete_comment", { actor, repo, commentId }), | |
| Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar | 489 | workspaceAgentComment: (repo, number, agent, actingFor, body) => |
| 490 | call("workspace_agent_comment", { repo, number, agent, acting_for: actingFor, body }), | |
| 491 | workspaceAgentReview: (repo, number, agent, actingFor, verdict, body) => | |
| 492 | call("workspace_agent_review", { repo, number, agent, acting_for: actingFor, verdict, body }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 493 | startChecks: (pullId) => call("start_checks", { pullId }), |
| 494 | reportChecks: (runId, token, report) => | |
| 495 | call("report_checks", { runId, token, ...report }), | |
| 496 | startReview: (pullId) => call("start_review", { pullId }), | |
| 497 | failReview: (runId, token, error) => call("report_review", { runId, token, error }), | |
| 498 | startMergecheck: (pullId) => call("start_mergecheck", { pullId }), | |
| 499 | failMergecheck: (pullId, token, error) => call("report_mergecheck", { pullId, token, error }), | |
| 500 | advance: (pullId) => call("advance", { pullId }), | |
| 501 | stall: (pullId, reason) => call("stall", { pullId, reason }), | |
| 502 | managedPulls: (repoId) => call("managed_pulls", { repoId }), | |
| 503 | queue: (repo, viewer) => call("queue", { repo, viewer }), | |
| 504 | queueBuild: (repoId) => call("queue_build", { repoId }), | |
| 505 | failQueue: (entryId, token, error) => call("report_queue", { entryId, token, error }), | |
| 506 | removeFromQueue: (actor, repo, number) => call("remove_from_queue", { actor, repo, number }), | |
| 507 | messageAgent: (actor, repo, number, body) => call("message_agent", { actor, repo, number, body }), | |
| 508 | catchUpJob: (pullId) => call("catch_up_job", { pullId }), | |
| 509 | wakeForMessages: (pullId) => call("wake_for_messages", { pullId }), | |
| 510 | getSettings: (repo, viewer) => call("get_settings", { repo, viewer }), | |
| Merge rulesets: branch and tag rules, agent-first, enforced on push and merge | 511 | // Rulesets travel in snake_case (rules.ts). |
| 512 | listRulesets: (owner, viewer, includeParents = false) => | |
| 513 | call("list_rulesets", { viewer, ...owner, include_parents: includeParents }), | |
| 514 | getRuleset: (owner, id, viewer) => call("get_ruleset", { viewer, ...owner, id }), | |
| 515 | saveRuleset: (actor, owner, ruleset, id) => call("save_ruleset", { actor, ...owner, id: id ?? null, ruleset }), | |
| 516 | deleteRuleset: (actor, owner, id) => call("delete_ruleset", { actor, ...owner, id }), | |
| 517 | effectiveRules: (repo, name, viewer, target = "branch") => call("effective_rules", { viewer, repo, name, target }), | |
| 518 | ruleEvaluations: (owner, viewer, filter = {}) => call("rule_evaluations", { viewer, ...owner, ...filter }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 519 | seenChecks: (repo, viewer) => call("seen_checks", { repo, viewer }), |
| Merge checks: statuses and check runs on every commit | 520 | commitChecks: (repo, viewer, shas) => call("commit_checks", { repo, viewer, shas }), |
| 521 | getCheckRun: (repo, id, viewer) => call("get_check_run", { repo, id, viewer }), | |
| 522 | checkRunAnnotations: (repo, id, viewer) => call("check_run_annotations", { repo, id, viewer }), | |
| 523 | requestCheckAction: (actor, repo, id, identifier) => call("request_check_action", { actor, repo, id, identifier }), | |
| 524 | rerequestCheckRun: (actor, repo, id) => call("rerequest_check_run", { actor, repo, id }), | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 525 | codeownersErrors: (repo, viewer, ref) => call("codeowners_errors", { repo, viewer, ref: ref ?? null }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 526 | updateSettings: (actor, repo, settings) => |
| 527 | call("update_settings", { actor, repo, settings }), | |
| 528 | openPull: (actor, repo, input) => call("open_pull", { actor, repo, ...input }), | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 529 | listPulls: (repo, viewer, state, filter = {}) => call("list_pulls", { repo, viewer, state, ...filter }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 530 | pullsForRepos: (repoIds, viewer, limit) => call("pulls_for_repos", { repoIds, viewer, limit }), |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 531 | // Its own fields arrive in snake_case: read into camelCase here. |
| 532 | getPull: (repo, number, viewer) => | |
| 533 | call<Result<Record<string, unknown>>>("get_pull", { repo, number, viewer }).then( | |
| 534 | (found): Result<PullDetail> => (found.ok ? { ok: true, value: pullDetailFromWire(found.value) } : found), | |
| 535 | ), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 536 | updatePull: (actor, repo, number, changes) => |
| 537 | call("update_pull", { actor, repo, number, ...changes }), | |
| 538 | catchUpPull: (actor, repo, number) => call("catch_up_pull", { actor, repo, number }), | |
| 539 | readyPull: (actor, repo, number, summary) => | |
| 540 | call("ready_pull", { actor, repo, number, summary }), | |
| 541 | closePull: (actor, repo, number) => call("close_pull", { actor, repo, number }), | |
| Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts | 542 | reopenPull: (actor, repo, number) => call("reopen_pull", { actor, repo, number }), |
| 543 | convertPullToDraft: (actor, repo, number) => call("convert_pull_to_draft", { actor, repo, number }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 544 | mergePull: (actor, repo, number, options = {}) => |
| 545 | call("merge_pull", { actor, repo, number, ...options }), | |
| 546 | listActivePulls: (viewer) => call("list_active_pulls", { viewer }), | |
| 547 | byAuthor: (username, viewer, filter = {}) => call("by_author", { username, viewer, ...filter }), | |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 548 | contributions: (username, viewer) => call("contributions", { username, viewer }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 549 | startPlan: (actor, repo, brief) => call("start_plan", { actor, repo, brief }), |
| 550 | failPlan: (planId, token, error) => call("report_plan", { planId, token, error }), | |
| 551 | getPlan: (repo, viewer, id) => call("get_plan", { repo, viewer, id }), | |
| 552 | listPlans: (repo, viewer) => call("list_plans", { repo, viewer }), | |
| 553 | applyPlan: (actor, repo, id, options = {}) => | |
| 554 | call("apply_plan", { actor, repo, id, ...options }), | |
| 555 | queueIssue: (actor, repo, number, queued) => | |
| 556 | call("queue_issue", { actor, repo, number, queued }), | |
| 557 | readyIssues: (repoId) => call("ready_issues", { repoId }), | |
| 558 | listAssignedIssues: (viewer) => call("list_assigned_issues", { viewer }), | |
| 559 | appendSession: (actor, repo, number, entries) => | |
| 560 | call("append_session", { actor, repo, number, entries }), | |
| 561 | readSession: (repo, number, viewer, afterSeq = 0) => | |
| 562 | call("read_session", { repo, number, viewer, afterSeq }), | |
| 563 | }; | |
| 564 | } | |
| 565 | ||
| 566 | export function billingClient(service: ServiceBinding): BillingApi { | |
| 567 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 568 | return { | |
| 569 | status: () => call("status", {}), | |
| 570 | account: (workspace, viewer) => call("account", { workspace, viewer }), | |
| 571 | ledger: (workspace, viewer) => call("ledger", { workspace, viewer }), | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 572 | credits: (workspace, viewer) => call("credits", { workspace, viewer }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 573 | statement: (workspace, viewer, month = null, group = "day") => call("statement", { workspace, viewer, month, group }), |
| 574 | statementEntries: (workspace, viewer, filter) => | |
| 575 | call("statement_entries", { | |
| 576 | workspace, | |
| 577 | viewer, | |
| 578 | month: filter.month, | |
| 579 | kind: filter.kind, | |
| 580 | day: filter.day ?? null, | |
| 581 | project: filter.project ?? null, | |
| 582 | before: filter.before ?? null, | |
| 583 | }), | |
| 584 | usage: (workspace, viewer, since) => call("usage", { workspace, viewer, since }), | |
| Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix | 585 | tokenUsage: (workspace, viewer, options = {}) => |
| 586 | call("token_usage", { workspace, viewer, person: options.person ?? null, days: options.days ?? null }), | |
| 587 | recordTokens: (usage) => call("record_tokens", usage), | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 588 | gatewayModels: () => call("gateway_models", {}), |
| Merge branch 'main' into actions-toolkit-oidc-artifacts | 589 | modelDefaults: () => call("model_defaults", {}), |
| 590 | recordDiscovery: (provider, models, by, error = null) => call("record_discovery", { provider, models, by, error }), | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 591 | gatewayAdmit: (workspace) => call("gateway_admit", { workspace }), |
| 592 | recordGateway: (record) => call("record_gateway", record), | |
| 593 | gatewayRequests: (workspace, viewer, options = {}) => | |
| 594 | call("gateway_requests", { workspace, viewer, limit: options.limit ?? null, before: options.before ?? null }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 595 | checkout: (actor, workspace, amountCents, returnUrl, method = "card") => |
| 596 | call("checkout", { actor, workspace, amountCents, returnUrl, method }), | |
| 597 | confirm: (workspace, viewer, session) => call("confirm", { workspace, viewer, session }), | |
| 598 | canStart: (workspace) => call("can_start", { workspace }), | |
| 599 | trial: (workspace, exempt) => call("trial", { workspace, exempt }), | |
| 600 | startRun: (run) => call("start_run", run), | |
| 601 | features: (workspace, viewer) => call("features", { workspace, viewer }), | |
| 602 | subscribe: (actor, workspace, feature, returnUrl) => | |
| 603 | call("subscribe", { actor, workspace, feature, returnUrl }), | |
| 604 | confirmSubscription: (workspace, viewer, session) => | |
| 605 | call("confirm_subscription", { workspace, viewer, session }), | |
| 606 | cancelSubscription: (actor, workspace, feature, resume = false) => | |
| 607 | call("cancel_subscription", { actor, workspace, feature, resume }), | |
| 608 | hasFeature: (workspace, feature) => call("has_feature", { workspace, feature }), | |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 609 | freeWorkspaces: (workspaces) => call("free_workspaces", { workspaces }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 610 | chargeFeature: (charge) => call("charge_feature", charge), |
| 611 | billingPortal: (actor, workspace, returnUrl) => call("billing_portal", { actor, workspace, return_url: returnUrl }), | |
| 612 | recordSandbox: (usage) => call("record_sandbox", usage), | |
| 613 | limit: (workspace, viewer) => call("limit", { workspace, viewer }), | |
| 614 | checkLimit: (workspace) => call("check_limit", { workspace }), | |
| 615 | prices: () => call("prices", {}), | |
| 616 | notePending: (workspace, source, costMicros, detail = null) => call("note_pending", { workspace, source, costMicros, detail }), | |
| 617 | usageMeters: (workspace, viewer) => call("usage_meters", { workspace, viewer }), | |
| 618 | setSpendLimit: (actor, workspace, spendLimitMicros, useFullLimit = false, raiseOnce = false) => | |
| 619 | call("set_spend_limit", { actor, workspace, spendLimitMicros, use_full_limit: useFullLimit, raise_once: raiseOnce }), | |
| 620 | invoices: (workspace, viewer) => call("invoices", { workspace, viewer }), | |
| 621 | entitlements: (workspace) => call("entitlements", { workspace }), | |
| 622 | reserve: (reservation) => call("reserve", reservation), | |
| 623 | settle: (reservationId, actualMicros) => call("settle", { reservationId, actualMicros }), | |
| 624 | cardCheck: (actor, workspace, returnUrl) => call("card_check", { actor, workspace, returnUrl }), | |
| 625 | confirmCardCheck: (workspace, viewer, session) => call("confirm_card_check", { workspace, viewer, session }), | |
| 626 | requestLimit: (actor, workspace, request) => call("request_limit", { actor, workspace, ...request }), | |
| 627 | limitRequests: (workspace, viewer) => call("limit_requests", { workspace, viewer }), | |
| 628 | confirmSpike: (actor, workspace, keepGoing) => call("confirm_spike", { actor, workspace, keepGoing }), | |
| 629 | setCaps: (actor, workspace, caps) => call("set_caps", { actor, workspace, ...caps }), | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 630 | usageReport: (workspace, viewer, range) => |
| 631 | call("usage_report", { workspace, viewer, from: range.from, until: range.until, products: range.products ?? [], projects: range.projects ?? [] }), | |
| 632 | aiCredit: (workspace, viewer) => call("ai_credit", { workspace, viewer }), | |
| 633 | buyAiCredit: (actor, workspace, amountCents, returnUrl) => call("buy_ai_credit", { actor, workspace, amountCents, returnUrl }), | |
| 634 | confirmAiCredit: (workspace, viewer, session) => call("confirm_ai_credit", { workspace, viewer, session }), | |
| 635 | setAiReload: (actor, workspace, reload) => call("set_ai_reload", { actor, workspace, ...reload }), | |
| 636 | setBudget: (actor, workspace, budget) => call("set_budget", { actor, workspace, ...budget }), | |
| 637 | billingDetails: (workspace, viewer) => call("billing_details", { workspace, viewer }), | |
| 638 | setBillingDetails: (actor, workspace, details) => call("set_billing_details", { actor, workspace, ...details }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 639 | }; |
| 640 | } | |
| 641 | ||
| 642 | export function billingAdminClient(service: ServiceBinding): BillingAdminApi { | |
| 643 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 644 | return { | |
| 645 | accounts: (query) => call("admin_accounts", { query: query ?? null }), | |
| 646 | account: (id) => call("admin_account", { id }), | |
| 647 | setTerms: (id, terms, by) => call("admin_set_terms", { id, terms, by }), | |
| 648 | setAllowances: (id, allowances, note, by) => call("admin_set_allowances", { id, allowances, note, by }), | |
| 649 | createEnterprise: (name, workspaces, by) => call("admin_create_enterprise", { name, workspaces, by }), | |
| 650 | attach: (workspace, account, by) => call("admin_attach", { workspace, account, by }), | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 651 | credit: (workspace, amountMicros, note, by, options = { kind: "goodwill" }) => |
| 652 | call("admin_credit", { | |
| 653 | workspace, | |
| 654 | amount_micros: amountMicros, | |
| 655 | note, | |
| 656 | by, | |
| 657 | kind: options.kind, | |
| 658 | expires_at: options.expiresAt ?? null, | |
| 659 | refund_for: options.refundFor ?? null, | |
| 660 | refund_day: options.refundDay ?? null, | |
| 661 | }), | |
| 662 | credits: (filter = {}) => | |
| 663 | call("admin_credits", { | |
| 664 | workspace: filter.workspace ?? null, | |
| 665 | kind: filter.kind ?? null, | |
| 666 | month: filter.month ?? null, | |
| 667 | by: filter.by ?? null, | |
| 668 | }), | |
| 669 | revokeCredit: (id, note, by) => call("admin_revoke_credit", { id, note, by }), | |
| sudo: reset a test workspace's billing so it starts again as a new customer; refused on a live Stripe key, for comped workspaces and for an enterprise's | 670 | resetBilling: (workspace, confirm, note, by) => call("admin_reset_billing", { workspace, confirm, note, by }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 671 | billingLink: (workspace, by) => call("admin_billing_link", { workspace, by }), |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 672 | stripe: (fix = false, by) => call("admin_stripe", { fix, by: by ?? null }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 673 | enterpriseBilling: (id, email, by) => call("admin_enterprise_billing", { id, email, by }), |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 674 | enterpriseAddress: (id, address, taxIdType, taxId, by) => call("admin_enterprise_address", { id, address, taxIdType, taxId, by }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 675 | invoiceEnterprise: (id, by) => call("admin_invoice_enterprise", { id, by }), |
| 676 | accountsFor: (workspaces) => call("admin_accounts", { query: null, workspaces }), | |
| 677 | signals: () => call("admin_signals", {}), | |
| 678 | overview: () => call("admin_overview", {}), | |
| 679 | sales: (workspace) => call("admin_sales", { workspace }), | |
| 680 | setSales: (workspace, record, by) => | |
| 681 | call("admin_set_sales", { | |
| 682 | workspace, | |
| 683 | stage: record.stage, | |
| 684 | owner: record.owner ?? null, | |
| 685 | next_step: record.nextStep ?? null, | |
| 686 | next_at: record.nextAt ?? null, | |
| 687 | by, | |
| 688 | }), | |
| 689 | addNote: (workspace, text, by) => call("admin_add_note", { workspace, text, by }), | |
| 690 | workspaceInvoices: (workspace) => call("admin_workspace_invoices", { workspace }), | |
| 691 | allInvoices: (filter = {}) => call("admin_invoices", { status: filter.status ?? null, month: filter.month ?? null }), | |
| 692 | audit: (filter = {}) => | |
| 693 | call("admin_audit", { by: filter.by ?? null, action: filter.action ?? null, before: filter.before ?? null }), | |
| 694 | limitRequests: (status = "open") => call("admin_limit_requests", { status }), | |
| 695 | decideLimitRequest: (id, decision, amountMicros, note, by) => | |
| 696 | call("admin_decide_limit_request", { id, decision, amount_micros: amountMicros, note, by }), | |
| 697 | overages: () => call("admin_overages", {}), | |
| 698 | goodwill: (workspace, amountMicros, reason, by, day = null) => | |
| 699 | call("admin_goodwill", { workspace, amount_micros: amountMicros, reason, by, day }), | |
| 700 | velocity: () => call("admin_velocity", {}), | |
| 701 | recordPayment: (workspace, amountMicros, reference, note, by) => | |
| 702 | call("admin_record_payment", { workspace, amount_micros: amountMicros, reference, note, by }), | |
| 703 | costs: (days) => call("admin_costs", { days: days ?? null }), | |
| 704 | costAlerts: () => call("admin_cost_alerts", {}), | |
| 705 | spendCaps: () => call("admin_spend_caps", {}), | |
| 706 | liftBreaker: (note, by) => call("admin_lift_breaker", { note, by }), | |
| Merge platform pause and the hourly usage watcher: staff can pause compute, schedules, indexing or renders for everyone, the watcher emails on a breach and is never blind quietly, and the models proxy holds each run to its cap (billing 0051, integrations 0006) | 707 | platformGuard: () => call("admin_platform_guard", {}), |
| 708 | setPause: (level, paused, note, by) => call("admin_set_pause", { level, paused, note, by }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 709 | decideProposal: (id, decision, note, by) => call("admin_decide_proposal", { id, decision, note, by }), |
| 710 | setCostSettings: (settings, by) => call("admin_set_cost_settings", { settings, by }), | |
| 711 | setCostMapping: (mapping, by) => | |
| 712 | call("admin_set_cost_mapping", { | |
| 713 | product: mapping.product, | |
| 714 | meter: mapping.meter, | |
| 715 | bucket: mapping.bucket ?? "", | |
| 716 | price_meter: mapping.priceMeter ?? null, | |
| 717 | own_meter: mapping.ownMeter ?? null, | |
| 718 | scale_to_own: mapping.scaleToOwn ?? false, | |
| 719 | drift_percent: mapping.driftPercent ?? null, | |
| 720 | note: mapping.note ?? "", | |
| 721 | remove: mapping.remove ?? false, | |
| 722 | by, | |
| 723 | }), | |
| 724 | runCosts: (by) => call("admin_run_costs", { by }), | |
| Merge branch 'main' into actions-toolkit-oidc-artifacts | 725 | models: () => call("admin_models", {}), |
| 726 | decideModel: (model, decision, details, reason, by) => | |
| 727 | call("admin_decide_model", { | |
| 728 | model, | |
| 729 | decision, | |
| 730 | name: details.name ?? null, | |
| 731 | tier_hint: details.tierHint ?? null, | |
| 732 | prices: details.prices | |
| 733 | ? { | |
| 734 | input_micros: details.prices.inputMicros, | |
| 735 | output_micros: details.prices.outputMicros, | |
| 736 | cache_read_micros: details.prices.cacheReadMicros, | |
| 737 | cache_write_micros: details.prices.cacheWriteMicros, | |
| 738 | cache_write_1h_micros: details.prices.cacheWrite1hMicros, | |
| 739 | threshold: details.prices.threshold, | |
| 740 | over_input_micros: details.prices.overInputMicros, | |
| 741 | over_output_micros: details.prices.overOutputMicros, | |
| 742 | over_cache_read_micros: details.prices.overCacheReadMicros, | |
| 743 | over_cache_write_micros: details.prices.overCacheWriteMicros, | |
| 744 | over_cache_write_1h_micros: details.prices.overCacheWrite1hMicros, | |
| 745 | } | |
| 746 | : null, | |
| 747 | reason, | |
| 748 | by, | |
| 749 | }), | |
| 750 | setModelDefault: (purpose, value, reason, by) => | |
| 751 | call("admin_set_model_default", { | |
| 752 | purpose, | |
| 753 | model: value.model ?? null, | |
| 754 | tier: value.tier ?? null, | |
| 755 | effort: value.effort ?? null, | |
| 756 | reason, | |
| 757 | by, | |
| 758 | }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 759 | }; |
| 760 | } | |
| 761 | ||
| 762 | export function eventsClient(service: ServiceBinding): EventsApi { | |
| 763 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 764 | return { | |
| 765 | publish: (events) => call("publish", { events }), | |
| 766 | list: (query) => call("list", query), | |
| Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales. | 767 | activityDigest: (args) => call("activity_digest", args), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 768 | }; |
| 769 | } | |
| 770 | ||
| 771 | export function integrationsClient(service: ServiceBinding): IntegrationsApi { | |
| 772 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 773 | return { | |
| 774 | list: (workspace, viewer) => call("list", { workspace, viewer }), | |
| 775 | connect: (actor, workspace, input) => call("connect", { actor, workspace, ...input }), | |
| 776 | update: (actor, workspace, id, input) => call("update", { actor, workspace, id, ...input }), | |
| 777 | disconnect: (actor, workspace, id) => call("disconnect", { actor, workspace, id }), | |
| 778 | test: (actor, workspace, id) => call("test", { actor, workspace, id }), | |
| 779 | deliveries: (workspace, viewer, id) => call("deliveries", { workspace, viewer, id }), | |
| 780 | resolve: (workspace, viewer, reference) => call("resolve", { workspace, viewer, reference }), | |
| 781 | references: (workspace, text, limit) => call("references", { workspace, text, limit }), | |
| 782 | import: (actor, repo, reference, assign) => call("import", { actor, repo, reference, assign }), | |
| Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how. | 783 | comment: (actor, workspace, reference, text, link) => call("comment", { actor, workspace, reference, text, link }), |
| 784 | close: (actor, workspace, reference, text, link) => call("close", { actor, workspace, reference, text, link }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 785 | links: (repo, number) => call("links", { repo, number }), |
| 786 | modelProvider: (workspace) => call("model_provider", { workspace }), | |
| 787 | openModelSession: (run) => call("open_model_session", run), | |
| 788 | modelUpstream: (token) => call("model_upstream", { token }), | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 789 | gatewayUpstream: (workspace) => call("gateway_upstream", { workspace }), |
| AI Gateway: OpenAI's format, open models, and your own providers | 790 | gatewayProviders: (workspace) => call("gateway_providers", { workspace }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 791 | closeModelSessions: (tokenHashes) => call("close_model_sessions", { token_hashes: tokenHashes }), |
| Merge platform pause and the hourly usage watcher: staff can pause compute, schedules, indexing or renders for everyone, the watcher emails on a breach and is never blind quietly, and the models proxy holds each run to its cap (billing 0051, integrations 0006) | 792 | capModelSessions: (tokenHashes, capMicros) => call("cap_model_sessions", { token_hashes: tokenHashes, cap_micros: capMicros }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 793 | routes: (workspace, viewer) => call("routes", { workspace, viewer }), |
| 794 | setRoutes: (actor, workspace, routes) => call("set_routes", { actor, workspace, routes }), | |
| 795 | }; | |
| 796 | } | |
| 797 | ||
| 798 | export function webhooksClient(service: ServiceBinding): WebhooksApi { | |
| 799 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 800 | return { | |
| 801 | list: (viewer, owner) => call("list", { viewer, ...owner }), | |
| 802 | create: (actor, owner, input) => call("create", { actor, ...owner, ...input }), | |
| 803 | update: (actor, owner, id, input) => call("update", { actor, ...owner, id, ...input }), | |
| 804 | delete: (actor, owner, id) => call("delete", { actor, ...owner, id }), | |
| 805 | ping: (actor, owner, id) => call("ping", { actor, ...owner, id }), | |
| 806 | deliveries: (viewer, owner, id) => call("deliveries", { viewer, ...owner, id }), | |
| 807 | redeliver: (actor, owner, deliveryId) => call("redeliver", { actor, ...owner, deliveryId }), | |
| 808 | }; | |
| 809 | } | |
| 810 | ||
| 811 | export function actionsClient(service: ServiceBinding): ActionsApi { | |
| 812 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 813 | return { | |
| 814 | workflows: (repo, viewer) => call("workflows", { repo, viewer }), | |
| 815 | runs: (repo, viewer, filter = {}) => call("runs", { repo, viewer, ...filter }), | |
| Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009) | 816 | run: (repo, viewer, id, attempt) => call("run", { repo, viewer, id, attempt: attempt ?? null }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 817 | logs: (repo, viewer, job, after = 0) => call("logs", { repo, viewer, job, after }), |
| Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009) | 818 | summaries: (repo, viewer, id, attempt) => call("summaries", { repo, viewer, id, attempt: attempt ?? null }), |
| 819 | jobLogText: (repo, viewer, job) => call("job_log_text", { repo, viewer, job }), | |
| 820 | runLogs: (repo, viewer, id, attempt) => call("run_logs", { repo, viewer, id, attempt: attempt ?? null }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 821 | dispatch: (actor, repo, workflow, ref, inputs) => call("dispatch", { actor, repo, workflow, ref, inputs }), |
| Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009) | 822 | cancel: (actor, repo, id, force = false) => call("cancel", { actor, repo, id, force }), |
| 823 | rerun: (actor, repo, id, failedOnly = false, options = {}) => | |
| 824 | call("rerun", { actor, repo, id, failed_only: failedOnly, job: options.job ?? null, debug: options.debug ?? false }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 825 | setWorkflowEnabled: (actor, repo, workflow, enabled) => |
| 826 | call("set_workflow_enabled", { actor, repo, workflow, enabled }), | |
| 827 | settings: (actor, owner, kind) => call("settings", { actor, ...owner, kind }), | |
| 828 | setSetting: (actor, owner, kind, name, value, options = {}) => | |
| 829 | call("set_setting", { actor, ...owner, kind, name, value, ...options }), | |
| 830 | deleteSetting: (actor, owner, kind, name, id) => call("delete_setting", { actor, ...owner, kind, name, id }), | |
| Merge branch 'worktree-agent-a3abfcce648e87dca' | 831 | approveRun: (actor, repo, id) => call("approve_run", { actor, repo, id }), |
| 832 | pendingDeployments: (repo, viewer, id) => call("pending_deployments", { repo, viewer, id }), | |
| 833 | reviewDeployments: (actor, repo, id, state, environments = [], comment) => | |
| 834 | call("review_deployments", { actor, repo, id, state, environments, comment: comment ?? null }), | |
| 835 | actionsSettings: (repo, viewer) => call("actions_settings", { repo, viewer }), | |
| 836 | setActionsSettings: (actor, repo, change) => call("set_actions_settings", { actor, repo, ...change }), | |
| 837 | workspaceActionsSettings: (workspace, viewer) => call("workspace_actions_settings", { workspace, viewer }), | |
| 838 | setWorkspaceActionsSettings: (actor, workspace, change) => | |
| 839 | call("set_workspace_actions_settings", { actor, workspace, ...change }), | |
| Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's. | 840 | workspaceManagedWorkflows: (workspace, viewer) => call("workspace_managed_workflows", { workspace, viewer }), |
| 841 | setWorkspaceManagedWorkflow: (actor, workspace, name, change, create = false) => | |
| 842 | call("set_workspace_managed_workflow", { actor, workspace, name, create, ...change }), | |
| 843 | removeWorkspaceManagedWorkflow: (actor, workspace, name) => | |
| 844 | call("remove_workspace_managed_workflow", { actor, workspace, name }), | |
| Merge branch 'worktree-agent-a3abfcce648e87dca' | 845 | environments: (repo, viewer) => call("environments", { repo, viewer }), |
| 846 | setEnvironment: (actor, repo, name, change) => call("set_environment", { actor, repo, name, ...change }), | |
| 847 | deleteEnvironment: (actor, repo, name) => call("delete_environment", { actor, repo, name }), | |
| Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2 | 848 | artifacts: (repo, viewer, filter = {}) => call("artifacts", { repo, viewer, ...filter }), |
| 849 | artifactDownload: (repo, viewer, by) => call("artifact_download", { repo, viewer, ...by }), | |
| 850 | deleteArtifact: (actor, repo, id) => call("delete_artifact", { actor, repo, id }), | |
| 851 | artifactRetention: (repo, viewer, days) => call("artifact_retention", { repo, viewer, days }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 852 | }; |
| 853 | } | |
| 854 | ||
| 855 | ||
| 856 | /** Self-hosted runners, kept by the actions service. */ | |
| 857 | export function runnersClient(service: ServiceBinding): RunnersApi { | |
| 858 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 859 | return { | |
| 860 | stuck: (viewer) => call("stuck_jobs", { viewer }), | |
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 861 | activity: (actor, workspace) => call("runner_activity", { actor, workspace }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 862 | list: (actor, owner) => call("runners", { actor, ...owner }), |
| 863 | createToken: (actor, owner, group) => call("create_registration_token", { actor, ...owner, group }), | |
| 864 | remove: (actor, owner, id) => call("remove_runner", { actor, ...owner, id }), | |
| 865 | groups: (actor, workspace) => call("runner_groups", { actor, workspace }), | |
| 866 | setGroup: (actor, workspace, group) => call("set_runner_group", { actor, workspace, ...group }), | |
| 867 | deleteGroup: (actor, workspace, id) => call("delete_runner_group", { actor, workspace, id }), | |
| 868 | settings: (actor, owner) => call("runner_settings", { actor, ...owner }), | |
| 869 | setSettings: (actor, owner, change) => call("set_runner_settings", { actor, ...owner, ...change }), | |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 870 | reportDesktop: (actor, report) => call("report_desktop", { actor, ...report }), |
| 871 | desktops: (actor, workspace) => call("desktop_runners", { actor, workspace }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 872 | }; |
| 873 | } | |
| 874 | ||
| 875 | export function deploymentsClient(service: ServiceBinding): DeploymentsApi { | |
| 876 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 877 | return { | |
| 878 | settings: (project, viewer) => call("settings", { project, viewer }), | |
| 879 | updateSettings: (actor, project, changes) => call("update_settings", { actor, project, changes }), | |
| 880 | list: (project, viewer) => call("list", { project, viewer }), | |
| 881 | get: (project, id, viewer) => call("get", { project, id, viewer }), | |
| 882 | redeploy: (actor, project, branch) => call("redeploy", { actor, project, branch }), | |
| 883 | takeDown: (actor, project, branch) => call("take_down", { actor, project, branch }), | |
| 884 | overview: (workspace, viewer) => call("overview", { workspace, viewer }), | |
| 885 | usage: (workspace, viewer) => call("usage", { workspace, viewer }), | |
| 886 | domains: (project, viewer) => call("domains", { project, viewer }), | |
| 887 | addDomain: (actor, project, hostname, options = {}) => | |
| 888 | call("add_domain", { actor, project, hostname, twin: !!options.twin }), | |
| 889 | removeDomain: (actor, project, id) => call("remove_domain", { actor, project, id }), | |
| 890 | refreshDomain: (actor, project, id) => call("refresh_domain", { actor, project, id }), | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 891 | repoDeployments: (repo, viewer, filter = {}) => call("list_deployments", { repo, viewer, ...filter }), |
| 892 | repoDeployment: (repo, id, viewer) => call("get_deployment", { repo, id, viewer }), | |
| 893 | environments: (repo, viewer) => call("list_environments", { repo, viewer }), | |
| 894 | createDeployment: (actor, repo, input) => call("create_deployment", { repo, actor, ...input }), | |
| 895 | createDeploymentStatus: (actor, repo, id, input) => call("create_deployment_status", { repo, actor, id, ...input }), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 896 | }; |
| 897 | } | |
| 898 | ||
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 899 | /** Packages: the registries beside the code (services/packages). */ |
| 900 | export function packagesClient(service: ServiceBinding): PackagesApi { | |
| 901 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 902 | return { | |
| 903 | list: (workspace, viewer, filter = {}) => | |
| 904 | call("list_packages", { | |
| 905 | workspace, | |
| 906 | viewer, | |
| 907 | ecosystem: filter.ecosystem ?? null, | |
| 908 | repo_id: filter.repoId ?? null, | |
| 909 | query: filter.query ?? null, | |
| 910 | }), | |
| 911 | get: (workspace, ecosystem, name, viewer) => call("get_package", { workspace, ecosystem, name, viewer }), | |
| 912 | deleteVersion: (actor, workspace, ecosystem, name, version, surface) => | |
| 913 | call("delete_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }), | |
| 914 | deletePackage: (actor, workspace, ecosystem, name, surface) => | |
| 915 | call("delete_package", { actor, workspace, ecosystem, name, surface: surface ?? null }), | |
| 916 | set: (actor, workspace, ecosystem, name, change, surface) => | |
| 917 | call("set_package", { | |
| 918 | actor, | |
| 919 | workspace, | |
| 920 | ecosystem, | |
| 921 | name, | |
| 922 | visibility: change.visibility ?? null, | |
| 923 | link: change.link ?? null, | |
| 924 | unlink: change.unlink ?? false, | |
| Merge packages: roles, Actions access, source label, soft delete, API | 925 | inherit_access: change.inheritAccess ?? null, |
| 926 | surface: surface ?? null, | |
| 927 | }), | |
| 928 | settings: (workspace, ecosystem, name, viewer) => call("package_settings", { workspace, ecosystem, name, viewer }), | |
| 929 | deleted: (workspace, viewer) => call("deleted_packages", { workspace, viewer }), | |
| 930 | restorePackage: (actor, workspace, ecosystem, name, surface) => | |
| 931 | call("restore_package", { actor, workspace, ecosystem, name, surface: surface ?? null }), | |
| 932 | restoreVersion: (actor, workspace, ecosystem, name, version, surface) => | |
| 933 | call("restore_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }), | |
| 934 | setAccess: (actor, workspace, ecosystem, name, who, role, surface) => | |
| 935 | call("set_package_access", { | |
| 936 | actor, | |
| 937 | workspace, | |
| 938 | ecosystem, | |
| 939 | name, | |
| 940 | user: "user" in who ? who.user : null, | |
| 941 | team: "team" in who ? who.team : null, | |
| 942 | role, | |
| 943 | surface: surface ?? null, | |
| 944 | }), | |
| 945 | removeAccess: (actor, workspace, ecosystem, name, who, surface) => | |
| 946 | call("remove_package_access", { | |
| 947 | actor, | |
| 948 | workspace, | |
| 949 | ecosystem, | |
| 950 | name, | |
| 951 | user: "user" in who ? who.user : null, | |
| 952 | team: "team" in who ? who.team : null, | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 953 | surface: surface ?? null, |
| 954 | }), | |
| Merge packages: roles, Actions access, source label, soft delete, API | 955 | setActionsAccess: (actor, workspace, ecosystem, name, repo, role, surface) => |
| 956 | call("set_actions_access", { actor, workspace, ecosystem, name, repo, role, surface: surface ?? null }), | |
| 957 | removeActionsAccess: (actor, workspace, ecosystem, name, repo, surface) => | |
| 958 | call("remove_actions_access", { actor, workspace, ecosystem, name, repo, surface: surface ?? null }), | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 959 | storage: (workspace) => call("storage", { workspace }), |
| 960 | storageAll: () => call("storage_all", {}), | |
| Composer from the workspace's own repositories, and go get from g1t.sh | 961 | syncComposer: (repoId) => call("sync_composer", { repo_id: repoId }), |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 962 | }; |
| 963 | } | |
| 964 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 965 | export function projectsClient(service: ServiceBinding): ProjectsApi { |
| 966 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 967 | return { | |
| 968 | list: (workspace, viewer) => call("list", { workspace, viewer }), | |
| 969 | get: (workspace, slug, viewer) => call("get", { workspace, slug, viewer }), | |
| 970 | byRepo: (repoId) => call("by_repo", { repoId }), | |
| 971 | create: (actor, workspace, input) => call("create", { actor, workspace, input }), | |
| 972 | update: (actor, workspace, slug, changes) => call("update", { actor, workspace, slug, changes }), | |
| Merge branch 'worktree-agent-a5b0d9e2733e9e382' | 973 | setProductionImage: (actor, workspace, slug, change) => call("set_production_image", { actor, workspace, slug, change }), |
| 974 | externalProductions: () => call("external_productions", {}), | |
| A project is an app or a library: libraries show their package and how to ship a release, not production | 975 | deploymentsChanged: (projectId, enabled) => call("deployments_changed", { projectId, enabled }), |
| Projects: pinned and recent projects, per person per workspace | 976 | shortcuts: (workspace, viewer) => call("shortcuts", { workspace, viewer }), |
| 977 | pin: (actor, workspace, slug, position) => call("pin", { actor, workspace, slug, position: position ?? null }), | |
| 978 | unpin: (actor, workspace, slug) => call("unpin", { actor, workspace, slug }), | |
| 979 | reorderPins: (actor, workspace, slugs) => call("reorder_pins", { actor, workspace, slugs }), | |
| 980 | visited: (actor, projectId) => call("visited", { actor, projectId }), | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 981 | publicLinks: (repos) => call("public_links", { repos }), |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 982 | }; |
| 983 | } | |
| Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next. | 984 | |
| 985 | /** | |
| 986 | * An agent's own computer, on the runner (services/runner `AgentComputer`): | |
| 987 | * what the agents service calls for a session's `run_command` and file | |
| 988 | * tools, and for the Computer tab. Arguments and answers are snake_case. | |
| 989 | */ | |
| 990 | export function runnerComputerClient(service: ServiceBinding): RunnerComputerApi { | |
| 991 | const call = <T>(method: string, args: object) => rpc<T>(service, method, args); | |
| 992 | return { | |
| 993 | computerStatus: (agentId) => call("computer_status", { agent_id: agentId }), | |
| 994 | computerWake: (args) => call("computer_wake", args), | |
| 995 | computerExec: (args) => call("computer_exec", args), | |
| 996 | computerReadFile: (args) => call("computer_read_file", args), | |
| 997 | computerWriteFile: (args) => call("computer_write_file", args), | |
| 998 | computerSleep: (agentId) => call("computer_sleep", { agent_id: agentId }), | |
| 999 | computerReset: (agentId) => call("computer_reset", { agent_id: agentId }), | |
| 1000 | computerForget: (agentId) => call("computer_forget", { agent_id: agentId }), | |
| An archived agent can be found, brought back, or deleted for good. The Agents page ends with Archived, folded, where owners see every archived agent and members their own personal ones, each with when and by whom it was archived and what deleting it would take; Restore brings it back, under a new handle when a live agent has taken its old one, and Delete permanently asks for the handle to be typed and then removes its sessions, replies, memories, routines, versions, drafts, skills and its computer's home, leaving its messages in chat under its saved name, the workspace's spend totals and the audit log. In Chat an archived agent is deactivated the way people expect: never offered as someone to message or mention, never woken, a conversation with it kept in the list, dimmed and marked Archived, open to read with a note in place of the composer and a way to restore it for owners; after deletion the conversation still reads, its messages marked as a deleted agent's. The agents, chat and agent budgets guides say so. | 1001 | computerKeep: (agentId) => call("computer_keep", { agent_id: agentId }), |
| 1002 | computerErase: (agentId) => call("computer_erase", { agent_id: agentId }), | |
| Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next. | 1003 | computerCommands: (agentId, sessionId) => call("computer_commands", { agent_id: agentId, session_id: sessionId ?? null }), |
| 1004 | }; | |
| 1005 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.