Files
Orkas/scripts/bootstrap-node.ps1
wyt 8208720188 Expand local agent workflows, task recovery, and connector support
Add device-local CLI and merchant connector workflows with catalog-guided
setup, scoped authorization, encrypted credentials, and action confirmation.
Extend task queues, retry recovery, project to-dos, library processing, local
agent inputs, Office artifacts, and desktop notification/navigation handling.
Update shared agents, skills, tool contracts, resource fixtures, and coverage.

Preserve newer conversation drafts and reference identities across delayed
send acceptance and navigation. Publish process-test readiness atomically
so cleanup scenarios cannot observe a partially written PID record.

Retain the independent date version, Linux source runtime, existing BYO
provider policies, production API-key access, and open-source settings.

Prompt confirmation:
  Confirmed-by: Requester in the current implementation task
  Scope: Apply the existing approved shared agent, skill, task, and connector
    capabilities within the documented open-source product boundaries.

Prompt audit:
- Keep: Commander and actor task identity, ordered execution, user input,
  project context, and tool-use responsibilities in the shared prompt
  templates and their existing assembly path. Type: resident.
  Why: These clauses determine behavior across ordinary turns; the update
  preserves the shared product's source-of-truth context and recovery
  ownership without adding an independent open-source policy layer.
- Keep: Project-task, scheduling, memory, connector setup, UI artifact,
  image/video composition, Office, and SEO workflows in their owning
  skills and referenced guides. Type: on-demand.
  Why: Load specialized procedures only for the corresponding work; retain
  source metadata and dependency ownership rather than duplicate those
  procedures in every actor prompt.
- Keep: Scoped tool schemas, queue/permission state, persisted recovery
  evidence, output storage, resource manifests, and deterministic validators
  at their existing host boundaries. Type: non-prompt enforcement.
  Why: Code and structured events own the invariants and prevent the prompt
  from becoming a second authorization, persistence, or execution engine.
- Drop: Hosted account, membership, cloud/relay, telemetry, internal debug,
  managed voice, watermark, welcome, and official updater instructions or
  affordances from the public execution paths. Type: source-only/on-demand.
  Why: Those paths require services absent from the open-source product;
  shared local operations and public API-key/BYO capabilities remain usable.

Validation: Full deterministic JavaScript and resource suites, platform
native and GUI checks, typechecks, startup smoke, public endpoint contracts,
Office artifact/fixture smoke, and desktop E2E are recorded with the frozen
tree with emitted-log review.
2026-09-11 11:23:43 +08:00

156 lines
5.7 KiB
PowerShell

