diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 904e1f5f..ae8b70f8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -92,16 +92,17 @@ jobs: # compose (leaf — TeamSpec parsing and preview planning) # delivery (→ runtime; consumed by external agents) # workload-router (→ persona-kit; consumed by cli) + # persona-registry (→ persona-kit + workload-router; consumed by local-surface + cli) # deploy (→ persona-kit + runtime; consumed by cli) # review-kit (→ persona-kit + runtime; authoring infrastructure) # turn-kit (→ persona-kit + runtime; multi-turn agent infrastructure) # mcp-workforce (→ persona-kit + runtime) # daytona-runner (no workspace deps) - # local-surface (→ deploy + persona-kit + runtime; consumed by cli) - # cli (→ persona-kit + workload-router + deploy + local-surface) + # local-surface (→ deploy + persona-kit + persona-registry + runtime; consumed by cli) + # cli (→ persona-kit + persona-registry + workload-router + deploy + local-surface) # agentworkforce (→ cli — umbrella wrapper, must publish last) # personas-core publishes via the separate publish-personas.yml workflow. - echo "packages=events persona-kit runtime compose delivery workload-router deploy review-kit turn-kit mcp-workforce daytona-runner local-surface cli agentworkforce" >> "$GITHUB_OUTPUT" + echo "packages=events persona-kit runtime compose delivery workload-router persona-registry deploy review-kit turn-kit mcp-workforce daytona-runner local-surface cli agentworkforce" >> "$GITHUB_OUTPUT" # Lockstep baseline heal. The workspace publishes every package at the # same version, so if any package's local version lags either its own diff --git a/.github/workflows/verify-publish.yml b/.github/workflows/verify-publish.yml index 917a536f..7ff91898 100644 --- a/.github/workflows/verify-publish.yml +++ b/.github/workflows/verify-publish.yml @@ -20,6 +20,7 @@ on: - '@agentworkforce/compose' - '@agentworkforce/delivery' - '@agentworkforce/workload-router' + - '@agentworkforce/persona-registry' - '@agentworkforce/deploy' - '@agentworkforce/review-kit' - '@agentworkforce/turn-kit' diff --git a/CHANGELOG.md b/CHANGELOG.md index b847f81c..d39e7398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `@agentworkforce/persona-registry` exposes the CLI's built-in, project, + personal, configured-directory, and exact JSON-path persona resolution as an + in-process SDK. +- `@agentworkforce/local-surface` adds a deduplicated `spawn:persona` Fleet + capability. It prepares interactive personas through `persona-kit`, layers a + concrete task over standing instructions, auto-syncs isolated-mount changes, + and requests verified Relay registration and readiness without invoking the + `agentworkforce` binary. It requires `@agent-relay/fleet` 11.5 or newer. + ## [4.1.15] - 2026-07-13 ### Added @@ -34,4 +45,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `@agentworkforce/cli`: requires `@relayfile/local-mount` ^0.10.23 (git-list population, `attachMount`, exported autosync state, and the external-teardown safety guard). - diff --git a/README.md b/README.md index 7dd382f0..84f7af2e 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,11 @@ grammar, skill staging, and sandbox mount behavior live in triggers. - `packages/workload-router` — TypeScript SDK for typed persona and routing profile resolution. +- `packages/persona-registry` — in-process resolution of persona ids and exact + JSON paths through the CLI's project, personal, configured, and built-in + source cascade. +- `packages/local-surface` — Agent Relay fleet-node adapters for proactive + personas and verified interactive `spawn:persona` launches. - `packages/cli` — command-line implementation used by the `agentworkforce` wrapper. - `packages/runtime` — deploy runtime facade and per-integration clients. diff --git a/packages/cli/package.json b/packages/cli/package.json index d9cf9e31..c099d524 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -15,6 +15,7 @@ "@agentworkforce/events": "workspace:*", "@agentworkforce/local-surface": "workspace:*", "@agentworkforce/persona-kit": "workspace:*", + "@agentworkforce/persona-registry": "workspace:*", "@agentworkforce/runtime": "workspace:*", "@agentworkforce/workload-router": "workspace:*", "@relayburn/sdk": "^2.5.2", diff --git a/packages/cli/src/local-personas.ts b/packages/cli/src/local-personas.ts index 012a52b5..30e9317a 100644 --- a/packages/cli/src/local-personas.ts +++ b/packages/cli/src/local-personas.ts @@ -1,1224 +1,4 @@ -import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'; -import { homedir } from 'node:os'; -import { dirname, isAbsolute, join, resolve as resolvePath } from 'node:path'; - -import { - CODEX_APPROVAL_POLICIES, - CODEX_SANDBOX_MODES, - HARNESS_VALUES, - SIDECAR_MD_MODES, - type CodexApprovalPolicy, - type CodexSandboxMode, - type Harness, - type HarnessSettings, - type McpServerSpec, - type PersonaInputSpec, - type PersonaMount, - type PersonaPermissions, - type PersonaSkill, - type PersonaSpec, - type PersonaTag, - type SidecarMdMode, - parseHarnessSettings -} from '@agentworkforce/persona-kit'; -import { listBuiltInPersonas, personaCatalog } from '@agentworkforce/workload-router'; - -/** - * User-defined persona override. Local files are partial overlays — only the - * fields you specify replace the inherited base; everything else cascades down - * through cwd → configured persona dirs → library. - * - * `extends` names the base explicitly by id or intent. If omitted and the file - * has no `intent`, the loader implicitly inherits from the same-id persona - * found in the next lower layer. Files with `intent` are standalone personas. - */ -export interface LocalPersonaOverride { - id: string; - extends?: string; - /** - * When present without `extends`, the file is a complete standalone - * persona instead of an overlay inheriting from a lower cascade layer. - */ - intent?: string; - /** - * Classification tags. When provided, replaces the inherited base's tags - * entirely (matching the replace-wholesale semantics used for `skills`). - */ - tags?: PersonaTag[]; - description?: string; - skills?: PersonaSpec['skills']; - inputs?: Record; - env?: Record; - mcpServers?: Record; - /** - * Relayfile mount policy. Pattern lists append to the inherited base so - * gitignore negations in the overlay can intentionally narrow/reopen scope. - */ - mount?: PersonaMount; - /** - * Permission policy. `allow` and `deny` append to the base's lists (dedup - * on merge); `mode` replaces the base's mode when set. - */ - permissions?: PersonaPermissions; - /** Replaces the inherited systemPrompt when set. */ - systemPrompt?: string; - /** Replaces the inherited harness when set. */ - harness?: Harness; - /** Replaces the inherited model when set. */ - model?: string; - /** Per-field harness settings override; merged on top of the inherited harnessSettings. */ - harnessSettings?: Partial; - /** - * Path to a `CLAUDE.md` sidecar, relative to this JSON file's directory. - * The loader stats the file and resolves it to an absolute path on the - * merged spec; missing files surface as load warnings rather than throws. - */ - claudeMd?: string; - claudeMdMode?: SidecarMdMode; - agentsMd?: string; - agentsMdMode?: SidecarMdMode; - claudeMdContent?: string; - agentsMdContent?: string; - /** @internal — directory of the JSON file that declared this override. */ - __sourceDir?: string; -} - -export type PersonaSource = string; - -/** - * Map an internal {@link PersonaSource} cascade label to the human-readable - * vocabulary surfaced in `agentworkforce list`, `sources list`, and the - * interactive picker: - * - * - `library` → `built-in` — bundled with `@agentworkforce/cli`, - * available to every user without an install step. - * - `user` → `personal` — `~/.agentworkforce/workforce/personas/`, - * i.e. personas a single user keeps across all repos. - * - `cwd` → `cwd` — `/.agentworkforce/workforce/personas/`, - * the working-tree dir; both installed library packs and - * hand-authored team overrides live here. Kept as-is - * because it's a precise pointer to a real directory. - * - `dir:N` → `dir:N` — extra configurable persona dirs (passed - * through unchanged so position is still legible). - * - * Internal strings are left alone so `--save-in-directory ` and the - * JSON outputs of `list` / `sources list` keep their existing values. - */ -export function formatPersonaSourceLabel(source: PersonaSource): string { - if (source === 'library') return 'built-in'; - if (source === 'user') return 'personal'; - return source; -} - -interface SourceLayer { - key: string; - source: PersonaSource; - dir: string; -} - -export interface PersonaSourceDirectory { - source: PersonaSource; - dir: string; - configurable: boolean; -} - -export interface PersonaSourceConfig { - configPath: string; - personaDirs: string[]; - defaultCreateTarget?: string; - userPersonaDir: string; - warnings: string[]; -} - -export interface LoadedLocalPersonas { - /** Final resolved specs by id, with the cascade applied (higher source dirs win). */ - byId: Map; - /** Where each id in `byId` was defined (top-most layer that declared it). */ - sources: Map; - /** - * Absolute path to the JSON file that produced each id in `byId`. Comes - * from the topmost layer that declared the id (the same layer - * `sources` records). Mutating tools — e.g. the post-session - * persona-improver flow — apply accepted patches at this path. - */ - paths: Map; - warnings: string[]; -} - -export interface LoadOptions { - cwd?: string; - /** - * Back-compat alias for userPersonaDir. Historically local personas lived - * directly in this directory. - */ - homeDir?: string; - userPersonaDir?: string; - workforceHomeDir?: string; - configPath?: string; - /** Full ordered list of configurable persona dirs after cwd and before library. */ - personaDirs?: string[]; - /** Override target used by `agentworkforce create` when set. Defaults to `cwd`. */ - defaultCreateTarget?: string; -} - -export function defaultWorkforceHomeDir(): string { - const override = process.env.AGENT_WORKFORCE_HOME?.trim(); - if (override) return override; - return join(homedir(), '.agentworkforce', 'workforce'); -} - -export function defaultUserPersonaDir(workforceHomeDir = defaultWorkforceHomeDir()): string { - const legacyOverride = process.env.AGENT_WORKFORCE_CONFIG_DIR?.trim(); - if (legacyOverride) return legacyOverride; - return join(workforceHomeDir, 'personas'); -} - -export function defaultPersonaConfigPath(workforceHomeDir = defaultWorkforceHomeDir()): string { - return join(workforceHomeDir, 'config.json'); -} - -export function defaultCwdPersonaDir(cwd: string): string { - return join(cwd, '.agentworkforce', 'workforce', 'personas'); -} - -export function expandHomePath(input: string): string { - if (input === '~') return homedir(); - if (input.startsWith('~/') || input.startsWith('~\\')) { - return join(homedir(), input.slice(2)); - } - return input; -} - -export function normalizePersonaDir(input: string, baseDir = process.cwd()): string { - const expanded = expandHomePath(input.trim()); - return isAbsolute(expanded) ? resolvePath(expanded) : resolvePath(baseDir, expanded); -} - -interface RawPersonaSourceConfig { - personaDirs?: string[]; - defaultCreateTarget?: string; -} - -function readRawPersonaSourceConfig( - path: string, - warnings: string[] -): RawPersonaSourceConfig | undefined { - if (!existsSync(path)) return undefined; - try { - const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown; - if (!isPlainObject(parsed)) { - warnings.push(`[config] ${path}: must be a JSON object`); - return undefined; - } - const dirs = parsed.personaDirs; - let personaDirs: string[] | undefined; - if ( - dirs !== undefined && - (!Array.isArray(dirs) || - dirs.some((dir) => typeof dir !== 'string' || !dir.trim())) - ) { - warnings.push(`[config] ${path}: personaDirs must be an array of non-empty strings`); - } else if (dirs !== undefined) { - personaDirs = dirs.map((dir) => normalizePersonaDir(dir, dirname(path))); - } - const defaultCreateTarget = parsed.defaultCreateTarget; - if ( - defaultCreateTarget !== undefined && - (typeof defaultCreateTarget !== 'string' || !defaultCreateTarget.trim()) - ) { - warnings.push(`[config] ${path}: defaultCreateTarget must be a non-empty string if provided`); - return personaDirs ? { personaDirs } : {}; - } - return { - ...(personaDirs ? { personaDirs } : {}), - ...(typeof defaultCreateTarget === 'string' ? { defaultCreateTarget: defaultCreateTarget.trim() } : {}) - }; - } catch (err) { - warnings.push(`[config] ${path}: ${(err as Error).message}`); - return undefined; - } -} - -function dedupeDirs(dirs: readonly string[], warnings: string[]): string[] { - const seen = new Set(); - const out: string[] = []; - for (const dir of dirs) { - const normalized = normalizePersonaDir(dir); - if (seen.has(normalized)) { - warnings.push(`[config] duplicate persona source directory ${normalized}; keeping first.`); - continue; - } - seen.add(normalized); - out.push(normalized); - } - return out; -} - -export function loadPersonaSourceConfig(options: LoadOptions = {}): PersonaSourceConfig { - const workforceHomeDir = options.workforceHomeDir ?? defaultWorkforceHomeDir(); - const userPersonaDir = normalizePersonaDir( - options.userPersonaDir ?? options.homeDir ?? defaultUserPersonaDir(workforceHomeDir) - ); - const configPath = normalizePersonaDir( - options.configPath ?? defaultPersonaConfigPath(workforceHomeDir) - ); - const warnings: string[] = []; - const rawConfig = readRawPersonaSourceConfig(configPath, warnings); - const configuredDirs = - options.personaDirs ?? rawConfig?.personaDirs ?? [userPersonaDir]; - const defaultCreateTarget = options.defaultCreateTarget ?? rawConfig?.defaultCreateTarget; - - return { - configPath, - personaDirs: dedupeDirs(configuredDirs, warnings), - ...(defaultCreateTarget ? { defaultCreateTarget } : {}), - userPersonaDir, - warnings - }; -} - -export function savePersonaSourceConfig( - personaDirs: readonly string[], - options: LoadOptions = {} -): PersonaSourceConfig { - const config = loadPersonaSourceConfig({ ...options, personaDirs: [...personaDirs] }); - mkdirSync(dirname(config.configPath), { recursive: true }); - const serialized = { - personaDirs: config.personaDirs, - ...(config.defaultCreateTarget ? { defaultCreateTarget: config.defaultCreateTarget } : {}) - }; - writeFileSync( - config.configPath, - JSON.stringify(serialized, null, 2) + '\n', - 'utf8' - ); - return config; -} - -function sourceForPersonaDir( - dir: string, - idx: number, - userPersonaDir: string -): PersonaSource { - return dir === userPersonaDir ? 'user' : `dir:${idx + 1}`; -} - -export function buildPersonaSourceDirectories( - options: LoadOptions = {} -): { directories: PersonaSourceDirectory[]; config: PersonaSourceConfig } { - const cwd = options.cwd ?? process.cwd(); - const config = loadPersonaSourceConfig(options); - const directories: PersonaSourceDirectory[] = [ - { - source: 'cwd', - dir: defaultCwdPersonaDir(cwd), - configurable: false - }, - ...config.personaDirs.map((dir, idx) => ({ - source: sourceForPersonaDir(dir, idx, config.userPersonaDir), - dir, - configurable: true - })) - ]; - return { directories, config }; -} - -function readLayerDir( - dir: string, - layer: SourceLayer, - warnings: string[], - filePaths: Map -): Map { - const out = new Map(); - if (!existsSync(dir)) return out; - - let entries: string[]; - try { - entries = readdirSync(dir).filter((n) => n.endsWith('.json')); - } catch (err) { - warnings.push(`[${layer.source}] could not read ${dir}: ${(err as Error).message}`); - return out; - } - - for (const file of entries) { - const path = join(dir, file); - try { - const raw = readFileSync(path, 'utf8'); - const parsed = parseOverride(JSON.parse(raw), `[${layer.source}] ${file}`); - parsed.__sourceDir = dir; - if (out.has(parsed.id)) { - warnings.push(`[${layer.source}] ${file}: duplicate id "${parsed.id}" within layer; skipping.`); - continue; - } - out.set(parsed.id, parsed); - filePaths.set(`${layer.key}:${parsed.id}`, path); - } catch (err) { - warnings.push(`[${layer.source}] ${file}: ${(err as Error).message}`); - } - } - return out; -} - -function isPlainObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -/** - * Sidecar path validation shared by local overrides and standalone specs. - * Mirrors {@link assertSafeRelativePath} but adds the `.md` suffix - * requirement called out in the schema. Throws on absolute paths, - * `..` segments, empty strings, or non-`.md` extensions. - */ -function assertSidecarPath(value: unknown, context: string): void { - if (typeof value !== 'string' || !value.trim()) { - throw new Error(`${context} must be a non-empty string`); - } - if ( - value.startsWith('/') || - value.startsWith('\\') || // covers `\persona.md` and `\\server\share\…` UNC - /^[A-Za-z]:/.test(value) // covers `C:\persona.md` and drive-relative `C:persona.md` - ) { - throw new Error(`${context} must be a relative path; got absolute "${value}"`); - } - const segments = value.split(/[\\/]+/); - if (segments.some((s) => s === '..')) { - throw new Error(`${context} must not contain ".." segments`); - } - if (!value.toLowerCase().endsWith('.md')) { - throw new Error(`${context} must end with .md`); - } -} - -function assertSidecarMode(value: unknown, context: string): void { - if (!SIDECAR_MD_MODES.includes(value as SidecarMdMode)) { - throw new Error(`${context} must be one of: ${SIDECAR_MD_MODES.join(', ')}`); - } -} - -function assertInlineSidecarContent(value: unknown, context: string): void { - if (typeof value !== 'string' || !value.trim()) { - throw new Error(`${context} must be a non-empty string`); - } -} - -function parseOverride(value: unknown, context: string): LocalPersonaOverride { - if (!isPlainObject(value)) { - throw new Error(`${context} must be a JSON object`); - } - const raw = value; - if (typeof raw.id !== 'string' || !raw.id.trim()) { - throw new Error(`${context}.id must be a non-empty string`); - } - if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) { - throw new Error(`${context}.extends must be a non-empty string if provided`); - } - if (raw.extends !== undefined && raw.intent !== undefined) { - throw new Error(`${context}.intent cannot be combined with .extends; omit extends for standalone personas`); - } - if (raw.intent !== undefined && (typeof raw.intent !== 'string' || !raw.intent.trim())) { - throw new Error(`${context}.intent must be a non-empty string if provided`); - } - if (raw.systemPrompt !== undefined && typeof raw.systemPrompt !== 'string') { - throw new Error(`${context}.systemPrompt must be a string if provided`); - } - if (raw.description !== undefined && typeof raw.description !== 'string') { - throw new Error(`${context}.description must be a string if provided`); - } - let normalizedTags: PersonaTag[] | undefined; - if (raw.tags !== undefined && raw.tags !== null) { - if (!Array.isArray(raw.tags)) { - throw new Error(`${context}.tags must be an array of strings if provided`); - } - const tags = new Set(); - for (const [idx, tag] of raw.tags.entries()) { - if (typeof tag !== 'string' || !tag.trim()) { - throw new Error(`${context}.tags[${idx}] must be a non-empty string`); - } - const trimmed = tag.trim(); - if (trimmed.length > 64) { - throw new Error(`${context}.tags[${idx}] must be ≤64 characters`); - } - tags.add(trimmed); - } - if (tags.size > 0) { - normalizedTags = Array.from(tags).sort() as PersonaTag[]; - } - } - - if (raw.skills !== undefined && !Array.isArray(raw.skills)) { - throw new Error(`${context}.skills must be an array if provided`); - } - const inputs = parseInputsShape(raw.inputs, `${context}.inputs`); - assertStringMap(raw.env, `${context}.env`); - assertMcpServersShape(raw.mcpServers, `${context}.mcpServers`); - assertMountShape(raw.mount, `${context}.mount`); - assertPermissionsShape(raw.permissions, `${context}.permissions`); - - if (raw.tiers !== undefined) { - throw new Error( - `${context}.tiers is no longer supported; declare harness/model/systemPrompt/harnessSettings at the top level` - ); - } - if (raw.defaultTier !== undefined) { - throw new Error( - `${context}.defaultTier is no longer supported (tiers have been removed)` - ); - } - if (raw.harness !== undefined) { - if (typeof raw.harness !== 'string' || !HARNESS_VALUES.includes(raw.harness as Harness)) { - throw new Error(`${context}.harness must be one of: ${HARNESS_VALUES.join(', ')}`); - } - } - if (raw.model !== undefined) { - if (typeof raw.model !== 'string' || !raw.model.trim()) { - throw new Error(`${context}.model must be a non-empty string if provided`); - } - } - if (raw.harnessSettings !== undefined) { - if (!isPlainObject(raw.harnessSettings)) { - throw new Error(`${context}.harnessSettings must be an object if provided`); - } - assertPartialHarnessSettingsShape(raw.harnessSettings, `${context}.harnessSettings`); - } - - if (raw.claudeMd !== undefined) assertSidecarPath(raw.claudeMd, `${context}.claudeMd`); - if (raw.agentsMd !== undefined) assertSidecarPath(raw.agentsMd, `${context}.agentsMd`); - if (raw.claudeMdContent !== undefined) { - assertInlineSidecarContent(raw.claudeMdContent, `${context}.claudeMdContent`); - } - if (raw.agentsMdContent !== undefined) { - assertInlineSidecarContent(raw.agentsMdContent, `${context}.agentsMdContent`); - } - // Mode is allowed without a same-layer path so an overlay can flip - // `extend` ↔ `overwrite` while inheriting the path from a lower layer. - if (raw.claudeMdMode !== undefined) assertSidecarMode(raw.claudeMdMode, `${context}.claudeMdMode`); - if (raw.agentsMdMode !== undefined) assertSidecarMode(raw.agentsMdMode, `${context}.agentsMdMode`); - - return { - id: raw.id, - extends: raw.extends as string | undefined, - intent: raw.intent as string | undefined, - ...(normalizedTags !== undefined ? { tags: normalizedTags } : {}), - description: raw.description as string | undefined, - skills: raw.skills as PersonaSpec['skills'] | undefined, - inputs, - env: raw.env as LocalPersonaOverride['env'], - mcpServers: raw.mcpServers as LocalPersonaOverride['mcpServers'], - mount: raw.mount as LocalPersonaOverride['mount'], - permissions: raw.permissions as LocalPersonaOverride['permissions'], - systemPrompt: raw.systemPrompt as string | undefined, - ...(raw.harness !== undefined ? { harness: raw.harness as Harness } : {}), - ...(raw.model !== undefined ? { model: raw.model as string } : {}), - ...(raw.harnessSettings !== undefined - ? { harnessSettings: raw.harnessSettings as Partial } - : {}), - ...(typeof raw.claudeMd === 'string' ? { claudeMd: raw.claudeMd } : {}), - ...(raw.claudeMdMode ? { claudeMdMode: raw.claudeMdMode as SidecarMdMode } : {}), - ...(typeof raw.agentsMd === 'string' ? { agentsMd: raw.agentsMd } : {}), - ...(raw.agentsMdMode ? { agentsMdMode: raw.agentsMdMode as SidecarMdMode } : {}), - ...(typeof raw.claudeMdContent === 'string' ? { claudeMdContent: raw.claudeMdContent } : {}), - ...(typeof raw.agentsMdContent === 'string' ? { agentsMdContent: raw.agentsMdContent } : {}) - }; -} - -const INPUT_NAME_RE = /^[A-Z_][A-Z0-9_]*$/; - -function assertInputName(name: string, context: string): void { - if (!INPUT_NAME_RE.test(name)) { - throw new Error(`${context} must be an env-style name matching ${INPUT_NAME_RE.source}`); - } -} - -function parseInputsShape( - value: unknown, - context: string -): Record | undefined { - if (value === undefined) return undefined; - if (!isPlainObject(value)) { - throw new Error(`${context} must be an object if provided`); - } - const out: Record = {}; - for (const [name, raw] of Object.entries(value)) { - assertInputName(name, `${context}.${name}`); - if (typeof raw === 'string') { - if (!raw) throw new Error(`${context}.${name} default must be non-empty`); - out[name] = { default: raw }; - continue; - } - if (!isPlainObject(raw)) { - throw new Error(`${context}.${name} must be a string default or an object`); - } - const spec = raw as Record; - if (spec.description !== undefined && (typeof spec.description !== 'string' || !spec.description.trim())) { - throw new Error(`${context}.${name}.description must be a non-empty string if provided`); - } - if (spec.env !== undefined) { - if (typeof spec.env !== 'string' || !spec.env.trim()) { - throw new Error(`${context}.${name}.env must be a non-empty string if provided`); - } - assertInputName(spec.env, `${context}.${name}.env`); - } - if (spec.default !== undefined && (typeof spec.default !== 'string' || !spec.default)) { - throw new Error(`${context}.${name}.default must be a non-empty string if provided`); - } - if (spec.optional !== undefined && typeof spec.optional !== 'boolean') { - throw new Error(`${context}.${name}.optional must be a boolean if provided`); - } - if (spec.optional === true && spec.default !== undefined) { - throw new Error(`${context}.${name} cannot combine optional:true with a default`); - } - out[name] = { - ...(typeof spec.description === 'string' ? { description: spec.description } : {}), - ...(typeof spec.env === 'string' ? { env: spec.env } : {}), - ...(typeof spec.default === 'string' ? { default: spec.default } : {}), - ...(spec.optional === true ? { optional: true } : {}) - }; - } - return Object.keys(out).length > 0 ? out : undefined; -} - -function assertStringMap(value: unknown, context: string): void { - if (value === undefined) return; - if (!isPlainObject(value)) { - throw new Error(`${context} must be an object if provided`); - } - for (const [k, v] of Object.entries(value)) { - if (typeof v !== 'string') { - throw new Error(`${context}.${k} must be a string`); - } - } -} - -function assertMcpServersShape(value: unknown, context: string): void { - if (value === undefined) return; - if (!isPlainObject(value)) { - throw new Error(`${context} must be an object if provided`); - } - for (const [name, spec] of Object.entries(value)) { - const path = `${context}.${name}`; - if (!isPlainObject(spec)) { - throw new Error(`${path} must be an object`); - } - const type = spec.type; - if (type !== 'http' && type !== 'sse' && type !== 'stdio') { - throw new Error(`${path}.type must be one of: http, sse, stdio`); - } - if (type === 'stdio') { - if (typeof spec.command !== 'string' || !spec.command.trim()) { - throw new Error(`${path}.command must be a non-empty string`); - } - if (spec.args !== undefined) { - if (!Array.isArray(spec.args) || spec.args.some((a) => typeof a !== 'string')) { - throw new Error(`${path}.args must be an array of strings`); - } - } - assertStringMap(spec.env, `${path}.env`); - } else { - if (typeof spec.url !== 'string' || !spec.url.trim()) { - throw new Error(`${path}.url must be a non-empty string`); - } - assertStringMap(spec.headers, `${path}.headers`); - } - } -} - -function assertPermissionsShape(value: unknown, context: string): void { - if (value === undefined) return; - if (!isPlainObject(value)) { - throw new Error(`${context} must be an object if provided`); - } - for (const key of ['allow', 'deny'] as const) { - const list = value[key]; - if (list === undefined) continue; - if (!Array.isArray(list) || list.some((s) => typeof s !== 'string' || !s.trim())) { - throw new Error(`${context}.${key} must be an array of non-empty strings`); - } - } - const mode = value.mode; - if (mode !== undefined && typeof mode !== 'string') { - throw new Error(`${context}.mode must be a string if provided`); - } -} - -function assertMountShape(value: unknown, context: string): void { - if (value === undefined) return; - if (!isPlainObject(value)) { - throw new Error(`${context} must be an object if provided`); - } - for (const key of ['ignoredPatterns', 'readonlyPatterns'] as const) { - const list = value[key]; - if (list === undefined) continue; - if (!Array.isArray(list) || list.some((s) => typeof s !== 'string' || !s.trim())) { - throw new Error(`${context}.${key} must be an array of non-empty strings`); - } - } -} - -function assertPartialHarnessSettingsShape(value: Record, context: string): void { - const { - reasoning, - timeoutSeconds, - sandboxMode, - approvalPolicy, - workspaceWriteNetworkAccess, - webSearch, - dangerouslyBypassApprovalsAndSandbox - } = value; - if ( - reasoning !== undefined && - reasoning !== 'low' && - reasoning !== 'medium' && - reasoning !== 'high' - ) { - throw new Error(`${context}.reasoning must be one of: low, medium, high`); - } - if ( - timeoutSeconds !== undefined && - (typeof timeoutSeconds !== 'number' || !Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) - ) { - throw new Error(`${context}.timeoutSeconds must be a positive number`); - } - if ( - sandboxMode !== undefined && - !CODEX_SANDBOX_MODES.includes(sandboxMode as CodexSandboxMode) - ) { - throw new Error(`${context}.sandboxMode must be one of: ${CODEX_SANDBOX_MODES.join(', ')}`); - } - if ( - approvalPolicy !== undefined && - !CODEX_APPROVAL_POLICIES.includes(approvalPolicy as CodexApprovalPolicy) - ) { - throw new Error( - `${context}.approvalPolicy must be one of: ${CODEX_APPROVAL_POLICIES.join(', ')}` - ); - } - if (workspaceWriteNetworkAccess !== undefined && typeof workspaceWriteNetworkAccess !== 'boolean') { - throw new Error(`${context}.workspaceWriteNetworkAccess must be a boolean`); - } - if (webSearch !== undefined && typeof webSearch !== 'boolean') { - throw new Error(`${context}.webSearch must be a boolean`); - } - if ( - dangerouslyBypassApprovalsAndSandbox !== undefined && - typeof dangerouslyBypassApprovalsAndSandbox !== 'boolean' - ) { - throw new Error(`${context}.dangerouslyBypassApprovalsAndSandbox must be a boolean`); - } -} - -function findInLibrary(key: string): PersonaSpec | undefined { - const byIntent = (personaCatalog as Record)[key]; - if (byIntent) return byIntent; - for (const spec of listBuiltInPersonas()) { - if (spec.id === key) return spec; - } - return undefined; -} - -function isStandaloneOverride( - override: LocalPersonaOverride -): override is LocalPersonaOverride & { intent: string } { - return override.extends === undefined && override.intent !== undefined; -} - -function requireStandaloneField(value: T | undefined, context: string): T { - if (value === undefined) { - throw new Error(`${context} is required for standalone personas`); - } - return value; -} - -function assertStandaloneHarnessSettings( - settings: Record, - context: string -): HarnessSettings { - return parseHarnessSettings(settings, context); -} - -function standaloneSpecFromOverride( - override: LocalPersonaOverride & { intent: string }, - sidecarWarnings: string[] = [], - cwd = process.cwd() -): PersonaSpec { - const context = `standalone persona "${override.id}"`; - const harness = requireStandaloneField(override.harness, `${context}.harness`); - if (!HARNESS_VALUES.includes(harness)) { - throw new Error(`${context}.harness must be one of: ${HARNESS_VALUES.join(', ')}`); - } - const model = requireStandaloneField(override.model, `${context}.model`); - if (typeof model !== 'string' || !model.trim()) { - throw new Error(`${context}.model must be a non-empty string`); - } - const fallbackSystemPrompt = override.claudeMdContent ?? override.agentsMdContent; - const systemPrompt = - typeof override.systemPrompt === 'string' && override.systemPrompt.trim() - ? override.systemPrompt - : fallbackSystemPrompt; - if (typeof systemPrompt !== 'string' || !systemPrompt.trim()) { - throw new Error(`${context}.systemPrompt must be a non-empty string`); - } - const settingsRaw = override.harnessSettings; - if (!settingsRaw || !isPlainObject(settingsRaw)) { - throw new Error(`${context}.harnessSettings must be an object`); - } - const harnessSettings = assertStandaloneHarnessSettings( - settingsRaw as Record, - `${context}.harnessSettings` - ); - - const inputs = override.inputs; - const env = override.env; - const mcpServers = override.mcpServers; - const mount = override.mount; - const permissions = override.permissions; - - const claudeMdContent = override.claudeMdContent; - let claudeMd: string | undefined; - if (override.claudeMd !== undefined && claudeMdContent === undefined) { - const { abs, warning } = resolveSidecarPath( - override.claudeMd, - override.__sourceDir, - `[${override.id}].claudeMd` - ); - if (warning) sidecarWarnings.push(warning); - claudeMd = abs; - } - const agentsMdContent = override.agentsMdContent; - let agentsMd: string | undefined; - if (override.agentsMd !== undefined && agentsMdContent === undefined) { - const { abs, warning } = resolveSidecarPath( - override.agentsMd, - override.__sourceDir, - `[${override.id}].agentsMd` - ); - if (warning) sidecarWarnings.push(warning); - agentsMd = abs; - } - - return { - id: override.id, - intent: override.intent, - // Tags are optional per cloud#553 (denormalized catalog metadata). - ...(override.tags ? { tags: override.tags } : {}), - description: requireStandaloneField(override.description, `${context}.description`), - skills: resolveLocalSkillSources( - override.skills ?? [], - override.__sourceDir, - override.id, - sidecarWarnings, - cwd - ), - ...(inputs ? { inputs } : {}), - harness, - model, - systemPrompt, - harnessSettings, - ...(env ? { env } : {}), - ...(mcpServers ? { mcpServers } : {}), - ...(mount ? { mount } : {}), - ...(permissions ? { permissions } : {}), - ...(claudeMd ? { claudeMd } : {}), - ...(override.claudeMdMode ? { claudeMdMode: override.claudeMdMode } : {}), - ...(agentsMd ? { agentsMd } : {}), - ...(override.agentsMdMode ? { agentsMdMode: override.agentsMdMode } : {}), - ...(claudeMdContent ? { claudeMdContent } : {}), - ...(agentsMdContent ? { agentsMdContent } : {}) - }; -} - -/** - * Mutual-recursion with resolveInLayer: given a base key, walk strictly-lower - * layers until we find a persona with that id (local layers) or an id/intent - * match in the library. Returns a fully-merged PersonaSpec or undefined. - */ -function findInLowerLayers( - key: string, - startLayerIdx: number, - layers: readonly SourceLayer[], - overrides: Map>, - resolving: Set, - sidecarWarnings: string[], - cwd: string -): PersonaSpec | undefined { - for (let i = startLayerIdx; i < layers.length; i++) { - const layer = layers[i]; - const layerOverrides = overrides.get(layer.key); - if (!layerOverrides) continue; - const overrideId = findOverrideIdInLayer(key, layerOverrides, layer.source); - if (overrideId) { - return resolveInLayer(overrideId, i, layers, overrides, resolving, sidecarWarnings, cwd); - } - } - return findInLibrary(key); -} - -function findOverrideIdInLayer( - key: string, - layerOverrides: Map, - source: PersonaSource -): string | undefined { - if (layerOverrides.has(key)) return key; - - const matches: string[] = []; - for (const [id, override] of layerOverrides) { - if (override.intent === key) matches.push(id); - } - if (matches.length > 1) { - throw new Error( - `extends "${key}" is ambiguous in ${source}; matched local persona intents on ids: ${matches.join(', ')}` - ); - } - return matches[0]; -} - -function resolveInLayer( - id: string, - layerIdx: number, - layers: readonly SourceLayer[], - overrides: Map>, - resolving: Set, - sidecarWarnings: string[], - cwd: string -): PersonaSpec { - const layer = layers[layerIdx]; - const key = `${layer.key}:${id}`; - if (resolving.has(key)) { - throw new Error(`extends cycle detected through ${[...resolving, key].join(' -> ')}`); - } - resolving.add(key); - try { - const override = overrides.get(layer.key)?.get(id); - if (!override) { - throw new Error(`internal: resolveInLayer called for missing ${key}`); - } - if (isStandaloneOverride(override)) { - return standaloneSpecFromOverride(override, sidecarWarnings, cwd); - } - const baseKey = override.extends ?? override.id; - const base = findInLowerLayers( - baseKey, - layerIdx + 1, - layers, - overrides, - resolving, - sidecarWarnings, - cwd - ); - if (!base) { - const lowerLayers = [ - ...layers.slice(layerIdx + 1).map((lower) => lower.source), - 'library' - ].join(', '); - const hint = override.extends - ? `extends "${override.extends}" does not match any persona in lower layers (${lowerLayers})` - : `no lower-layer persona with id "${override.id}" to implicitly inherit from; add extends or define the persona in a lower layer`; - throw new Error(hint); - } - return mergeOverride(base, override, sidecarWarnings, cwd); - } finally { - resolving.delete(key); - } -} - -// Local skill source detection mirrors persona-kit's local provider: a -// non-URL path ending in `.md`. Anything else (prpm refs, skill.sh URLs) -// passes through untouched. -const LOCAL_SKILL_MD_RE = /\.md$/i; -const SKILL_URL_PREFIX_RE = /^[a-z][a-z0-9+.-]*:\/\//i; - -function isFile(path: string): boolean { - try { - return statSync(path).isFile(); - } catch { - return false; - } -} - -/** - * Resolve relative local skill sources (`./skills/foo.md`) declared by an - * override against the directory of the JSON file that declared them, the - * same way sidecar paths resolve. Two fallbacks cover the other layouts in - * the wild: persona packs keep `skills/` at the package root (one level - * above `personas/`), and installer-rewritten specs use cwd-relative - * `__assets` paths. - * - * Missing files surface as warnings and the skill is dropped, matching - * sidecar semantics. A broken pointer must not abort the launch: skill - * installs run as one `&&` chain, so a failing `cp` would otherwise kill - * the harness spawn AND prevent the skill cache marker from being written, - * forcing every subsequent spawn to re-download all skills. - */ -function resolveLocalSkillSources( - skills: readonly PersonaSkill[], - sourceDir: string | undefined, - personaId: string, - warnings: string[], - cwd = process.cwd() -): PersonaSkill[] { - return skills.flatMap((skill) => { - const source = skill.source; - if ( - typeof source !== 'string' || - SKILL_URL_PREFIX_RE.test(source) || - !LOCAL_SKILL_MD_RE.test(source) - ) { - return [skill]; - } - if (isAbsolute(source)) { - if (isFile(source)) return [skill]; - warnings.push( - `[${personaId}].skills "${skill.id}": local skill file not found at ${source}; skipping skill` - ); - return []; - } - const candidates = [ - ...(sourceDir - ? [resolvePath(sourceDir, source), resolvePath(sourceDir, '..', source)] - : []), - resolvePath(cwd, source) - ]; - const resolved = candidates.find(isFile); - if (!resolved) { - warnings.push( - `[${personaId}].skills "${skill.id}": local skill file not found (tried ${candidates.join(', ')}); skipping skill` - ); - return []; - } - return [{ ...skill, source: resolved }]; - }); -} - -/** - * Resolve a sidecar markdown path declared on `override` against the - * directory of the JSON file that declared it. Returns the absolute path - * along with any warnings about missing files; missing-file is non-fatal - * (the field is dropped from the resolved spec) so a developer iterating - * locally doesn't get blocked by a typo. - */ -function resolveSidecarPath( - relPath: string | undefined, - sourceDir: string | undefined, - label: string -): { abs?: string; warning?: string } { - if (!relPath) return {}; - if (!sourceDir) { - return { warning: `${label}: cannot resolve "${relPath}" without a source directory` }; - } - const abs = resolvePath(sourceDir, relPath); - let stat; - try { - stat = statSync(abs); - } catch (err) { - const e = err as NodeJS.ErrnoException; - if (e.code === 'ENOENT' || e.code === 'ENOTDIR') { - return { warning: `${label}: sidecar file not found at ${abs}` }; - } - // Surface real I/O failures (permissions, etc.) — silently treating - // an EACCES as "missing" hides config bugs from developers. - return { warning: `${label}: sidecar at ${abs} is not readable: ${e.message}` }; - } - if (!stat.isFile()) { - return { warning: `${label}: sidecar at ${abs} is not a file` }; - } - return { abs }; -} - -function mergeOverride( - base: PersonaSpec, - override: LocalPersonaOverride, - sidecarWarnings: string[] = [], - cwd = process.cwd() -): PersonaSpec { - const harness = override.harness ?? base.harness; - const model = override.model ?? base.model; - const systemPrompt = override.systemPrompt ?? base.systemPrompt; - const harnessSettings: HarnessSettings = { - ...base.harnessSettings, - ...(override.harnessSettings ?? {}) - }; - - const env = - override.env || base.env - ? { ...(base.env ?? {}), ...(override.env ?? {}) } - : undefined; - const inputs = - override.inputs || base.inputs - ? { ...(base.inputs ?? {}), ...(override.inputs ?? {}) } - : undefined; - const mcpServers = - override.mcpServers || base.mcpServers - ? { ...(base.mcpServers ?? {}), ...(override.mcpServers ?? {}) } - : undefined; - const mount = mergeMount(base.mount, override.mount); - const permissions = mergePermissions(base.permissions, override.permissions); - - // When the override sets a new path, the override owns the channel — - // drop inherited `*Content` so the override path isn't shadowed by an - // inlined built-in body. When the override leaves the path alone, the - // inherited content (if any) stays. - let claudeMd: string | undefined = base.claudeMd; - let claudeMdContent: string | undefined = base.claudeMdContent; - if (override.claudeMdContent !== undefined) { - claudeMd = undefined; - claudeMdContent = override.claudeMdContent; - } else if (override.claudeMd !== undefined) { - const { abs, warning } = resolveSidecarPath( - override.claudeMd, - override.__sourceDir, - `[${override.id}].claudeMd` - ); - if (warning) sidecarWarnings.push(warning); - claudeMd = abs; - claudeMdContent = undefined; - } - let agentsMd: string | undefined = base.agentsMd; - let agentsMdContent: string | undefined = base.agentsMdContent; - if (override.agentsMdContent !== undefined) { - agentsMd = undefined; - agentsMdContent = override.agentsMdContent; - } else if (override.agentsMd !== undefined) { - const { abs, warning } = resolveSidecarPath( - override.agentsMd, - override.__sourceDir, - `[${override.id}].agentsMd` - ); - if (warning) sidecarWarnings.push(warning); - agentsMd = abs; - agentsMdContent = undefined; - } - const claudeMdMode = override.claudeMdMode ?? base.claudeMdMode; - const agentsMdMode = override.agentsMdMode ?? base.agentsMdMode; - - const mergedTags = override.tags ?? base.tags; - // Skills replace wholesale, so resolution context is unambiguous: an - // override's skills resolve against its own __sourceDir; inherited skills - // were already resolved when the base layer was built. - const skills = - override.skills !== undefined - ? resolveLocalSkillSources( - override.skills, - override.__sourceDir, - override.id, - sidecarWarnings, - cwd - ) - : base.skills; - return { - id: override.id, - intent: base.intent, - ...(mergedTags ? { tags: mergedTags } : {}), - description: override.description ?? base.description, - skills, - ...(inputs ? { inputs } : {}), - harness, - model, - systemPrompt, - harnessSettings, - ...(env ? { env } : {}), - ...(mcpServers ? { mcpServers } : {}), - ...(mount ? { mount } : {}), - ...(permissions ? { permissions } : {}), - ...(claudeMd ? { claudeMd } : {}), - ...(claudeMdMode ? { claudeMdMode } : {}), - ...(agentsMd ? { agentsMd } : {}), - ...(agentsMdMode ? { agentsMdMode } : {}), - ...(claudeMdContent ? { claudeMdContent } : {}), - ...(agentsMdContent ? { agentsMdContent } : {}) - }; -} - -/** - * Test-only seam. Built-in personas are the only specs that can carry - * `claudeMdContent` / `agentsMdContent` (the catalog generator inlines - * sibling `.md` files at build time), and none ship sidecars today — - * so the file-based loader path can't be used to produce a `base` with - * inherited content. This export lets regression tests construct that - * scenario directly. - * - * @internal - */ -export const __mergeOverrideForTests = mergeOverride; - -function mergeMount( - base: PersonaMount | undefined, - override: PersonaMount | undefined -): PersonaMount | undefined { - if (!base && !override) return undefined; - const ignoredPatterns = [ - ...(base?.ignoredPatterns ?? []), - ...(override?.ignoredPatterns ?? []) - ]; - const readonlyPatterns = [ - ...(base?.readonlyPatterns ?? []), - ...(override?.readonlyPatterns ?? []) - ]; - if (ignoredPatterns.length === 0 && readonlyPatterns.length === 0) return undefined; - return { - ...(ignoredPatterns.length > 0 ? { ignoredPatterns } : {}), - ...(readonlyPatterns.length > 0 ? { readonlyPatterns } : {}) - }; -} - -function mergePermissions( - base: PersonaPermissions | undefined, - override: PersonaPermissions | undefined -): PersonaPermissions | undefined { - if (!base && !override) return undefined; - const allow = dedupe([...(base?.allow ?? []), ...(override?.allow ?? [])]); - const deny = dedupe([...(base?.deny ?? []), ...(override?.deny ?? [])]); - const mode = override?.mode ?? base?.mode; - const out: PersonaPermissions = {}; - if (allow.length > 0) out.allow = allow; - if (deny.length > 0) out.deny = deny; - if (mode) out.mode = mode; - return Object.keys(out).length > 0 ? out : undefined; -} - -function dedupe(values: string[]): string[] { - return [...new Set(values)]; -} - -export function loadLocalPersonas(options: LoadOptions = {}): LoadedLocalPersonas { - const cwd = options.cwd ?? process.cwd(); - const sourceDirs = buildPersonaSourceDirectories(options); - const warnings: string[] = [...sourceDirs.config.warnings]; - const layers: SourceLayer[] = sourceDirs.directories.map((sourceDir, idx) => ({ - key: `${idx}:${sourceDir.source}:${sourceDir.dir}`, - source: sourceDir.source, - dir: sourceDir.dir - })); - - const overrides = new Map>(); - const layerFilePaths = new Map(); - for (const layer of layers) { - overrides.set(layer.key, readLayerDir(layer.dir, layer, warnings, layerFilePaths)); - } - - const byId = new Map(); - const sources = new Map(); - const paths = new Map(); - - for (let i = 0; i < layers.length; i++) { - const layer = layers[i]; - const layerOverrides = overrides.get(layer.key); - if (!layerOverrides) continue; - for (const id of layerOverrides.keys()) { - if (byId.has(id)) continue; // higher-layer already won - const sidecarWarnings: string[] = []; - try { - const resolved = resolveInLayer(id, i, layers, overrides, new Set(), sidecarWarnings, cwd); - byId.set(id, resolved); - sources.set(id, layer.source); - const filePath = layerFilePaths.get(`${layer.key}:${id}`); - if (filePath) paths.set(id, filePath); - for (const warning of sidecarWarnings) { - warnings.push(`[${layer.source}] ${warning}`); - } - } catch (err) { - warnings.push(`[${layer.source}] ${id}: ${(err as Error).message}`); - } - } - } - - return { byId, sources, paths, warnings }; -} +// Backwards-compatible source import for CLI internals and tests. The registry +// implementation is an SDK now so non-CLI callers resolve the exact same +// cwd → configured → built-in cascade without invoking `agentworkforce`. +export * from '@agentworkforce/persona-registry'; diff --git a/packages/local-surface/README.md b/packages/local-surface/README.md new file mode 100644 index 00000000..f2578cc4 --- /dev/null +++ b/packages/local-surface/README.md @@ -0,0 +1,37 @@ +# @agentworkforce/local-surface + +Compose AgentWorkforce personas into Agent Relay fleet nodes without starting +the `agentworkforce` CLI. + +## Interactive persona spawning + +`defineWorkforcePersonaSpawnNode` advertises `spawn:persona`. The target node +resolves persona ids and JSON paths through the same project, personal, +configured-directory, and built-in registry as `agentworkforce agent`. + +```ts +import { serveNode } from '@agent-relay/fleet'; +import { defineWorkforcePersonaSpawnNode } from '@agentworkforce/local-surface'; + +const definition = defineWorkforcePersonaSpawnNode({ + nodeName: 'workforce-personas', + cwd: process.cwd() +}); + +await serveNode({ definition, connection }); +``` + +The capability prepares the persona in process with `persona-kit`, including +its skills, MCP servers, sidecars, harness, model, and harness settings. A +request `task` is delivered separately as the concrete assignment. Concurrent +requests for the same node, project, persona, and agent name share one launch, and the +Relay broker verifies node registration plus the harness `worker_ready` +handshake before the action succeeds. This path requires Agent Relay 11.5 or +newer. The isolated mount auto-syncs agent changes back to the project and +flushes once more during teardown. + +## Proactive event persona + +`defineWorkforcePersonaNode` remains the long-lived channel `onMessage` surface. +It composes `@agentworkforce/deploy` for a persona that consumes Relay message +events rather than launching an interactive worker per request. diff --git a/packages/local-surface/package.json b/packages/local-surface/package.json index ae711d10..92a4f4fe 100644 --- a/packages/local-surface/package.json +++ b/packages/local-surface/package.json @@ -32,9 +32,10 @@ "lint": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@agent-relay/fleet": "^10.1.0", + "@agent-relay/fleet": "^11.5.0", "@agentworkforce/deploy": "workspace:*", "@agentworkforce/persona-kit": "workspace:*", + "@agentworkforce/persona-registry": "workspace:*", "@agentworkforce/runtime": "workspace:*" } } diff --git a/packages/local-surface/src/index.ts b/packages/local-surface/src/index.ts index 71dfd657..35a28334 100644 --- a/packages/local-surface/src/index.ts +++ b/packages/local-surface/src/index.ts @@ -8,6 +8,17 @@ import { import { deploy as deployPersona, type DeployIO, type ModeLaunchHandle } from '@agentworkforce/deploy'; import type { RawGatewayEnvelope } from '@agentworkforce/runtime'; +export { + WORKFORCE_PERSONA_SPAWN_CAPABILITY, + __setPersonaSpawnImplementationsForTest, + defineWorkforcePersonaSpawnNode, + workforcePersonaSpawnCapability, + type DefineWorkforcePersonaSpawnNodeOptions, + type WorkforcePersonaSpawnInput, + type WorkforcePersonaSpawnOptions, + type WorkforcePersonaSpawnResult +} from './persona-spawn.js'; + // Test seam: swap the `deploy()` call `launchPersonaRunner` makes without // changing `defineWorkforcePersonaNode`'s public signature. Not part of the // package's public contract — used by this package's own tests only. diff --git a/packages/local-surface/src/persona-spawn.test.ts b/packages/local-surface/src/persona-spawn.test.ts new file mode 100644 index 00000000..a654d305 --- /dev/null +++ b/packages/local-surface/src/persona-spawn.test.ts @@ -0,0 +1,155 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { invokeNodeHandler, type FleetActionContext } from '@agent-relay/fleet'; +import type { PersonaSpawnPlan } from '@agentworkforce/persona-kit'; +import type { ResolvedPersonaReference } from '@agentworkforce/persona-registry'; + +import { + __setPersonaSpawnImplementationsForTest, + defineWorkforcePersonaSpawnNode +} from './persona-spawn.js'; + +const resolved: ResolvedPersonaReference = { + source: 'built-in', + warnings: [], + spec: { + id: 'reviewer', + intent: 'review', + description: 'Reviews code', + skills: [], + harness: 'codex', + model: 'openai-codex/test', + systemPrompt: 'Standing review instructions', + harnessSettings: { reasoning: 'medium', timeoutSeconds: 60 } + }, + selection: { + personaId: 'reviewer', + harness: 'codex', + model: 'openai-codex/test', + systemPrompt: 'Standing review instructions', + harnessSettings: { reasoning: 'medium', timeoutSeconds: 60 }, + skills: [], + rationale: 'test' + } +}; + +const plan: PersonaSpawnPlan = { + persona: resolved.selection, + cli: 'codex', + args: ['-m', 'test'], + initialPrompt: 'Standing review instructions', + configFiles: [], + skills: { harness: 'codex', installs: [] }, + sidecars: [], + inputs: [], + env: {} +}; + +test('coalesces concurrent node/project/persona/name launches and layers the task', async () => { + let executeCalls = 0; + let spawnCalls = 0; + let resolveSpawn: ((value: unknown) => void) | undefined; + let signalSpawnEntered: (() => void) | undefined; + const spawnEntered = new Promise((resolve) => { + signalSpawnEntered = resolve; + }); + const spawnPending = new Promise((resolve) => { + resolveSpawn = resolve; + }); + __setPersonaSpawnImplementationsForTest({ + resolvePersona: () => resolved, + buildPlan: () => plan, + checkFleetCompatibility: () => undefined, + executePlan: async (_plan, options) => { + executeCalls += 1; + assert.equal(options.mount?.autoSync, true); + return { cwd: '/tmp/persona-runtime', dispose: async () => undefined }; + } + }); + + const node = defineWorkforcePersonaSpawnNode({ nodeName: 'persona-node', cwd: '/tmp/project' }); + const spawnAgent = async (input: unknown) => { + spawnCalls += 1; + signalSpawnEntered?.(); + const value = input as { + initialTask?: string; + agent: { + args?: string[]; + model?: string; + harness_config?: { metadata?: Record }; + }; + }; + assert.equal(value.initialTask, 'Review PR 42'); + assert.deepEqual(value.agent.args, ['-m', 'test', 'Standing review instructions']); + assert.equal(value.agent.model, 'openai-codex/test'); + assert.deepEqual(value.agent.harness_config?.metadata, { + workforce_persona: 'reviewer', + verify_ready: true, + require_node_registration: true + }); + return spawnPending; + }; + const ctx = { + node: { name: 'persona-node', capabilities: ['spawn:persona'] }, + relay: { sendMessage: async () => undefined }, + spawnAgent + } satisfies FleetActionContext; + + try { + const first = invokeNodeHandler(node, 'spawn:persona', { + name: 'reviewer-42', + persona: 'reviewer', + task: 'Review PR 42' + }, ctx); + const second = invokeNodeHandler(node, 'spawn:persona', { + name: 'reviewer-42', + persona: 'reviewer', + task: 'Review PR 42' + }, ctx); + await spawnEntered; + assert.equal(executeCalls, 1); + assert.equal(spawnCalls, 1); + resolveSpawn?.({ ready: true }); + const [a, b] = await Promise.all([first, second]); + assert.deepEqual(a, b); + assert.equal((a as { persona: string }).persona, 'reviewer'); + } finally { + __setPersonaSpawnImplementationsForTest(); + } +}); + +test('does not coalesce concurrent launches with distinct agent names', async () => { + let executeCalls = 0; + let spawnCalls = 0; + __setPersonaSpawnImplementationsForTest({ + resolvePersona: () => resolved, + buildPlan: () => plan, + checkFleetCompatibility: () => undefined, + executePlan: async () => { + executeCalls += 1; + return { cwd: `/tmp/persona-runtime-${executeCalls}`, dispose: async () => undefined }; + } + }); + + const node = defineWorkforcePersonaSpawnNode({ nodeName: 'persona-node', cwd: '/tmp/project' }); + const ctx = { + node: { name: 'persona-node', capabilities: ['spawn:persona'] }, + relay: { sendMessage: async () => undefined }, + spawnAgent: async () => { + spawnCalls += 1; + return { ready: true }; + } + } satisfies FleetActionContext; + + try { + await Promise.all([ + invokeNodeHandler(node, 'spawn:persona', { name: 'reviewer-42', persona: 'reviewer' }, ctx), + invokeNodeHandler(node, 'spawn:persona', { name: 'reviewer-43', persona: 'reviewer' }, ctx) + ]); + assert.equal(executeCalls, 2); + assert.equal(spawnCalls, 2); + } finally { + __setPersonaSpawnImplementationsForTest(); + } +}); diff --git a/packages/local-surface/src/persona-spawn.ts b/packages/local-surface/src/persona-spawn.ts new file mode 100644 index 00000000..5f063545 --- /dev/null +++ b/packages/local-surface/src/persona-spawn.ts @@ -0,0 +1,294 @@ +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join, resolve as resolvePath } from 'node:path'; + +import * as fleetSdk from '@agent-relay/fleet'; +import { + action, + defineNode, + type FleetActionContext, + type FleetActionDefinition, + type FleetNodeDefinition +} from '@agent-relay/fleet'; +import { + buildPersonaSpawnPlan, + executePersonaSpawnPlan, + type ExecutionHandle, + type PersonaSpawnPlan +} from '@agentworkforce/persona-kit'; +import { + resolvePersonaReference, + type LoadOptions, + type ResolvedPersonaReference +} from '@agentworkforce/persona-registry'; + +export const WORKFORCE_PERSONA_SPAWN_CAPABILITY = 'spawn:persona'; + +export interface WorkforcePersonaSpawnInput { + /** Agent Relay identity to register for the launched harness. */ + name: string; + /** Persona id, built-in intent, or JSON path. */ + persona: string; + /** Concrete assignment layered over the persona's standing instructions. */ + task?: string; + /** Project cwd used for registry lookup and the isolated runtime mount. */ + cwd?: string; + /** Relay channels the persona should join. */ + channels?: string[]; +} + +export interface WorkforcePersonaSpawnResult { + spawned: true; + name: string; + persona: string; + harness: string; + model: string; + source: string; + result: unknown; +} + +export interface WorkforcePersonaSpawnOptions { + /** Default project cwd. Request-local `cwd` wins. */ + cwd?: string; + /** Registry source configuration shared with `agentworkforce agent`. */ + registry?: Omit; + /** Forward non-fatal registry warnings to the node host. */ + onWarning?: (warning: string) => void; +} + +export interface DefineWorkforcePersonaSpawnNodeOptions extends WorkforcePersonaSpawnOptions { + nodeName: string; + maxAgents?: number; + tags?: string[]; + version?: string; +} + +interface PreparedPersonaExecution { + handle: ExecutionHandle; + scratchDir: string; + plan: PersonaSpawnPlan; + resolved: ResolvedPersonaReference; +} + +type ResolvePersona = typeof resolvePersonaReference; +type BuildPlan = typeof buildPersonaSpawnPlan; +type ExecutePlan = typeof executePersonaSpawnPlan; +type CheckFleetCompatibility = () => void; + +let resolvePersonaImpl: ResolvePersona = resolvePersonaReference; +let buildPlanImpl: BuildPlan = buildPersonaSpawnPlan; +let executePlanImpl: ExecutePlan = executePersonaSpawnPlan; +let checkFleetCompatibilityImpl: CheckFleetCompatibility = assertFleetCompatibility; + +/** @internal Test seam for SDK orchestration; not part of the stable API. */ +export function __setPersonaSpawnImplementationsForTest(input?: { + resolvePersona?: ResolvePersona; + buildPlan?: BuildPlan; + executePlan?: ExecutePlan; + checkFleetCompatibility?: CheckFleetCompatibility; +}): void { + resolvePersonaImpl = input?.resolvePersona ?? resolvePersonaReference; + buildPlanImpl = input?.buildPlan ?? buildPersonaSpawnPlan; + executePlanImpl = input?.executePlan ?? executePersonaSpawnPlan; + checkFleetCompatibilityImpl = input?.checkFleetCompatibility ?? assertFleetCompatibility; +} + +/** + * A reusable `spawn:persona` capability for custom fleet node definitions. + * Resolution and preparation happen on the target node, so local persona + * paths, installed skills, and MCP configuration never have to exist on the + * orchestrator. + */ +export function workforcePersonaSpawnCapability( + options: WorkforcePersonaSpawnOptions = {} +): FleetActionDefinition { + const inFlight = new Map>(); + const active = new Map(); + + return action({}, async (rawInput, ctx) => { + checkFleetCompatibilityImpl(); + const input = parseSpawnInput(rawInput); + const cwd = resolvePath(input.cwd ?? options.cwd ?? process.cwd()); + const resolved = resolvePersonaImpl(input.persona, { + ...(options.registry ?? {}), + cwd + }); + for (const warning of resolved.warnings) options.onWarning?.(warning); + + // A Relay identity is a capacity request. Coalesce only callers that name + // the same node, project, persona, and registered agent identity. + const key = [ctx.node.name, input.name, cwd, resolved.path ?? resolved.spec.id].join('\u001f'); + const existing = inFlight.get(key); + if (existing) return existing; + + const launch = launchResolvedPersona({ input, cwd, resolved, ctx, active }); + inFlight.set(key, launch); + try { + return await launch; + } finally { + if (inFlight.get(key) === launch) inFlight.delete(key); + } + }); +} + +/** + * Define a fleet node whose capacity is interactive AgentWorkforce personas. + * This is the ergonomic counterpart to `defineWorkforcePersonaNode`, which is + * intentionally for one long-lived cloud/onEvent persona. + */ +export function defineWorkforcePersonaSpawnNode( + options: DefineWorkforcePersonaSpawnNodeOptions +): FleetNodeDefinition { + const nodeName = requireNonEmpty(options.nodeName, 'nodeName'); + return defineNode({ + name: nodeName, + ...(options.maxAgents !== undefined ? { maxAgents: options.maxAgents } : {}), + capabilities: { + [WORKFORCE_PERSONA_SPAWN_CAPABILITY]: workforcePersonaSpawnCapability(options) + }, + ...(options.tags ? { tags: [...options.tags] } : {}), + ...(options.version ? { version: options.version } : {}) + }); +} + +async function launchResolvedPersona(input: { + input: WorkforcePersonaSpawnInput; + cwd: string; + resolved: ResolvedPersonaReference; + ctx: FleetActionContext; + active: Map; +}): Promise { + const { resolved, ctx } = input; + const scratchDir = await mkdtemp(join(tmpdir(), 'agentworkforce-relay-persona-')); + let execution: ExecutionHandle | undefined; + try { + const built = buildPlanImpl(resolved.selection, { + // Input values are resolved on the target node. Forward only declared + // input variables instead of serializing the node's whole environment + // into this inspectable plan. + processEnv: declaredInputEnv(resolved.selection) + }); + // Interactive CLI launches use an isolated mount by default even when the + // persona has no custom filters. Preserve that safety property: skills, + // AGENTS.md/CLAUDE.md, and generated MCP config must never overwrite the + // real project while a remote spawn is running. + const plan: PersonaSpawnPlan = built.mount + ? built + : { + ...built, + mount: { ignoredPatterns: [], readonlyPatterns: [] } + }; + execution = await executePlanImpl(plan, { + cwd: input.cwd, + mount: { mountDir: scratchDir, includeGit: true, autoSync: true } + }); + + const args = plan.initialPrompt ? [...plan.args, plan.initialPrompt] : [...plan.args]; + const result = await ctx.spawnAgent({ + agent: { + name: input.input.name, + runtime: 'pty', + cli: plan.cli, + model: resolved.selection.model, + args, + ...(input.input.channels ? { channels: [...input.input.channels] } : {}), + cwd: execution.cwd, + harness_config: { + runtime: 'pty', + command: plan.cli, + args, + cwd: execution.cwd, + ...(Object.keys(plan.env).length > 0 ? { env: plan.env } : {}), + metadata: { + workforce_persona: resolved.spec.id, + // Relay 11.5+ treats this typed launch contract as synchronous: + // node registration + worker_ready, with release on either failure. + verify_ready: true, + require_node_registration: true + } + } + }, + ...(input.input.task !== undefined ? { initialTask: input.input.task } : {}), + skipRelayPrompt: false, + invocationId: ctx.invocationId + }); + + input.active.set(input.input.name, { + handle: execution, + scratchDir, + plan, + resolved + }); + return { + spawned: true, + name: input.input.name, + persona: resolved.spec.id, + harness: resolved.selection.harness, + model: resolved.selection.model, + source: resolved.source, + result + }; + } catch (error) { + await execution?.dispose(); + await rm(scratchDir, { recursive: true, force: true }); + throw error; + } +} + +function assertFleetCompatibility(): void { + const supported = ( + fleetSdk as unknown as { FLEET_DYNAMIC_SPAWN_DELEGATION?: unknown } + ).FLEET_DYNAMIC_SPAWN_DELEGATION; + if (supported !== true) { + throw new Error( + 'spawn:persona requires @agent-relay/fleet 11.5 or newer; older Fleet versions cannot delegate a persona action to its resolved harness' + ); + } +} + +function declaredInputEnv(selection: ResolvedPersonaReference['selection']): NodeJS.ProcessEnv { + const env: NodeJS.ProcessEnv = {}; + for (const [name, input] of Object.entries(selection.inputs ?? {})) { + const envName = input.env ?? name; + const value = process.env[envName]; + if (typeof value === 'string') env[envName] = value; + } + return env; +} + +function parseSpawnInput(value: unknown): WorkforcePersonaSpawnInput { + if (!isRecord(value)) throw new Error('spawn:persona input must be an object'); + const name = requireNonEmpty(value.name, 'name'); + const persona = requireNonEmpty(value.persona, 'persona'); + const task = optionalString(value.task, 'task'); + const cwd = optionalString(value.cwd, 'cwd'); + let channels: string[] | undefined; + if (value.channels !== undefined) { + if (!Array.isArray(value.channels)) throw new Error('channels must be an array of strings'); + channels = value.channels.map((channel, index) => requireNonEmpty(channel, `channels[${index}]`)); + } + return { + name, + persona, + ...(task !== undefined ? { task } : {}), + ...(cwd !== undefined ? { cwd } : {}), + ...(channels ? { channels } : {}) + }; +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function optionalString(value: unknown, label: string): string | undefined { + if (value === undefined) return undefined; + if (typeof value !== 'string') throw new Error(`${label} must be a string`); + return value; +} + +function requireNonEmpty(value: unknown, label: string): string { + if (typeof value !== 'string' || !value.trim()) { + throw new Error(`${label} must be a non-empty string`); + } + return value.trim(); +} diff --git a/packages/persona-kit/src/mount.test.ts b/packages/persona-kit/src/mount.test.ts index 392b8ccb..db81e29d 100644 --- a/packages/persona-kit/src/mount.test.ts +++ b/packages/persona-kit/src/mount.test.ts @@ -1,5 +1,8 @@ import test from 'node:test'; import assert from 'node:assert/strict'; +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { applyPersonaMount } from './mount.js'; import type { ResolvedMountPolicy } from './plan.js'; @@ -40,3 +43,30 @@ test('applyPersonaMount: declared policy without personaId throws a clear error' /options\.personaId is required when a mount policy is supplied/ ); }); + +test('applyPersonaMount: autoSync flushes harness changes before disposal', async () => { + const root = await mkdtemp(join(tmpdir(), 'persona-mount-autosync-')); + const project = join(root, 'project'); + const mountDir = join(root, 'mount'); + await mkdir(project); + await writeFile(join(project, 'input.txt'), 'input'); + + try { + const handle = await applyPersonaMount( + { ignoredPatterns: [], readonlyPatterns: [] }, + { + cwd: project, + mountDir, + personaId: 'autosync-test', + includeGit: false, + autoSync: { scanIntervalMs: 0, healthyScanIntervalMs: 0 } + } + ); + await writeFile(join(handle.cwd, 'result.txt'), 'persisted'); + await handle.dispose(); + + assert.equal(await readFile(join(project, 'result.txt'), 'utf8'), 'persisted'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/packages/persona-kit/src/mount.ts b/packages/persona-kit/src/mount.ts index e0660ab0..d46f6dc1 100644 --- a/packages/persona-kit/src/mount.ts +++ b/packages/persona-kit/src/mount.ts @@ -1,4 +1,5 @@ import type { ResolvedMountPolicy } from './plan.js'; +import type { AutoSyncHandle, AutoSyncOptions } from '@relayfile/local-mount'; // `@relayfile/local-mount` pulls in `@parcel/watcher`, which loads a // per-platform native binary at module evaluation time @@ -48,6 +49,12 @@ export interface ApplyPersonaMountOptions { * pure file overlay. */ includeGit?: boolean; + /** + * Keep the project and mount synchronized for the lifetime of the handle. + * `dispose()` stops the watchers and performs their final flush before the + * mount is removed. + */ + autoSync?: boolean | AutoSyncOptions; } /** @@ -93,6 +100,19 @@ export async function applyPersonaMount( agentName: options.personaId, includeGit: options.includeGit ?? true }); + let autoSync: AutoSyncHandle | undefined; + try { + autoSync = options.autoSync + ? handle.startAutoSync(typeof options.autoSync === 'object' ? options.autoSync : undefined) + : undefined; + // Do not hand the mount to a fast-starting harness before watcher setup has + // settled. A degraded watcher is still usable: stop() performs a full + // reconcile, so setup errors intentionally fall through to that fallback. + await Promise.resolve(autoSync?.ready()).catch(() => undefined); + } catch (error) { + await handle.cleanup(); + throw error; + } let disposed = false; return { @@ -100,10 +120,14 @@ export async function applyPersonaMount( async dispose(): Promise { if (disposed) return; disposed = true; - // Defensive await — relayfile's typed signature is void today, but - // future versions may return a promise; awaiting a non-promise is a - // no-op and protects the dispose contract executors rely on. - await handle.cleanup(); + try { + await autoSync?.stop(); + } finally { + // Defensive await — relayfile's typed signature is void today, but + // future versions may return a promise; awaiting a non-promise is a + // no-op and protects the dispose contract executors rely on. + await handle.cleanup(); + } } }; } diff --git a/packages/persona-registry/README.md b/packages/persona-registry/README.md new file mode 100644 index 00000000..7c35660b --- /dev/null +++ b/packages/persona-registry/README.md @@ -0,0 +1,16 @@ +# @agentworkforce/persona-registry + +Programmatic resolution for AgentWorkforce personas. It owns the same source +cascade used by the CLI: cwd personas, configured directories (including the +personal directory), then the built-in catalog. + +```ts +import { resolvePersonaReference } from '@agentworkforce/persona-registry'; + +const resolved = resolvePersonaReference('persona-maker', { cwd: process.cwd() }); +console.log(resolved.selection.harness, resolved.selection.model); +``` + +`resolvePersonaReference` accepts a persona id, built-in intent, or JSON path. +It returns both the merged `PersonaSpec` and the interactive +`PersonaSelection` consumed by `@agentworkforce/persona-kit`'s spawn-plan SDK. diff --git a/packages/persona-registry/package.json b/packages/persona-registry/package.json new file mode 100644 index 00000000..96350199 --- /dev/null +++ b/packages/persona-registry/package.json @@ -0,0 +1,38 @@ +{ + "name": "@agentworkforce/persona-registry", + "version": "4.1.37", + "private": false, + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md", + "package.json" + ], + "repository": { + "type": "git", + "url": "https://github.com/AgentWorkforce/workforce", + "directory": "packages/persona-registry" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput", + "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "tsc -p tsconfig.json && node --test dist/*.test.js", + "lint": "tsc -p tsconfig.json --noEmit" + }, + "dependencies": { + "@agentworkforce/persona-kit": "workspace:*", + "@agentworkforce/workload-router": "workspace:*" + } +} diff --git a/packages/persona-registry/src/index.test.ts b/packages/persona-registry/src/index.test.ts new file mode 100644 index 00000000..2f72fee8 --- /dev/null +++ b/packages/persona-registry/src/index.test.ts @@ -0,0 +1,107 @@ +import assert from 'node:assert/strict'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; + +import type { PersonaSpec } from '@agentworkforce/persona-kit'; +import { PersonaResolutionError, resolvePersonaReference, __mergeOverrideForTests } from './index.js'; + +test('a later mount layer can re-enable inherited mount patterns', () => { + const base: PersonaSpec = { + id: 'base', + intent: 'review', + description: 'Base persona', + skills: [], + harness: 'codex', + model: 'openai-codex/test', + systemPrompt: 'Review', + harnessSettings: { reasoning: 'medium', timeoutSeconds: 60 }, + mount: { + ignoredPatterns: ['.env'], + readonlyPatterns: ['docs/**'] + } + }; + const disabled = __mergeOverrideForTests(base, { + id: 'disabled', + mount: { enabled: false } + }); + const reenabled = __mergeOverrideForTests(disabled, { + id: 'reenabled', + mount: { enabled: true } + }); + + assert.deepEqual(reenabled.mount, { + enabled: true, + ignoredPatterns: ['.env'], + readonlyPatterns: ['docs/**'] + }); +}); + +test('resolves a built-in persona to an interactive selection', () => { + const cwd = mkdtempSync(join(tmpdir(), 'persona-registry-built-in-')); + try { + const resolved = resolvePersonaReference('persona-maker', { cwd, personaDirs: [] }); + assert.equal(resolved.source, 'built-in'); + assert.equal(resolved.spec.id, 'persona-maker'); + assert.equal(resolved.selection.personaId, 'persona-maker'); + assert.equal(resolved.selection.harness, resolved.spec.harness); + assert.equal(resolved.selection.model, resolved.spec.model); + } finally { + rmSync(cwd, { recursive: true, force: true }); + } +}); + +test('an explicit JSON path is the highest registry layer and inherits normally', () => { + const root = mkdtempSync(join(tmpdir(), 'persona-registry-path-')); + const project = join(root, 'project'); + const external = join(root, 'external'); + mkdirSync(project, { recursive: true }); + mkdirSync(external, { recursive: true }); + const projectPersonas = join(project, '.agentworkforce', 'workforce', 'personas'); + mkdirSync(projectPersonas, { recursive: true }); + const path = join(external, 'review.json'); + writeFileSync( + path, + JSON.stringify({ + id: 'review-via-path', + extends: 'persona-maker', + description: 'Path-selected reviewer' + }) + ); + writeFileSync( + join(projectPersonas, 'conflict.json'), + JSON.stringify({ + id: 'review-via-path', + extends: 'persona-maker', + description: 'Project persona that must not override an explicit path' + }) + ); + + try { + const resolved = resolvePersonaReference(path, { + cwd: project, + personaDirs: [] + }); + assert.equal(resolved.source, 'path'); + assert.equal(resolved.path, path); + assert.equal(resolved.spec.id, 'review-via-path'); + assert.equal(resolved.spec.description, 'Path-selected reviewer'); + assert.equal(resolved.selection.harness, resolved.spec.harness); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test('unknown names fail with a typed resolution error', () => { + const cwd = mkdtempSync(join(tmpdir(), 'persona-registry-unknown-')); + try { + assert.throws( + () => resolvePersonaReference('does-not-exist', { cwd, personaDirs: [] }), + (error: unknown) => + error instanceof PersonaResolutionError && error.code === 'unknown_persona' + ); + } finally { + rmSync(cwd, { recursive: true, force: true }); + } +}); diff --git a/packages/persona-registry/src/index.ts b/packages/persona-registry/src/index.ts new file mode 100644 index 00000000..9aa7977c --- /dev/null +++ b/packages/persona-registry/src/index.ts @@ -0,0 +1,220 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { isAbsolute, resolve as resolvePath } from 'node:path'; + +import { + resolveSidecar, + type Harness, + type PersonaSelection, + type PersonaSpec +} from '@agentworkforce/persona-kit'; +import { + listBuiltInPersonas, + personaCatalog, + routingProfiles +} from '@agentworkforce/workload-router'; + +export * from './local-personas.js'; + +import { + loadLocalPersonas, + loadPersonaSourceConfig, + type LoadOptions, + type PersonaSource +} from './local-personas.js'; + +export interface ResolvedPersonaReference { + /** Fully merged spec from the winning registry layer. */ + spec: PersonaSpec; + /** Interactive runtime projection accepted by persona-kit's spawn planner. */ + selection: PersonaSelection; + /** Registry source (`cwd`, `personal`, `dir:N`, or `built-in`). */ + source: PersonaSource; + /** Declaring JSON file for file-backed personas. */ + path?: string; + /** Non-fatal config/source warnings collected while loading the registry. */ + warnings: string[]; +} + +export class PersonaResolutionError extends Error { + readonly code: 'invalid_reference' | 'unknown_persona' | 'not_interactive'; + + constructor(code: PersonaResolutionError['code'], message: string) { + super(message); + this.name = 'PersonaResolutionError'; + this.code = code; + } +} + +/** Replace the only harness placeholder the CLI resolves before launch. */ +export function resolveSystemPromptPlaceholders(prompt: string, harness: Harness): string { + return prompt.replaceAll('', harness); +} + +/** + * Resolve a persona id, built-in intent, or JSON path through the canonical + * registry cascade without starting the AgentWorkforce CLI. + */ +export function resolvePersonaReference( + reference: string, + options: LoadOptions = {} +): ResolvedPersonaReference { + const selector = reference.trim(); + if (!selector) { + throw new PersonaResolutionError('invalid_reference', 'persona reference must be non-empty'); + } + if (selector.includes('@') && !looksLikePath(selector)) { + throw new PersonaResolutionError( + 'invalid_reference', + '@ selectors were removed; pass the persona id without a tier suffix' + ); + } + + const cwd = options.cwd ?? process.cwd(); + const candidatePath = isAbsolute(selector) ? selector : resolvePath(cwd, selector); + const explicitPath = looksLikePath(selector); + const baseConfig = loadPersonaSourceConfig(options); + let lookupOptions = options; + let lookupId = selector; + let requestedPath: string | undefined; + + if (explicitPath) { + if (!existsSync(candidatePath)) { + throw new PersonaResolutionError('invalid_reference', `persona file not found: ${selector}`); + } + if (!candidatePath.toLowerCase().endsWith('.json')) { + throw new PersonaResolutionError( + 'invalid_reference', + 'interactive persona paths must point to a JSON persona file' + ); + } + const raw = readJsonPersona(candidatePath); + if (typeof raw.id !== 'string' || !raw.id.trim()) { + throw new PersonaResolutionError( + 'invalid_reference', + `persona file ${selector} is missing a non-empty id` + ); + } + lookupId = raw.id.trim(); + requestedPath = candidatePath; + lookupOptions = { + ...options, + cwd, + // The exact file is a dedicated highest-priority layer. The normal cwd, + // configured, personal, and built-in layers remain beneath it for + // inheritance. + personaPath: candidatePath, + personaDirs: baseConfig.personaDirs + }; + } + + const local = loadLocalPersonas(lookupOptions); + const localSpec = local.byId.get(lookupId); + if ( + requestedPath && + (local.sources.get(lookupId) !== 'path' || local.paths.get(lookupId) !== requestedPath) + ) { + const detail = local.warnings.find((warning) => warning.startsWith('[path]')); + throw new PersonaResolutionError( + 'invalid_reference', + detail ?? `could not resolve persona file: ${selector}` + ); + } + if (localSpec) { + const source = local.sources.get(lookupId) ?? 'cwd'; + return { + spec: localSpec, + selection: buildPersonaSelection(localSpec, 'local'), + source, + path: requestedPath ?? local.paths.get(lookupId), + warnings: [...local.warnings] + }; + } + + const byIntent = (personaCatalog as Record)[lookupId]; + const builtIn = byIntent ?? listBuiltInPersonas().find((persona) => persona.id === lookupId); + if (!builtIn) { + const known = new Set([ + ...local.byId.keys(), + ...listBuiltInPersonas().map((persona) => persona.id) + ]); + throw new PersonaResolutionError( + 'unknown_persona', + `Unknown persona "${lookupId}". Known personas: ${[...known].sort().join(', ') || '(none)'}` + ); + } + return { + spec: builtIn, + selection: buildPersonaSelection(builtIn, 'built-in'), + source: 'built-in', + warnings: [...local.warnings] + }; +} + +function readJsonPersona(path: string): Record { + try { + const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown; + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { + throw new Error('must contain a JSON object'); + } + return parsed as Record; + } catch (error) { + throw new PersonaResolutionError( + 'invalid_reference', + `could not read persona file: ${error instanceof Error ? error.message : String(error)}` + ); + } +} + +function looksLikePath(value: string): boolean { + return value.endsWith('.json') || value.includes('/') || value.includes('\\'); +} + +function buildPersonaSelection( + spec: PersonaSpec, + kind: 'local' | 'built-in' +): PersonaSelection { + if (!spec.harness || !spec.model || spec.systemPrompt === undefined) { + const missing = [ + !spec.harness && 'harness', + !spec.model && 'model', + spec.systemPrompt === undefined && 'systemPrompt' + ].filter(Boolean).join(', '); + throw new PersonaResolutionError( + 'not_interactive', + `persona "${spec.id}" cannot be spawned interactively because it omits ${missing}` + ); + } + + const sidecar = resolveSidecar(spec); + const rationale = kind === 'local' + ? `local-override: ${spec.id}` + : ( + routingProfiles.default.intents as Partial> + )[spec.intent]?.rationale ?? `registry: ${spec.id}`; + + return { + personaId: spec.id, + harness: spec.harness, + model: spec.model, + systemPrompt: resolveSystemPromptPlaceholders(spec.systemPrompt, spec.harness), + harnessSettings: spec.harnessSettings, + skills: spec.skills, + rationale, + ...(spec.inputs ? { inputs: spec.inputs } : {}), + ...(spec.env ? { env: spec.env } : {}), + ...(spec.mcpServers ? { mcpServers: spec.mcpServers } : {}), + ...(spec.permissions ? { permissions: spec.permissions } : {}), + ...(spec.mount ? { mount: spec.mount } : {}), + ...(spec.memory !== undefined ? { memory: spec.memory } : {}), + ...(sidecar.claudeMd ? { claudeMd: sidecar.claudeMd } : {}), + ...(sidecar.claudeMdContent ? { claudeMdContent: sidecar.claudeMdContent } : {}), + ...(sidecar.claudeMd || sidecar.claudeMdContent + ? { claudeMdMode: sidecar.claudeMdMode } + : {}), + ...(sidecar.agentsMd ? { agentsMd: sidecar.agentsMd } : {}), + ...(sidecar.agentsMdContent ? { agentsMdContent: sidecar.agentsMdContent } : {}), + ...(sidecar.agentsMd || sidecar.agentsMdContent + ? { agentsMdMode: sidecar.agentsMdMode } + : {}) + }; +} diff --git a/packages/persona-registry/src/local-personas.ts b/packages/persona-registry/src/local-personas.ts new file mode 100644 index 00000000..010b1c13 --- /dev/null +++ b/packages/persona-registry/src/local-personas.ts @@ -0,0 +1,1214 @@ +import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { basename, dirname, isAbsolute, join, resolve as resolvePath } from 'node:path'; + +import { + CODEX_APPROVAL_POLICIES, + CODEX_SANDBOX_MODES, + HARNESS_VALUES, + PERMISSION_MODES, + SIDECAR_MD_MODES, + type CodexApprovalPolicy, + type CodexSandboxMode, + type Harness, + type HarnessSettings, + type McpServerSpec, + type PersonaInputSpec, + type PersonaMount, + type PersonaPermissions, + type PersonaSkill, + type PersonaSpec, + type PersonaTag, + type SidecarMdMode, + parseHarnessSettings, + parseInputs +} from '@agentworkforce/persona-kit'; +import { listBuiltInPersonas, personaCatalog } from '@agentworkforce/workload-router'; + +/** + * User-defined persona override. Local files are partial overlays — only the + * fields you specify replace the inherited base; everything else cascades down + * through cwd → configured persona dirs → library. + * + * `extends` names the base explicitly by id or intent. If omitted and the file + * has no `intent`, the loader implicitly inherits from the same-id persona + * found in the next lower layer. Files with `intent` are standalone personas. + */ +export interface LocalPersonaOverride { + id: string; + extends?: string; + /** + * When present without `extends`, the file is a complete standalone + * persona instead of an overlay inheriting from a lower cascade layer. + */ + intent?: string; + /** + * Classification tags. When provided, replaces the inherited base's tags + * entirely (matching the replace-wholesale semantics used for `skills`). + */ + tags?: PersonaTag[]; + description?: string; + skills?: PersonaSpec['skills']; + inputs?: Record; + env?: Record; + mcpServers?: Record; + /** + * Relayfile mount policy. Pattern lists append to the inherited base so + * gitignore negations in the overlay can intentionally narrow/reopen scope. + */ + mount?: PersonaMount; + /** + * Permission policy. `allow` and `deny` append to the base's lists (dedup + * on merge); `mode` replaces the base's mode when set. + */ + permissions?: PersonaPermissions; + /** Replaces the inherited systemPrompt when set. */ + systemPrompt?: string; + /** Replaces the inherited harness when set. */ + harness?: Harness; + /** Replaces the inherited model when set. */ + model?: string; + /** Per-field harness settings override; merged on top of the inherited harnessSettings. */ + harnessSettings?: Partial; + /** + * Path to a `CLAUDE.md` sidecar, relative to this JSON file's directory. + * The loader stats the file and resolves it to an absolute path on the + * merged spec; missing files surface as load warnings rather than throws. + */ + claudeMd?: string; + claudeMdMode?: SidecarMdMode; + agentsMd?: string; + agentsMdMode?: SidecarMdMode; + claudeMdContent?: string; + agentsMdContent?: string; + /** @internal — directory of the JSON file that declared this override. */ + __sourceDir?: string; +} + +export type PersonaSource = string; + +/** + * Map an internal {@link PersonaSource} cascade label to the human-readable + * vocabulary surfaced in `agentworkforce list`, `sources list`, and the + * interactive picker: + * + * - `library` → `built-in` — bundled with `@agentworkforce/cli`, + * available to every user without an install step. + * - `user` → `personal` — `~/.agentworkforce/workforce/personas/`, + * i.e. personas a single user keeps across all repos. + * - `cwd` → `cwd` — `/.agentworkforce/workforce/personas/`, + * the working-tree dir; both installed library packs and + * hand-authored team overrides live here. Kept as-is + * because it's a precise pointer to a real directory. + * - `dir:N` → `dir:N` — extra configurable persona dirs (passed + * through unchanged so position is still legible). + * + * Internal strings are left alone so `--save-in-directory ` and the + * JSON outputs of `list` / `sources list` keep their existing values. + */ +export function formatPersonaSourceLabel(source: PersonaSource): string { + if (source === 'library') return 'built-in'; + if (source === 'user') return 'personal'; + return source; +} + +interface SourceLayer { + key: string; + source: PersonaSource; + dir: string; + /** When set, this priority layer loads only the explicitly selected file. */ + file?: string; +} + +export interface PersonaSourceDirectory { + source: PersonaSource; + dir: string; + configurable: boolean; +} + +export interface PersonaSourceConfig { + configPath: string; + personaDirs: string[]; + defaultCreateTarget?: string; + userPersonaDir: string; + warnings: string[]; +} + +export interface LoadedLocalPersonas { + /** Final resolved specs by id, with the cascade applied (higher source dirs win). */ + byId: Map; + /** Where each id in `byId` was defined (top-most layer that declared it). */ + sources: Map; + /** + * Absolute path to the JSON file that produced each id in `byId`. Comes + * from the topmost layer that declared the id (the same layer + * `sources` records). Mutating tools — e.g. the post-session + * persona-improver flow — apply accepted patches at this path. + */ + paths: Map; + warnings: string[]; +} + +export interface LoadOptions { + cwd?: string; + /** Exact persona JSON file to prepend as the highest registry layer. */ + personaPath?: string; + /** + * Back-compat alias for userPersonaDir. Historically local personas lived + * directly in this directory. + */ + homeDir?: string; + userPersonaDir?: string; + workforceHomeDir?: string; + configPath?: string; + /** Full ordered list of configurable persona dirs after cwd and before library. */ + personaDirs?: string[]; + /** Override target used by `agentworkforce create` when set. Defaults to `cwd`. */ + defaultCreateTarget?: string; +} + +export function defaultWorkforceHomeDir(): string { + const override = process.env.AGENT_WORKFORCE_HOME?.trim(); + if (override) return override; + return join(homedir(), '.agentworkforce', 'workforce'); +} + +export function defaultUserPersonaDir(workforceHomeDir = defaultWorkforceHomeDir()): string { + const legacyOverride = process.env.AGENT_WORKFORCE_CONFIG_DIR?.trim(); + if (legacyOverride) return legacyOverride; + return join(workforceHomeDir, 'personas'); +} + +export function defaultPersonaConfigPath(workforceHomeDir = defaultWorkforceHomeDir()): string { + return join(workforceHomeDir, 'config.json'); +} + +export function defaultCwdPersonaDir(cwd: string): string { + return join(cwd, '.agentworkforce', 'workforce', 'personas'); +} + +export function expandHomePath(input: string): string { + if (input === '~') return homedir(); + if (input.startsWith('~/') || input.startsWith('~\\')) { + return join(homedir(), input.slice(2)); + } + return input; +} + +export function normalizePersonaDir(input: string, baseDir = process.cwd()): string { + const expanded = expandHomePath(input.trim()); + return isAbsolute(expanded) ? resolvePath(expanded) : resolvePath(baseDir, expanded); +} + +interface RawPersonaSourceConfig { + personaDirs?: string[]; + defaultCreateTarget?: string; +} + +function readRawPersonaSourceConfig( + path: string, + warnings: string[] +): RawPersonaSourceConfig | undefined { + if (!existsSync(path)) return undefined; + try { + const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown; + if (!isPlainObject(parsed)) { + warnings.push(`[config] ${path}: must be a JSON object`); + return undefined; + } + const dirs = parsed.personaDirs; + let personaDirs: string[] | undefined; + if ( + dirs !== undefined && + (!Array.isArray(dirs) || + dirs.some((dir) => typeof dir !== 'string' || !dir.trim())) + ) { + warnings.push(`[config] ${path}: personaDirs must be an array of non-empty strings`); + } else if (dirs !== undefined) { + personaDirs = dirs.map((dir) => normalizePersonaDir(dir, dirname(path))); + } + const defaultCreateTarget = parsed.defaultCreateTarget; + if ( + defaultCreateTarget !== undefined && + (typeof defaultCreateTarget !== 'string' || !defaultCreateTarget.trim()) + ) { + warnings.push(`[config] ${path}: defaultCreateTarget must be a non-empty string if provided`); + return personaDirs ? { personaDirs } : {}; + } + return { + ...(personaDirs ? { personaDirs } : {}), + ...(typeof defaultCreateTarget === 'string' ? { defaultCreateTarget: defaultCreateTarget.trim() } : {}) + }; + } catch (err) { + warnings.push(`[config] ${path}: ${(err as Error).message}`); + return undefined; + } +} + +function dedupeDirs(dirs: readonly string[], warnings: string[], baseDir: string): string[] { + const seen = new Set(); + const out: string[] = []; + for (const dir of dirs) { + const normalized = normalizePersonaDir(dir, baseDir); + if (seen.has(normalized)) { + warnings.push(`[config] duplicate persona source directory ${normalized}; keeping first.`); + continue; + } + seen.add(normalized); + out.push(normalized); + } + return out; +} + +export function loadPersonaSourceConfig(options: LoadOptions = {}): PersonaSourceConfig { + const workforceHomeDir = options.workforceHomeDir ?? defaultWorkforceHomeDir(); + const baseDir = options.cwd ?? process.cwd(); + const userPersonaDir = normalizePersonaDir( + options.userPersonaDir ?? options.homeDir ?? defaultUserPersonaDir(workforceHomeDir), + baseDir + ); + const configPath = normalizePersonaDir( + options.configPath ?? defaultPersonaConfigPath(workforceHomeDir) + ); + const warnings: string[] = []; + const rawConfig = readRawPersonaSourceConfig(configPath, warnings); + // An explicit legacy/user directory is itself a caller-supplied source + // configuration unless the caller also explicitly selected a config scope. + // Do not silently replace it with an unrelated ambient personal config file + // (tests, embedders, and fleet nodes use this to isolate registry lookup). + const explicitConfigScope = + options.configPath !== undefined || options.workforceHomeDir !== undefined; + const explicitUserScope = + options.userPersonaDir !== undefined || + options.homeDir !== undefined; + const configuredDirs = + options.personaDirs ?? + (explicitConfigScope + ? rawConfig?.personaDirs ?? [userPersonaDir] + : explicitUserScope + ? [userPersonaDir] + : rawConfig?.personaDirs ?? [userPersonaDir]); + const defaultCreateTarget = options.defaultCreateTarget ?? rawConfig?.defaultCreateTarget; + + return { + configPath, + personaDirs: dedupeDirs(configuredDirs, warnings, baseDir), + ...(defaultCreateTarget ? { defaultCreateTarget } : {}), + userPersonaDir, + warnings + }; +} + +export function savePersonaSourceConfig( + personaDirs: readonly string[], + options: LoadOptions = {} +): PersonaSourceConfig { + const config = loadPersonaSourceConfig({ ...options, personaDirs: [...personaDirs] }); + mkdirSync(dirname(config.configPath), { recursive: true }); + const serialized = { + personaDirs: config.personaDirs, + ...(config.defaultCreateTarget ? { defaultCreateTarget: config.defaultCreateTarget } : {}) + }; + writeFileSync( + config.configPath, + JSON.stringify(serialized, null, 2) + '\n', + 'utf8' + ); + return config; +} + +function sourceForPersonaDir( + dir: string, + idx: number, + userPersonaDir: string +): PersonaSource { + return dir === userPersonaDir ? 'user' : `dir:${idx + 1}`; +} + +export function buildPersonaSourceDirectories( + options: LoadOptions = {} +): { directories: PersonaSourceDirectory[]; config: PersonaSourceConfig } { + const cwd = options.cwd ?? process.cwd(); + const config = loadPersonaSourceConfig(options); + const directories: PersonaSourceDirectory[] = [ + { + source: 'cwd', + dir: defaultCwdPersonaDir(cwd), + configurable: false + }, + ...config.personaDirs.map((dir, idx) => ({ + source: sourceForPersonaDir(dir, idx, config.userPersonaDir), + dir, + configurable: true + })) + ]; + return { directories, config }; +} + +function readLayerDir( + dir: string, + layer: SourceLayer, + warnings: string[], + filePaths: Map +): Map { + const out = new Map(); + if (!existsSync(dir)) return out; + + let entries: string[]; + try { + entries = layer.file + ? [basename(layer.file)] + : readdirSync(dir).filter((n) => n.endsWith('.json')); + } catch (err) { + warnings.push(`[${layer.source}] could not read ${dir}: ${(err as Error).message}`); + return out; + } + + for (const file of entries) { + const path = join(dir, file); + try { + const raw = readFileSync(path, 'utf8'); + const parsed = parseOverride(JSON.parse(raw), `[${layer.source}] ${file}`); + parsed.__sourceDir = dir; + if (out.has(parsed.id)) { + warnings.push(`[${layer.source}] ${file}: duplicate id "${parsed.id}" within layer; skipping.`); + continue; + } + out.set(parsed.id, parsed); + filePaths.set(`${layer.key}:${parsed.id}`, path); + } catch (err) { + warnings.push(`[${layer.source}] ${file}: ${(err as Error).message}`); + } + } + return out; +} + +function isPlainObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** + * Sidecar path validation shared by local overrides and standalone specs. + * Mirrors {@link assertSafeRelativePath} but adds the `.md` suffix + * requirement called out in the schema. Throws on absolute paths, + * `..` segments, empty strings, or non-`.md` extensions. + */ +function assertSidecarPath(value: unknown, context: string): void { + if (typeof value !== 'string' || !value.trim()) { + throw new Error(`${context} must be a non-empty string`); + } + if ( + value.startsWith('/') || + value.startsWith('\\') || // covers `\persona.md` and `\\server\share\…` UNC + /^[A-Za-z]:/.test(value) // covers `C:\persona.md` and drive-relative `C:persona.md` + ) { + throw new Error(`${context} must be a relative path; got absolute "${value}"`); + } + const segments = value.split(/[\\/]+/); + if (segments.some((s) => s === '..')) { + throw new Error(`${context} must not contain ".." segments`); + } + if (!value.toLowerCase().endsWith('.md')) { + throw new Error(`${context} must end with .md`); + } +} + +function assertSidecarMode(value: unknown, context: string): void { + if (!SIDECAR_MD_MODES.includes(value as SidecarMdMode)) { + throw new Error(`${context} must be one of: ${SIDECAR_MD_MODES.join(', ')}`); + } +} + +function assertInlineSidecarContent(value: unknown, context: string): void { + if (typeof value !== 'string' || !value.trim()) { + throw new Error(`${context} must be a non-empty string`); + } +} + +function parseOverride(value: unknown, context: string): LocalPersonaOverride { + if (!isPlainObject(value)) { + throw new Error(`${context} must be a JSON object`); + } + const raw = value; + if (typeof raw.id !== 'string' || !raw.id.trim()) { + throw new Error(`${context}.id must be a non-empty string`); + } + if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) { + throw new Error(`${context}.extends must be a non-empty string if provided`); + } + if (raw.extends !== undefined && raw.intent !== undefined) { + throw new Error(`${context}.intent cannot be combined with .extends; omit extends for standalone personas`); + } + if (raw.intent !== undefined && (typeof raw.intent !== 'string' || !raw.intent.trim())) { + throw new Error(`${context}.intent must be a non-empty string if provided`); + } + if (raw.systemPrompt !== undefined && typeof raw.systemPrompt !== 'string') { + throw new Error(`${context}.systemPrompt must be a string if provided`); + } + if (raw.description !== undefined && typeof raw.description !== 'string') { + throw new Error(`${context}.description must be a string if provided`); + } + let normalizedTags: PersonaTag[] | undefined; + if (raw.tags !== undefined && raw.tags !== null) { + if (!Array.isArray(raw.tags)) { + throw new Error(`${context}.tags must be an array of strings if provided`); + } + const tags = new Set(); + for (const [idx, tag] of raw.tags.entries()) { + if (typeof tag !== 'string' || !tag.trim()) { + throw new Error(`${context}.tags[${idx}] must be a non-empty string`); + } + const trimmed = tag.trim(); + if (trimmed.length > 64) { + throw new Error(`${context}.tags[${idx}] must be ≤64 characters`); + } + tags.add(trimmed); + } + if (tags.size > 0) { + normalizedTags = Array.from(tags).sort() as PersonaTag[]; + } + } + + if (raw.skills !== undefined && !Array.isArray(raw.skills)) { + throw new Error(`${context}.skills must be an array if provided`); + } + const inputs = parseInputsShape(raw.inputs, `${context}.inputs`); + assertStringMap(raw.env, `${context}.env`); + assertMcpServersShape(raw.mcpServers, `${context}.mcpServers`); + assertMountShape(raw.mount, `${context}.mount`); + assertPermissionsShape(raw.permissions, `${context}.permissions`); + + if (raw.tiers !== undefined) { + throw new Error( + `${context}.tiers is no longer supported; declare harness/model/systemPrompt/harnessSettings at the top level` + ); + } + if (raw.defaultTier !== undefined) { + throw new Error( + `${context}.defaultTier is no longer supported (tiers have been removed)` + ); + } + if (raw.harness !== undefined) { + if (typeof raw.harness !== 'string' || !HARNESS_VALUES.includes(raw.harness as Harness)) { + throw new Error(`${context}.harness must be one of: ${HARNESS_VALUES.join(', ')}`); + } + } + if (raw.model !== undefined) { + if (typeof raw.model !== 'string' || !raw.model.trim()) { + throw new Error(`${context}.model must be a non-empty string if provided`); + } + } + if (raw.harnessSettings !== undefined) { + if (!isPlainObject(raw.harnessSettings)) { + throw new Error(`${context}.harnessSettings must be an object if provided`); + } + assertPartialHarnessSettingsShape(raw.harnessSettings, `${context}.harnessSettings`); + } + + if (raw.claudeMd !== undefined) assertSidecarPath(raw.claudeMd, `${context}.claudeMd`); + if (raw.agentsMd !== undefined) assertSidecarPath(raw.agentsMd, `${context}.agentsMd`); + if (raw.claudeMdContent !== undefined) { + assertInlineSidecarContent(raw.claudeMdContent, `${context}.claudeMdContent`); + } + if (raw.agentsMdContent !== undefined) { + assertInlineSidecarContent(raw.agentsMdContent, `${context}.agentsMdContent`); + } + // Mode is allowed without a same-layer path so an overlay can flip + // `extend` ↔ `overwrite` while inheriting the path from a lower layer. + if (raw.claudeMdMode !== undefined) assertSidecarMode(raw.claudeMdMode, `${context}.claudeMdMode`); + if (raw.agentsMdMode !== undefined) assertSidecarMode(raw.agentsMdMode, `${context}.agentsMdMode`); + + return { + id: raw.id.trim(), + ...(typeof raw.extends === 'string' ? { extends: raw.extends.trim() } : {}), + ...(typeof raw.intent === 'string' ? { intent: raw.intent.trim() } : {}), + ...(normalizedTags !== undefined ? { tags: normalizedTags } : {}), + description: raw.description as string | undefined, + skills: raw.skills as PersonaSpec['skills'] | undefined, + inputs, + env: raw.env as LocalPersonaOverride['env'], + mcpServers: raw.mcpServers as LocalPersonaOverride['mcpServers'], + mount: raw.mount as LocalPersonaOverride['mount'], + permissions: raw.permissions as LocalPersonaOverride['permissions'], + systemPrompt: raw.systemPrompt as string | undefined, + ...(raw.harness !== undefined ? { harness: raw.harness as Harness } : {}), + ...(raw.model !== undefined ? { model: raw.model as string } : {}), + ...(raw.harnessSettings !== undefined + ? { harnessSettings: raw.harnessSettings as Partial } + : {}), + ...(typeof raw.claudeMd === 'string' ? { claudeMd: raw.claudeMd } : {}), + ...(raw.claudeMdMode ? { claudeMdMode: raw.claudeMdMode as SidecarMdMode } : {}), + ...(typeof raw.agentsMd === 'string' ? { agentsMd: raw.agentsMd } : {}), + ...(raw.agentsMdMode ? { agentsMdMode: raw.agentsMdMode as SidecarMdMode } : {}), + ...(typeof raw.claudeMdContent === 'string' ? { claudeMdContent: raw.claudeMdContent } : {}), + ...(typeof raw.agentsMdContent === 'string' ? { agentsMdContent: raw.agentsMdContent } : {}) + }; +} + +function parseInputsShape( + value: unknown, + context: string +): Record | undefined { + return parseInputs(value, context); +} + +function assertStringMap(value: unknown, context: string): void { + if (value === undefined) return; + if (!isPlainObject(value)) { + throw new Error(`${context} must be an object if provided`); + } + for (const [k, v] of Object.entries(value)) { + if (typeof v !== 'string') { + throw new Error(`${context}.${k} must be a string`); + } + } +} + +function assertMcpServersShape(value: unknown, context: string): void { + if (value === undefined) return; + if (!isPlainObject(value)) { + throw new Error(`${context} must be an object if provided`); + } + for (const [name, spec] of Object.entries(value)) { + const path = `${context}.${name}`; + if (!isPlainObject(spec)) { + throw new Error(`${path} must be an object`); + } + const type = spec.type; + if (type !== 'http' && type !== 'sse' && type !== 'stdio') { + throw new Error(`${path}.type must be one of: http, sse, stdio`); + } + if (type === 'stdio') { + if (typeof spec.command !== 'string' || !spec.command.trim()) { + throw new Error(`${path}.command must be a non-empty string`); + } + if (spec.args !== undefined) { + if (!Array.isArray(spec.args) || spec.args.some((a) => typeof a !== 'string')) { + throw new Error(`${path}.args must be an array of strings`); + } + } + assertStringMap(spec.env, `${path}.env`); + } else { + if (typeof spec.url !== 'string' || !spec.url.trim()) { + throw new Error(`${path}.url must be a non-empty string`); + } + assertStringMap(spec.headers, `${path}.headers`); + } + } +} + +function assertPermissionsShape(value: unknown, context: string): void { + if (value === undefined) return; + if (!isPlainObject(value)) { + throw new Error(`${context} must be an object if provided`); + } + for (const key of ['allow', 'deny'] as const) { + const list = value[key]; + if (list === undefined) continue; + if (!Array.isArray(list) || list.some((s) => typeof s !== 'string' || !s.trim())) { + throw new Error(`${context}.${key} must be an array of non-empty strings`); + } + } + const mode = value.mode; + if (mode !== undefined && (!PERMISSION_MODES.includes(mode as typeof PERMISSION_MODES[number]))) { + throw new Error(`${context}.mode must be one of: ${PERMISSION_MODES.join(', ')}`); + } +} + +function assertMountShape(value: unknown, context: string): void { + if (value === undefined) return; + if (!isPlainObject(value)) { + throw new Error(`${context} must be an object if provided`); + } + if (value.enabled !== undefined && typeof value.enabled !== 'boolean') { + throw new Error(`${context}.enabled must be a boolean if provided`); + } + for (const key of ['ignoredPatterns', 'readonlyPatterns'] as const) { + const list = value[key]; + if (list === undefined) continue; + if (!Array.isArray(list) || list.some((s) => typeof s !== 'string' || !s.trim())) { + throw new Error(`${context}.${key} must be an array of non-empty strings`); + } + } +} + +function assertPartialHarnessSettingsShape(value: Record, context: string): void { + const { + reasoning, + timeoutSeconds, + sandboxMode, + approvalPolicy, + workspaceWriteNetworkAccess, + webSearch, + dangerouslyBypassApprovalsAndSandbox + } = value; + if ( + reasoning !== undefined && + reasoning !== 'low' && + reasoning !== 'medium' && + reasoning !== 'high' + ) { + throw new Error(`${context}.reasoning must be one of: low, medium, high`); + } + if ( + timeoutSeconds !== undefined && + (typeof timeoutSeconds !== 'number' || !Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) + ) { + throw new Error(`${context}.timeoutSeconds must be a positive number`); + } + if ( + sandboxMode !== undefined && + !CODEX_SANDBOX_MODES.includes(sandboxMode as CodexSandboxMode) + ) { + throw new Error(`${context}.sandboxMode must be one of: ${CODEX_SANDBOX_MODES.join(', ')}`); + } + if ( + approvalPolicy !== undefined && + !CODEX_APPROVAL_POLICIES.includes(approvalPolicy as CodexApprovalPolicy) + ) { + throw new Error( + `${context}.approvalPolicy must be one of: ${CODEX_APPROVAL_POLICIES.join(', ')}` + ); + } + if (workspaceWriteNetworkAccess !== undefined && typeof workspaceWriteNetworkAccess !== 'boolean') { + throw new Error(`${context}.workspaceWriteNetworkAccess must be a boolean`); + } + if (webSearch !== undefined && typeof webSearch !== 'boolean') { + throw new Error(`${context}.webSearch must be a boolean`); + } + if ( + dangerouslyBypassApprovalsAndSandbox !== undefined && + typeof dangerouslyBypassApprovalsAndSandbox !== 'boolean' + ) { + throw new Error(`${context}.dangerouslyBypassApprovalsAndSandbox must be a boolean`); + } +} + +function findInLibrary(key: string): PersonaSpec | undefined { + const byIntent = (personaCatalog as Record)[key]; + if (byIntent) return byIntent; + for (const spec of listBuiltInPersonas()) { + if (spec.id === key) return spec; + } + return undefined; +} + +function isStandaloneOverride( + override: LocalPersonaOverride +): override is LocalPersonaOverride & { intent: string } { + return override.extends === undefined && override.intent !== undefined; +} + +function requireStandaloneField(value: T | undefined, context: string): T { + if (value === undefined) { + throw new Error(`${context} is required for standalone personas`); + } + return value; +} + +function assertStandaloneHarnessSettings( + settings: Record, + context: string +): HarnessSettings { + return parseHarnessSettings(settings, context); +} + +function standaloneSpecFromOverride( + override: LocalPersonaOverride & { intent: string }, + sidecarWarnings: string[] = [], + cwd = process.cwd() +): PersonaSpec { + const context = `standalone persona "${override.id}"`; + const harness = requireStandaloneField(override.harness, `${context}.harness`); + if (!HARNESS_VALUES.includes(harness)) { + throw new Error(`${context}.harness must be one of: ${HARNESS_VALUES.join(', ')}`); + } + const model = requireStandaloneField(override.model, `${context}.model`); + if (typeof model !== 'string' || !model.trim()) { + throw new Error(`${context}.model must be a non-empty string`); + } + const fallbackSystemPrompt = override.claudeMdContent ?? override.agentsMdContent; + const systemPrompt = + typeof override.systemPrompt === 'string' && override.systemPrompt.trim() + ? override.systemPrompt + : fallbackSystemPrompt; + if (typeof systemPrompt !== 'string' || !systemPrompt.trim()) { + throw new Error(`${context}.systemPrompt must be a non-empty string`); + } + const settingsRaw = override.harnessSettings; + if (!settingsRaw || !isPlainObject(settingsRaw)) { + throw new Error(`${context}.harnessSettings must be an object`); + } + const harnessSettings = assertStandaloneHarnessSettings( + settingsRaw as Record, + `${context}.harnessSettings` + ); + + const inputs = override.inputs; + const env = override.env; + const mcpServers = override.mcpServers; + const mount = override.mount; + const permissions = override.permissions; + + const claudeMdContent = override.claudeMdContent; + let claudeMd: string | undefined; + if (override.claudeMd !== undefined && claudeMdContent === undefined) { + const { abs, warning } = resolveSidecarPath( + override.claudeMd, + override.__sourceDir, + `[${override.id}].claudeMd` + ); + if (warning) sidecarWarnings.push(warning); + claudeMd = abs; + } + const agentsMdContent = override.agentsMdContent; + let agentsMd: string | undefined; + if (override.agentsMd !== undefined && agentsMdContent === undefined) { + const { abs, warning } = resolveSidecarPath( + override.agentsMd, + override.__sourceDir, + `[${override.id}].agentsMd` + ); + if (warning) sidecarWarnings.push(warning); + agentsMd = abs; + } + + return { + id: override.id, + intent: override.intent, + // Tags are optional per cloud#553 (denormalized catalog metadata). + ...(override.tags ? { tags: override.tags } : {}), + description: requireStandaloneField(override.description, `${context}.description`), + skills: resolveLocalSkillSources( + override.skills ?? [], + override.__sourceDir, + override.id, + sidecarWarnings, + cwd + ), + ...(inputs ? { inputs } : {}), + harness, + model, + systemPrompt, + harnessSettings, + ...(env ? { env } : {}), + ...(mcpServers ? { mcpServers } : {}), + ...(mount ? { mount } : {}), + ...(permissions ? { permissions } : {}), + ...(claudeMd ? { claudeMd } : {}), + ...(override.claudeMdMode ? { claudeMdMode: override.claudeMdMode } : {}), + ...(agentsMd ? { agentsMd } : {}), + ...(override.agentsMdMode ? { agentsMdMode: override.agentsMdMode } : {}), + ...(claudeMdContent ? { claudeMdContent } : {}), + ...(agentsMdContent ? { agentsMdContent } : {}) + }; +} + +/** + * Mutual-recursion with resolveInLayer: given a base key, walk strictly-lower + * layers until we find a persona with that id (local layers) or an id/intent + * match in the library. Returns a fully-merged PersonaSpec or undefined. + */ +function findInLowerLayers( + key: string, + startLayerIdx: number, + layers: readonly SourceLayer[], + overrides: Map>, + resolving: Set, + sidecarWarnings: string[], + cwd: string +): PersonaSpec | undefined { + for (let i = startLayerIdx; i < layers.length; i++) { + const layer = layers[i]; + const layerOverrides = overrides.get(layer.key); + if (!layerOverrides) continue; + const overrideId = findOverrideIdInLayer(key, layerOverrides, layer.source); + if (overrideId) { + return resolveInLayer(overrideId, i, layers, overrides, resolving, sidecarWarnings, cwd); + } + } + return findInLibrary(key); +} + +function findOverrideIdInLayer( + key: string, + layerOverrides: Map, + source: PersonaSource +): string | undefined { + if (layerOverrides.has(key)) return key; + + const matches: string[] = []; + for (const [id, override] of layerOverrides) { + if (override.intent === key) matches.push(id); + } + if (matches.length > 1) { + throw new Error( + `extends "${key}" is ambiguous in ${source}; matched local persona intents on ids: ${matches.join(', ')}` + ); + } + return matches[0]; +} + +function resolveInLayer( + id: string, + layerIdx: number, + layers: readonly SourceLayer[], + overrides: Map>, + resolving: Set, + sidecarWarnings: string[], + cwd: string +): PersonaSpec { + const layer = layers[layerIdx]; + const key = `${layer.key}:${id}`; + if (resolving.has(key)) { + throw new Error(`extends cycle detected through ${[...resolving, key].join(' -> ')}`); + } + resolving.add(key); + try { + const override = overrides.get(layer.key)?.get(id); + if (!override) { + throw new Error(`internal: resolveInLayer called for missing ${key}`); + } + if (isStandaloneOverride(override)) { + return standaloneSpecFromOverride(override, sidecarWarnings, cwd); + } + const baseKey = override.extends ?? override.id; + const base = findInLowerLayers( + baseKey, + layerIdx + 1, + layers, + overrides, + resolving, + sidecarWarnings, + cwd + ); + if (!base) { + const lowerLayers = [ + ...layers.slice(layerIdx + 1).map((lower) => lower.source), + 'library' + ].join(', '); + const hint = override.extends + ? `extends "${override.extends}" does not match any persona in lower layers (${lowerLayers})` + : `no lower-layer persona with id "${override.id}" to implicitly inherit from; add extends or define the persona in a lower layer`; + throw new Error(hint); + } + return mergeOverride(base, override, sidecarWarnings, cwd); + } finally { + resolving.delete(key); + } +} + +// Local skill source detection mirrors persona-kit's local provider: a +// non-URL path ending in `.md`. Anything else (prpm refs, skill.sh URLs) +// passes through untouched. +const LOCAL_SKILL_MD_RE = /\.md$/i; +const SKILL_URL_PREFIX_RE = /^[a-z][a-z0-9+.-]*:\/\//i; + +function isFile(path: string): boolean { + try { + return statSync(path).isFile(); + } catch { + return false; + } +} + +/** + * Resolve relative local skill sources (`./skills/foo.md`) declared by an + * override against the directory of the JSON file that declared them, the + * same way sidecar paths resolve. Two fallbacks cover the other layouts in + * the wild: persona packs keep `skills/` at the package root (one level + * above `personas/`), and installer-rewritten specs use cwd-relative + * `__assets` paths. + * + * Missing files surface as warnings and the skill is dropped, matching + * sidecar semantics. A broken pointer must not abort the launch: skill + * installs run as one `&&` chain, so a failing `cp` would otherwise kill + * the harness spawn AND prevent the skill cache marker from being written, + * forcing every subsequent spawn to re-download all skills. + */ +function resolveLocalSkillSources( + skills: readonly PersonaSkill[], + sourceDir: string | undefined, + personaId: string, + warnings: string[], + cwd = process.cwd() +): PersonaSkill[] { + return skills.flatMap((skill) => { + const source = skill.source; + if ( + typeof source !== 'string' || + SKILL_URL_PREFIX_RE.test(source) || + !LOCAL_SKILL_MD_RE.test(source) + ) { + return [skill]; + } + if (isAbsolute(source)) { + if (isFile(source)) return [skill]; + warnings.push( + `[${personaId}].skills "${skill.id}": local skill file not found at ${source}; skipping skill` + ); + return []; + } + const candidates = [ + ...(sourceDir + ? [resolvePath(sourceDir, source), resolvePath(sourceDir, '..', source)] + : []), + resolvePath(cwd, source) + ]; + const resolved = candidates.find(isFile); + if (!resolved) { + warnings.push( + `[${personaId}].skills "${skill.id}": local skill file not found (tried ${candidates.join(', ')}); skipping skill` + ); + return []; + } + return [{ ...skill, source: resolved }]; + }); +} + +/** + * Resolve a sidecar markdown path declared on `override` against the + * directory of the JSON file that declared it. Returns the absolute path + * along with any warnings about missing files; missing-file is non-fatal + * (the field is dropped from the resolved spec) so a developer iterating + * locally doesn't get blocked by a typo. + */ +function resolveSidecarPath( + relPath: string | undefined, + sourceDir: string | undefined, + label: string +): { abs?: string; warning?: string } { + if (!relPath) return {}; + if (!sourceDir) { + return { warning: `${label}: cannot resolve "${relPath}" without a source directory` }; + } + const abs = resolvePath(sourceDir, relPath); + let stat; + try { + stat = statSync(abs); + } catch (err) { + const e = err as NodeJS.ErrnoException; + if (e.code === 'ENOENT' || e.code === 'ENOTDIR') { + return { warning: `${label}: sidecar file not found at ${abs}` }; + } + // Surface real I/O failures (permissions, etc.) — silently treating + // an EACCES as "missing" hides config bugs from developers. + return { warning: `${label}: sidecar at ${abs} is not readable: ${e.message}` }; + } + if (!stat.isFile()) { + return { warning: `${label}: sidecar at ${abs} is not a file` }; + } + return { abs }; +} + +function mergeOverride( + base: PersonaSpec, + override: LocalPersonaOverride, + sidecarWarnings: string[] = [], + cwd = process.cwd() +): PersonaSpec { + const harness = override.harness ?? base.harness; + const model = override.model ?? base.model; + const systemPrompt = override.systemPrompt ?? base.systemPrompt; + const harnessSettings: HarnessSettings = parseHarnessSettings({ + ...base.harnessSettings, + ...(override.harnessSettings ?? {}) + }, `persona[${override.id}].harnessSettings`); + + const env = + override.env || base.env + ? { ...(base.env ?? {}), ...(override.env ?? {}) } + : undefined; + const inputs = + override.inputs || base.inputs + ? { ...(base.inputs ?? {}), ...(override.inputs ?? {}) } + : undefined; + const mcpServers = + override.mcpServers || base.mcpServers + ? { ...(base.mcpServers ?? {}), ...(override.mcpServers ?? {}) } + : undefined; + const mount = mergeMount(base.mount, override.mount); + const permissions = mergePermissions(base.permissions, override.permissions); + + // When the override sets a new path, the override owns the channel — + // drop inherited `*Content` so the override path isn't shadowed by an + // inlined built-in body. When the override leaves the path alone, the + // inherited content (if any) stays. + let claudeMd: string | undefined = base.claudeMd; + let claudeMdContent: string | undefined = base.claudeMdContent; + if (override.claudeMdContent !== undefined) { + claudeMd = undefined; + claudeMdContent = override.claudeMdContent; + } else if (override.claudeMd !== undefined) { + const { abs, warning } = resolveSidecarPath( + override.claudeMd, + override.__sourceDir, + `[${override.id}].claudeMd` + ); + if (warning) sidecarWarnings.push(warning); + claudeMd = abs; + claudeMdContent = undefined; + } + let agentsMd: string | undefined = base.agentsMd; + let agentsMdContent: string | undefined = base.agentsMdContent; + if (override.agentsMdContent !== undefined) { + agentsMd = undefined; + agentsMdContent = override.agentsMdContent; + } else if (override.agentsMd !== undefined) { + const { abs, warning } = resolveSidecarPath( + override.agentsMd, + override.__sourceDir, + `[${override.id}].agentsMd` + ); + if (warning) sidecarWarnings.push(warning); + agentsMd = abs; + agentsMdContent = undefined; + } + const claudeMdMode = override.claudeMdMode ?? base.claudeMdMode; + const agentsMdMode = override.agentsMdMode ?? base.agentsMdMode; + + const mergedTags = override.tags ?? base.tags; + // Skills replace wholesale, so resolution context is unambiguous: an + // override's skills resolve against its own __sourceDir; inherited skills + // were already resolved when the base layer was built. + const skills = + override.skills !== undefined + ? resolveLocalSkillSources( + override.skills, + override.__sourceDir, + override.id, + sidecarWarnings, + cwd + ) + : base.skills; + return { + id: override.id, + intent: base.intent, + ...(mergedTags ? { tags: mergedTags } : {}), + description: override.description ?? base.description, + skills, + ...(inputs ? { inputs } : {}), + harness, + model, + systemPrompt, + harnessSettings, + ...(env ? { env } : {}), + ...(mcpServers ? { mcpServers } : {}), + ...(mount ? { mount } : {}), + ...(permissions ? { permissions } : {}), + ...(claudeMd ? { claudeMd } : {}), + ...(claudeMdMode ? { claudeMdMode } : {}), + ...(agentsMd ? { agentsMd } : {}), + ...(agentsMdMode ? { agentsMdMode } : {}), + ...(claudeMdContent ? { claudeMdContent } : {}), + ...(agentsMdContent ? { agentsMdContent } : {}) + }; +} + +/** + * Test-only seam. Built-in personas are the only specs that can carry + * `claudeMdContent` / `agentsMdContent` (the catalog generator inlines + * sibling `.md` files at build time), and none ship sidecars today — + * so the file-based loader path can't be used to produce a `base` with + * inherited content. This export lets regression tests construct that + * scenario directly. + * + * @internal + */ +export const __mergeOverrideForTests = mergeOverride; + +function mergeMount( + base: PersonaMount | undefined, + override: PersonaMount | undefined +): PersonaMount | undefined { + if (!base && !override) return undefined; + const ignoredPatterns = [ + ...(base?.ignoredPatterns ?? []), + ...(override?.ignoredPatterns ?? []) + ]; + const readonlyPatterns = [ + ...(base?.readonlyPatterns ?? []), + ...(override?.readonlyPatterns ?? []) + ]; + const enabled = override?.enabled ?? base?.enabled; + if (ignoredPatterns.length === 0 && readonlyPatterns.length === 0) { + return enabled === true ? { enabled: true } : undefined; + } + return { + ...(enabled !== undefined ? { enabled } : {}), + ...(ignoredPatterns.length > 0 ? { ignoredPatterns } : {}), + ...(readonlyPatterns.length > 0 ? { readonlyPatterns } : {}) + }; +} + +function mergePermissions( + base: PersonaPermissions | undefined, + override: PersonaPermissions | undefined +): PersonaPermissions | undefined { + if (!base && !override) return undefined; + const allow = dedupe([...(base?.allow ?? []), ...(override?.allow ?? [])]); + const deny = dedupe([...(base?.deny ?? []), ...(override?.deny ?? [])]); + const mode = override?.mode ?? base?.mode; + const out: PersonaPermissions = {}; + if (allow.length > 0) out.allow = allow; + if (deny.length > 0) out.deny = deny; + if (mode) out.mode = mode; + return Object.keys(out).length > 0 ? out : undefined; +} + +function dedupe(values: string[]): string[] { + return [...new Set(values)]; +} + +export function loadLocalPersonas(options: LoadOptions = {}): LoadedLocalPersonas { + const cwd = options.cwd ?? process.cwd(); + const sourceDirs = buildPersonaSourceDirectories(options); + const warnings: string[] = [...sourceDirs.config.warnings]; + const personaPath = options.personaPath ? resolvePath(options.personaPath) : undefined; + const orderedSources: Array = [ + ...(personaPath + ? [{ source: 'path', dir: dirname(personaPath), configurable: false, file: personaPath }] + : []), + ...sourceDirs.directories + ]; + const layers: SourceLayer[] = orderedSources.map((sourceDir, idx) => ({ + key: `${idx}:${sourceDir.source}:${sourceDir.file ?? sourceDir.dir}`, + source: sourceDir.source, + dir: sourceDir.dir, + ...(sourceDir.file ? { file: sourceDir.file } : {}) + })); + + const overrides = new Map>(); + const layerFilePaths = new Map(); + for (const layer of layers) { + overrides.set(layer.key, readLayerDir(layer.dir, layer, warnings, layerFilePaths)); + } + + const byId = new Map(); + const sources = new Map(); + const paths = new Map(); + + for (let i = 0; i < layers.length; i++) { + const layer = layers[i]; + const layerOverrides = overrides.get(layer.key); + if (!layerOverrides) continue; + for (const id of layerOverrides.keys()) { + if (byId.has(id)) continue; // higher-layer already won + const sidecarWarnings: string[] = []; + try { + const resolved = resolveInLayer(id, i, layers, overrides, new Set(), sidecarWarnings, cwd); + byId.set(id, resolved); + sources.set(id, layer.source); + const filePath = layerFilePaths.get(`${layer.key}:${id}`); + if (filePath) paths.set(id, filePath); + for (const warning of sidecarWarnings) { + warnings.push(`[${layer.source}] ${warning}`); + } + } catch (err) { + warnings.push(`[${layer.source}] ${id}: ${(err as Error).message}`); + } + } + } + + return { byId, sources, paths, warnings }; +} diff --git a/packages/persona-registry/tsconfig.json b/packages/persona-registry/tsconfig.json new file mode 100644 index 00000000..df59da57 --- /dev/null +++ b/packages/persona-registry/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 469a25cb..7c720808 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,6 +58,9 @@ importers: '@agentworkforce/persona-kit': specifier: workspace:* version: link:../persona-kit + '@agentworkforce/persona-registry': + specifier: workspace:* + version: link:../persona-registry '@agentworkforce/runtime': specifier: workspace:* version: link:../runtime @@ -137,14 +140,17 @@ importers: packages/local-surface: dependencies: '@agent-relay/fleet': - specifier: ^10.1.0 - version: 10.2.0 + specifier: ^11.4.2 + version: 11.4.2(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)) '@agentworkforce/deploy': specifier: workspace:* version: link:../deploy '@agentworkforce/persona-kit': specifier: workspace:* version: link:../persona-kit + '@agentworkforce/persona-registry': + specifier: workspace:* + version: link:../persona-registry '@agentworkforce/runtime': specifier: workspace:* version: link:../runtime @@ -189,6 +195,15 @@ importers: packages/persona-nango-integrations: {} + packages/persona-registry: + dependencies: + '@agentworkforce/persona-kit': + specifier: workspace:* + version: link:../persona-kit + '@agentworkforce/workload-router': + specifier: workspace:* + version: link:../workload-router + packages/persona-repo-router: {} packages/persona-slack-comms: {} @@ -248,7 +263,7 @@ importers: devDependencies: '@agent-assistant/turn-context': specifier: ^0.4.35 - version: 0.4.35 + version: 0.4.35(ai@7.0.33(zod@3.25.76)) packages/workload-router: dependencies: @@ -302,8 +317,9 @@ packages: '@agent-assistant/vfs@0.4.35': resolution: {integrity: sha512-0pMO157u9+g+LO9DOqtlJbQ566n8/bXbY2ktZoz24ldvni0U6qyDLKZEcJDtZXWaIE6LlwHtXxdHNqhCm0R5/g==} - '@agent-relay/broker-darwin-arm64@10.2.0': - resolution: {integrity: sha512-JuIU0FbL50d6iXgp3NimdzWwXMc++R3TK5D4a0FlnMXMFUYbMLTwZdbtvOf4SdfLowTLhh+P+2HHG4x3dCuDVw==} + '@agent-relay/broker-darwin-arm64@11.4.2': + resolution: {integrity: sha512-RGisJluLF1bKYyRGZPdBb70CPfWbIxgUSAEWPCjoilIF8cw5/7/1rsilo7WgVgZPacrOMO73C7l2z2m4q9n8Gg==} + engines: {node: '>=22.0.0'} cpu: [arm64] os: [darwin] @@ -312,8 +328,9 @@ packages: cpu: [arm64] os: [darwin] - '@agent-relay/broker-darwin-x64@10.2.0': - resolution: {integrity: sha512-KtPSTenzaYB0D1AG0ZBVv90N/Owm2Qu4PE2sfRBB0FPGZUj1VzeT5ecTE0j1Pt1XJji5jX9Hg3R87pgGq9Ev+A==} + '@agent-relay/broker-darwin-x64@11.4.2': + resolution: {integrity: sha512-IomJjkdgdDnBtJx+k0zB91QrzkN1N0BR/eFpzFVov/Y96I06z38AYsjSppCxrycL+u7lHfpGnNkXdA57NdS5rg==} + engines: {node: '>=22.0.0'} cpu: [x64] os: [darwin] @@ -322,8 +339,9 @@ packages: cpu: [x64] os: [darwin] - '@agent-relay/broker-linux-arm64@10.2.0': - resolution: {integrity: sha512-cY0/rrmI1h5thGbiFbi4dneZ/2lOiixH4ldjGEUtkkYt1KM+z51dpgXEhOgADibzkQUZayaHlue7+JzlMgXJFA==} + '@agent-relay/broker-linux-arm64@11.4.2': + resolution: {integrity: sha512-iRsdTyrHMOEEQJUMuCFne/2BQPhGV+8DVQxRcMfvM1zlsuPiSQ4EtZSv5z8XSByBkKWnrNKUjo6ffOmC01BIWg==} + engines: {node: '>=22.0.0'} cpu: [arm64] os: [linux] @@ -332,8 +350,9 @@ packages: cpu: [arm64] os: [linux] - '@agent-relay/broker-linux-x64@10.2.0': - resolution: {integrity: sha512-rj9PsbeDv1NoJVkBpSCEkagT8wzfOO26Wcwg1OxwREvo2pLCtns2b3okxqxxTLStnujcyJ+1bNrjmLyV4nTNUQ==} + '@agent-relay/broker-linux-x64@11.4.2': + resolution: {integrity: sha512-ZY/pok/Qq0C88eHO+1mGqjYuLzitKmoUOwb1CyDWsdnrmp7nXE+wJAH8QZsJU9AgVieyxMZKrzkysU9+w3p48g==} + engines: {node: '>=22.0.0'} cpu: [x64] os: [linux] @@ -342,8 +361,9 @@ packages: cpu: [x64] os: [linux] - '@agent-relay/broker-win32-x64@10.2.0': - resolution: {integrity: sha512-+x1kN6stM4ZC8K7j45swXYLgYh5TD/hX1m/Xr5nBoFyBWp9dPjZHss2uyFtM+UpL46l3nO0mzYAcM1qdulrjZA==} + '@agent-relay/broker-win32-x64@11.4.2': + resolution: {integrity: sha512-Rv52iNLrRp/3A0JKstbWuzHiylxd7gatfWSB2IkxZ5ZtFGpiCCFgs3XrrWdHRiZVfmHGdIaBfFTaybgKLTKvbQ==} + engines: {node: '>=22.0.0'} cpu: [x64] os: [win32] @@ -393,17 +413,20 @@ packages: '@relayfile/adapter-slack': optional: true - '@agent-relay/fleet@10.2.0': - resolution: {integrity: sha512-Injya3fvQYOn9GoYrycgaHOOoh6TkyNkwnoYiHpPx56JhzsEQxMrQwjWzpvA+mpfPS7elO4W7m0Va2aF8PHBvQ==} + '@agent-relay/fleet@11.4.2': + resolution: {integrity: sha512-s9f0buOVQ/2O02KCC/HY3GkhA+W4s7FMh8k2AVQrAwQ7oG4VJ8hYJtLkXlc4KMTWVphGEfhrZ4CfqWyF0y2dHg==} + engines: {node: '>=22.0.0'} '@agent-relay/github-primitive@6.3.6': resolution: {integrity: sha512-yMGGN5ahqCr+REy1pFRvD0QJm7s+4Qh2UwZ5e8Jg7VpSEzsHtu6zXRwdB+Jwrr3lHmDGtST5ief8cWy23l0wJg==} - '@agent-relay/harness-driver@10.2.0': - resolution: {integrity: sha512-EJRbOwBF8IHPrHjyJVEkQ1XJb3WwnZ4GeUe7iy22Sdh+n0CX5EfjxOs4K5hSdKmwH29VbwjXE+lzPXbj7qMixQ==} + '@agent-relay/harness-driver@11.4.2': + resolution: {integrity: sha512-/pxeJpkOFEEHDXP+32CHV0PhyuqqPpwJKIJs+3OzlDoj71LjYmQ1RANxM9wrPyF+Qa/K5nbngeysnMerblAs5g==} + engines: {node: '>=22.0.0'} - '@agent-relay/harnesses@10.2.0': - resolution: {integrity: sha512-P7Ud1hfVUjFeTbbXeYeYK7GcQy7bYixm2xgWbi1OVGEd/RqpncNl7obpmc+Tf6lnXPSEUXx7KUTBp2We5FhzlQ==} + '@agent-relay/harnesses@11.4.2': + resolution: {integrity: sha512-7NfVt0XFQDhwQryTdrmRkJGox9H9mFDsBoD8xcbYAKFyFbcrniOkGuAO/KAo5S9RO2Hi1HdsJCxAjJQy7uMVHg==} + engines: {node: '>=22.0.0'} '@agent-relay/hooks@4.0.40': resolution: {integrity: sha512-WVbmXtJV3dHsKXs7zVOMpjeuEGBBWt0DCkqLuANKQMAaR2FkrhUbK0XZWL3lz2IHDlByM3tu9y4KgzbSoKu5hA==} @@ -417,8 +440,9 @@ packages: '@agent-relay/memory@6.3.6': resolution: {integrity: sha512-jetbd2FPOuflN4Ykjw/1QTCNxXTc9s8gzq0iSOmQJF4lpGSRXHfP8sdQCLPNnh0oun17Q7NQmtoRGau3xkr55g==} - '@agent-relay/sdk@10.2.0': - resolution: {integrity: sha512-NGN+UbN5PNf4Yh9y3PkUXOHlziPX9zccqbsh8URtiviJ3WZN6+6/JH25xadj7RlZQP7izwEdl89ql2PTapp1aQ==} + '@agent-relay/sdk@11.4.2': + resolution: {integrity: sha512-VnXMgh8ll1iA13RDac87Y1pyekHe4P4BYfREuqtoaHzy92sdXqSgSRXDpRiStl/bnswPcVWoo3hNMt7DI++QAw==} + engines: {node: '>=22.0.0'} '@agent-relay/sdk@4.0.40': resolution: {integrity: sha512-/65zrEALDUOPU96SBMBl462r6J5w/vQyshR0OV9KnLfzp5eRBhgv9p3beeFDgq6WuLto/A28U5zgnTyST3/n4g==} @@ -496,6 +520,93 @@ packages: '@agentworkforce/workload-router@0.11.0': resolution: {integrity: sha512-6Fn4oDsYeNRPe+k7hVfS3Ae3yIocNjuvscVvRswn74CzxSC1X9+1wDhQ5eCvE+S1m1ixAjYGFC9/MNwuhFwjHw==} + '@ai-sdk/gateway@4.0.21': + resolution: {integrity: sha512-GafyeyHFDKJjdtbyhCQ0aC2FORdyE56IxK45EZ1JLO1iVdg8XqEU3bwnRzI0+5S1XHV7W2qQsxkZnGYPbsFiXA==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/gateway@4.0.25': + resolution: {integrity: sha512-HhRLsdmvOCbFK7kJ1NdaE9f3ce2IYZlaAsTYXI7IMFj9G66BaXbR6d4zraulJPGqCbZ6TEltaSSLG9GFQH8Xdg==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/harness-claude-code@1.0.35': + resolution: {integrity: sha512-ORajIdslCIwWXgg4EH1vzuA8SZowH05w64LEG7QXLRAhHMLN1dOtHZrEijCnJ73b50aGPgXqt4sxHwgk82ulFQ==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/harness-codex@1.0.40': + resolution: {integrity: sha512-xkRqMoxk8kc053b5x+HQ7H7eDMSoT1u/ahwnpwfWwMw/B62QoucK1Tc+kdCsGW6ml8WqACFzeLJ0hsnh9Jn54A==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/harness-deepagents@1.0.33': + resolution: {integrity: sha512-P/8coDqvHEtJOHrWd3BJEAWwOdYpZJNUtpMAhHPl+/Sp18ZBtARO5eizZwSBcVQmfIo6oihLQOqv8asHG+VXqw==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/harness-opencode@1.0.35': + resolution: {integrity: sha512-K/0TsNUoKVg2tPHy98xNTMK9amAw7hJXx0KGLGppfYJ8DjTl3jifaZZ4tg3flsS/PrqOv3TVmkxgAgVrxbpVNw==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/harness-pi@1.0.34': + resolution: {integrity: sha512-rVidMyDSp0EiWKMnfj8edkLZqhi2BECHY6Y7EYR7CAncY7JdzpysdVcYtMnDJak1EMSNFZZlS8UnHSvoTNUdWA==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/harness@1.0.34': + resolution: {integrity: sha512-t7y30GqSZqNK85mVEnC6FsFAFln4YdBtZV0YLzPIyWCozzeG3fHI3mv+Opa4d1+KbdZk0zP2Zuflw5tYLWnwuA==} + engines: {node: '>=22'} + peerDependencies: + ws: '>=8.20.1' + zod: ^3.25.76 || ^4.1.8 + peerDependenciesMeta: + ws: + optional: true + + '@ai-sdk/harness@1.0.38': + resolution: {integrity: sha512-M413w44C9Ylilcv6nMpyWMKoJwyNkOUHp2FV2IgFxaXDXcEkXpeqAQipN75V1FYgKwkkWdmk1YSp0W/eDBCIyw==} + engines: {node: '>=22'} + peerDependencies: + ws: '>=8.20.1' + zod: ^3.25.76 || ^4.1.8 + peerDependenciesMeta: + ws: + optional: true + + '@ai-sdk/provider-utils@5.0.10': + resolution: {integrity: sha512-uPyec0+85dwxZYXtb8qe8gCjhjDfxP4LCDo/uRQS/iG+FIgYbHPRhr/ys281udG90bTaE18+5cxWraYaf8oHCw==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@5.0.12': + resolution: {integrity: sha512-bbhlOgHeYwrIGheLkM6fhS8hVger8uFPmcOLg+kxc9EFh7y30XYorWhthlYAgpadO3SJhFZrIcEknN7qEqEVvA==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@4.0.3': + resolution: {integrity: sha512-e0CpNWJUY7OxAFAnCZkw+ri9QOHWwTs1tXP42782KFGCU07qt8NiXCrCVowyCB5dP2r5/Uls+g2oPd8kOJn9dw==} + engines: {node: '>=22'} + + '@anthropic-ai/sdk@0.91.1': + resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -519,6 +630,10 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + '@aws-sdk/client-bedrock-runtime@3.1048.0': + resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/client-s3@3.1020.0': resolution: {integrity: sha512-ibfxjP5zLUqpujLE0OTgD+jZ3KStx9dTASL7d7Eekw4sv7ZHv1UN6CPDcKnCNXdPzlBWi5Wc5lWJ4sU1M8ygEQ==} engines: {node: '>=20.0.0'} @@ -531,6 +646,10 @@ packages: resolution: {integrity: sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==} engines: {node: '>=20.0.0'} + '@aws-sdk/core@3.977.6': + resolution: {integrity: sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/crc64-nvme@3.972.7': resolution: {integrity: sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg==} engines: {node: '>=20.0.0'} @@ -539,34 +658,70 @@ packages: resolution: {integrity: sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-env@3.972.67': + resolution: {integrity: sha512-rcIpk5kxUqDaaNa6Xk23pQ6ViY7jlqzmfFWCahQcBT97ddXaXYYwzCen9Tz1Jvo6aJft6wDl5bN44/Jw5B4oLA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-http@3.972.36': resolution: {integrity: sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-http@3.972.69': + resolution: {integrity: sha512-nggwJtZ4eeNsUw5IeWBMXsi1ryct5idi0K+/SCRF3kybLubOMaNTb3XCihXpWMiVpyzyPeIrl0zTkzhBH9porA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-ini@3.972.38': resolution: {integrity: sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-ini@3.973.12': + resolution: {integrity: sha512-pNEf/OeyN5X3VmLKlgSO6TqaWmW10CvI3TfwL1XhsuhYjSLT2VDaxFnCPHnOeQXSaFisMX4jNhpETriqN8DOmg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-login@3.972.38': resolution: {integrity: sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-login@3.972.74': + resolution: {integrity: sha512-0AQfDcf99TNmqVKv0owHrw/TQs6i4ZE5t9qmz6NvO53bE/sA/tpXhXL9AAcEP1qHc6Zzjd1UMb69+/9zdhvY3g==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-node@3.972.39': resolution: {integrity: sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-node@3.972.78': + resolution: {integrity: sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-process@3.972.34': resolution: {integrity: sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-process@3.972.67': + resolution: {integrity: sha512-IlUEejorGTWKb4/Dm7K5Yw4QxUmXLThLhrvBmzVBqZFTbW72cv9LTcITmo1dsnYriALE4h68mOq4LB99x6sQ7Q==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-sso@3.972.38': resolution: {integrity: sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-sso@3.973.11': + resolution: {integrity: sha512-gAQBkBZxUB84d71+pPcI9L+jh2ujhuAVxc/4FgGiWFDjkPBlMKxzd5XDtkSXTFX8Ro7ansnT88+XadasxMeCRw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-web-identity@3.972.38': resolution: {integrity: sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-web-identity@3.972.73': + resolution: {integrity: sha512-SnlEmQa6SjOgs6iOPLUQl1Eyq4AKiAdPQlkOhFhqNfDtDCwibMGvL6QlkSmf3o6vAUSImzdPCxowT5dfQUZP1A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/eventstream-handler-node@3.972.31': + resolution: {integrity: sha512-/BRzvkp46mF6eXBL/l9WKPQQfifLlUPaWli6n9/T/WDLUg8he7TCyuNFnk6RvHP5j5W/kMj5Gxw7W778LJaXDA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/lib-storage@3.1045.0': resolution: {integrity: sha512-F7dp2ST/83Iz6JTMMmUYEXxg7R1JDewfvzJeWWiDTwe0vLsg67JTN7OsBe6G8DWYbLQ+EyPWkyc3oFnxOjCVfg==} engines: {node: '>=20.0.0'} @@ -577,6 +732,10 @@ packages: resolution: {integrity: sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA==} engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-eventstream@3.972.26': + resolution: {integrity: sha512-2eIvouTZoxPu5ClHY6ij13De1yhY8Rmllt0dlGeBNXX3wmR7fU1pvMCGb50fKm1GxcuntWK0t1T0cMjTyDoUQA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-expect-continue@3.972.10': resolution: {integrity: sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ==} engines: {node: '>=20.0.0'} @@ -613,6 +772,14 @@ packages: resolution: {integrity: sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==} engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-websocket@3.972.49': + resolution: {integrity: sha512-wGYJvu1/stdWuzGcNyh44u8aY7ArU8XFrMesBlzIYn70HWVCRBNEngQexDuPIMu0NEgsKGKmTc0uvnS/bF7KWw==} + engines: {node: '>= 14.0.0'} + + '@aws-sdk/nested-clients@3.997.41': + resolution: {integrity: sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/nested-clients@3.997.6': resolution: {integrity: sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==} engines: {node: '>=20.0.0'} @@ -625,14 +792,30 @@ packages: resolution: {integrity: sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==} engines: {node: '>=20.0.0'} + '@aws-sdk/signature-v4-multi-region@3.996.43': + resolution: {integrity: sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1041.0': resolution: {integrity: sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==} engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1048.0': + resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1103.0': + resolution: {integrity: sha512-N4wy26MNn31ItGVHYHPrEuCIFY4MBBjC+C5v1lJKqIUSA7OZBdhleCY53zCCrXn27hsk7YNOaTuhQu807S4AfQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/types@3.973.8': resolution: {integrity: sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==} engines: {node: '>=20.0.0'} + '@aws-sdk/types@3.974.2': + resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-arn-parser@3.972.3': resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} engines: {node: '>=20.0.0'} @@ -661,10 +844,22 @@ packages: resolution: {integrity: sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==} engines: {node: '>=20.0.0'} + '@aws-sdk/xml-builder@3.972.37': + resolution: {integrity: sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==} + engines: {node: '>=20.0.0'} + '@aws/lambda-invoke-store@0.2.4': resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} engines: {node: '>=18.0.0'} + '@aws/lambda-invoke-store@0.3.0': + resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} + engines: {node: '>=18.0.0'} + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + '@clack/core@0.3.5': resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==} @@ -683,6 +878,24 @@ packages: resolution: {integrity: sha512-yVOzRShA9fmoGSG+wlSq6JrRLmc4h9yUTAjjfGonD6f2NjzwQuu1FadUCYlyVOmasKEn/Du9w27NgmXdMQp6JA==} deprecated: 'Moved to @daytona/sdk, same API, no breaking changes. Please update: npm uninstall @daytonaio/sdk && npm i @daytona/sdk' + '@earendil-works/pi-agent-core@0.79.10': + resolution: {integrity: sha512-XKxgdjhcPuyjrthCOFSgfzT3xZ1uBrJ1IMVDxci1to6hIN6BIg9J5iY8q0pGXK1DLgATLP23da+1UyZLwA360Q==} + engines: {node: '>=22.19.0'} + + '@earendil-works/pi-ai@0.79.10': + resolution: {integrity: sha512-9jR23tOl0BIUdQMn70Gr72xYBpM7Xgl9Lyv7gAnU1USfkNRuYG/f/edLl+n/Dp/RafDW3JI4DF7y/GhgkORuew==} + engines: {node: '>=22.19.0'} + hasBin: true + + '@earendil-works/pi-coding-agent@0.79.10': + resolution: {integrity: sha512-YxaRhmgyDTvLDdGVbe7YzTHV80oL5mX5odg6EhGHz3w5Wu1Ix8DCw7bhtiOBLGQNFRcknia0zPmVWIj30XP1EA==} + engines: {node: '>=22.19.0'} + hasBin: true + + '@earendil-works/pi-tui@0.79.10': + resolution: {integrity: sha512-FUVOjDn1DVwM1uHD5MNYboXQrXjIDbSt+BQ3py7nQWCY62tKfxgiM1OBMxTcwRWLfSdZHUPpV0hm1loIdUJnPw==} + engines: {node: '>=22.19.0'} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -995,6 +1208,15 @@ packages: cpu: [x64] os: [win32] + '@google/genai@1.52.0': + resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.2 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + '@grpc/grpc-js@1.14.3': resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} engines: {node: '>=12.10.0'} @@ -1020,6 +1242,77 @@ packages: '@js-sdsl/ordered-map@4.4.2': resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@mariozechner/clipboard-darwin-arm64@0.3.9': + resolution: {integrity: sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@mariozechner/clipboard-darwin-universal@0.3.9': + resolution: {integrity: sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==} + engines: {node: '>= 10'} + os: [darwin] + + '@mariozechner/clipboard-darwin-x64@0.3.9': + resolution: {integrity: sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@mariozechner/clipboard-linux-arm64-gnu@0.3.9': + resolution: {integrity: sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@mariozechner/clipboard-linux-arm64-musl@0.3.9': + resolution: {integrity: sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@mariozechner/clipboard-linux-riscv64-gnu@0.3.9': + resolution: {integrity: sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@mariozechner/clipboard-linux-x64-gnu@0.3.9': + resolution: {integrity: sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@mariozechner/clipboard-linux-x64-musl@0.3.9': + resolution: {integrity: sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@mariozechner/clipboard-win32-arm64-msvc@0.3.9': + resolution: {integrity: sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@mariozechner/clipboard-win32-x64-msvc@0.3.9': + resolution: {integrity: sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@mariozechner/clipboard@0.3.9': + resolution: {integrity: sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==} + engines: {node: '>= 10'} + + '@mistralai/mistralai@2.2.6': + resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@modelcontextprotocol/sdk@1.29.0': resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} engines: {node: '>=18'} @@ -1053,6 +1346,10 @@ packages: resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==} engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} @@ -1486,6 +1783,9 @@ packages: resolution: {integrity: sha512-Y/tMuRZG34wEfpTxDfXFp5o2X3ibb5ojGWupGJ9ZxkThCx7rOGydnszJPzEbgDK3eF6nJ6UuE7bCTpIEutYnPw==} engines: {node: '>=22'} + '@silvia-odwyer/photon-node@0.3.4': + resolution: {integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==} + '@sinclair/typebox@0.34.52': resolution: {integrity: sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==} @@ -1510,10 +1810,18 @@ packages: engines: {node: '>=18.0.0'} deprecated: Deprecated due to bug in browser bundling instructions https://github.com/smithy-lang/smithy-typescript/issues/2025 + '@smithy/core@3.31.1': + resolution: {integrity: sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.3.1': resolution: {integrity: sha512-0S/acwHnqX4WrjXzhdiDRxsG2s9SC0cpPIK9nZ1R6UOHd+j7uL28+4bHu22urbLk2TVw3fkp6na/+fkUt/pLNQ==} engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.4.16': + resolution: {integrity: sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-browser@4.3.1': resolution: {integrity: sha512-X7MyI1fu8M84IPKk49kO4kb27Mqp6un9/0o/MsA1ngZ5OxxWKGUxPS3S/AJ9q1cPVTSGmRcbaGNfGUSsflTJkg==} engines: {node: '>=18.0.0'} @@ -1530,6 +1838,10 @@ packages: resolution: {integrity: sha512-r7bN6spQ+caZC8AnyvSxkRUb57zt2jhhRw3Z+2Ez8hjq6coIikDBFUUI/+CQ1xx9K6eX1Gx6wUKo4ylU66TIqw==} engines: {node: '>=18.0.0'} + '@smithy/fetch-http-handler@5.6.13': + resolution: {integrity: sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==} + engines: {node: '>=18.0.0'} + '@smithy/hash-blob-browser@4.3.1': resolution: {integrity: sha512-2fbltQVQYmGd0OzPv2oDMRF0pxkzeIx8cbpx2x6W3UJWGaEyUzVPxF4d0sDXZ/r2obg+RbTyhTidXWlPDsKRKw==} engines: {node: '>=18.0.0'} @@ -1586,6 +1898,14 @@ packages: resolution: {integrity: sha512-BdEYko85f/ldp68uH8XEyIvo810xFk6eyPH81SRggTOApYHWA+Xu7B2EzLuHbe37WVLaUA7F1fWR3/zBeme2WA==} engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.7.3': + resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.9.13': + resolution: {integrity: sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==} + engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.3.1': resolution: {integrity: sha512-3NHoqVBhzpY2b4YBx9AqyKC4C8nnEjl5FyKuxrCjvnjinG0ODj+yg1xX360nNahT6wghYjSw1SooCt3kIdnqIA==} engines: {node: '>=18.0.0'} @@ -1602,6 +1922,10 @@ packages: resolution: {integrity: sha512-728lZZEWYWubBESrfntNslZQYDKRlJDY4dcDnYbL50+gu35pGPLblu4S0/RH/RDLF6me1M87ECHsHELGL7dA/Q==} engines: {node: '>=18.0.0'} + '@smithy/signature-v4@5.6.12': + resolution: {integrity: sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==} + engines: {node: '>=18.0.0'} + '@smithy/smithy-client@4.13.1': resolution: {integrity: sha512-IcznNM8Qd9u1X3oflp12tkzyOB4HbT+sfYWlWiyEysgNzSHoWcHUUsTT4y1jjDjtVuuVVQbYks+g1kVd7u1eGQ==} engines: {node: '>=18.0.0'} @@ -1610,6 +1934,10 @@ packages: resolution: {integrity: sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==} engines: {node: '>=18.0.0'} + '@smithy/types@4.16.1': + resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==} + engines: {node: '>=18.0.0'} + '@smithy/url-parser@4.3.1': resolution: {integrity: sha512-tuelFlF2PZR/wogFC58NIrPOv+Zna4N1+3kA161/33D1Gbwvl6Nh4WsAsW05ZyPp0O6CMGsdbb0S2b/qVjRMCw==} engines: {node: '>=18.0.0'} @@ -1670,6 +1998,9 @@ packages: resolution: {integrity: sha512-G/gWDykZNL0NVcd1qXkoKm45jxJECp6q53DSomM5QKMsyAMEsGksVq+HwgonqYxfFJEzzHi6ljtWKXVS1pl0/Q==} engines: {node: '>=18.0.0'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1685,6 +2016,13 @@ packages: '@types/validate-npm-package-name@4.0.2': resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} + + '@workflow/serde@4.1.0': + resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1699,6 +2037,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + agent-trajectories@0.5.3: resolution: {integrity: sha512-Yb/aVdxE1uWdSWWoXocFSpg1yriLlz/aEMVj4f6RktMFBCqPK3ZbNutTiWxGzBtv7swId7ivoWK9wTYxRd3/Kg==} engines: {node: '>=20.0.0'} @@ -1709,6 +2051,18 @@ packages: engines: {node: '>=20.0.0'} hasBin: true + ai@7.0.29: + resolution: {integrity: sha512-q+A+skhl6SyjWliU6W7zNYgDUrEk5SbNrCc5vt4S9n6+n6e7jSorDmu51hlhjDOsS7VwzWGCgbWFvvT3iomtvg==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ai@7.0.33: + resolution: {integrity: sha512-31X29OiXn7OBUcar1RRf1q75FirvGXCL/xL3VykeLG+Lp3AcZkeG0tthO6hvNM/jZo3h/0g32fhgHVYekiBAZQ==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -1759,6 +2113,9 @@ packages: bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} @@ -1777,6 +2134,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer@5.6.0: resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} @@ -1892,6 +2252,10 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1913,6 +2277,10 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -1934,6 +2302,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -2035,6 +2406,9 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2055,6 +2429,10 @@ packages: fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -2076,6 +2454,10 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} @@ -2095,6 +2477,14 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gaxios@7.3.0: + resolution: {integrity: sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==} + engines: {node: '>=18'} + + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2103,6 +2493,10 @@ packages: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2119,10 +2513,21 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + google-auth-library@10.9.1: + resolution: {integrity: sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==} + engines: {node: '>=18'} + + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2139,6 +2544,9 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} @@ -2147,6 +2555,10 @@ packages: resolution: {integrity: sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==} engines: {node: '>=16.9.0'} + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} @@ -2154,6 +2566,14 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -2230,20 +2650,40 @@ packages: peerDependencies: ws: '>=8.20.1' + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + listr2@10.2.2: resolution: {integrity: sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==} engines: {node: '>=22.13.0'} @@ -2266,6 +2706,11 @@ packages: resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} engines: {node: 20 || >=22} + marked@18.0.5: + resolution: {integrity: sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2339,6 +2784,15 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2365,6 +2819,18 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + openai@6.26.0: + resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==} + hasBin: true + peerDependencies: + ws: '>=8.20.1' + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + ora@9.4.0: resolution: {integrity: sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==} engines: {node: '>=20'} @@ -2402,6 +2868,9 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + partial-json@0.1.7: + resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + path-expression-matcher@1.5.0: resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} engines: {node: '>=14.0.0'} @@ -2435,6 +2904,9 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + protobufjs@7.6.3: resolution: {integrity: sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==} engines: {node: '>=12.0.0'} @@ -2486,6 +2958,10 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -2514,6 +2990,11 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -2558,6 +3039,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2638,6 +3122,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + ts-json-schema-generator@2.9.0: resolution: {integrity: sha512-NR5ZE108uiPtBHBJNGnhwoUaUx5vWTDJzDFG9YlRoqxPU76n+5FClRh92dcGgysbe1smRmYalM9Saj97GW1J4Q==} engines: {node: '>=22.0.0'} @@ -2658,6 +3145,12 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + typebox@1.1.38: + resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==} + + typebox@1.3.11: + resolution: {integrity: sha512-tZGKIS02Opbh4EYMmAEVuXl+y3EQJ9ZlkitLZ1CmFCY4ZOqr/xWR9dDSCFmIjNDICGMWkOqMh6WxGTyRXqcSGQ==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -2670,6 +3163,10 @@ packages: resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} engines: {node: '>=20.18.1'} + undici@8.5.0: + resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==} + engines: {node: '>=22.19.0'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -2685,6 +3182,10 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -2786,16 +3287,16 @@ snapshots: dependencies: '@agent-assistant/traits': 0.2.24 - '@agent-assistant/harness@0.4.35': + '@agent-assistant/harness@0.4.35(ai@7.0.33(zod@3.25.76))': dependencies: '@agent-assistant/connectivity': 0.2.24 '@agent-assistant/coordination': 0.2.24 '@agent-assistant/core': 0.2.24(@agent-assistant/traits@0.2.24) - '@agent-assistant/memory': 0.4.35 + '@agent-assistant/memory': 0.4.35(ai@7.0.33(zod@3.25.76)) '@agent-assistant/traits': 0.2.24 - '@agent-assistant/turn-context': 0.3.21 + '@agent-assistant/turn-context': 0.3.21(ai@7.0.33(zod@3.25.76)) '@agent-assistant/vfs': 0.2.24 - '@agent-relay/sdk': 6.3.6 + '@agent-relay/sdk': 6.3.6(ai@7.0.33(zod@3.25.76)) zod: 3.25.76 transitivePeerDependencies: - '@agent-relay/credential-proxy' @@ -2811,9 +3312,9 @@ snapshots: - debug - utf-8-validate - '@agent-assistant/memory@0.2.24': + '@agent-assistant/memory@0.2.24(ai@7.0.33(zod@3.25.76))': dependencies: - '@agent-relay/memory': 4.0.40 + '@agent-relay/memory': 4.0.40(ai@7.0.33(zod@3.25.76)) transitivePeerDependencies: - '@agent-relay/credential-proxy' - '@anthropic-ai/claude-agent-sdk' @@ -2826,9 +3327,9 @@ snapshots: - crewai - utf-8-validate - '@agent-assistant/memory@0.4.35': + '@agent-assistant/memory@0.4.35(ai@7.0.33(zod@3.25.76))': dependencies: - '@agent-relay/memory': 6.3.6 + '@agent-relay/memory': 6.3.6(ai@7.0.33(zod@3.25.76)) supermemory: 4.24.2 transitivePeerDependencies: - '@agent-relay/credential-proxy' @@ -2854,10 +3355,10 @@ snapshots: '@agent-assistant/traits@0.2.24': {} - '@agent-assistant/turn-context@0.3.21': + '@agent-assistant/turn-context@0.3.21(ai@7.0.33(zod@3.25.76))': dependencies: - '@agent-assistant/harness': 0.4.35 - '@agent-assistant/memory': 0.2.24 + '@agent-assistant/harness': 0.4.35(ai@7.0.33(zod@3.25.76)) + '@agent-assistant/memory': 0.2.24(ai@7.0.33(zod@3.25.76)) '@agent-assistant/traits': 0.2.24 transitivePeerDependencies: - '@agent-relay/credential-proxy' @@ -2873,10 +3374,10 @@ snapshots: - debug - utf-8-validate - '@agent-assistant/turn-context@0.4.35': + '@agent-assistant/turn-context@0.4.35(ai@7.0.33(zod@3.25.76))': dependencies: - '@agent-assistant/harness': 0.4.35 - '@agent-assistant/memory': 0.2.24 + '@agent-assistant/harness': 0.4.35(ai@7.0.33(zod@3.25.76)) + '@agent-assistant/memory': 0.2.24(ai@7.0.33(zod@3.25.76)) '@agent-assistant/traits': 0.2.24 '@agent-assistant/vfs': 0.4.35 transitivePeerDependencies: @@ -2897,31 +3398,31 @@ snapshots: '@agent-assistant/vfs@0.4.35': {} - '@agent-relay/broker-darwin-arm64@10.2.0': + '@agent-relay/broker-darwin-arm64@11.4.2': optional: true '@agent-relay/broker-darwin-arm64@6.3.6': optional: true - '@agent-relay/broker-darwin-x64@10.2.0': + '@agent-relay/broker-darwin-x64@11.4.2': optional: true '@agent-relay/broker-darwin-x64@6.3.6': optional: true - '@agent-relay/broker-linux-arm64@10.2.0': + '@agent-relay/broker-linux-arm64@11.4.2': optional: true '@agent-relay/broker-linux-arm64@6.3.6': optional: true - '@agent-relay/broker-linux-x64@10.2.0': + '@agent-relay/broker-linux-x64@11.4.2': optional: true '@agent-relay/broker-linux-x64@6.3.6': optional: true - '@agent-relay/broker-win32-x64@10.2.0': + '@agent-relay/broker-win32-x64@11.4.2': optional: true '@agent-relay/broker-win32-x64@6.3.6': @@ -2991,48 +3492,60 @@ snapshots: '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@agent-relay/fleet@10.2.0': + '@agent-relay/fleet@11.4.2(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))': dependencies: - '@agent-relay/harness-driver': 10.2.0 - '@agent-relay/harnesses': 10.2.0 + '@agent-relay/harness-driver': 11.4.2 + '@agent-relay/harnesses': 11.4.2(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) '@relaycast/sdk': 6.0.5 ws: 8.21.0 - zod: 3.25.76 + zod: 4.4.3 transitivePeerDependencies: + - '@modelcontextprotocol/sdk' - bufferutil + - supports-color - utf-8-validate '@agent-relay/github-primitive@6.3.6': dependencies: '@agent-relay/workflow-types': 6.3.6 - '@agent-relay/harness-driver@10.2.0': + '@agent-relay/harness-driver@11.4.2': dependencies: - '@agent-relay/sdk': 10.2.0 + '@agent-relay/sdk': 11.4.2 ws: 8.21.0 - zod: 3.25.76 + zod: 4.4.3 optionalDependencies: - '@agent-relay/broker-darwin-arm64': 10.2.0 - '@agent-relay/broker-darwin-x64': 10.2.0 - '@agent-relay/broker-linux-arm64': 10.2.0 - '@agent-relay/broker-linux-x64': 10.2.0 - '@agent-relay/broker-win32-x64': 10.2.0 + '@agent-relay/broker-darwin-arm64': 11.4.2 + '@agent-relay/broker-darwin-x64': 11.4.2 + '@agent-relay/broker-linux-arm64': 11.4.2 + '@agent-relay/broker-linux-x64': 11.4.2 + '@agent-relay/broker-win32-x64': 11.4.2 transitivePeerDependencies: - bufferutil - utf-8-validate - '@agent-relay/harnesses@10.2.0': + '@agent-relay/harnesses@11.4.2(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': dependencies: - '@agent-relay/harness-driver': 10.2.0 - '@agent-relay/sdk': 10.2.0 + '@agent-relay/harness-driver': 11.4.2 + '@agent-relay/sdk': 11.4.2 + '@ai-sdk/harness': 1.0.34(ws@8.21.0)(zod@4.4.3) + '@ai-sdk/harness-claude-code': 1.0.35(zod@4.4.3) + '@ai-sdk/harness-codex': 1.0.40(zod@4.4.3) + '@ai-sdk/harness-deepagents': 1.0.33(zod@4.4.3) + '@ai-sdk/harness-opencode': 1.0.35(zod@4.4.3) + '@ai-sdk/harness-pi': 1.0.34(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) transitivePeerDependencies: + - '@modelcontextprotocol/sdk' - bufferutil + - supports-color - utf-8-validate + - ws + - zod - '@agent-relay/hooks@4.0.40': + '@agent-relay/hooks@4.0.40(ai@7.0.33(zod@3.25.76))': dependencies: '@agent-relay/config': 4.0.40 - '@agent-relay/sdk': 4.0.40 + '@agent-relay/sdk': 4.0.40(ai@7.0.33(zod@3.25.76)) '@agent-relay/trajectory': 4.0.40 transitivePeerDependencies: - '@agent-relay/credential-proxy' @@ -3046,10 +3559,10 @@ snapshots: - crewai - utf-8-validate - '@agent-relay/hooks@6.3.6': + '@agent-relay/hooks@6.3.6(ai@7.0.33(zod@3.25.76))': dependencies: '@agent-relay/config': 6.3.6 - '@agent-relay/sdk': 6.3.6 + '@agent-relay/sdk': 6.3.6(ai@7.0.33(zod@3.25.76)) '@agent-relay/trajectory': 6.3.6 transitivePeerDependencies: - '@agent-relay/credential-proxy' @@ -3065,9 +3578,9 @@ snapshots: - debug - utf-8-validate - '@agent-relay/memory@4.0.40': + '@agent-relay/memory@4.0.40(ai@7.0.33(zod@3.25.76))': dependencies: - '@agent-relay/hooks': 4.0.40 + '@agent-relay/hooks': 4.0.40(ai@7.0.33(zod@3.25.76)) transitivePeerDependencies: - '@agent-relay/credential-proxy' - '@anthropic-ai/claude-agent-sdk' @@ -3080,9 +3593,9 @@ snapshots: - crewai - utf-8-validate - '@agent-relay/memory@6.3.6': + '@agent-relay/memory@6.3.6(ai@7.0.33(zod@3.25.76))': dependencies: - '@agent-relay/hooks': 6.3.6 + '@agent-relay/hooks': 6.3.6(ai@7.0.33(zod@3.25.76)) transitivePeerDependencies: - '@agent-relay/credential-proxy' - '@anthropic-ai/claude-agent-sdk' @@ -3097,11 +3610,13 @@ snapshots: - debug - utf-8-validate - '@agent-relay/sdk@10.2.0': + '@agent-relay/sdk@11.4.2': dependencies: '@relaycast/sdk': 6.0.5 - - '@agent-relay/sdk@4.0.40': + '@relaycast/types': 6.0.5 + zod: 4.4.3 + + '@agent-relay/sdk@4.0.40(ai@7.0.33(zod@3.25.76))': dependencies: '@agent-relay/config': 4.0.40 '@relaycast/sdk': 1.2.0 @@ -3114,11 +3629,13 @@ snapshots: tar: 7.5.15 ws: 8.21.0 yaml: 2.9.0 + optionalDependencies: + ai: 7.0.33(zod@3.25.76) transitivePeerDependencies: - bufferutil - utf-8-validate - '@agent-relay/sdk@6.3.6': + '@agent-relay/sdk@6.3.6(ai@7.0.33(zod@3.25.76))': dependencies: '@agent-relay/cloud': 6.3.6 '@agent-relay/config': 6.3.6 @@ -3143,6 +3660,7 @@ snapshots: '@agent-relay/broker-linux-arm64': 6.3.6 '@agent-relay/broker-linux-x64': 6.3.6 '@agent-relay/broker-win32-x64': 6.3.6 + ai: 7.0.33(zod@3.25.76) transitivePeerDependencies: - aws-crt - bufferutil @@ -3172,6 +3690,135 @@ snapshots: '@agentworkforce/workload-router@0.11.0': {} + '@ai-sdk/gateway@4.0.21(zod@4.4.3)': + dependencies: + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) + '@vercel/oidc': 3.2.0 + zod: 4.4.3 + + '@ai-sdk/gateway@4.0.25(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + '@vercel/oidc': 3.2.0 + zod: 3.25.76 + optional: true + + '@ai-sdk/gateway@4.0.25(zod@4.4.3)': + dependencies: + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@4.4.3) + '@vercel/oidc': 3.2.0 + zod: 4.4.3 + + '@ai-sdk/harness-claude-code@1.0.35(zod@4.4.3)': + dependencies: + '@ai-sdk/harness': 1.0.34(ws@8.21.0)(zod@4.4.3) + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) + ws: 8.21.0 + zod: 4.4.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@ai-sdk/harness-codex@1.0.40(zod@4.4.3)': + dependencies: + '@ai-sdk/harness': 1.0.38(ws@8.21.0)(zod@4.4.3) + '@ai-sdk/provider-utils': 5.0.12(zod@4.4.3) + ws: 8.21.0 + zod: 4.4.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@ai-sdk/harness-deepagents@1.0.33(zod@4.4.3)': + dependencies: + '@ai-sdk/harness': 1.0.34(ws@8.21.0)(zod@4.4.3) + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) + ws: 8.21.0 + zod: 4.4.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@ai-sdk/harness-opencode@1.0.35(zod@4.4.3)': + dependencies: + '@ai-sdk/harness': 1.0.34(ws@8.21.0)(zod@4.4.3) + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) + ws: 8.21.0 + zod: 4.4.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@ai-sdk/harness-pi@1.0.34(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@ai-sdk/harness': 1.0.34(ws@8.21.0)(zod@4.4.3) + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) + '@earendil-works/pi-coding-agent': 0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + typebox: 1.3.11 + zod: 4.4.3 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + + '@ai-sdk/harness@1.0.34(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) + ai: 7.0.29(zod@4.4.3) + zod: 4.4.3 + optionalDependencies: + ws: 8.21.0 + + '@ai-sdk/harness@1.0.38(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@4.4.3) + ai: 7.0.33(zod@4.4.3) + zod: 4.4.3 + optionalDependencies: + ws: 8.21.0 + + '@ai-sdk/provider-utils@5.0.10(zod@4.4.3)': + dependencies: + '@ai-sdk/provider': 4.0.3 + '@standard-schema/spec': 1.1.0 + '@workflow/serde': 4.1.0 + eventsource-parser: 3.0.8 + zod: 4.4.3 + + '@ai-sdk/provider-utils@5.0.12(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 4.0.3 + '@standard-schema/spec': 1.1.0 + '@workflow/serde': 4.1.0 + eventsource-parser: 3.0.8 + zod: 3.25.76 + optional: true + + '@ai-sdk/provider-utils@5.0.12(zod@4.4.3)': + dependencies: + '@ai-sdk/provider': 4.0.3 + '@standard-schema/spec': 1.1.0 + '@workflow/serde': 4.1.0 + eventsource-parser: 3.0.8 + zod: 4.4.3 + + '@ai-sdk/provider@4.0.3': + dependencies: + json-schema: 0.4.0 + + '@anthropic-ai/sdk@0.91.1(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + optionalDependencies: + zod: 4.4.3 + '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -3219,6 +3866,23 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + '@aws-sdk/client-bedrock-runtime@3.1048.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/credential-provider-node': 3.972.78 + '@aws-sdk/eventstream-handler-node': 3.972.31 + '@aws-sdk/middleware-eventstream': 3.972.26 + '@aws-sdk/middleware-websocket': 3.972.49 + '@aws-sdk/token-providers': 3.1048.0 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.7.3 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/client-s3@3.1020.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 @@ -3356,6 +4020,17 @@ snapshots: '@smithy/util-utf8': 4.3.1 tslib: 2.8.1 + '@aws-sdk/core@3.977.6': + dependencies: + '@aws-sdk/types': 3.974.2 + '@aws-sdk/xml-builder': 3.972.37 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.31.1 + '@smithy/signature-v4': 5.6.12 + '@smithy/types': 4.16.1 + bowser: 2.14.1 + tslib: 2.8.1 + '@aws-sdk/crc64-nvme@3.972.7': dependencies: '@smithy/types': 4.14.1 @@ -3369,6 +4044,14 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.972.67': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-http@3.972.36': dependencies: '@aws-sdk/core': 3.974.8 @@ -3382,6 +4065,16 @@ snapshots: '@smithy/util-stream': 4.6.1 tslib: 2.8.1 + '@aws-sdk/credential-provider-http@3.972.69': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-ini@3.972.38': dependencies: '@aws-sdk/core': 3.974.8 @@ -3401,6 +4094,22 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-ini@3.973.12': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/credential-provider-env': 3.972.67 + '@aws-sdk/credential-provider-http': 3.972.69 + '@aws-sdk/credential-provider-login': 3.972.74 + '@aws-sdk/credential-provider-process': 3.972.67 + '@aws-sdk/credential-provider-sso': 3.973.11 + '@aws-sdk/credential-provider-web-identity': 3.972.73 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/credential-provider-imds': 4.4.16 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-login@3.972.38': dependencies: '@aws-sdk/core': 3.974.8 @@ -3414,6 +4123,15 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-login@3.972.74': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-node@3.972.39': dependencies: '@aws-sdk/credential-provider-env': 3.972.34 @@ -3431,6 +4149,20 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-node@3.972.78': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.67 + '@aws-sdk/credential-provider-http': 3.972.69 + '@aws-sdk/credential-provider-ini': 3.973.12 + '@aws-sdk/credential-provider-process': 3.972.67 + '@aws-sdk/credential-provider-sso': 3.973.11 + '@aws-sdk/credential-provider-web-identity': 3.972.73 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/credential-provider-imds': 4.4.16 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-process@3.972.34': dependencies: '@aws-sdk/core': 3.974.8 @@ -3440,6 +4172,14 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 + '@aws-sdk/credential-provider-process@3.972.67': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-sso@3.972.38': dependencies: '@aws-sdk/core': 3.974.8 @@ -3453,6 +4193,16 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-sso@3.973.11': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/token-providers': 3.1103.0 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-web-identity@3.972.38': dependencies: '@aws-sdk/core': 3.974.8 @@ -3465,6 +4215,22 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-web-identity@3.972.73': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/eventstream-handler-node@3.972.31': + dependencies: + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/lib-storage@3.1045.0(@aws-sdk/client-s3@3.1045.0)': dependencies: '@aws-sdk/client-s3': 3.1045.0 @@ -3487,6 +4253,13 @@ snapshots: '@smithy/util-config-provider': 4.3.1 tslib: 2.8.1 + '@aws-sdk/middleware-eventstream@3.972.26': + dependencies: + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/middleware-expect-continue@3.972.10': dependencies: '@aws-sdk/types': 3.973.8 @@ -3572,6 +4345,27 @@ snapshots: '@smithy/util-retry': 4.4.1 tslib: 2.8.1 + '@aws-sdk/middleware-websocket@3.972.49': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/signature-v4': 5.6.12 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.41': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/signature-v4-multi-region': 3.996.43 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/nested-clients@3.997.6': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -3633,6 +4427,13 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.996.43': + dependencies: + '@aws-sdk/types': 3.974.2 + '@smithy/signature-v4': 5.6.12 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/token-providers@3.1041.0': dependencies: '@aws-sdk/core': 3.974.8 @@ -3645,11 +4446,34 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/token-providers@3.1048.0': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.31.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1103.0': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/types@3.973.8': dependencies: '@smithy/types': 4.14.1 tslib: 2.8.1 + '@aws-sdk/types@3.974.2': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/util-arn-parser@3.972.3': dependencies: tslib: 2.8.1 @@ -3689,8 +4513,17 @@ snapshots: fast-xml-parser: 5.7.2 tslib: 2.8.1 + '@aws-sdk/xml-builder@3.972.37': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws/lambda-invoke-store@0.2.4': {} + '@aws/lambda-invoke-store@0.3.0': {} + + '@babel/runtime@7.29.7': {} + '@clack/core@0.3.5': dependencies: picocolors: 1.1.1 @@ -3747,6 +4580,76 @@ snapshots: - supports-color - utf-8-validate + '@earendil-works/pi-agent-core@0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@earendil-works/pi-ai': 0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + ignore: 7.0.5 + typebox: 1.1.38 + yaml: 2.9.0 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-ai@0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) + '@aws-sdk/client-bedrock-runtime': 3.1048.0 + '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)) + '@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.0 + '@smithy/node-http-handler': 4.7.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + openai: 6.26.0(ws@8.21.0)(zod@4.4.3) + partial-json: 0.1.7 + typebox: 1.1.38 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-coding-agent@0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@earendil-works/pi-agent-core': 0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-tui': 0.79.10 + '@silvia-odwyer/photon-node': 0.3.4 + chalk: 5.6.2 + cross-spawn: 7.0.6 + diff: 8.0.4 + glob: 13.0.6 + highlight.js: 10.7.3 + hosted-git-info: 9.0.3 + ignore: 7.0.5 + jiti: 2.7.0 + minimatch: 10.2.5 + proper-lockfile: 4.1.2 + semver: 7.8.0 + typebox: 1.1.38 + undici: 8.5.0 + yaml: 2.9.0 + optionalDependencies: + '@mariozechner/clipboard': 0.3.9 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-tui@0.79.10': + dependencies: + get-east-asian-width: 1.6.0 + marked: 18.0.5 + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -3903,6 +4806,19 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true + '@google/genai@1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))': + dependencies: + google-auth-library: 10.9.1 + p-retry: 4.6.2 + protobufjs: 7.6.3 + ws: 8.21.0 + optionalDependencies: + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@grpc/grpc-js@1.14.3': dependencies: '@grpc/proto-loader': 0.8.1 @@ -3927,6 +4843,62 @@ snapshots: '@js-sdsl/ordered-map@4.4.2': {} + '@mariozechner/clipboard-darwin-arm64@0.3.9': + optional: true + + '@mariozechner/clipboard-darwin-universal@0.3.9': + optional: true + + '@mariozechner/clipboard-darwin-x64@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-arm64-gnu@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-arm64-musl@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-riscv64-gnu@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-x64-gnu@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-x64-musl@0.3.9': + optional: true + + '@mariozechner/clipboard-win32-arm64-msvc@0.3.9': + optional: true + + '@mariozechner/clipboard-win32-x64-msvc@0.3.9': + optional: true + + '@mariozechner/clipboard@0.3.9': + optionalDependencies: + '@mariozechner/clipboard-darwin-arm64': 0.3.9 + '@mariozechner/clipboard-darwin-universal': 0.3.9 + '@mariozechner/clipboard-darwin-x64': 0.3.9 + '@mariozechner/clipboard-linux-arm64-gnu': 0.3.9 + '@mariozechner/clipboard-linux-arm64-musl': 0.3.9 + '@mariozechner/clipboard-linux-riscv64-gnu': 0.3.9 + '@mariozechner/clipboard-linux-x64-gnu': 0.3.9 + '@mariozechner/clipboard-linux-x64-musl': 0.3.9 + '@mariozechner/clipboard-win32-arm64-msvc': 0.3.9 + '@mariozechner/clipboard-win32-x64-msvc': 0.3.9 + optional: true + + '@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/semantic-conventions': 1.40.0 + ws: 8.21.0 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + optionalDependencies: + '@opentelemetry/api': 1.9.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.14(hono@4.12.25) @@ -3949,6 +4921,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.25) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.8 + express: 5.2.1 + express-rate-limit: 8.5.1(express@5.2.1) + hono: 4.12.25 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + optional: true + '@nodable/entities@2.1.0': {} '@nodelib/fs.scandir@2.1.5': @@ -3971,6 +4966,8 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.1 + '@opentelemetry/api@1.9.0': {} + '@opentelemetry/api@1.9.1': {} '@opentelemetry/configuration@0.217.0(@opentelemetry/api@1.9.1)': @@ -4457,6 +5454,8 @@ snapshots: '@scalar/helpers': 0.5.1 '@scalar/openapi-types': 0.7.0 + '@silvia-odwyer/photon-node@0.3.4': {} + '@sinclair/typebox@0.34.52': {} '@slack/logger@4.0.1': @@ -4493,12 +5492,23 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 + '@smithy/core@3.31.1': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@smithy/credential-provider-imds@4.3.1': dependencies: '@smithy/core': 3.24.1 '@smithy/types': 4.14.1 tslib: 2.8.1 + '@smithy/credential-provider-imds@4.4.16': + dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@smithy/eventstream-serde-browser@4.3.1': dependencies: '@smithy/core': 3.24.1 @@ -4520,6 +5530,12 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 + '@smithy/fetch-http-handler@5.6.13': + dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@smithy/hash-blob-browser@4.3.1': dependencies: '@smithy/core': 3.24.1 @@ -4590,6 +5606,18 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 + '@smithy/node-http-handler@4.7.3': + dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.9.13': + dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@smithy/property-provider@4.3.1': dependencies: '@smithy/core': 3.24.1 @@ -4611,6 +5639,12 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 + '@smithy/signature-v4@5.6.12': + dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@smithy/smithy-client@4.13.1': dependencies: '@smithy/core': 3.24.1 @@ -4621,6 +5655,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/types@4.16.1': + dependencies: + tslib: 2.8.1 + '@smithy/url-parser@4.3.1': dependencies: '@smithy/core': 3.24.1 @@ -4696,6 +5734,8 @@ snapshots: '@smithy/core': 3.24.1 tslib: 2.8.1 + '@standard-schema/spec@1.1.0': {} + '@types/json-schema@7.0.15': {} '@types/node@22.19.15': @@ -4708,6 +5748,10 @@ snapshots: '@types/validate-npm-package-name@4.0.2': {} + '@vercel/oidc@3.2.0': {} + + '@workflow/serde@4.1.0': {} + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -4719,6 +5763,8 @@ snapshots: acorn@8.16.0: {} + agent-base@7.1.4: {} + agent-trajectories@0.5.3: dependencies: '@clack/prompts': 0.7.0 @@ -4731,6 +5777,28 @@ snapshots: commander: 12.1.0 zod: 3.25.76 + ai@7.0.29(zod@4.4.3): + dependencies: + '@ai-sdk/gateway': 4.0.21(zod@4.4.3) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) + zod: 4.4.3 + + ai@7.0.33(zod@3.25.76): + dependencies: + '@ai-sdk/gateway': 4.0.25(zod@3.25.76) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 + optional: true + + ai@7.0.33(zod@4.4.3): + dependencies: + '@ai-sdk/gateway': 4.0.25(zod@4.4.3) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@4.4.3) + zod: 4.4.3 + ajv-formats@3.0.1(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 @@ -4780,6 +5848,8 @@ snapshots: tweetnacl: 0.14.5 optional: true + bignumber.js@9.3.1: {} + body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -4806,6 +5876,8 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer-equal-constant-time@1.0.1: {} + buffer@5.6.0: dependencies: base64-js: 1.5.1 @@ -4930,6 +6002,8 @@ snapshots: css-what@6.2.2: {} + data-uri-to-buffer@4.0.1: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -4940,6 +6014,8 @@ snapshots: detect-libc@2.1.2: {} + diff@8.0.4: {} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -4966,6 +6042,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} emoji-regex@10.6.0: {} @@ -5120,6 +6200,8 @@ snapshots: transitivePeerDependencies: - supports-color + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -5148,6 +6230,11 @@ snapshots: dependencies: reusify: 1.1.0 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -5173,6 +6260,10 @@ snapshots: hasown: 2.0.3 mime-types: 2.1.35 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + forwarded-parse@2.1.2: {} forwarded@0.2.0: {} @@ -5184,10 +6275,28 @@ snapshots: function-bind@1.1.2: {} + gaxios@7.3.0: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + transitivePeerDependencies: + - supports-color + + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.3.0 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + get-caller-file@2.0.5: {} get-east-asian-width@1.5.0: {} + get-east-asian-width@1.6.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5216,8 +6325,23 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 + google-auth-library@10.9.1: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.3.0 + gcp-metadata: 8.1.2 + google-logging-utils: 1.1.3 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + + google-logging-utils@1.1.3: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -5230,12 +6354,18 @@ snapshots: dependencies: function-bind: 1.1.2 + highlight.js@10.7.3: {} + homedir-polyfill@1.0.3: dependencies: parse-passwd: 1.0.0 hono@4.12.25: {} + hosted-git-info@9.0.3: + dependencies: + lru-cache: 11.3.6 + htmlparser2@10.1.0: dependencies: domelementtype: 2.3.0 @@ -5251,6 +6381,20 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -5306,14 +6450,38 @@ snapshots: dependencies: ws: 8.21.0 + jiti@2.7.0: {} + jose@6.2.3: {} + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.7 + ts-algebra: 2.0.0 + json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} + json-schema@0.4.0: {} + json5@2.2.3: {} + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + listr2@10.2.2: dependencies: cli-truncate: 5.2.0 @@ -5341,6 +6509,8 @@ snapshots: lru-cache@11.3.6: {} + marked@18.0.5: {} + math-intrinsics@1.1.0: {} media-typer@1.1.0: {} @@ -5391,6 +6561,14 @@ snapshots: node-addon-api@7.1.1: {} + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + normalize-path@3.0.0: {} nth-check@2.1.1: @@ -5413,6 +6591,11 @@ snapshots: dependencies: mimic-function: 5.0.1 + openai@6.26.0(ws@8.21.0)(zod@4.4.3): + optionalDependencies: + ws: 8.21.0 + zod: 4.4.3 + ora@9.4.0: dependencies: chalk: 5.6.2 @@ -5457,6 +6640,8 @@ snapshots: parseurl@1.3.3: {} + partial-json@0.1.7: {} + path-expression-matcher@1.5.0: {} path-key@3.1.1: {} @@ -5478,6 +6663,12 @@ snapshots: pkce-challenge@5.0.1: {} + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + protobufjs@7.6.3: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -5541,6 +6732,8 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 + retry@0.12.0: {} + retry@0.13.1: {} reusify@1.1.0: {} @@ -5567,6 +6760,8 @@ snapshots: safer-buffer@2.1.2: {} + semver@7.8.0: {} + semver@7.8.5: {} send@1.2.1: @@ -5632,6 +6827,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} sisteransi@1.0.5: {} @@ -5717,6 +6914,8 @@ snapshots: toidentifier@1.0.1: {} + ts-algebra@2.0.0: {} + ts-json-schema-generator@2.9.0: dependencies: '@types/json-schema': 7.0.15 @@ -5745,12 +6944,18 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + typebox@1.1.38: {} + + typebox@1.3.11: {} + typescript@5.9.3: {} undici-types@6.21.0: {} undici@7.28.0: {} + undici@8.5.0: {} + unpipe@1.0.0: {} util-deprecate@1.0.2: {} @@ -5759,6 +6964,8 @@ snapshots: vary@1.1.2: {} + web-streams-polyfill@3.3.3: {} + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 @@ -5817,6 +7024,11 @@ snapshots: dependencies: zod: 3.25.76 + zod-to-json-schema@3.25.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + optional: true + zod@3.25.76: {} zod@4.4.3: {}