Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions solutions/ess-maker-skills/.github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,9 @@ When helping a customer, match their request to one of these patterns:

| Customer says... | Pattern | What to create |
|-----------------|---------|---------------|
| "I need to look up X from ServiceNow/Workday" | Product extension required | Explain that DA-GA extension setup guidance is not yet available |
| "I need to create a ticket/case/request" | Product extension required | Explain that DA-GA extension setup guidance is not yet available |
| "I need to show the user their X data" | Product extension required | Explain that DA-GA extension setup guidance is not yet available |
| "I need to look up X from ServiceNow/Workday" | Product extension required | Route connection/setup requests through `src/skills/connect/SKILL.md`; create topics only after the supported integration is connected |
| "I need to create a ticket/case/request" | Product extension required | Route connection/setup requests through `src/skills/connect/SKILL.md`; create topics only after the supported integration is connected |
| "I need to show the user their X data" | Product extension required | Route connection/setup requests through `src/skills/connect/SKILL.md`; create topics only after the supported integration is connected |
| "I need to call a non-ESS system (Jira, custom API)" | Standalone Topic + Workflow | Topic + new cloud flow (only for connectors without a shared orchestrator) |
| "I need to add a step to an existing flow" | Modify topic | Edit the existing topic YAML |
| "I need to change how the agent responds to X" | Modify topic | Update trigger phrases, messages, or conditions |
Expand Down Expand Up @@ -430,7 +430,7 @@ pushed. Run the push pipeline when the maker asks to push local changes.
| User intent | Skill to read |
|-------------|--------------|
| Run common ESS foundation setup (`/setup`) | `src/skills/foundation-setup/SKILL.md` |
| Provision/connect the Workday setup environment (`/connect workday`) | `src/skills/setup/SKILL.md` |
| Provision/connect Workday for the active ESS HR agent (`/connect workday` or `/connect-workday`) | `src/skills/connect/SKILL.md` |
| Connect to ServiceNow/Workday | `src/skills/connect/SKILL.md` |
| Create a topic | `src/skills/topics/create-eval-driven/SKILL.md` |
| Create a workflow | `src/skills/workflows/create/SKILL.md` |
Expand Down
6 changes: 3 additions & 3 deletions solutions/ess-maker-skills/.github/prompts/connect.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ Rules:
4. Do not compose your own messages. If there is no Message block for a
situation, stay silent and proceed to the next action.

