From dcb127dc8508bc0d3c1930d0faf0aa20faa79058 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 18 Sep 2026 14:53:23 -0700 Subject: [PATCH 1/4] feat(myra): Myra carries the artifact tools through a hub agent token (CL-8526) --- agents/myra/package.json | 1 + agents/myra/src/index.ts | 14 +- agents/myra/src/system-prompt.ts | 5 +- agents/myra/tsconfig.src.json | 3 +- apps/hub/package.json | 3 +- apps/hub/src/migrate.ts | 6 +- apps/hub/src/server.ts | 26 +++ apps/web/src/agent-deploy.ts | 22 +++ apps/web/src/agent-hub-credential.ts | 228 +++++++++++++++++++++++++++ apps/web/src/myra-deploy.ts | 45 ++++++ apps/web/src/workbench-create.ts | 4 +- bun.lock | 14 +- 12 files changed, 356 insertions(+), 15 deletions(-) create mode 100644 apps/web/src/agent-hub-credential.ts diff --git a/agents/myra/package.json b/agents/myra/package.json index 495e54e74..a3bc7634c 100644 --- a/agents/myra/package.json +++ b/agents/myra/package.json @@ -33,6 +33,7 @@ "build": "bun run scripts/build-bundle.ts" }, "dependencies": { + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#b8f1941a3720da8627012a48d7d0b91271b19052", "@intx/tools-mail": "0.3.0", "@intx/tools-posix": "0.3.0" }, diff --git a/agents/myra/src/index.ts b/agents/myra/src/index.ts index c76b7ed56..6f73d76a2 100644 --- a/agents/myra/src/index.ts +++ b/agents/myra/src/index.ts @@ -13,6 +13,7 @@ import { defineWorkflow, step } from "@intx/workflow"; import type { WorkflowDefinition } from "@intx/workflow"; import { mail } from "@intx/tools-mail/sidecar-bundle"; import { posix } from "@intx/tools-posix/sidecar-bundle"; +import { artifacts } from "@corbits/artifacts/sidecar-bundle"; import { ASSISTANT_STEP_ID } from "./workflow-ids"; @@ -25,10 +26,15 @@ export const ASSISTANT_DESCRIPTION = "A general-purpose assistant that answers questions, drafts " + "text, and reasons through problems for the team"; -// Upstream's own tool packages, the only kind of tool Interchange has: -// mail over the agent's transport and posix over its working tree. Neither -// calls a hub API — nothing in the platform gives an agent that reach. -export const MYRA_TOOL_FACTORIES = [mail, posix] as unknown as readonly AnnotatedToolFactory[]; +// Tool packages in the shape Interchange has: mail over the agent's +// transport, posix over its working tree, and artifacts through the hub +// credential the deploy binds — the agent itself holds no client code and +// no secret. +export const MYRA_TOOL_FACTORIES = [ + mail, + posix, + artifacts, +] as unknown as readonly AnnotatedToolFactory[]; /** Everything the definition needs that is per-deployment data. */ export interface MyraWorkflowInput { diff --git a/agents/myra/src/system-prompt.ts b/agents/myra/src/system-prompt.ts index bcb908650..a40e7c6f0 100644 --- a/agents/myra/src/system-prompt.ts +++ b/agents/myra/src/system-prompt.ts @@ -19,8 +19,9 @@ export const ASSISTANT_SYSTEM_PROMPT = "hard-to-undo effect, such as sending mail on someone else's " + "behalf.\n" + "\n" + - "You have mail and a working tree, and nothing else reaches the " + - "workbench itself. To stand up a new agent or workflow, write the " + + "You have mail, a working tree, and artifacts — the workbench's " + + "own shared library, where you save work worth keeping and read " + + "back what is already there. To stand up a new agent or workflow, write the " + "package in your working tree, then reply with its two files as " + "fenced code blocks, each labelled with its filename on the line " + 'above the fence — a "package.json" plus a "definition.json" holding {"name", ' + diff --git a/agents/myra/tsconfig.src.json b/agents/myra/tsconfig.src.json index fc8724305..d8b2c50d1 100644 --- a/agents/myra/tsconfig.src.json +++ b/agents/myra/tsconfig.src.json @@ -7,6 +7,7 @@ "composite": true, "emitDeclarationOnly": true, "outDir": "dist", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", + "customConditions": ["bun"] } } diff --git a/apps/hub/package.json b/apps/hub/package.json index dabd4185f..6adb5a6f2 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -13,8 +13,9 @@ }, "dependencies": { "@chat-adapter/state-memory": "^4.35.0", + "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#a4d45821abe7ac2a0387f6036255ed4d7ec1b17c", "@corbits/approvals": "workspace:*", - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#5309a15d4aa504f132c54b4da08bb672b4903873", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#b8f1941a3720da8627012a48d7d0b91271b19052", "@corbits/codex-provider": "github:corbitsdev/corbits-codex-provider#a1f475f341261c05fa8dade0c82f1246c175b5b3", "@corbits/cron": "github:corbitsdev/corbits-cron#c60e62e0459fbb48c4eccb2e97b3ceb8e7d0d47e", "@corbits/error-sink": "workspace:*", diff --git a/apps/hub/src/migrate.ts b/apps/hub/src/migrate.ts index 197caa160..05348d539 100644 --- a/apps/hub/src/migrate.ts +++ b/apps/hub/src/migrate.ts @@ -7,6 +7,7 @@ // service, no ledger table, just the hub doing the work itself. import { runMigrations } from "@intx/db"; import { sql } from "drizzle-orm"; +import { applyAgentTokenMigrations } from "@corbits/agent-token/migrations"; import { applyCronMigrations } from "@corbits/cron"; import { createMailboxDb, runMailboxMigrations } from "@corbits/mailbox"; import { runArtifactMigrations } from "@corbits/artifacts"; @@ -40,8 +41,8 @@ function databaseUrlFrom(config: HubMigrateConfig): string { /** * Apply the platform schema plus every mounted Corbits package's own - * migration, in the order the hub mounts them: cron, mailbox, artifacts, - * memory. `@corbits/webhooks` owns no table of its own, so it has no + * migration, in the order the hub mounts them: cron, agent tokens, mailbox, + * artifacts, memory. `@corbits/webhooks` owns no table of its own, so it has no * migration step. `db` is the same drizzle handle the rest * of the hub uses, so `runArtifactMigrations` (which takes a drizzle * db, not a URL) shares the one connection pool rather than opening @@ -60,6 +61,7 @@ export async function migrateHub( await runMigrations(config, { schema }); } await applyCronMigrations(databaseUrl, { tenantSchema: schema }); + await applyAgentTokenMigrations(databaseUrl, { tenantSchema: schema }); const mailboxDb = createMailboxDb(databaseUrl); try { diff --git a/apps/hub/src/server.ts b/apps/hub/src/server.ts index c45d0eff1..409afb6d3 100644 --- a/apps/hub/src/server.ts +++ b/apps/hub/src/server.ts @@ -75,6 +75,7 @@ import { exchangeXaiCode, refreshXaiTokens, } from "@corbits/xai-provider"; +import { createAgentTokenVerifier, mountAgentTokens } from "@corbits/agent-token"; import { createCronTicker, createRunTriggerCronDeliver, mountCron } from "@corbits/cron"; import { createHubMailboxAuthorizeSender, @@ -521,13 +522,38 @@ export async function createHubServer({ }); app.route(TENANT_PREFIX, artifactsApi); } + { + const agentTokensApp = new Hono(); + mountAgentTokens(agentTokensApp, { + db, + requireGrant: (ctx, tenantId) => { + const c = ctx as { get(key: "tenant"): { id: string } }; + return c.get("tenant").id === tenantId; + }, + }); + app.route("/", agentTokensApp); + } { const workflowArtifactsApi = new Hono(); const workflowRunAuthenticator = createWorkflowRunAuthenticator({ db }); + const verifyAgentToken = createAgentTokenVerifier({ db }); mountWorkflowArtifacts(workflowArtifactsApi, { db, contentStore: artifactContentStore, resolveRunScope: (token, runAddress) => workflowRunAuthenticator.resolve(token, runAddress), + // A deployed agent has no sidecar token; it presents the bearer this + // hub minted for its definition and is scoped to the run it names. + agentToken: { + verify: (ctx) => verifyAgentToken(ctx), + resolveRun: async (runAddress) => { + if (runAddress === "") return null; + const run = await db.query.workflowRun.findFirst({ + where: eq(workflowRun.address, runAddress), + }); + if (run === undefined || run.principalId === null) return null; + return { tenantId: run.tenantId, principalId: run.principalId, runId: run.id }; + }, + }, }); app.route("/api/workflow-artifacts", workflowArtifactsApi); } diff --git a/apps/web/src/agent-deploy.ts b/apps/web/src/agent-deploy.ts index afdc20ade..ab256df9a 100644 --- a/apps/web/src/agent-deploy.ts +++ b/apps/web/src/agent-deploy.ts @@ -5,6 +5,11 @@ import { type } from "arktype"; import { WorkflowDeploymentResponse } from "@intx/types"; import { reportError } from "@corbits/error-sink"; +import { + artifactToolsCredentialBinding, + ensureAgentHubCredential, + grantArtifactToolsCredentialUse, +} from "./agent-hub-credential"; import { resolveExistingOffering } from "./onboarding/provider-connect-step"; import { isValidSlug, slugify } from "@/lib/slug"; @@ -95,6 +100,8 @@ export function buildAgentDefinitionJson(args: { // `to` only feeds the deploy-time mail.address/mail.send grants; it is // not how mail reaches this agent — that happens at its run address. triggers: [{ type: "mail", to: args.triggerAddress }], + // Resolved at deploy into the `hub` handle the artifact tools use. + credentialBindings: [artifactToolsCredentialBinding(args.slug)], steps: { [stepId]: { kind: "step", @@ -338,6 +345,12 @@ export async function deployAgentSource( } const assetId = await ensureAgentSourceAsset(args.tenantId, assetName, name, fetchImpl); + // Minted before the push: the definition's credential binding resolves + // this credential by name at deploy time, so it must already exist. + const credentialId = await ensureAgentHubCredential( + { tenantId: args.tenantId, definitionId: slug }, + fetchImpl, + ); // Grant configuration only, not a routable address: the hub mints the // agent's real address (its run address) at deploy time. const triggerAddress = `${slug}@${tenant.domain}`; @@ -370,6 +383,15 @@ export async function deployAgentSource( if (parsed instanceof type.errors) { throw new AgentDeployError(`this deployment came back an unexpected shape: ${parsed.summary}`); } + const authorized = await grantArtifactToolsCredentialUse( + { tenantId: args.tenantId, deploymentId: parsed.id, credentialId }, + fetchImpl, + ); + if (authorized === "no-principal-yet") { + reportError(new Error("this agent's hub credential is not authorized yet"), { + operation: "agent_deploy_grant_hub_credential", + }); + } if (args.input.schedule !== undefined) { await scheduleAgentRun( args.tenantId, diff --git a/apps/web/src/agent-hub-credential.ts b/apps/web/src/agent-hub-credential.ts new file mode 100644 index 000000000..23ffcccb4 --- /dev/null +++ b/apps/web/src/agent-hub-credential.ts @@ -0,0 +1,228 @@ +// How a deployed agent gets to call this hub back: the workbench mints an +// agent token for the definition, stores it as a tenant-owned credential +// pinned to the hub's own origin, and the definition names it through a +// credential binding. The agent never holds the secret — the sidecar shapes +// a mediated fetch from it and hands the tools only that. +import { type } from "arktype"; + +/** The tool package the binding authorizes, and the consumer identity the + * credential-use grant is conditioned on. */ +export const ARTIFACT_TOOLS_PACKAGE = "@corbits/artifacts/sidecar-bundle"; + +/** The handle `@corbits/artifacts`' sidecar bundle resolves. */ +export const HUB_CREDENTIAL_HANDLE = "hub"; + +/** One provider row per workbench, standing for the hub itself. Its plugin + * is `@corbits/credential-header`'s raw-`authorization` preset, which sends + * the secret verbatim — so the stored secret is the whole header value. */ +export const HUB_PROVIDER_NAME = "workbench-hub"; +export const HUB_PROVIDER_PLUGIN = "http-raw-authorization"; + +export class AgentHubCredentialError extends Error {} + +const ProviderShape = type({ id: "string", name: "string" }); +const ProvidersPage = type({ data: ProviderShape.array() }); +const CredentialShape = type({ id: "string", name: "string" }); +const CredentialsPage = type({ data: CredentialShape.array() }); +const MintedTokenShape = type({ token: { id: "string", token: "string" } }); +const PrincipalShape = type({ id: "string", refId: "string" }); +const DeploymentShape = type({ + id: "string", + definitionAssetId: "string", + status: "string", +}); +const DeploymentsPage = type({ data: DeploymentShape.array() }); +const PrincipalsPage = type({ data: PrincipalShape.array() }); + +function tenantPath(tenantId: string, suffix: string): string { + return `/api/tenants/${encodeURIComponent(tenantId)}${suffix}`; +} + +async function readJson( + response: Response, + shape: (value: unknown) => T | type.errors, + what: string, +): Promise { + const parsed = shape(await response.json()); + if (parsed instanceof type.errors) { + throw new AgentHubCredentialError(`${what} came back an unexpected shape: ${parsed.summary}`); + } + return parsed; +} + +/** The credential name a given agent's hub token is stored under. It is also + * the binding's `name` tiebreaker, so both derive it here. */ +export function agentHubCredentialName(definitionId: string): string { + return `${definitionId}-hub`; +} + +/** The `credentialBindings` entry a definition carries so the deploy resolves + * this credential into the artifact tools' `hub` handle. */ +export function artifactToolsCredentialBinding(definitionId: string): { + readonly package: string; + readonly handle: string; + readonly provider: string; + readonly name: string; + readonly locator: "tenant"; +} { + return { + package: ARTIFACT_TOOLS_PACKAGE, + handle: HUB_CREDENTIAL_HANDLE, + provider: HUB_PROVIDER_NAME, + name: agentHubCredentialName(definitionId), + locator: "tenant", + }; +} + +/** The stored hub credential for an agent, without rotating it. */ +export async function resolveAgentHubCredentialId( + args: { readonly tenantId: string; readonly definitionId: string }, + fetchImpl: typeof fetch = fetch, +): Promise { + const listed = await fetchImpl(tenantPath(args.tenantId, "/credentials")); + if (!listed.ok) { + throw new AgentHubCredentialError("listing this workbench's credentials failed"); + } + const page = await readJson(listed, CredentialsPage, "this workbench's credentials"); + const name = agentHubCredentialName(args.definitionId); + return page.data.find((row) => row.name === name)?.id ?? null; +} + +async function ensureHubProvider(tenantId: string, fetchImpl: typeof fetch): Promise { + const listed = await fetchImpl(tenantPath(tenantId, "/providers")); + if (!listed.ok) { + throw new AgentHubCredentialError("listing this workbench's providers failed"); + } + const page = await readJson(listed, ProvidersPage, "this workbench's providers"); + const existing = page.data.find((provider) => provider.name === HUB_PROVIDER_NAME); + if (existing !== undefined) return existing.id; + + const created = await fetchImpl(tenantPath(tenantId, "/providers"), { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + name: HUB_PROVIDER_NAME, + plugin: HUB_PROVIDER_PLUGIN, + // A tool credential must resolve to an origin-pinned handle, so the + // provider names the hub the browser is already talking to. + apiBaseUrl: globalThis.location.origin, + }), + }); + if (!created.ok) { + throw new AgentHubCredentialError("preparing this workbench's hub provider failed"); + } + return (await readJson(created, ProviderShape, "this workbench's hub provider")).id; +} + +/** + * Mints a fresh agent token for `definitionId` and stores it as the tenant's + * hub credential for that agent, replacing any prior one so a redeploy + * rotates rather than accumulates. Returns the credential id the use-grant is + * written against. + */ +export async function ensureAgentHubCredential( + args: { readonly tenantId: string; readonly definitionId: string }, + fetchImpl: typeof fetch = fetch, +): Promise { + const { tenantId, definitionId } = args; + const providerId = await ensureHubProvider(tenantId, fetchImpl); + const credentialName = agentHubCredentialName(definitionId); + + const listed = await fetchImpl(tenantPath(tenantId, "/credentials")); + if (!listed.ok) { + throw new AgentHubCredentialError("listing this workbench's credentials failed"); + } + const page = await readJson(listed, CredentialsPage, "this workbench's credentials"); + for (const credential of page.data.filter((row) => row.name === credentialName)) { + await fetchImpl(tenantPath(tenantId, `/credentials/${encodeURIComponent(credential.id)}`), { + method: "DELETE", + }); + } + + const minted = await fetchImpl(tenantPath(tenantId, "/agent-tokens"), { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ definitionId, name: credentialName }), + }); + if (!minted.ok) { + throw new AgentHubCredentialError("minting this agent's hub token failed"); + } + const { token } = await readJson(minted, MintedTokenShape, "this agent's hub token"); + + const stored = await fetchImpl(tenantPath(tenantId, "/credentials"), { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + providerId, + name: credentialName, + type: "api_key", + // The raw-authorization provider sends the secret verbatim, so the + // secret is the finished header value, not the bare token. + secret: `Bearer ${token.token}`, + }), + }); + if (!stored.ok) { + throw new AgentHubCredentialError("storing this agent's hub token failed"); + } + return (await readJson(stored, CredentialShape, "this agent's hub credential")).id; +} + +/** + * Authorizes the deployed run to use its hub credential, scoped to the + * artifact tool package. Stock mints a deployment's principal at its first + * run, so a deploy that has not run yet has nothing to target — the caller + * hears that back rather than a failure, and the deploy still stands. + */ +export async function grantArtifactToolsCredentialUse( + args: { + readonly tenantId: string; + readonly deploymentId: string; + readonly credentialId: string; + }, + fetchImpl: typeof fetch = fetch, +): Promise<"granted" | "no-principal-yet"> { + const listed = await fetchImpl(tenantPath(args.tenantId, "/principals?kind=workflow")); + if (!listed.ok) { + throw new AgentHubCredentialError("listing this workbench's agents failed"); + } + const page = await readJson(listed, PrincipalsPage, "this workbench's agents"); + const principal = page.data.find((row) => row.refId === args.deploymentId); + if (principal === undefined) return "no-principal-yet"; + const created = await fetchImpl(tenantPath(args.tenantId, "/grants"), { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + principalId: principal.id, + resource: `credential:${args.credentialId}`, + action: "use", + effect: "allow", + origin: "creator", + conditions: { tool: `tool:${ARTIFACT_TOOLS_PACKAGE}` }, + }), + }); + if (!created.ok) { + throw new AgentHubCredentialError("authorizing this agent's hub credential failed"); + } + return "granted"; +} + +const TERMINAL_DEPLOYMENT_STATUSES = new Set(["failed", "released", "destroy_failed"]); + +/** The live deployment anchored to a workflow asset, for a caller that + * deployed through a pipeline which does not hand back the deployment. */ +export async function resolveLiveDeploymentId( + args: { readonly tenantId: string; readonly assetId: string }, + fetchImpl: typeof fetch = fetch, +): Promise { + const listed = await fetchImpl(tenantPath(args.tenantId, "/workflows/deployments")); + if (!listed.ok) { + throw new AgentHubCredentialError("listing this workbench's deployments failed"); + } + const page = await readJson(listed, DeploymentsPage, "this workbench's deployments"); + const live = page.data.find( + (deployment) => + deployment.definitionAssetId === args.assetId && + !TERMINAL_DEPLOYMENT_STATUSES.has(deployment.status), + ); + return live?.id ?? null; +} diff --git a/apps/web/src/myra-deploy.ts b/apps/web/src/myra-deploy.ts index 7719c7e82..050b4085b 100644 --- a/apps/web/src/myra-deploy.ts +++ b/apps/web/src/myra-deploy.ts @@ -5,6 +5,14 @@ import { ASSISTANT_STEP_ID, ASSISTANT_WORKFLOW_ID } from "@corbits/myra/workflow import { renderBundledWorkflowSourceTree } from "@corbits/workflows/client"; import { type } from "arktype"; +import { + artifactToolsCredentialBinding, + ensureAgentHubCredential, + grantArtifactToolsCredentialUse, + resolveAgentHubCredentialId, + resolveLiveDeploymentId, +} from "./agent-hub-credential"; + import { MYRA_SOURCE_CONFIG } from "./myra-source"; import type { WorkflowDeployInput } from "./needs-list"; import type { DeclaredSource } from "./onboarding/provider-connect-step"; @@ -82,6 +90,8 @@ export function buildMyraDefinitionJson( ): unknown { return { id: ASSISTANT_WORKFLOW_ID, + // Resolved at deploy into the `hub` handle the artifact tools use. + credentialBindings: [artifactToolsCredentialBinding(ASSISTANT_WORKFLOW_ID)], // `to` only feeds the deploy-time mail.address/mail.send grants; Myra is // actually reached at her run address, minted at deploy time. triggers: [{ type: "mail", to: triggerAddress }], @@ -231,6 +241,12 @@ export async function deployMyraSource( fetchImpl: typeof fetch = fetch, ): Promise { const assetId = await ensureMyraSourceAsset(args.tenantId, fetchImpl); + // Minted before the push: the definition's credential binding resolves + // this credential by name at deploy time, so it must already exist. + await ensureAgentHubCredential( + { tenantId: args.tenantId, definitionId: ASSISTANT_WORKFLOW_ID }, + fetchImpl, + ); const commitSha = await pushMyraSource( args.tenantId, assetId, @@ -245,3 +261,32 @@ export async function deployMyraSource( defaultSourceOfferingId: args.defaultSourceOfferingId, }); } + +/** + * Authorizes Myra's deployed run to use her hub credential. Separate from + * `deployMyraSource` because the deployment does not exist until the caller + * has sent the deploy input; it is safe to call again, and answers + * `"no-principal-yet"` while stock has not minted the run's principal. + */ +export async function authorizeMyraHubCredential( + args: { readonly tenantId: string }, + fetchImpl: typeof fetch = fetch, +): Promise<"granted" | "no-principal-yet" | "no-deployment"> { + const assetId = await ensureMyraSourceAsset(args.tenantId, fetchImpl); + const deploymentId = await resolveLiveDeploymentId( + { tenantId: args.tenantId, assetId }, + fetchImpl, + ); + if (deploymentId === null) return "no-deployment"; + // Never re-mints: rotating here would invalidate the token the deploy + // already delivered to the running agent. + const credentialId = await resolveAgentHubCredentialId( + { tenantId: args.tenantId, definitionId: ASSISTANT_WORKFLOW_ID }, + fetchImpl, + ); + if (credentialId === null) return "no-deployment"; + return await grantArtifactToolsCredentialUse( + { tenantId: args.tenantId, deploymentId, credentialId }, + fetchImpl, + ); +} diff --git a/apps/web/src/workbench-create.ts b/apps/web/src/workbench-create.ts index e5ef35c0f..96bc919d4 100644 --- a/apps/web/src/workbench-create.ts +++ b/apps/web/src/workbench-create.ts @@ -4,7 +4,7 @@ import { isMyraAgent, listWorkbenchParticipants, sendToWorkbench } from "@/chat/threads-api"; import { agentSlugFromSourceAssetName, deployAgentSource } from "./agent-deploy"; import { readAgentSource } from "./agent-source-read"; -import { deployMyraSource } from "./myra-deploy"; +import { authorizeMyraHubCredential, deployMyraSource } from "./myra-deploy"; import { createFetchStockHub } from "./needs-converge"; import { resolveExistingOffering } from "./onboarding/provider-connect-step"; @@ -88,6 +88,7 @@ export async function createWorkbench(input: CreateWorkbenchInput): Promise=0.3.0", "@intx/types": ">=0.3.0" } }, "corbitsdev-corbits-codex-provider-a1f475f", "sha512-D6d0lblWx/4KudfLwQoQGrLB8GKRLP/HVp1cx08L2/hOGZAgH6txLPpeEtBqjl+R520qXSSPfM0kDC+3XNaAfw=="], From 7be5b9377b3c3f0364bd2869ac944a3166a0c4d7 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 18 Sep 2026 14:59:23 -0700 Subject: [PATCH 2/4] chore(deps): pin @corbits/artifacts to its branch head --- agents/myra/package.json | 2 +- apps/hub/package.json | 2 +- bun.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/agents/myra/package.json b/agents/myra/package.json index a3bc7634c..e84deed0c 100644 --- a/agents/myra/package.json +++ b/agents/myra/package.json @@ -33,7 +33,7 @@ "build": "bun run scripts/build-bundle.ts" }, "dependencies": { - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#b8f1941a3720da8627012a48d7d0b91271b19052", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", "@intx/tools-mail": "0.3.0", "@intx/tools-posix": "0.3.0" }, diff --git a/apps/hub/package.json b/apps/hub/package.json index 6adb5a6f2..13e852dd0 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -15,7 +15,7 @@ "@chat-adapter/state-memory": "^4.35.0", "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#a4d45821abe7ac2a0387f6036255ed4d7ec1b17c", "@corbits/approvals": "workspace:*", - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#b8f1941a3720da8627012a48d7d0b91271b19052", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", "@corbits/codex-provider": "github:corbitsdev/corbits-codex-provider#a1f475f341261c05fa8dade0c82f1246c175b5b3", "@corbits/cron": "github:corbitsdev/corbits-cron#c60e62e0459fbb48c4eccb2e97b3ceb8e7d0d47e", "@corbits/error-sink": "workspace:*", diff --git a/bun.lock b/bun.lock index 646c1f0dd..57747f900 100644 --- a/bun.lock +++ b/bun.lock @@ -36,7 +36,7 @@ "name": "@corbits/myra", "version": "0.0.3", "dependencies": { - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#b8f1941a3720da8627012a48d7d0b91271b19052", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", "@intx/tools-mail": "0.3.0", "@intx/tools-posix": "0.3.0", }, @@ -57,7 +57,7 @@ "@chat-adapter/state-memory": "^4.35.0", "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#a4d45821abe7ac2a0387f6036255ed4d7ec1b17c", "@corbits/approvals": "workspace:*", - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#b8f1941a3720da8627012a48d7d0b91271b19052", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", "@corbits/codex-provider": "github:corbitsdev/corbits-codex-provider#a1f475f341261c05fa8dade0c82f1246c175b5b3", "@corbits/cron": "github:corbitsdev/corbits-cron#c60e62e0459fbb48c4eccb2e97b3ceb8e7d0d47e", "@corbits/error-sink": "workspace:*", @@ -1831,7 +1831,7 @@ "@corbits/mailbox/hono-openapi": ["hono-openapi@1.3.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.11.2", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-NLVeVkhKZ3drmQNEIPac8HX8Y54uf1hJAgIM/7MfDsaeVVmB+QILWQxx5x3R3NvRHgedcbEbOCGY2uR7WQYyMw=="], - "@corbits/myra/@corbits/artifacts": ["@corbits/artifacts@github:corbitsdev/corbits-artifacts#b8f1941", { "dependencies": { "@hono/standard-validator": "^0.2.3" }, "peerDependencies": { "@intx/agent": "^0.3.0", "@intx/hub-api": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.2", "hono": "^4.12.32", "hono-openapi": "^1.2.0", "postgres": "^3.4.9" } }, "corbitsdev-corbits-artifacts-b8f1941", "sha512-E0C+sT66mWfi7Tj24pIpz9fSaIwxmyPz+wOXGAptIdsVSM3dC/q5AwYDUO+u1hZRKmbQD18Xh+LxNQzYo4c8JA=="], + "@corbits/myra/@corbits/artifacts": ["@corbits/artifacts@github:corbitsdev/corbits-artifacts#9152ba5", { "dependencies": { "@hono/standard-validator": "^0.2.3" }, "peerDependencies": { "@intx/agent": "^0.3.0", "@intx/hub-api": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.2", "hono": "^4.12.32", "hono-openapi": "^1.2.0", "postgres": "^3.4.9" } }, "corbitsdev-corbits-artifacts-9152ba5", "sha512-ZE2BLMND2elhGfvXBgkJlSD7x/WO6uTfloLajtXvneyuRKzaeXHXfqIKSVxC6NY9Qetyu4dOqZUPoeJBZopKKQ=="], "@corbits/xai-provider/@corbits/oauth-core": ["@corbits/oauth-core@github:corbitsdev/corbits-oauth-core#f186ed0", { "dependencies": { "@intx/db": "^0.3.0", "@intx/hub-api": "^0.3.0", "@intx/hub-common": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "2.2.3", "drizzle-orm": "^0.45.2", "hono": "^4.11.9" } }, "corbitsdev-corbits-oauth-core-f186ed0", "sha512-wjKVTa26EoUqg8biyC4CumDi6y1hBck6xJTB/oEaTkc1lQ3Foum89KoXGHSvact+N1j9uwLGFEnOfgYp2FFi5Q=="], @@ -1851,7 +1851,7 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@workbench/hub/@corbits/artifacts": ["@corbits/artifacts@github:corbitsdev/corbits-artifacts#b8f1941", { "dependencies": { "@hono/standard-validator": "^0.2.3" }, "peerDependencies": { "@intx/agent": "^0.3.0", "@intx/hub-api": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.2", "hono": "^4.12.32", "hono-openapi": "^1.2.0", "postgres": "^3.4.9" } }, "corbitsdev-corbits-artifacts-b8f1941", "sha512-E0C+sT66mWfi7Tj24pIpz9fSaIwxmyPz+wOXGAptIdsVSM3dC/q5AwYDUO+u1hZRKmbQD18Xh+LxNQzYo4c8JA=="], + "@workbench/hub/@corbits/artifacts": ["@corbits/artifacts@github:corbitsdev/corbits-artifacts#9152ba5", { "dependencies": { "@hono/standard-validator": "^0.2.3" }, "peerDependencies": { "@intx/agent": "^0.3.0", "@intx/hub-api": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.2", "hono": "^4.12.32", "hono-openapi": "^1.2.0", "postgres": "^3.4.9" } }, "corbitsdev-corbits-artifacts-9152ba5", "sha512-ZE2BLMND2elhGfvXBgkJlSD7x/WO6uTfloLajtXvneyuRKzaeXHXfqIKSVxC6NY9Qetyu4dOqZUPoeJBZopKKQ=="], "@workbench/hub/@corbits/codex-provider": ["@corbits/codex-provider@github:corbitsdev/corbits-codex-provider#a1f475f", { "dependencies": { "@corbits/oauth-core": "github:corbitsdev/corbits-oauth-core", "@corbits/openai-responses": "github:corbitsdev/corbits-openai-responses", "arktype": "2.2.3" }, "peerDependencies": { "@intx/inference": ">=0.3.0", "@intx/types": ">=0.3.0" } }, "corbitsdev-corbits-codex-provider-a1f475f", "sha512-D6d0lblWx/4KudfLwQoQGrLB8GKRLP/HVp1cx08L2/hOGZAgH6txLPpeEtBqjl+R520qXSSPfM0kDC+3XNaAfw=="], From f02835222fffdf104caf41d82184039c4ab3aeed Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 18 Sep 2026 15:09:13 -0700 Subject: [PATCH 3/4] fix(hub): gate agent-token minting on the stock credential grant and make the use-grant mandatory (CL-8526) --- apps/hub/package.json | 2 +- apps/hub/src/server.ts | 33 +++++++-- apps/web/src/agent-deploy.ts | 9 +-- apps/web/src/agent-hub-credential.ts | 100 +++++++++++++++++++++------ apps/web/src/myra-deploy.ts | 18 +++-- bun.lock | 4 +- 6 files changed, 123 insertions(+), 43 deletions(-) diff --git a/apps/hub/package.json b/apps/hub/package.json index 13e852dd0..cacdc1b94 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@chat-adapter/state-memory": "^4.35.0", - "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#a4d45821abe7ac2a0387f6036255ed4d7ec1b17c", + "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#0a3bd10d496927a2b1d3f854420b2d42de040254", "@corbits/approvals": "workspace:*", "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", "@corbits/codex-provider": "github:corbitsdev/corbits-codex-provider#a1f475f341261c05fa8dade0c82f1246c175b5b3", diff --git a/apps/hub/src/server.ts b/apps/hub/src/server.ts index 409afb6d3..483d96076 100644 --- a/apps/hub/src/server.ts +++ b/apps/hub/src/server.ts @@ -7,8 +7,12 @@ import { resolveFrameSenderKey, resolveSenderKey, } from "@intx/db"; -import { principal as principalTable, tenant as tenantTable } from "@intx/db/schema"; -import { eq } from "drizzle-orm"; +import { + asset as assetTable, + principal as principalTable, + tenant as tenantTable, +} from "@intx/db/schema"; +import { and, eq } from "drizzle-orm"; import { createEnvKeyCredentialCipher, sha256 } from "@intx/crypto"; import { hexDecode, hexEncode, type SidecarCapabilityRule } from "@intx/types"; import { @@ -523,15 +527,32 @@ export async function createHubServer({ app.route(TENANT_PREFIX, artifactsApi); } { + // Minting an agent token is minting a credential, so it is gated by the + // same stock grant the credential routes are, not by tenant membership. const agentTokensApp = new Hono(); + const requireAgentTokenGrant = createRequireGrant({ + grantStore, + conditionRegistry: grantConditionRegistry, + }); mountAgentTokens(agentTokensApp, { db, - requireGrant: (ctx, tenantId) => { - const c = ctx as { get(key: "tenant"): { id: string } }; - return c.get("tenant").id === tenantId; + requireGrant: requireAgentTokenGrant("credential:*", "create"), + resolveTenantId: (ctx) => (ctx as { get(key: "tenant"): { id: string } }).get("tenant").id, + // A token is scoped to the agent's `workflow` source asset: the + // tenant-owned thing that already exists when the workbench mints the + // token, before the deploy that would create a run. + resolveDefinition: async (tenantId, definitionId) => { + const row = await db.query.asset.findFirst({ + where: and( + eq(assetTable.id, definitionId), + eq(assetTable.tenantId, tenantId), + eq(assetTable.kind, "workflow"), + ), + }); + return row !== undefined; }, }); - app.route("/", agentTokensApp); + app.route(TENANT_PREFIX, agentTokensApp); } { const workflowArtifactsApi = new Hono(); diff --git a/apps/web/src/agent-deploy.ts b/apps/web/src/agent-deploy.ts index ab256df9a..ae0454215 100644 --- a/apps/web/src/agent-deploy.ts +++ b/apps/web/src/agent-deploy.ts @@ -348,7 +348,7 @@ export async function deployAgentSource( // Minted before the push: the definition's credential binding resolves // this credential by name at deploy time, so it must already exist. const credentialId = await ensureAgentHubCredential( - { tenantId: args.tenantId, definitionId: slug }, + { tenantId: args.tenantId, definitionId: slug, assetId }, fetchImpl, ); // Grant configuration only, not a routable address: the hub mints the @@ -383,15 +383,10 @@ export async function deployAgentSource( if (parsed instanceof type.errors) { throw new AgentDeployError(`this deployment came back an unexpected shape: ${parsed.summary}`); } - const authorized = await grantArtifactToolsCredentialUse( + await grantArtifactToolsCredentialUse( { tenantId: args.tenantId, deploymentId: parsed.id, credentialId }, fetchImpl, ); - if (authorized === "no-principal-yet") { - reportError(new Error("this agent's hub credential is not authorized yet"), { - operation: "agent_deploy_grant_hub_credential", - }); - } if (args.input.schedule !== undefined) { await scheduleAgentRun( args.tenantId, diff --git a/apps/web/src/agent-hub-credential.ts b/apps/web/src/agent-hub-credential.ts index 23ffcccb4..485b67d0c 100644 --- a/apps/web/src/agent-hub-credential.ts +++ b/apps/web/src/agent-hub-credential.ts @@ -22,7 +22,11 @@ export class AgentHubCredentialError extends Error {} const ProviderShape = type({ id: "string", name: "string" }); const ProvidersPage = type({ data: ProviderShape.array() }); -const CredentialShape = type({ id: "string", name: "string" }); +const CredentialShape = type({ + id: "string", + name: "string", + "metadata?": "Record | null", +}); const CredentialsPage = type({ data: CredentialShape.array() }); const MintedTokenShape = type({ token: { id: "string", token: "string" } }); const PrincipalShape = type({ id: "string", refId: "string" }); @@ -32,7 +36,10 @@ const DeploymentShape = type({ status: "string", }); const DeploymentsPage = type({ data: DeploymentShape.array() }); -const PrincipalsPage = type({ data: PrincipalShape.array() }); +const PrincipalsPage = type({ + data: PrincipalShape.array(), + nextCursor: "string | null", +}); function tenantPath(tenantId: string, suffix: string): string { return `/api/tenants/${encodeURIComponent(tenantId)}${suffix}`; @@ -121,10 +128,17 @@ async function ensureHubProvider(tenantId: string, fetchImpl: typeof fetch): Pro * written against. */ export async function ensureAgentHubCredential( - args: { readonly tenantId: string; readonly definitionId: string }, + args: { + readonly tenantId: string; + readonly definitionId: string; + /** The agent's `workflow` source asset. It is what the token is scoped + * to, because it is the tenant-owned thing that already exists when the + * token is minted — the deploy has not run yet. */ + readonly assetId: string; + }, fetchImpl: typeof fetch = fetch, ): Promise { - const { tenantId, definitionId } = args; + const { tenantId, definitionId, assetId } = args; const providerId = await ensureHubProvider(tenantId, fetchImpl); const credentialName = agentHubCredentialName(definitionId); @@ -134,15 +148,32 @@ export async function ensureAgentHubCredential( } const page = await readJson(listed, CredentialsPage, "this workbench's credentials"); for (const credential of page.data.filter((row) => row.name === credentialName)) { - await fetchImpl(tenantPath(tenantId, `/credentials/${encodeURIComponent(credential.id)}`), { - method: "DELETE", - }); + // Revoke the bearer before dropping the row that recorded it: deleting + // only the credential would leave the old token valid forever. + const priorTokenId = credential.metadata?.["agentTokenId"]; + if (typeof priorTokenId === "string" && priorTokenId !== "") { + const revoked = await fetchImpl( + tenantPath(tenantId, `/agent-tokens/${encodeURIComponent(priorTokenId)}`), + { method: "DELETE" }, + ); + // 404 means it was revoked already; anything else left a live bearer. + if (!revoked.ok && revoked.status !== 404) { + throw new AgentHubCredentialError("revoking this agent's previous hub token failed"); + } + } + const dropped = await fetchImpl( + tenantPath(tenantId, `/credentials/${encodeURIComponent(credential.id)}`), + { method: "DELETE" }, + ); + if (!dropped.ok) { + throw new AgentHubCredentialError("replacing this agent's hub credential failed"); + } } const minted = await fetchImpl(tenantPath(tenantId, "/agent-tokens"), { method: "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify({ definitionId, name: credentialName }), + body: JSON.stringify({ definitionId: assetId, name: credentialName }), }); if (!minted.ok) { throw new AgentHubCredentialError("minting this agent's hub token failed"); @@ -159,6 +190,9 @@ export async function ensureAgentHubCredential( // The raw-authorization provider sends the secret verbatim, so the // secret is the finished header value, not the bare token. secret: `Bearer ${token.token}`, + // The token id, so the next deploy revokes this bearer instead of + // orphaning it. + metadata: { agentTokenId: token.id }, }), }); if (!stored.ok) { @@ -167,11 +201,39 @@ export async function ensureAgentHubCredential( return (await readJson(stored, CredentialShape, "this agent's hub credential")).id; } +/** The stock principals route filters by kind and status only, so the run's + * principal is found by walking the pages rather than by a server-side + * refId filter. */ +async function findWorkflowPrincipalId( + tenantId: string, + refId: string, + fetchImpl: typeof fetch, +): Promise { + let cursor: string | null = null; + do { + const query = new URLSearchParams({ kind: "workflow", limit: "100" }); + if (cursor !== null) query.set("cursor", cursor); + const listed = await fetchImpl(tenantPath(tenantId, `/principals?${query.toString()}`)); + if (!listed.ok) { + throw new AgentHubCredentialError("listing this workbench's agents failed"); + } + const page: typeof PrincipalsPage.infer = await readJson( + listed, + PrincipalsPage, + "this workbench's agents", + ); + const match = page.data.find((row) => row.refId === refId); + if (match !== undefined) return match.id; + cursor = page.nextCursor; + } while (cursor !== null); + return null; +} + /** * Authorizes the deployed run to use its hub credential, scoped to the - * artifact tool package. Stock mints a deployment's principal at its first - * run, so a deploy that has not run yet has nothing to target — the caller - * hears that back rather than a failure, and the deploy still stands. + * artifact tool package. The deploy request creates the run's principal, so + * by the time its 201 is in hand the principal exists; an absent one means + * the deploy did not land what it claimed and the caller hears about it. */ export async function grantArtifactToolsCredentialUse( args: { @@ -180,19 +242,18 @@ export async function grantArtifactToolsCredentialUse( readonly credentialId: string; }, fetchImpl: typeof fetch = fetch, -): Promise<"granted" | "no-principal-yet"> { - const listed = await fetchImpl(tenantPath(args.tenantId, "/principals?kind=workflow")); - if (!listed.ok) { - throw new AgentHubCredentialError("listing this workbench's agents failed"); +): Promise { + const principalId = await findWorkflowPrincipalId(args.tenantId, args.deploymentId, fetchImpl); + if (principalId === null) { + throw new AgentHubCredentialError( + "this agent has no principal to authorize, so its artifact tools would not work", + ); } - const page = await readJson(listed, PrincipalsPage, "this workbench's agents"); - const principal = page.data.find((row) => row.refId === args.deploymentId); - if (principal === undefined) return "no-principal-yet"; const created = await fetchImpl(tenantPath(args.tenantId, "/grants"), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ - principalId: principal.id, + principalId, resource: `credential:${args.credentialId}`, action: "use", effect: "allow", @@ -203,7 +264,6 @@ export async function grantArtifactToolsCredentialUse( if (!created.ok) { throw new AgentHubCredentialError("authorizing this agent's hub credential failed"); } - return "granted"; } const TERMINAL_DEPLOYMENT_STATUSES = new Set(["failed", "released", "destroy_failed"]); diff --git a/apps/web/src/myra-deploy.ts b/apps/web/src/myra-deploy.ts index 050b4085b..564819787 100644 --- a/apps/web/src/myra-deploy.ts +++ b/apps/web/src/myra-deploy.ts @@ -244,7 +244,7 @@ export async function deployMyraSource( // Minted before the push: the definition's credential binding resolves // this credential by name at deploy time, so it must already exist. await ensureAgentHubCredential( - { tenantId: args.tenantId, definitionId: ASSISTANT_WORKFLOW_ID }, + { tenantId: args.tenantId, definitionId: ASSISTANT_WORKFLOW_ID, assetId }, fetchImpl, ); const commitSha = await pushMyraSource( @@ -265,27 +265,31 @@ export async function deployMyraSource( /** * Authorizes Myra's deployed run to use her hub credential. Separate from * `deployMyraSource` because the deployment does not exist until the caller - * has sent the deploy input; it is safe to call again, and answers - * `"no-principal-yet"` while stock has not minted the run's principal. + * has sent the deploy input. It is safe to call again, and never re-mints: + * rotating here would invalidate the token the deploy already delivered. */ export async function authorizeMyraHubCredential( args: { readonly tenantId: string }, fetchImpl: typeof fetch = fetch, -): Promise<"granted" | "no-principal-yet" | "no-deployment"> { +): Promise { const assetId = await ensureMyraSourceAsset(args.tenantId, fetchImpl); const deploymentId = await resolveLiveDeploymentId( { tenantId: args.tenantId, assetId }, fetchImpl, ); - if (deploymentId === null) return "no-deployment"; + if (deploymentId === null) { + throw new MyraDeployError("Myra has no live deployment to authorize"); + } // Never re-mints: rotating here would invalidate the token the deploy // already delivered to the running agent. const credentialId = await resolveAgentHubCredentialId( { tenantId: args.tenantId, definitionId: ASSISTANT_WORKFLOW_ID }, fetchImpl, ); - if (credentialId === null) return "no-deployment"; - return await grantArtifactToolsCredentialUse( + if (credentialId === null) { + throw new MyraDeployError("Myra has no hub credential to authorize"); + } + await grantArtifactToolsCredentialUse( { tenantId: args.tenantId, deploymentId, credentialId }, fetchImpl, ); diff --git a/bun.lock b/bun.lock index 57747f900..73a3d7180 100644 --- a/bun.lock +++ b/bun.lock @@ -55,7 +55,7 @@ "version": "0.0.1", "dependencies": { "@chat-adapter/state-memory": "^4.35.0", - "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#a4d45821abe7ac2a0387f6036255ed4d7ec1b17c", + "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#0a3bd10d496927a2b1d3f854420b2d42de040254", "@corbits/approvals": "workspace:*", "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", "@corbits/codex-provider": "github:corbitsdev/corbits-codex-provider#a1f475f341261c05fa8dade0c82f1246c175b5b3", @@ -603,7 +603,7 @@ "@chat-adapter/state-memory": ["@chat-adapter/state-memory@4.40.0", "", { "dependencies": { "chat": "4.40.0" } }, "sha512-NmFFkvUfnDBDS/mkzrtKHdW757EGt0eX3OQH5IPxrKqhjSsNzpqUdkJ/uDB4BP0Rbex4oW/HgwhZ+JGONVbMBQ=="], - "@corbits/agent-token": ["@corbits/agent-token@github:corbitsdev/corbits-agent-token#a4d4582", { "dependencies": { "@intx/db": "^0.3.0", "arktype": "^2.2.0", "drizzle-orm": "^0.45.2", "hono": "^4.11.9", "postgres": "^3.4.8" } }, "corbitsdev-corbits-agent-token-a4d4582", "sha512-5iMQc+uNFpOWrsQ2rPww3fdBBEPGHC7L5tArhd/kNFEozrtXSHtFzeaVutsqF7AlaV7DPo3Pubc9PxZ9AcNCiw=="], + "@corbits/agent-token": ["@corbits/agent-token@github:corbitsdev/corbits-agent-token#0a3bd10", { "dependencies": { "@intx/db": "^0.3.0", "arktype": "^2.2.0", "drizzle-orm": "^0.45.2", "hono": "^4.11.9", "postgres": "^3.4.8" } }, "corbitsdev-corbits-agent-token-0a3bd10", "sha512-8FX7qUOFQYK/SnmjtRrcyszinNV26UuWpEYReJiJSr9Xm8rRiNgzf9CNBYu9GDXYqgwf6LRCQ30y/RCCrmtRsQ=="], "@corbits/approvals": ["@corbits/approvals@workspace:packages/approvals"], From f6d39eca6518e51a666a5cbd0a179c71af7ba50f Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 18 Sep 2026 15:12:23 -0700 Subject: [PATCH 4/4] chore(hub,myra): pin agent-token and artifacts to their merged main shas (CL-8526) --- agents/myra/package.json | 2 +- apps/hub/package.json | 4 ++-- bun.lock | 16 ++++++---------- package.json | 2 +- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/agents/myra/package.json b/agents/myra/package.json index e84deed0c..4bbeeb715 100644 --- a/agents/myra/package.json +++ b/agents/myra/package.json @@ -33,7 +33,7 @@ "build": "bun run scripts/build-bundle.ts" }, "dependencies": { - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#4a2a3c22187991a6ce598faf794908fedd252e98", "@intx/tools-mail": "0.3.0", "@intx/tools-posix": "0.3.0" }, diff --git a/apps/hub/package.json b/apps/hub/package.json index cacdc1b94..5345b7392 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -13,9 +13,9 @@ }, "dependencies": { "@chat-adapter/state-memory": "^4.35.0", - "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#0a3bd10d496927a2b1d3f854420b2d42de040254", + "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#ed286f4592190f911de480ef5330ce03d8605f00", "@corbits/approvals": "workspace:*", - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#4a2a3c22187991a6ce598faf794908fedd252e98", "@corbits/codex-provider": "github:corbitsdev/corbits-codex-provider#a1f475f341261c05fa8dade0c82f1246c175b5b3", "@corbits/cron": "github:corbitsdev/corbits-cron#c60e62e0459fbb48c4eccb2e97b3ceb8e7d0d47e", "@corbits/error-sink": "workspace:*", diff --git a/bun.lock b/bun.lock index 73a3d7180..b6370c550 100644 --- a/bun.lock +++ b/bun.lock @@ -8,7 +8,7 @@ "arktype": "catalog:", }, "devDependencies": { - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#5309a15d4aa504f132c54b4da08bb672b4903873", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#4a2a3c22187991a6ce598faf794908fedd252e98", "@corbits/codex-provider": "github:corbitsdev/corbits-codex-provider#a1f475f341261c05fa8dade0c82f1246c175b5b3", "@corbits/mailbox": "github:corbitsdev/corbits-mailbox#65590a85fa143251b3ac20ba2eca92fc35e70e51", "@corbits/ollama-adapter": "github:corbitsdev/corbits-ollama-adapter#0387eb17bee5ac87530e9c48bb3bffd9a6998ded", @@ -36,7 +36,7 @@ "name": "@corbits/myra", "version": "0.0.3", "dependencies": { - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#4a2a3c22187991a6ce598faf794908fedd252e98", "@intx/tools-mail": "0.3.0", "@intx/tools-posix": "0.3.0", }, @@ -55,9 +55,9 @@ "version": "0.0.1", "dependencies": { "@chat-adapter/state-memory": "^4.35.0", - "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#0a3bd10d496927a2b1d3f854420b2d42de040254", + "@corbits/agent-token": "github:corbitsdev/corbits-agent-token#ed286f4592190f911de480ef5330ce03d8605f00", "@corbits/approvals": "workspace:*", - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#9152ba5d55f1bdc88f992c5de54f01760992541b", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#4a2a3c22187991a6ce598faf794908fedd252e98", "@corbits/codex-provider": "github:corbitsdev/corbits-codex-provider#a1f475f341261c05fa8dade0c82f1246c175b5b3", "@corbits/cron": "github:corbitsdev/corbits-cron#c60e62e0459fbb48c4eccb2e97b3ceb8e7d0d47e", "@corbits/error-sink": "workspace:*", @@ -603,11 +603,11 @@ "@chat-adapter/state-memory": ["@chat-adapter/state-memory@4.40.0", "", { "dependencies": { "chat": "4.40.0" } }, "sha512-NmFFkvUfnDBDS/mkzrtKHdW757EGt0eX3OQH5IPxrKqhjSsNzpqUdkJ/uDB4BP0Rbex4oW/HgwhZ+JGONVbMBQ=="], - "@corbits/agent-token": ["@corbits/agent-token@github:corbitsdev/corbits-agent-token#0a3bd10", { "dependencies": { "@intx/db": "^0.3.0", "arktype": "^2.2.0", "drizzle-orm": "^0.45.2", "hono": "^4.11.9", "postgres": "^3.4.8" } }, "corbitsdev-corbits-agent-token-0a3bd10", "sha512-8FX7qUOFQYK/SnmjtRrcyszinNV26UuWpEYReJiJSr9Xm8rRiNgzf9CNBYu9GDXYqgwf6LRCQ30y/RCCrmtRsQ=="], + "@corbits/agent-token": ["@corbits/agent-token@github:corbitsdev/corbits-agent-token#ed286f4", { "dependencies": { "@intx/db": "^0.3.0", "arktype": "^2.2.0", "drizzle-orm": "^0.45.2", "hono": "^4.11.9", "postgres": "^3.4.8" } }, "corbitsdev-corbits-agent-token-ed286f4", "sha512-k7puSwtCC2ZF7W0lzswtBoKxu4n+VjWwtzf8oa1OWe72FEI4TDdQ5gybqq/oW9XJAUHeCkjPL9bdwxG6C2TfpQ=="], "@corbits/approvals": ["@corbits/approvals@workspace:packages/approvals"], - "@corbits/artifacts": ["@corbits/artifacts@github:corbitsdev/corbits-artifacts#5309a15", { "dependencies": { "@hono/standard-validator": "^0.2.3" }, "peerDependencies": { "@intx/hub-api": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.2", "hono": "^4.12.32", "hono-openapi": "^1.2.0", "postgres": "^3.4.9" } }, "corbitsdev-corbits-artifacts-5309a15", "sha512-zGHkq++juclUrOd0O8/Z5ziTldmJZ6gqyH/c9cs5S10UPU4gqn8CHwq174SiJXY0ujJ3cNpJXnrr+0fcqlfQ1w=="], + "@corbits/artifacts": ["@corbits/artifacts@github:corbitsdev/corbits-artifacts#4a2a3c2", { "dependencies": { "@hono/standard-validator": "^0.2.3" }, "peerDependencies": { "@intx/agent": "^0.3.0", "@intx/hub-api": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.2", "hono": "^4.12.32", "hono-openapi": "^1.2.0", "postgres": "^3.4.9" } }, "corbitsdev-corbits-artifacts-4a2a3c2", "sha512-UY5nkQ31kGmYgqgIOSlbigwvm7c/s2kpqEbF6Oqo0rzoS1RejpPp+w/Yim6h2MRpYCl5CXgAET1MhkDboHuWBA=="], "@corbits/codex-provider": ["@corbits/codex-provider@github:corbitsdev/corbits-codex-provider#a1f475f", { "dependencies": { "@corbits/oauth-core": "github:corbitsdev/corbits-oauth-core", "@corbits/openai-responses": "github:corbitsdev/corbits-openai-responses", "arktype": "2.2.3" }, "peerDependencies": { "@intx/inference": ">=0.3.0", "@intx/types": ">=0.3.0" } }, "corbitsdev-corbits-codex-provider-a1f475f", "sha512-D6d0lblWx/4KudfLwQoQGrLB8GKRLP/HVp1cx08L2/hOGZAgH6txLPpeEtBqjl+R520qXSSPfM0kDC+3XNaAfw=="], @@ -1831,8 +1831,6 @@ "@corbits/mailbox/hono-openapi": ["hono-openapi@1.3.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.11.2", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-NLVeVkhKZ3drmQNEIPac8HX8Y54uf1hJAgIM/7MfDsaeVVmB+QILWQxx5x3R3NvRHgedcbEbOCGY2uR7WQYyMw=="], - "@corbits/myra/@corbits/artifacts": ["@corbits/artifacts@github:corbitsdev/corbits-artifacts#9152ba5", { "dependencies": { "@hono/standard-validator": "^0.2.3" }, "peerDependencies": { "@intx/agent": "^0.3.0", "@intx/hub-api": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.2", "hono": "^4.12.32", "hono-openapi": "^1.2.0", "postgres": "^3.4.9" } }, "corbitsdev-corbits-artifacts-9152ba5", "sha512-ZE2BLMND2elhGfvXBgkJlSD7x/WO6uTfloLajtXvneyuRKzaeXHXfqIKSVxC6NY9Qetyu4dOqZUPoeJBZopKKQ=="], - "@corbits/xai-provider/@corbits/oauth-core": ["@corbits/oauth-core@github:corbitsdev/corbits-oauth-core#f186ed0", { "dependencies": { "@intx/db": "^0.3.0", "@intx/hub-api": "^0.3.0", "@intx/hub-common": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "2.2.3", "drizzle-orm": "^0.45.2", "hono": "^4.11.9" } }, "corbitsdev-corbits-oauth-core-f186ed0", "sha512-wjKVTa26EoUqg8biyC4CumDi6y1hBck6xJTB/oEaTkc1lQ3Foum89KoXGHSvact+N1j9uwLGFEnOfgYp2FFi5Q=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], @@ -1851,8 +1849,6 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@workbench/hub/@corbits/artifacts": ["@corbits/artifacts@github:corbitsdev/corbits-artifacts#9152ba5", { "dependencies": { "@hono/standard-validator": "^0.2.3" }, "peerDependencies": { "@intx/agent": "^0.3.0", "@intx/hub-api": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.2", "hono": "^4.12.32", "hono-openapi": "^1.2.0", "postgres": "^3.4.9" } }, "corbitsdev-corbits-artifacts-9152ba5", "sha512-ZE2BLMND2elhGfvXBgkJlSD7x/WO6uTfloLajtXvneyuRKzaeXHXfqIKSVxC6NY9Qetyu4dOqZUPoeJBZopKKQ=="], - "@workbench/hub/@corbits/codex-provider": ["@corbits/codex-provider@github:corbitsdev/corbits-codex-provider#a1f475f", { "dependencies": { "@corbits/oauth-core": "github:corbitsdev/corbits-oauth-core", "@corbits/openai-responses": "github:corbitsdev/corbits-openai-responses", "arktype": "2.2.3" }, "peerDependencies": { "@intx/inference": ">=0.3.0", "@intx/types": ">=0.3.0" } }, "corbitsdev-corbits-codex-provider-a1f475f", "sha512-D6d0lblWx/4KudfLwQoQGrLB8GKRLP/HVp1cx08L2/hOGZAgH6txLPpeEtBqjl+R520qXSSPfM0kDC+3XNaAfw=="], "@workbench/hub/@corbits/mailbox": ["@corbits/mailbox@github:corbitsdev/corbits-mailbox#65590a8", { "dependencies": { "@hono/standard-validator": "0.2.3", "@intx/crypto": "0.3.0", "@standard-community/standard-json": "0.3.5", "@standard-community/standard-openapi": "0.2.9", "arktype": "2.1.29", "hono-openapi": "1.3.1" }, "peerDependencies": { "@intx/log": "^0.2.2", "drizzle-orm": "^0.45.2", "hono": "^4.12.0", "postgres": "^3.4.0" } }, "corbitsdev-corbits-mailbox-65590a8", "sha512-tK7xXcL780iX3I+5R68gmyqgEA0uwYO1qdTDNn/LF6tOiFIm0lt2szjspJ8JMKMTzKC6SSai03p723rEGM1jOg=="], diff --git a/package.json b/package.json index 6a9f9f7f4..652279fee 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "reset": "bun scripts/reset.ts" }, "devDependencies": { - "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#5309a15d4aa504f132c54b4da08bb672b4903873", + "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#4a2a3c22187991a6ce598faf794908fedd252e98", "@corbits/codex-provider": "github:corbitsdev/corbits-codex-provider#a1f475f341261c05fa8dade0c82f1246c175b5b3", "@corbits/ollama-adapter": "github:corbitsdev/corbits-ollama-adapter#0387eb17bee5ac87530e9c48bb3bffd9a6998ded", "@corbits/xai-provider": "github:corbitsdev/corbits-xai-provider#9f2d4bac40ea075df092fef807404a13c638cf14",