11import { AsyncLocalStorage } from "node:async_hooks" ;
22import { createHash } from "node:crypto" ;
33import type { WorkflowSandboxApi } from "./workflow-sandbox.js" ;
4- import type { LocalAgentProvider } from "./local-agent-profiles.js" ;
4+ import {
5+ buildLocalAgentProfilePrompt ,
6+ fingerprintLocalAgentProfile ,
7+ type LocalAgentProfile ,
8+ type LocalAgentProvider ,
9+ } from "./local-agent-profiles.js" ;
510import type { JsonSchema , JsonValue } from "./json-types.js" ;
611import { jsonValueSchema } from "./json-types.js" ;
712import {
@@ -109,6 +114,8 @@ export interface WorkflowJournal {
109114 provider : LocalAgentProvider ;
110115 model ?: string ;
111116 effort ?: string ;
117+ profileName ?: string ;
118+ profileFingerprint ?: string ;
112119 label ?: string ;
113120 phase ?: string ;
114121 isolation ?: AgentIsolationMode ;
@@ -151,6 +158,8 @@ export interface WorkflowApiDeps {
151158 baseSha ?: string ;
152159 /** Already-filtered enabled ∩ live provider ids, preference order. */
153160 enabledProviders : LocalAgentProvider [ ] ;
161+ /** Loaded, enabled profiles exposed by open_workspace for this project. */
162+ agentProfiles ?: LocalAgentProfile [ ] ;
154163 runProvider : WorkflowRunProvider ;
155164 createWorktree ?: CreateAgentWorktree ;
156165 replay ?: WorkflowReplay ;
@@ -177,6 +186,7 @@ export class WorkflowEngineError extends Error {
177186 | "provider_disabled"
178187 | "provider_unavailable"
179188 | "no_provider"
189+ | "profile"
180190 | "nest_depth"
181191 | "worktree"
182192 | "schema"
@@ -250,7 +260,15 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
250260 const agentOpts = normalizeAgentOpts ( opts ) ;
251261 throwIfCancelled ( deps ) ;
252262
253- const provider = resolveProvider ( agentOpts . provider , deps . meta , deps . enabledProviders ) ;
263+ const target = resolveAgentTarget ( prompt , agentOpts , deps ) ;
264+ const {
265+ provider,
266+ model,
267+ effort,
268+ profileName,
269+ profileFingerprint,
270+ providerPrompt,
271+ } = target ;
254272 const phase = agentOpts . phase ?? phaseAls . getStore ( ) ;
255273 const isolation : AgentIsolationMode =
256274 agentOpts . isolation === "worktree" ? "worktree" : "shared" ;
@@ -259,9 +277,11 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
259277
260278 const cacheKeyInput = buildAgentCacheKeyInput ( {
261279 prompt,
280+ profileName,
281+ profileFingerprint,
262282 provider,
263- model : agentOpts . model ,
264- effort : agentOpts . effort ,
283+ model,
284+ effort,
265285 schema : agentOpts . schema ,
266286 isolation,
267287 } ) ;
@@ -277,8 +297,10 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
277297 prompt,
278298 schemaJson : agentOpts . schema ? JSON . stringify ( agentOpts . schema ) : undefined ,
279299 provider,
280- model : agentOpts . model ,
281- effort : agentOpts . effort ,
300+ model,
301+ effort,
302+ profileName,
303+ profileFingerprint,
282304 label : agentOpts . label ,
283305 phase,
284306 isolation,
@@ -349,8 +371,10 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
349371 prompt,
350372 schemaJson : agentOpts . schema ? JSON . stringify ( agentOpts . schema ) : undefined ,
351373 provider,
352- model : agentOpts . model ,
353- effort : agentOpts . effort ,
374+ model,
375+ effort,
376+ profileName,
377+ profileFingerprint,
354378 label : agentOpts . label ,
355379 phase,
356380 isolation,
@@ -377,9 +401,9 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
377401 const cwd = worktreePath ?? deps . workspaceRoot ;
378402 const providerBase = {
379403 provider,
380- prompt,
381- model : agentOpts . model ,
382- effort : agentOpts . effort ,
404+ prompt : providerPrompt ,
405+ model,
406+ effort,
383407 workspace : cwd ,
384408 signal : deps . signal ,
385409 label : agentOpts . label ,
@@ -395,7 +419,7 @@ export function createWorkflowApi(deps: WorkflowApiDeps): WorkflowApi {
395419 const { enforceAgentSchema } = await import ( "./workflow-schema.js" ) ;
396420 const enforced = await enforceAgentSchema ( {
397421 schema : agentOpts . schema ,
398- prompt,
422+ prompt : providerPrompt ,
399423 provider,
400424 run : ( p , options ) =>
401425 deps . runProvider ( {
@@ -690,6 +714,54 @@ export function resolveProvider(
690714 return first ;
691715}
692716
717+ interface ResolvedAgentTarget {
718+ provider : LocalAgentProvider ;
719+ model ?: string ;
720+ effort ?: string ;
721+ profileName ?: string ;
722+ profileFingerprint ?: string ;
723+ providerPrompt : string ;
724+ }
725+
726+ function resolveAgentTarget (
727+ prompt : string ,
728+ opts : AgentOpts ,
729+ deps : Pick < WorkflowApiDeps , "agentProfiles" | "enabledProviders" | "meta" > ,
730+ ) : ResolvedAgentTarget {
731+ if ( ! opts . profile ) {
732+ return {
733+ provider : resolveProvider ( opts . provider , deps . meta , deps . enabledProviders ) ,
734+ model : opts . model ,
735+ effort : opts . effort ,
736+ providerPrompt : prompt ,
737+ } ;
738+ }
739+
740+ const profile = deps . agentProfiles ?. find ( ( candidate ) => candidate . name === opts . profile ) ;
741+ if ( ! profile ) {
742+ const available = deps . agentProfiles ?. map ( ( candidate ) => candidate . name ) . join ( ", " ) ;
743+ throw new WorkflowEngineError (
744+ "profile" ,
745+ `Unknown agent profile: ${ opts . profile } ${ available ? `. Available profiles: ${ available } ` : "" } ` ,
746+ ) ;
747+ }
748+ if ( ! deps . enabledProviders . includes ( profile . provider ) ) {
749+ throw new WorkflowEngineError (
750+ "provider_unavailable" ,
751+ `Agent profile ${ profile . name } requires unavailable provider ${ profile . provider } ` ,
752+ ) ;
753+ }
754+
755+ return {
756+ provider : profile . provider ,
757+ model : opts . model ?? profile . model ,
758+ effort : opts . effort ?? profile . effort ,
759+ profileName : profile . name ,
760+ profileFingerprint : fingerprintLocalAgentProfile ( profile ) ,
761+ providerPrompt : buildLocalAgentProfilePrompt ( profile , prompt ) ,
762+ } ;
763+ }
764+
693765function normalizeAgentOpts ( opts : unknown ) : AgentOpts {
694766 if ( opts === undefined || opts === null ) return { } ;
695767 if ( typeof opts === "object" && opts !== null && "writeMode" in opts ) {
@@ -699,7 +771,13 @@ function normalizeAgentOpts(opts: unknown): AgentOpts {
699771 if ( parsed . success ) return parsed . data ;
700772 const issue = parsed . error . issues [ 0 ] ;
701773 const path = issue ?. path . join ( "." ) || "opts" ;
702- const kind = path === "schema" ? "schema" : path === "isolation" ? "worktree" : "internal" ;
774+ const kind = path === "schema"
775+ ? "schema"
776+ : path === "isolation"
777+ ? "worktree"
778+ : path === "profile" || issue ?. message . includes ( "profile and provider" )
779+ ? "profile"
780+ : "internal" ;
703781 throw new WorkflowEngineError (
704782 kind ,
705783 `Invalid agent ${ path } : ${ issue ?. message ?? "validation failed" } ` ,
0 commit comments