After reading SKILL.md, your first action is to check for
`.local/connect/steps.md`. If starting fresh, your first message to the user
is the checklist table from the Fresh Start section.
After reading `SKILL.md`, follow its integration-specific state and routing
instructions. Do not assume a shared `.local/connect/steps.md` file or a
generic Fresh Start section.
49 changes: 49 additions & 0 deletions solutions/ess-maker-skills/scripts/agentbuilder_object_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,52 @@ def object_models_to_yaml(
except Exception as exc:
results.append(_error_result(key, exc))
return results


def yaml_to_object_models(
items: list[dict[str, Any]],
) -> list[dict[str, Any]]:
"""Convert canonical Copilot Studio YAML to Object Model JSON elements."""
if not items:
return []

types = _load_object_model()
options = types.element_serializer.CreateOptions(False)
results: list[dict[str, Any]] = []
for item in items:
key = item.get("key")
if not isinstance(key, str) or not key.strip():
raise ObjectModelConverterError(
"Each item key must be a non-empty string."
)
yaml_content = item.get("yaml")
if not isinstance(yaml_content, str) or not yaml_content.strip():
raise ObjectModelConverterError("yaml must be a non-empty string.")

try:
element = types.yaml_serializer.Deserialize[types.bot_element](
yaml_content
)
if element is None:
raise ValueError(
"The Copilot Studio YAML did not contain a BotElement."
)
serialized = types.json_serializer.Serialize[
types.bot_element
](element, options)
object_model = json.loads(str(serialized))
if not isinstance(object_model, dict):
raise ValueError(
"The converted Object Model JSON was not an object."
)
results.append(
{
"key": key,
"success": True,
"elementType": element.GetType().Name,
"objectModel": object_model,
}
)
except Exception as exc:
results.append(_error_result(key, exc))
return results
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
.PARAMETER WorkflowId
One or more workflow GUIDs (the 'workflowid' of each cloud flow the agent calls).

.PARAMETER PreferredUsername
Environment Maker username that must be used for Dataverse authentication.

.PARAMETER TeamName
Optional display name for the access team.

Expand Down Expand Up @@ -57,6 +60,7 @@ param(
[Parameter(Mandatory = $true)][string] $OrgUrl,
[Parameter(Mandatory = $true)][guid] $BotId,
[Parameter(Mandatory = $true)][guid[]] $WorkflowId,
[string] $PreferredUsername,
[string] $TeamName,
[guid] $AdministratorId,
[guid] $BusinessUnitId,
Expand Down Expand Up @@ -100,25 +104,38 @@ function Test-DataverseToken {
}

function Get-DataverseToken {
param([string]$Resource)
param(
[string]$Resource,
[string]$PreferredUsername
)
$tok = $null
try {
$tok = az account get-access-token --resource $Resource --query accessToken -o tsv 2>$null
} catch {
$tok = $null
}
if (-not [string]::IsNullOrWhiteSpace($tok) -and (Test-DataverseToken -Resource $Resource -Token $tok)) {
return $tok
if ([string]::IsNullOrWhiteSpace($PreferredUsername)) {
try {
$tok = az account get-access-token --resource $Resource --query accessToken -o tsv 2>$null
} catch {
$tok = $null
}
if (-not [string]::IsNullOrWhiteSpace($tok) -and (Test-DataverseToken -Resource $Resource -Token $tok)) {
return $tok
}
}

Write-Host " Azure CLI token was unavailable or rejected; using the kit's Dataverse sign-in." -ForegroundColor Yellow
if ($PreferredUsername) {
Write-Host " Using the kit's Dataverse sign-in for $PreferredUsername." -ForegroundColor Yellow
} else {
Write-Host " Azure CLI token was unavailable or rejected; using the kit's Dataverse sign-in." -ForegroundColor Yellow
}
$helper = Join-Path $PSScriptRoot 'get_dataverse_token.py'
$python = Get-Command python -ErrorAction SilentlyContinue
if (-not $python -or -not (Test-Path $helper)) {
throw "Could not acquire a valid Dataverse token. Install Python, then run the kit setup or sign in with an account that can access $Resource."
}

$output = @(& $python.Source $helper --environment $Resource 2>&1)
$helperArgs = @('--environment', $Resource)
if ($PreferredUsername) {
$helperArgs += @('--preferred-username', $PreferredUsername)
}
$output = @(& $python.Source $helper @helperArgs 2>&1)
if ($LASTEXITCODE -ne 0) {
$safeError = ($output | Where-Object { $_ -notmatch '^ESS_DATAVERSE_TOKEN=' }) -join [Environment]::NewLine
throw "Kit Dataverse authentication failed: $safeError"
Expand Down Expand Up @@ -171,7 +188,9 @@ function Invoke-Dv {

# --------------------------------------------------------------------------------------------
Write-Step "Connecting to $OrgUrl"
$script:Token = Get-DataverseToken -Resource $OrgUrl
$script:Token = Get-DataverseToken `
-Resource $OrgUrl `
-PreferredUsername $PreferredUsername
$who = Invoke-Dv -Path 'WhoAmI'
Write-Ok "Authenticated. UserId $($who.UserId), OrgId $($who.OrganizationId)"

Expand Down
18 changes: 17 additions & 1 deletion solutions/ess-maker-skills/scripts/alm/get_dataverse_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,30 @@
sys.path.insert(0, str(SCRIPTS_DIR))

import auth # noqa: E402
from workday_connect_auth import ( # noqa: E402
WorkdayConnectIdentityError,
require_identity,
)


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--environment", required=True)
parser.add_argument("--preferred-username")
args = parser.parse_args()

token = auth.authenticate(args.environment.rstrip("/"))
token = auth.authenticate(
args.environment.rstrip("/"),
preferred_username=args.preferred_username,
)
try:
require_identity(
token,
preferred_username=args.preferred_username,
)
except WorkdayConnectIdentityError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 1
print(f"ESS_DATAVERSE_TOKEN={token}")
return 0

Expand Down
9 changes: 8 additions & 1 deletion solutions/ess-maker-skills/scripts/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,21 @@ def main():
"--resolve-environment-url",
help="Resolve one environment URL to its Power Platform metadata",
)
parser.add_argument(
"--preferred-username",
help="Account to reuse for environment resolution when available",
)
parser.add_argument("--select", type=int, default=None,
help="Select environment by number and output JSON")
args = parser.parse_args()

if args.resolve_environment_url:
from list_environments import resolve_environment_for_user

selected = resolve_environment_for_user(args.resolve_environment_url)
selected = resolve_environment_for_user(
args.resolve_environment_url,
preferred_username=args.preferred_username,
)
if selected is None:
print(
"ERROR: The provided URL did not match a Dataverse-linked "
Expand Down
Loading
Loading