From 908077bdfbb5410566e7c756eb72fe21d088e51d Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 18 Sep 2026 15:42:14 -0700 Subject: [PATCH] fix(myra,web): the definition declares the hub credential binding and use requirement, so the client grant step goes away (CL-8603) --- agents/myra/src/definition.test.ts | 1 + agents/myra/src/index.ts | 15 ++- agents/myra/src/workflow-ids.ts | 61 +++++++++- apps/web/src/agent-deploy.test.ts | 2 + apps/web/src/agent-deploy.ts | 33 ++++-- apps/web/src/agent-hub-credential.ts | 163 ++------------------------- apps/web/src/myra-deploy.test.ts | 12 +- apps/web/src/myra-deploy.ts | 66 ++++------- apps/web/src/workbench-create.ts | 4 +- 9 files changed, 136 insertions(+), 221 deletions(-) diff --git a/agents/myra/src/definition.test.ts b/agents/myra/src/definition.test.ts index 6fa44e9a4..0924b6644 100644 --- a/agents/myra/src/definition.test.ts +++ b/agents/myra/src/definition.test.ts @@ -12,6 +12,7 @@ const INPUT = { triggerAddress: "ins_dep000000000000@example.test", inferencePreferences: [{ provider: "anthropic", model: "claude-test" }], systemPrompt: "You are Myra.", + hubCredentialId: "crd_000000000000000000000000000000ab", } as const; function assistantStep(definition: WorkflowDefinition): StepPrimitive { diff --git a/agents/myra/src/index.ts b/agents/myra/src/index.ts index 6f73d76a2..97ead1fc7 100644 --- a/agents/myra/src/index.ts +++ b/agents/myra/src/index.ts @@ -15,7 +15,11 @@ 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"; +import { + ASSISTANT_STEP_ID, + artifactToolsCredentialBinding, + artifactToolsCredentialUseRequirement, +} from "./workflow-ids"; export { ASSISTANT_SYSTEM_PROMPT } from "./system-prompt"; export { ASSISTANT_STEP_ID, ASSISTANT_WORKFLOW_ID } from "./workflow-ids"; @@ -47,6 +51,10 @@ export interface MyraWorkflowInput { readonly inferencePreferences: readonly InferencePreference[]; /** The prompt this deployment runs with. */ readonly systemPrompt: string; + /** The tenant credential holding this agent's hub token, minted by the + * deployer before the source is pushed. The definition binds it to the + * artifact tools and requires its use on the deployer's authority. */ + readonly hubCredentialId: string; } /** @@ -71,9 +79,14 @@ export function buildMyraWorkflow(input: MyraWorkflowInput): WorkflowDefinition if (input.systemPrompt === "") { throw new Error("buildMyraWorkflow requires a non-empty systemPrompt"); } + if (input.hubCredentialId === "") { + throw new Error("buildMyraWorkflow requires a non-empty hubCredentialId"); + } return defineWorkflow({ id: input.workflowId, trigger: { type: "mail", to: input.triggerAddress }, + credentialBindings: [artifactToolsCredentialBinding(input.workflowId)], + grantRequirements: [artifactToolsCredentialUseRequirement(input.hubCredentialId)], steps: { assistant: step({ agent: { diff --git a/agents/myra/src/workflow-ids.ts b/agents/myra/src/workflow-ids.ts index 1ee4e78c8..0f5bbb795 100644 --- a/agents/myra/src/workflow-ids.ts +++ b/agents/myra/src/workflow-ids.ts @@ -1,5 +1,62 @@ -// The workflow/step ids for Myra's mail-triggered assistant definition. -// Split from the prompt so the prompt module stays prompt-only. +// The workflow/step ids for Myra's mail-triggered assistant definition, and +// the hub-credential facts a deployer and the definition must agree on. +// Split from the prompt so the prompt module stays prompt-only, and kept +// free of the runtime so a browser can import it. export const ASSISTANT_WORKFLOW_ID = "wf_assistant"; export const ASSISTANT_STEP_ID = "assistant"; + +/** The tool package the hub credential is bound to; also the consumer the + * credential-use grant is conditioned on. */ +export const ARTIFACT_TOOLS_PACKAGE = "@corbits/artifacts/sidecar-bundle"; + +/** The handle the artifact tools resolve at run time. */ +export const HUB_CREDENTIAL_HANDLE = "hub"; + +/** The provider row standing for the hub itself, one per workbench. */ +export const HUB_PROVIDER_NAME = "workbench-hub"; + +/** The credential name an agent's hub token is stored under; the binding + * resolves it by this name, so both sides derive it here. */ +export function agentHubCredentialName(workflowId: string): string { + return `${workflowId}-hub`; +} + +/** The definition's credential binding: the deploy resolves the named + * tenant-owned credential into the artifact tools' `hub` handle. */ +export function artifactToolsCredentialBinding(workflowId: 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(workflowId), + locator: "tenant", + }; +} + +/** The definition's grant requirement: at the run's first trigger the hub + * resolves it against the definition creator's authority and stamps the + * `credential:{id}` / `use` grant the artifact tools' runtime gate checks, + * scoped to that one package. The run principal does not exist before + * then, so this is the only place the grant can be declared. */ +export function artifactToolsCredentialUseRequirement(credentialId: string): { + readonly resource: string; + readonly action: "use"; + readonly effect: "allow"; + readonly source: "creator"; + readonly conditions: { readonly tool: string }; +} { + return { + resource: `credential:${credentialId}`, + action: "use", + effect: "allow", + source: "creator", + conditions: { tool: `tool:${ARTIFACT_TOOLS_PACKAGE}` }, + }; +} diff --git a/apps/web/src/agent-deploy.test.ts b/apps/web/src/agent-deploy.test.ts index 1133b6116..e3ce10374 100644 --- a/apps/web/src/agent-deploy.test.ts +++ b/apps/web/src/agent-deploy.test.ts @@ -16,6 +16,7 @@ describe("buildAgentDefinitionJson", () => { systemPrompt: "You research things.", triggerAddress: "research-buddy@example.test", declaredSources: [{ provider: "anthropic", model: "claude-test" }], + hubCredentialId: "crd_000000000000000000000000000000ab", }; const projection = buildAgentDefinitionJson(args) as { id: string; @@ -27,6 +28,7 @@ describe("buildAgentDefinitionJson", () => { triggerAddress: args.triggerAddress, inferencePreferences: args.declaredSources, systemPrompt: args.systemPrompt, + hubCredentialId: args.hubCredentialId, }; expect(projection.id).toBe(buildInput.workflowId); diff --git a/apps/web/src/agent-deploy.ts b/apps/web/src/agent-deploy.ts index ae0454215..a1c6fd547 100644 --- a/apps/web/src/agent-deploy.ts +++ b/apps/web/src/agent-deploy.ts @@ -7,9 +7,10 @@ import { reportError } from "@corbits/error-sink"; import { artifactToolsCredentialBinding, - ensureAgentHubCredential, - grantArtifactToolsCredentialUse, -} from "./agent-hub-credential"; + artifactToolsCredentialUseRequirement, +} from "@corbits/myra/workflow-ids"; + +import { ensureAgentHubCredential } from "./agent-hub-credential"; import { resolveExistingOffering } from "./onboarding/provider-connect-step"; import { isValidSlug, slugify } from "@/lib/slug"; @@ -93,6 +94,7 @@ export function buildAgentDefinitionJson(args: { systemPrompt: string; triggerAddress: string; declaredSources: readonly { readonly provider: string; readonly model: string }[]; + hubCredentialId: string; }): unknown { const stepId = "run"; return { @@ -100,8 +102,10 @@ 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. + // Resolved at deploy into the `hub` handle the artifact tools use, and + // granted to the run on the deployer's authority at its first trigger. credentialBindings: [artifactToolsCredentialBinding(args.slug)], + grantRequirements: [artifactToolsCredentialUseRequirement(args.hubCredentialId)], steps: { [stepId]: { kind: "step", @@ -171,6 +175,7 @@ export async function pushAgentSource( readonly systemPrompt: string; readonly triggerAddress: string; readonly declaredSources: readonly { readonly provider: string; readonly model: string }[]; + readonly hubCredentialId: string; }, fetchImpl: typeof fetch = fetch, ): Promise { @@ -184,6 +189,7 @@ export async function pushAgentSource( triggerAddress: args.triggerAddress, inferencePreferences: args.declaredSources.map((source) => ({ ...source })), systemPrompt: args.systemPrompt, + hubCredentialId: args.hubCredentialId, }, workflowJson: JSON.stringify( buildAgentDefinitionJson({ @@ -191,6 +197,7 @@ export async function pushAgentSource( systemPrompt: args.systemPrompt, triggerAddress: args.triggerAddress, declaredSources: args.declaredSources, + hubCredentialId: args.hubCredentialId, }), ), }); @@ -345,9 +352,9 @@ 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( + // Minted before the push: the definition binds this credential by name + // and requires its use by id, so it must exist before the source does. + const hubCredentialId = await ensureAgentHubCredential( { tenantId: args.tenantId, definitionId: slug, assetId }, fetchImpl, ); @@ -359,7 +366,13 @@ export async function deployAgentSource( assetId, assetName, packageName, - { slug, systemPrompt, triggerAddress, declaredSources: offering.declaredSources }, + { + slug, + systemPrompt, + triggerAddress, + declaredSources: offering.declaredSources, + hubCredentialId, + }, fetchImpl, ); @@ -383,10 +396,6 @@ export async function deployAgentSource( if (parsed instanceof type.errors) { throw new AgentDeployError(`this deployment came back an unexpected shape: ${parsed.summary}`); } - await grantArtifactToolsCredentialUse( - { tenantId: args.tenantId, deploymentId: parsed.id, credentialId }, - fetchImpl, - ); 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 cf7627b1b..29e20237a 100644 --- a/apps/web/src/agent-hub-credential.ts +++ b/apps/web/src/agent-hub-credential.ts @@ -1,21 +1,14 @@ // 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. +// agent token for the definition and stores it as a tenant-owned credential +// pinned to the hub's own origin. The definition itself binds that credential +// to the artifact tools and requires its use on the deployer's authority, so +// nothing here touches grants. The agent never holds the secret: the sidecar +// shapes a mediated fetch from it and hands the tools only that. +import { agentHubCredentialName, HUB_PROVIDER_NAME } from "@corbits/myra/workflow-ids"; 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"; +/** `@corbits/credential-header`'s raw-`authorization` preset sends the + * secret verbatim, so the stored secret is the whole header value. */ export const HUB_PROVIDER_PLUGIN = "http-raw-authorization"; export class AgentHubCredentialError extends Error {} @@ -29,18 +22,6 @@ const CredentialShape = type({ }); 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", -}); -// The stock deployments route returns a bare array, not a `data` page. -const DeploymentsList = DeploymentShape.array(); -const PrincipalsPage = type({ - data: PrincipalShape.array(), - nextCursor: "string | null", -}); function tenantPath(tenantId: string, suffix: string): string { return `/api/tenants/${encodeURIComponent(tenantId)}${suffix}`; @@ -58,44 +39,6 @@ async function readJson( 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) { @@ -125,8 +68,8 @@ async function ensureHubProvider(tenantId: string, fetchImpl: typeof fetch): Pro /** * 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. + * rotates rather than accumulates. Returns the credential id the definition's + * use requirement names. */ export async function ensureAgentHubCredential( args: { @@ -201,89 +144,3 @@ 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. 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: { - readonly tenantId: string; - readonly deploymentId: string; - readonly credentialId: string; - }, - fetchImpl: typeof fetch = fetch, -): 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 created = await fetchImpl(tenantPath(args.tenantId, "/grants"), { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - principalId, - 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"); - } -} - -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 deployments = await readJson(listed, DeploymentsList, "this workbench's deployments"); - const live = deployments.find( - (deployment) => - deployment.definitionAssetId === args.assetId && - !TERMINAL_DEPLOYMENT_STATUSES.has(deployment.status), - ); - return live?.id ?? null; -} diff --git a/apps/web/src/myra-deploy.test.ts b/apps/web/src/myra-deploy.test.ts index 35e7ff8d9..eba1e0c11 100644 --- a/apps/web/src/myra-deploy.test.ts +++ b/apps/web/src/myra-deploy.test.ts @@ -5,10 +5,14 @@ import { MYRA_SOURCE_CONFIG } from "./myra-source"; describe("buildMyraDefinitionJson", () => { test("declares the offering chain's sources so the probe approves them", () => { - const definition = buildMyraDefinitionJson("assistant@ada.example", [ - { provider: "openai-compatible", model: "qwen2.5:7b" }, - { provider: "anthropic", model: "claude-sonnet-5" }, - ]) as { steps: Record }; + const definition = buildMyraDefinitionJson( + "assistant@alice.example", + [ + { provider: "openai-compatible", model: "qwen2.5:7b" }, + { provider: "anthropic", model: "claude-sonnet-5" }, + ], + "crd_000000000000000000000000000000ab", + ) as { steps: Record }; expect(definition.steps["assistant"]?.agent.inference.sources).toEqual([ { provider: "openai-compatible", model: "qwen2.5:7b" }, diff --git a/apps/web/src/myra-deploy.ts b/apps/web/src/myra-deploy.ts index 564819787..5c9ad572a 100644 --- a/apps/web/src/myra-deploy.ts +++ b/apps/web/src/myra-deploy.ts @@ -1,17 +1,16 @@ // Builds and publishes Myra's deployable definition entirely over stock // routes, ending with a `WorkflowDeployInput` pinned to the pushed commit. import { ASSISTANT_SYSTEM_PROMPT } from "@corbits/myra/prompt"; -import { ASSISTANT_STEP_ID, ASSISTANT_WORKFLOW_ID } from "@corbits/myra/workflow-ids"; +import { + ASSISTANT_STEP_ID, + ASSISTANT_WORKFLOW_ID, + artifactToolsCredentialBinding, + artifactToolsCredentialUseRequirement, +} from "@corbits/myra/workflow-ids"; import { renderBundledWorkflowSourceTree } from "@corbits/workflows/client"; import { type } from "arktype"; -import { - artifactToolsCredentialBinding, - ensureAgentHubCredential, - grantArtifactToolsCredentialUse, - resolveAgentHubCredentialId, - resolveLiveDeploymentId, -} from "./agent-hub-credential"; +import { ensureAgentHubCredential } from "./agent-hub-credential"; import { MYRA_SOURCE_CONFIG } from "./myra-source"; import type { WorkflowDeployInput } from "./needs-list"; @@ -87,11 +86,14 @@ export async function ensureMyraSourceAsset( export function buildMyraDefinitionJson( triggerAddress: string, declaredSources: readonly DeclaredSource[], + hubCredentialId: string, ): unknown { return { id: ASSISTANT_WORKFLOW_ID, - // Resolved at deploy into the `hub` handle the artifact tools use. + // Resolved at deploy into the `hub` handle the artifact tools use, and + // granted to the run on the deployer's authority at its first trigger. credentialBindings: [artifactToolsCredentialBinding(ASSISTANT_WORKFLOW_ID)], + grantRequirements: [artifactToolsCredentialUseRequirement(hubCredentialId)], // `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 }], @@ -169,6 +171,7 @@ export async function pushMyraSource( assetId: string, tenantDomain: string, declaredSources: readonly DeclaredSource[], + hubCredentialId: string, fetchImpl: typeof fetch = fetch, ): Promise { const triggerAddress = `assistant@${tenantDomain}`; @@ -184,8 +187,11 @@ export async function pushMyraSource( triggerAddress, inferencePreferences: declaredSources.map((source) => ({ ...source })), systemPrompt: ASSISTANT_SYSTEM_PROMPT, + hubCredentialId, }, - workflowJson: JSON.stringify(buildMyraDefinitionJson(triggerAddress, declaredSources)), + workflowJson: JSON.stringify( + buildMyraDefinitionJson(triggerAddress, declaredSources, hubCredentialId), + ), }); const url = new URL( `/api/tenants/${encodeURIComponent(tenantId)}/assets/${MYRA_SOURCE_CONFIG.assetKind}/${MYRA_SOURCE_CONFIG.assetName}.git`, @@ -241,9 +247,9 @@ 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( + // Minted before the push: the definition binds this credential by name + // and requires its use by id, so it must exist before the source does. + const hubCredentialId = await ensureAgentHubCredential( { tenantId: args.tenantId, definitionId: ASSISTANT_WORKFLOW_ID, assetId }, fetchImpl, ); @@ -252,6 +258,7 @@ export async function deployMyraSource( assetId, args.tenantDomain, args.declaredSources, + hubCredentialId, fetchImpl, ); return buildMyraDeployInput({ @@ -261,36 +268,3 @@ 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 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 { - const assetId = await ensureMyraSourceAsset(args.tenantId, fetchImpl); - const deploymentId = await resolveLiveDeploymentId( - { tenantId: args.tenantId, assetId }, - fetchImpl, - ); - 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) { - throw new MyraDeployError("Myra has no hub credential to authorize"); - } - 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 96bc919d4..e5ef35c0f 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 { authorizeMyraHubCredential, deployMyraSource } from "./myra-deploy"; +import { deployMyraSource } from "./myra-deploy"; import { createFetchStockHub } from "./needs-converge"; import { resolveExistingOffering } from "./onboarding/provider-connect-step"; @@ -88,7 +88,6 @@ export async function createWorkbench(input: CreateWorkbenchInput): Promise