[CmdletBinding()]
param(
[string]$PcRoot = '',
[string]$Architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
if (-not $PcRoot) {
$PcRoot = Split-Path -Parent $PSScriptRoot
}
$runtimeRoot = Join-Path $PcRoot 'resources\runtime'
$manifestPath = Join-Path $runtimeRoot 'manifest.json'
$manifest = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json
$platformKey = switch ($Architecture) {
'X64' { 'win32-x64' }
'Arm64' { 'win32-arm64' }
default { throw "Unsupported Windows architecture for the bundled Node runtime: $Architecture" }
}
$nodeSpec = $manifest.node
$assetProperty = $nodeSpec.assets.PSObject.Properties[$platformKey]
if (-not $assetProperty) {
throw "Node runtime asset is missing from the manifest: $platformKey"
}
$asset = $assetProperty.Value
$nodeRoot = Join-Path (Join-Path $runtimeRoot 'node') $platformKey
$nodeExe = Join-Path $nodeRoot 'node.exe'
$npmCmd = Join-Path $nodeRoot 'npm.cmd'
$npxCmd = Join-Path $nodeRoot 'npx.cmd'
$markerPath = Join-Path $nodeRoot '.orkas-runtime.json'
function Test-InstalledNode {
if (-not (Test-Path -LiteralPath $nodeExe -PathType Leaf) -or
-not (Test-Path -LiteralPath $npmCmd -PathType Leaf) -or
-not (Test-Path -LiteralPath $npxCmd -PathType Leaf) -or
-not (Test-Path -LiteralPath $markerPath -PathType Leaf)) {
return $false
}
try {
$marker = Get-Content -Raw -LiteralPath $markerPath | ConvertFrom-Json
if ($marker.schema -ne 1 -or
$marker.kind -ne 'node' -or
$marker.platformKey -ne $platformKey -or
$marker.version -ne $nodeSpec.version -or
$marker.sha256 -ne $asset.sha256) {
return $false
}
$version = (& $nodeExe --version 2>$null).Trim()
return $LASTEXITCODE -eq 0 -and $version -eq "v$($nodeSpec.version)"
} catch {
return $false
}
}
if (Test-InstalledNode) {
Write-Host "[node-bootstrap] bundled Node $($nodeSpec.version) is ready"
return
}
$nodeParent = Split-Path -Parent $nodeRoot
New-Item -ItemType Directory -Force -Path $nodeParent | Out-Null
$tempRoot = Join-Path $nodeParent ('.bootstrap-{0}-{1}' -f $PID, [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds())
$archivePath = Join-Path $tempRoot $asset.name
$extractRoot = Join-Path $tempRoot 'extract'
$payloadRoot = Join-Path $tempRoot 'payload'
$backupRoot = "$nodeRoot.bak-$PID"
try {
New-Item -ItemType Directory -Force -Path $extractRoot, $payloadRoot | Out-Null
Write-Host "[node-bootstrap] downloading Node $($nodeSpec.version) for $platformKey..."
Invoke-WebRequest -UseBasicParsing -Uri $asset.url -OutFile $archivePath -TimeoutSec 600
$actualSize = (Get-Item -LiteralPath $archivePath).Length
if ($actualSize -ne [int64]$asset.size) {
throw "Node archive size mismatch: expected $($asset.size), got $actualSize"
}
$actualSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash.ToLowerInvariant()
if ($actualSha256 -ne ([string]$asset.sha256).ToLowerInvariant()) {
throw "Node archive SHA-256 mismatch"
}
Expand-Archive -LiteralPath $archivePath -DestinationPath $extractRoot -Force
$sourceRoot = Get-ChildItem -LiteralPath $extractRoot -Directory |
Where-Object { $_.Name -like 'node-v*' } |
Select-Object -First 1
if (-not $sourceRoot) {
throw 'Node archive did not contain the expected top-level directory'
}
Get-ChildItem -LiteralPath $sourceRoot.FullName -Force |
Copy-Item -Destination $payloadRoot -Recurse -Force
$payloadNode = Join-Path $payloadRoot 'node.exe'
$payloadNpm = Join-Path $payloadRoot 'npm.cmd'
$payloadNpx = Join-Path $payloadRoot 'npx.cmd'
if (-not (Test-Path -LiteralPath $payloadNode -PathType Leaf) -or
-not (Test-Path -LiteralPath $payloadNpm -PathType Leaf) -or
-not (Test-Path -LiteralPath $payloadNpx -PathType Leaf)) {
throw 'Node archive is missing node.exe, npm.cmd, or npx.cmd'
}
$version = (& $payloadNode --version).Trim()
if ($LASTEXITCODE -ne 0 -or $version -ne "v$($nodeSpec.version)") {
throw "Node self-check failed: expected v$($nodeSpec.version), got $version"
}
$marker = [ordered]@{
schema = 1
kind = 'node'
platformKey = $platformKey
version = $nodeSpec.version
source = $nodeSpec.source
release = $nodeSpec.release
asset = $asset.name
sha256 = $asset.sha256
size = [int64]$asset.size
installedAt = [DateTime]::UtcNow.ToString('o')
}
$markerJson = $marker | ConvertTo-Json
$markerEncoding = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllText(
(Join-Path $payloadRoot '.orkas-runtime.json'),
$markerJson,
$markerEncoding
)
if (Test-Path -LiteralPath $backupRoot) {
Remove-Item -Recurse -Force -LiteralPath $backupRoot
}
if (Test-Path -LiteralPath $nodeRoot) {
Move-Item -LiteralPath $nodeRoot -Destination $backupRoot
}
try {
Move-Item -LiteralPath $payloadRoot -Destination $nodeRoot
if (Test-Path -LiteralPath $backupRoot) {
Remove-Item -Recurse -Force -LiteralPath $backupRoot
}
} catch {
if (Test-Path -LiteralPath $nodeRoot) {
Remove-Item -Recurse -Force -LiteralPath $nodeRoot
}
if (Test-Path -LiteralPath $backupRoot) {
Move-Item -LiteralPath $backupRoot -Destination $nodeRoot
}
throw
}
Write-Host "[node-bootstrap] Node $($nodeSpec.version) is ready"
} finally {
if (Test-Path -LiteralPath $tempRoot) {
Remove-Item -Recurse -Force -LiteralPath $tempRoot
}
}