@kynver-app/runtime 0.1.105 → 0.1.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/default-repo.ts", "../../src/config.ts", "../../src/default-repo-discovery.ts", "../../src/git.ts", "../../src/worker-env.ts", "../../src/path-values.ts", "../../src/wsl-host.ts", "../../src/disk-gate.ts", "../../src/paths.ts", "../../src/resource-gate.ts"],
4
- "sourcesContent": ["import path from \"node:path\";\r\nimport { loadUserConfig, saveUserConfig, type KynverUserConfig } from \"./config.js\";\r\nimport {\r\n discoverDefaultRepo,\r\n type DefaultRepoDiscoverySource,\r\n type DiscoveredDefaultRepo,\r\n} from \"./default-repo-discovery.js\";\r\nimport { displayUserPath, redactHomePath, resolveUserPath } from \"./path-values.js\";\r\n\r\nexport type DefaultRepoSource =\r\n | \"config\"\r\n | \"env_default_repo\"\r\n | \"env_harness_repo\"\r\n | DefaultRepoDiscoverySource;\r\n\r\nexport interface ResolvedDefaultRepo {\r\n repo: string;\r\n source: DefaultRepoSource;\r\n persistedInConfig: boolean;\r\n}\r\n\r\nexport interface ResolveDefaultRepoOptions {\r\n config?: KynverUserConfig;\r\n env?: NodeJS.ProcessEnv;\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}\r\n\r\nfunction expandConfiguredRepo(value: string): string {\r\n return path.resolve(resolveUserPath(value.trim()));\r\n}\r\n\r\nfunction fromConfigured(\r\n value: string | undefined,\r\n source: Extract<DefaultRepoSource, \"config\" | \"env_default_repo\" | \"env_harness_repo\">,\r\n persistedInConfig: boolean,\r\n): ResolvedDefaultRepo | null {\r\n const trimmed = value?.trim();\r\n if (!trimmed) return null;\r\n return {\r\n repo: expandConfiguredRepo(trimmed),\r\n source,\r\n persistedInConfig,\r\n };\r\n}\r\n\r\nexport function resolveDefaultRepo(opts: ResolveDefaultRepoOptions = {}): ResolvedDefaultRepo | null {\r\n const env = opts.env ?? process.env;\r\n const config = opts.config ?? loadUserConfig();\r\n\r\n const fromConfig = fromConfigured(config.defaultRepo, \"config\", true);\r\n if (fromConfig) return fromConfig;\r\n\r\n const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, \"env_default_repo\", false);\r\n if (fromDefaultEnv) return fromDefaultEnv;\r\n\r\n const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, \"env_harness_repo\", false);\r\n if (fromHarnessEnv) return fromHarnessEnv;\r\n\r\n const discovered = discoverDefaultRepo({\r\n cwd: opts.cwd,\r\n runtimeModuleUrl: opts.runtimeModuleUrl,\r\n });\r\n if (!discovered) return null;\r\n\r\n return {\r\n repo: discovered.repo,\r\n source: discovered.source,\r\n persistedInConfig: false,\r\n };\r\n}\r\n\r\nexport function persistDefaultRepo(repo: string, existing?: KynverUserConfig): KynverUserConfig {\r\n const config: KynverUserConfig = {\r\n ...(existing ?? loadUserConfig()),\r\n defaultRepo: redactHomePath(path.resolve(repo)),\r\n };\r\n saveUserConfig(config);\r\n return config;\r\n}\r\n\r\nexport function remediateDefaultRepo(opts?: ResolveDefaultRepoOptions): {\r\n ok: true;\r\n resolved: ResolvedDefaultRepo;\r\n config: KynverUserConfig;\r\n} | {\r\n ok: false;\r\n reason: string;\r\n} {\r\n const existing = opts?.config ?? loadUserConfig();\r\n const resolved = resolveDefaultRepo({ ...opts, config: existing });\r\n if (!resolved) {\r\n return {\r\n ok: false,\r\n reason:\r\n \"No Kynver git checkout found. Clone the repo, cd into it, then run `kynver setup --repo /path/to/Kynver` (or export KYNVER_DEFAULT_REPO).\",\r\n };\r\n }\r\n\r\n if (resolved.persistedInConfig) {\r\n return { ok: true, resolved, config: existing };\r\n }\r\n\r\n const config = persistDefaultRepo(resolved.repo, existing);\r\n return {\r\n ok: true,\r\n resolved: { ...resolved, persistedInConfig: true, source: \"config\" },\r\n config,\r\n };\r\n}\r\n\r\nexport function formatResolvedDefaultRepo(resolved: ResolvedDefaultRepo): {\r\n defaultRepo: string;\r\n source: DefaultRepoSource;\r\n persistedInConfig: boolean;\r\n} {\r\n return {\r\n defaultRepo: displayUserPath(resolved.repo),\r\n source: resolved.source,\r\n persistedInConfig: resolved.persistedInConfig,\r\n };\r\n}\r\n\r\nexport type { DiscoveredDefaultRepo };\r\n", "import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\r\nimport { homedir, totalmem } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { discoverDefaultRepo } from \"./default-repo-discovery.js\";\r\nimport { displayUserPath, redactHomePath } from \"./path-values.js\";\r\nimport { trimTrailingSlash } from \"./util.js\";\r\nimport { normalizeWorkerPoolBoxKind, resolveBoxIdentity } from \"./box-identity.js\";\r\nimport { recommendSetupWorkerCap } from \"./worker-cap-source.js\";\r\nimport { observeRunnerDiskGate } from \"./disk-gate.js\";\r\nimport os from \"node:os\";\r\n\r\nexport interface KynverUserConfig {\r\n apiBaseUrl?: string;\r\n agentOsSlug?: string;\r\n agentOsId?: string;\r\n defaultRepo?: string;\r\n workerProvider?: string;\r\n /** Default Claude model when dispatch does not infer or pass `--model`. */\r\n defaultModel?: string;\r\n harnessRoot?: string;\r\n /**\r\n * Operator attestation that the hosted Kynver deployment uses this scheduler provider\r\n * (set on user runners after Vercel env cutover \u2014 scheduling is deployment-owned).\r\n */\r\n deploymentSchedulerProvider?: \"qstash\" | \"kynver-cron\" | \"openclaw-cron\";\r\n /** Physical box pool for capacity snapshots (`forge` | `ghost`). Set via `kynver setup --box-kind`. */\r\n boxKind?: \"ghost\" | \"forge\";\r\n /** Max concurrent workers on this machine. Omit to auto-size from RAM. */\r\n maxConcurrentWorkers?: number;\r\n /** Where maxConcurrentWorkers came from. */\r\n maxConcurrentWorkersSource?: \"setup-auto\" | \"setup-flag\" | \"operator\";\r\n /** @internal Advanced tuning \u2014 not required for setup. */\r\n perWorkerMemBytes?: number;\r\n /** @internal Advanced tuning \u2014 not required for setup. */\r\n memReserveBytes?: number;\r\n /** @internal Advanced tuning \u2014 not required for setup. */\r\n memUtilization?: number;\r\n}\r\n\r\nconst CONFIG_DIR = path.join(homedir(), \".kynver\");\r\nconst CONFIG_FILE = path.join(CONFIG_DIR, \"config.json\");\r\nconst CREDENTIALS_FILE = path.join(CONFIG_DIR, \"credentials\");\r\n\r\ninterface KynverCredentialsFile {\r\n apiKey?: string;\r\n /** Scoped `krc1.*` runner token for AgentOS by-id callbacks. */\r\n runnerToken?: string;\r\n runnerTokenAgentOsId?: string;\r\n}\r\n\r\nexport function loadUserConfig(): KynverUserConfig {\r\n if (!existsSync(CONFIG_FILE)) return {};\r\n try {\r\n return JSON.parse(readFileSync(CONFIG_FILE, \"utf8\")) as KynverUserConfig;\r\n } catch {\r\n return {};\r\n }\r\n}\r\n\r\nexport function saveUserConfig(config: KynverUserConfig): void {\r\n mkdirSync(CONFIG_DIR, { recursive: true });\r\n writeFileSync(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}\\n`, { mode: 0o600 });\r\n}\r\n\r\n/** Persist path fields with `~` instead of absolute home directories. */\r\nexport function normalizeConfigPaths(config: KynverUserConfig): KynverUserConfig {\r\n return {\r\n ...config,\r\n ...(config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {}),\r\n ...(config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}),\r\n };\r\n}\r\n\r\n/** Values for setup output (never emit `/home/<user>/\u2026`). */\r\nexport function presentUserConfig(config: KynverUserConfig): KynverUserConfig {\r\n return normalizeConfigPaths(config);\r\n}\r\n\r\nfunction inferSetupFields(\r\n existing: KynverUserConfig,\r\n args: Record<string, string | boolean>,\r\n): Partial<KynverUserConfig> {\r\n const creds = loadCredentialsFile();\r\n const apiBaseUrl =\r\n (typeof args.apiBaseUrl === \"string\" ? args.apiBaseUrl : undefined) ||\r\n existing.apiBaseUrl?.trim() ||\r\n process.env.KYNVER_API_URL?.trim() ||\r\n process.env.KYNVER_CRON_FIRE_BASE_URL?.trim() ||\r\n process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();\r\n const agentOsId =\r\n (typeof args.agentOsId === \"string\" ? args.agentOsId : undefined) ||\r\n existing.agentOsId?.trim() ||\r\n process.env.KYNVER_AGENT_OS_ID?.trim() ||\r\n (creds.runnerToken?.trim().startsWith(\"krc1.\") ? creds.runnerTokenAgentOsId?.trim() : undefined);\r\n const explicitRepo =\r\n typeof args.repo === \"string\"\r\n ? args.repo\r\n : args.discoverRepo === true || args.discoverRepo === \"true\"\r\n ? discoverDefaultRepo()?.repo\r\n : undefined;\r\n const defaultRepo =\r\n explicitRepo ||\r\n existing.defaultRepo?.trim() ||\r\n process.env.KYNVER_DEFAULT_REPO?.trim() ||\r\n process.env.KYNVER_HARNESS_REPO?.trim() ||\r\n discoverDefaultRepo()?.repo;\r\n const harnessRoot =\r\n (typeof args.harnessRoot === \"string\" ? args.harnessRoot : undefined) ||\r\n existing.harnessRoot?.trim() ||\r\n process.env.KYNVER_HARNESS_ROOT?.trim() ||\r\n process.env.OPUS_HARNESS_ROOT?.trim();\r\n\r\n return {\r\n ...(apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {}),\r\n ...(agentOsId ? { agentOsId } : {}),\r\n ...(defaultRepo ? { defaultRepo } : {}),\r\n ...(harnessRoot ? { harnessRoot } : {}),\r\n ...(typeof args.agentOsSlug === \"string\"\r\n ? { agentOsSlug: args.agentOsSlug }\r\n : existing.agentOsSlug\r\n ? { agentOsSlug: existing.agentOsSlug }\r\n : {}),\r\n };\r\n}\r\n\r\nconst SETUP_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;\r\nconst SETUP_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;\r\nconst SETUP_MEM_UTILIZATION = 0.85;\r\nconst SETUP_AUTO_MAX_WORKERS_CEILING = 64;\r\n\r\nfunction normalizeBoxKind(raw: unknown): \"ghost\" | \"forge\" | undefined {\r\n const kind = String(raw ?? \"\").trim().toLowerCase();\r\n if (!kind) return undefined;\r\n if (kind === \"ghost\" || kind.includes(\"ghost\") || kind.includes(\"openclaw\")) return \"ghost\";\r\n if (kind === \"forge\" || kind.includes(\"forge\")) return \"forge\";\r\n return undefined;\r\n}\r\n\r\nexport function computeSetupAutoMaxWorkers(totalMemBytes: number): number {\r\n const budgetBytes = Math.max(0, Math.floor(totalMemBytes * SETUP_MEM_UTILIZATION) - SETUP_MEM_RESERVE_BYTES);\r\n const raw = Math.max(1, Math.floor(budgetBytes / SETUP_PER_WORKER_MEM_BYTES));\r\n return Math.min(raw, SETUP_AUTO_MAX_WORKERS_CEILING);\r\n}\r\n\r\nexport function resolveSetupWorkerConfig(\r\n existing: KynverUserConfig,\r\n args: Record<string, string | boolean>,\r\n totalMemBytes = totalmem(),\r\n): Pick<KynverUserConfig, \"maxConcurrentWorkers\" | \"maxConcurrentWorkersSource\" | \"boxKind\"> {\r\n const maxWorkersRaw =\r\n typeof args.maxWorkers === \"string\"\r\n ? args.maxWorkers\r\n : typeof args.maxConcurrentWorkers === \"string\"\r\n ? args.maxConcurrentWorkers\r\n : undefined;\r\n const explicitBoxKindArg =\r\n typeof args.boxKind === \"string\"\r\n ? args.boxKind\r\n : typeof args[\"box-kind\"] === \"string\"\r\n ? String(args[\"box-kind\"])\r\n : undefined;\r\n const boxKind = resolveBoxIdentity(process.env, {\r\n ...existing,\r\n ...(explicitBoxKindArg ? { boxKind: normalizeWorkerPoolBoxKind(explicitBoxKindArg) } : {}),\r\n }).boxKind;\r\n const diskGate = observeRunnerDiskGate({\r\n diskPath: typeof args.diskPath === \"string\" ? args.diskPath : \"/\",\r\n });\r\n const capRecommendation = recommendSetupWorkerCap({\r\n totalMemBytes,\r\n diskPath: diskGate.path,\r\n diskGateOk: diskGate.ok,\r\n diskFreeBytes: diskGate.freeBytes,\r\n config: existing,\r\n });\r\n if (maxWorkersRaw) {\r\n return {\r\n maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))),\r\n maxConcurrentWorkersSource: \"setup-flag\",\r\n boxKind,\r\n };\r\n }\r\n if (existing.maxConcurrentWorkers !== undefined && existing.maxConcurrentWorkers !== null) {\r\n return {\r\n maxConcurrentWorkers: Math.max(1, Math.floor(Number(existing.maxConcurrentWorkers))),\r\n maxConcurrentWorkersSource: existing.maxConcurrentWorkersSource ?? \"operator\",\r\n boxKind,\r\n };\r\n }\r\n return {\r\n maxConcurrentWorkers: capRecommendation.recommendedMaxWorkers,\r\n maxConcurrentWorkersSource: \"setup-auto\",\r\n boxKind,\r\n };\r\n}\r\n\r\nfunction loadCredentialsFile(): KynverCredentialsFile {\r\n if (!existsSync(CREDENTIALS_FILE)) return {};\r\n try {\r\n return JSON.parse(readFileSync(CREDENTIALS_FILE, \"utf8\")) as KynverCredentialsFile;\r\n } catch {\r\n return {};\r\n }\r\n}\r\n\r\nfunction saveCredentialsFile(parsed: KynverCredentialsFile): void {\r\n mkdirSync(CONFIG_DIR, { recursive: true });\r\n writeFileSync(CREDENTIALS_FILE, `${JSON.stringify(parsed, null, 2)}\\n`, { mode: 0o600 });\r\n}\r\n\r\nexport function loadApiKey(): string | undefined {\r\n if (process.env.KYNVER_API_KEY) return process.env.KYNVER_API_KEY;\r\n return loadCredentialsFile().apiKey;\r\n}\r\n\r\nexport function saveApiKey(apiKey: string): void {\r\n saveCredentialsFile({ ...loadCredentialsFile(), apiKey });\r\n}\r\n\r\nexport function loadRunnerToken(agentOsId?: string): string | undefined {\r\n const envToken = process.env.KYNVER_RUNNER_TOKEN?.trim();\r\n if (envToken) return envToken;\r\n\r\n const creds = loadCredentialsFile();\r\n if (!creds.runnerToken) return undefined;\r\n if (agentOsId && creds.runnerTokenAgentOsId && creds.runnerTokenAgentOsId !== agentOsId) {\r\n return undefined;\r\n }\r\n return creds.runnerToken;\r\n}\r\n\r\nexport function saveRunnerToken(agentOsId: string, token: string): void {\r\n saveCredentialsFile({\r\n ...loadCredentialsFile(),\r\n runnerToken: token,\r\n runnerTokenAgentOsId: agentOsId,\r\n });\r\n}\r\n\r\nexport function resolveBaseUrl(argsBaseUrl?: string): string {\r\n const baseUrl = resolveConfiguredBaseUrl(argsBaseUrl);\r\n if (!baseUrl) failConfig(\"requires --base-url, KYNVER_API_URL, KYNVER_CRON_FIRE_BASE_URL, or ~/.kynver/config.json apiBaseUrl\");\r\n return baseUrl;\r\n}\r\n\r\nfunction resolveConfiguredBaseUrl(argsBaseUrl?: string): string | undefined {\r\n const baseUrl =\r\n argsBaseUrl ||\r\n process.env.KYNVER_API_URL ||\r\n process.env.KYNVER_CRON_FIRE_BASE_URL ||\r\n process.env.OPENCLAW_CRON_FIRE_BASE_URL ||\r\n loadUserConfig().apiBaseUrl;\r\n return baseUrl ? trimTrailingSlash(String(baseUrl)) : undefined;\r\n}\r\n\r\nfunction resolveConfiguredCallbackSecret(argsSecret?: string, agentOsId?: string): string | undefined {\r\n const scoped =\r\n argsSecret ||\r\n loadRunnerToken(agentOsId) ||\r\n (agentOsId ? undefined : loadRunnerToken(loadUserConfig().agentOsId));\r\n if (scoped) return String(scoped);\r\n\r\n const globalSecret =\r\n process.env.KYNVER_RUNTIME_SECRET ||\r\n process.env.KYNVER_CRON_SECRET ||\r\n process.env.OPENCLAW_CRON_SECRET;\r\n if (globalSecret) {\r\n console.warn(\r\n \"[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token\",\r\n );\r\n return String(globalSecret);\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nexport function resolveCallbackSecret(argsSecret?: string, agentOsId?: string): string {\r\n const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);\r\n if (configured) return configured;\r\n\r\n failConfig(\r\n \"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET\",\r\n );\r\n}\r\n\r\nexport async function resolveCallbackSecretWithMint(\r\n argsSecret?: string,\r\n agentOsId?: string,\r\n opts?: { baseUrl?: string },\r\n): Promise<string> {\r\n const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);\r\n if (configured) return configured;\r\n\r\n const apiKey = loadApiKey();\r\n const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);\r\n if (apiKey && agentOsId && baseUrl) {\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });\r\n saveRunnerToken(agentOsId, token);\r\n return token;\r\n } catch (error) {\r\n failConfig(`runner credential mint failed: ${(error as Error).message}`);\r\n }\r\n }\r\n\r\n failConfig(\r\n \"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET\",\r\n );\r\n}\r\n\r\n/**\r\n * Force-mint a fresh scoped runner token for `agentOsId`, bypassing any cached\r\n * or env token. Recovery path for a callback that 401s because the configured\r\n * token is revoked, expired, or scoped to a *different* workspace (the\r\n * self-linked repair case). Requires an API key + base URL to mint; returns\r\n * `null` when a fresh token cannot be obtained, so the caller degrades to a\r\n * structural blocker instead of papering the worker over as done.\r\n */\r\nexport async function refreshRunnerToken(\r\n agentOsId: string,\r\n opts?: { baseUrl?: string },\r\n): Promise<string | null> {\r\n const apiKey = loadApiKey();\r\n const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);\r\n if (!apiKey || !agentOsId || !baseUrl) return null;\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });\r\n saveRunnerToken(agentOsId, token);\r\n return token;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport async function refreshRunnerTokenForAuthFailure(\r\n rejectedSecret: string,\r\n agentOsId: string,\r\n opts?: { baseUrl?: string },\r\n): Promise<{ ok: true; token: string } | { ok: false; reason: string }> {\r\n const apiKey = loadApiKey();\r\n const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);\r\n if (!apiKey) return { ok: false, reason: \"KYNVER_API_KEY is required to refresh a rejected runner token\" };\r\n if (!agentOsId) return { ok: false, reason: \"agentOsId is required to refresh a rejected runner token\" };\r\n if (!baseUrl) return { ok: false, reason: \"KYNVER_API_URL or --base-url is required to refresh a rejected runner token\" };\r\n\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });\r\n if (token && token !== rejectedSecret) {\r\n saveRunnerToken(agentOsId, token);\r\n return { ok: true, token };\r\n }\r\n return { ok: false, reason: \"runner credential refresh returned the rejected token\" };\r\n } catch (error) {\r\n return { ok: false, reason: (error as Error).message };\r\n }\r\n}\r\n\r\nexport async function fetchRunnerCredential(\r\n agentOsId: string,\r\n opts?: { baseUrl?: string; apiKey?: string },\r\n): Promise<string> {\r\n const apiKey = opts?.apiKey || loadApiKey();\r\n if (!apiKey) throw new Error(\"API key required \u2014 run `kynver login` first\");\r\n\r\n const base = resolveBaseUrl(opts?.baseUrl);\r\n const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runner-credentials`;\r\n const res = await fetch(url, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n Authorization: `Bearer ${apiKey}`,\r\n },\r\n body: JSON.stringify({}),\r\n });\r\n\r\n const text = await res.text();\r\n let parsed: { token?: string; error?: string } | null = null;\r\n try {\r\n parsed = JSON.parse(text) as { token?: string; error?: string };\r\n } catch {\r\n parsed = null;\r\n }\r\n if (!res.ok || !parsed?.token) {\r\n throw new Error(\r\n `runner credential mint failed (${res.status}): ${parsed?.error ?? text.slice(0, 200)}`,\r\n );\r\n }\r\n return parsed.token;\r\n}\r\n\r\nexport async function mintRunnerCredential(args: Record<string, string | boolean>): Promise<void> {\r\n const agentOsId =\r\n (args.agentOsId ? String(args.agentOsId) : loadUserConfig().agentOsId) || \"\";\r\n if (!agentOsId) failConfig(\"runner credential requires --agent-os-id or agentOsId in ~/.kynver/config.json\");\r\n\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, {\r\n baseUrl: args.baseUrl ? String(args.baseUrl) : undefined,\r\n });\r\n saveRunnerToken(agentOsId, token);\r\n console.log(\r\n JSON.stringify(\r\n {\r\n ok: true,\r\n agentOsId,\r\n credentialsPath: displayUserPath(CREDENTIALS_FILE),\r\n tokenPrefix: `${token.slice(0, 12)}\u2026`,\r\n note: \"Scoped runner token saved; callbacks use X-Kynver-Runner-Token.\",\r\n },\r\n null,\r\n 2,\r\n ),\r\n );\r\n } catch (err) {\r\n console.error(err instanceof Error ? err.message : String(err));\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction failConfig(message: string): never {\r\n console.error(message);\r\n process.exit(1);\r\n}\r\n\r\nexport function parseArgs(argv: string[]): Record<string, string | boolean> {\r\n const args: Record<string, string | boolean> = {};\r\n for (let i = 0; i < argv.length; i++) {\r\n const item = argv[i];\r\n if (!item.startsWith(\"--\")) continue;\r\n const key = item.slice(2).replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\r\n const next = argv[i + 1];\r\n if (!next || next.startsWith(\"--\")) args[key] = true;\r\n else {\r\n args[key] = next;\r\n i++;\r\n }\r\n }\r\n return args;\r\n}\r\n\r\nexport async function runSetup(args: Record<string, string | boolean>): Promise<void> {\r\n const existing = loadUserConfig();\r\n const diskGate = observeRunnerDiskGate({\r\n diskPath: typeof args.diskPath === \"string\" ? args.diskPath : \"/\",\r\n });\r\n const capRecommendation = recommendSetupWorkerCap({\r\n totalMemBytes: os.totalmem(),\r\n diskPath: diskGate.path,\r\n diskGateOk: diskGate.ok,\r\n diskFreeBytes: diskGate.freeBytes,\r\n config: existing,\r\n });\r\n const workerConfig = resolveSetupWorkerConfig(existing, args);\r\n const config: KynverUserConfig = normalizeConfigPaths({\r\n ...existing,\r\n ...inferSetupFields(existing, args),\r\n ...workerConfig,\r\n workerProvider:\r\n typeof args.provider === \"string\"\r\n ? args.provider\r\n : existing.workerProvider || \"cursor\",\r\n });\r\n saveUserConfig(config);\r\n const boxIdentity = resolveBoxIdentity(process.env, config);\r\n\r\n let runnerCredentialNote: string | undefined;\r\n const apiKey = loadApiKey();\r\n const agentOsId = config.agentOsId;\r\n if (apiKey && agentOsId) {\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, {\r\n baseUrl: typeof args.apiBaseUrl === \"string\" ? args.apiBaseUrl : config.apiBaseUrl,\r\n apiKey,\r\n });\r\n saveRunnerToken(agentOsId, token);\r\n runnerCredentialNote = \"Scoped runner token minted and saved to ~/.kynver/credentials.\";\r\n } catch {\r\n runnerCredentialNote =\r\n \"Runner token not minted (server offline or master secret unset). Run `kynver runner credential` after deploy.\";\r\n }\r\n }\r\n\r\n console.log(\r\n JSON.stringify(\r\n {\r\n ok: true,\r\n configPath: displayUserPath(CONFIG_FILE),\r\n config: presentUserConfig(config),\r\n boxKind: config.boxKind,\r\n boxKindSource: boxIdentity.source,\r\n workerCapRecommendation: capRecommendation,\r\n ...(boxIdentity.warnings.length ? { boxIdentityWarnings: boxIdentity.warnings } : {}),\r\n note:\r\n runnerCredentialNote ??\r\n \"boxKind and maxConcurrentWorkers persisted; override with --box-kind and --max-workers. Run `kynver login` + `kynver runner credential` for scoped callbacks.\",\r\n },\r\n null,\r\n 2,\r\n ),\r\n );\r\n}\r\n\r\nexport async function runLogin(args: Record<string, string | boolean>): Promise<void> {\r\n const apiKey = typeof args.apiKey === \"string\" ? args.apiKey : process.env.KYNVER_API_KEY;\r\n if (!apiKey) failConfig(\"kynver login requires --api-key or KYNVER_API_KEY\");\r\n saveApiKey(apiKey);\r\n console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));\r\n}\r\n", "import { existsSync, readFileSync } from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport { gitCapture } from \"./git.js\";\r\nimport { resolveUserPath } from \"./path-values.js\";\r\n\r\nexport type DefaultRepoDiscoverySource =\r\n | \"cwd_git\"\r\n | \"runtime_checkout\"\r\n | \"well_known_path\";\r\n\r\nexport interface DiscoveredDefaultRepo {\r\n repo: string;\r\n source: DefaultRepoDiscoverySource;\r\n}\r\n\r\nconst WELL_KNOWN_REPO_DIRS = [\r\n \"Kynver\",\r\n \"repos/Kynver\",\r\n \"repos/kynver-source-main\",\r\n \"code/Kynver\",\r\n \"projects/Kynver\",\r\n] as const;\r\n\r\nfunction readPackageName(repoRoot: string): string | null {\r\n const pkgPath = path.join(repoRoot, \"package.json\");\r\n if (!existsSync(pkgPath)) return null;\r\n try {\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { name?: string };\r\n return typeof pkg.name === \"string\" ? pkg.name.trim() : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function isKynverMonorepoRoot(repoRoot: string): boolean {\r\n return readPackageName(repoRoot) === \"kynver\";\r\n}\r\n\r\nexport function gitRepoRoot(startDir: string): string | null {\r\n const resolvedStart = path.resolve(startDir);\r\n if (!existsSync(resolvedStart)) return null;\r\n const probe = gitCapture(resolvedStart, [\"rev-parse\", \"--show-toplevel\"]);\r\n if (probe.status !== 0) return null;\r\n const root = probe.stdout.trim();\r\n return root.length ? path.resolve(root) : null;\r\n}\r\n\r\nfunction resolveRuntimePackageRoot(moduleUrl: string = import.meta.url): string | null {\r\n let dir = path.dirname(fileURLToPath(moduleUrl));\r\n for (let depth = 0; depth < 8; depth += 1) {\r\n const pkgPath = path.join(dir, \"package.json\");\r\n if (existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { name?: string };\r\n if (pkg.name === \"@kynver-app/runtime\") return dir;\r\n } catch {\r\n // continue walking\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction pushCandidate(\r\n seen: Set<string>,\r\n out: DiscoveredDefaultRepo[],\r\n repo: string | null,\r\n source: DefaultRepoDiscoverySource,\r\n): void {\r\n if (!repo) return;\r\n const resolved = path.resolve(repo);\r\n if (seen.has(resolved)) return;\r\n if (!isKynverMonorepoRoot(resolved)) return;\r\n seen.add(resolved);\r\n out.push({ repo: resolved, source });\r\n}\r\n\r\nexport function discoverDefaultRepoCandidates(opts?: {\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}): DiscoveredDefaultRepo[] {\r\n const cwd = opts?.cwd ?? process.cwd();\r\n const seen = new Set<string>();\r\n const candidates: DiscoveredDefaultRepo[] = [];\r\n\r\n pushCandidate(seen, candidates, gitRepoRoot(cwd), \"cwd_git\");\r\n\r\n const runtimePkgRoot = resolveRuntimePackageRoot(opts?.runtimeModuleUrl ?? import.meta.url);\r\n if (runtimePkgRoot) {\r\n pushCandidate(seen, candidates, gitRepoRoot(runtimePkgRoot), \"runtime_checkout\");\r\n }\r\n\r\n const home = homedir();\r\n for (const rel of WELL_KNOWN_REPO_DIRS) {\r\n pushCandidate(seen, candidates, resolveUserPath(path.join(home, rel)), \"well_known_path\");\r\n }\r\n\r\n return candidates;\r\n}\r\n\r\nexport function discoverDefaultRepo(opts?: {\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}): DiscoveredDefaultRepo | null {\r\n return discoverDefaultRepoCandidates(opts)[0] ?? null;\r\n}\r\n", "import { spawnSync } from \"node:child_process\";\r\nimport { fail } from \"./util.js\";\r\n\r\nexport interface GitOptions {\r\n allowFailure?: boolean;\r\n throwError?: boolean;\r\n}\r\n\r\nexport function git(cwd: string, args: string[], options: GitOptions = {}): string {\r\n const res = spawnSync(\"git\", args, { cwd, encoding: \"utf8\" });\r\n if (res.status !== 0 && !options.allowFailure) {\r\n const message = `git ${args.join(\" \")} failed: ${res.stderr || res.stdout}`;\r\n if (options.throwError) throw new Error(message);\r\n fail(message);\r\n }\r\n return res.stdout || \"\";\r\n}\r\n\r\nexport function ensureGitRepo(repo: string): void {\r\n git(repo, [\"rev-parse\", \"--show-toplevel\"]);\r\n}\r\n\r\nexport function gitStatusShort(worktreePath: string): string[] {\r\n return git(worktreePath, [\"status\", \"--short\"], { allowFailure: true })\r\n .split(\"\\n\")\r\n .map((line) => line.trim())\r\n .filter(Boolean);\r\n}\r\n\r\nexport interface GitCaptureResult {\r\n status: number | null;\r\n stdout: string;\r\n stderr: string;\r\n error: string | null;\r\n}\r\n\r\nexport function gitCapture(cwd: string, args: string[]): GitCaptureResult {\r\n try {\r\n const res = spawnSync(\"git\", args, { cwd, encoding: \"utf8\" });\r\n return {\r\n status: res.status,\r\n stdout: res.stdout || \"\",\r\n stderr: res.stderr || \"\",\r\n error: res.error ? res.error.message : null,\r\n };\r\n } catch (error) {\r\n return {\r\n status: null,\r\n stdout: \"\",\r\n stderr: \"\",\r\n error: (error as Error).message,\r\n };\r\n }\r\n}\r\n\r\nexport function gitIsAncestor(\r\n cwd: string,\r\n ancestor: string,\r\n descendant: string,\r\n): { isAncestor: boolean | null; error: string | null } {\r\n const res = gitCapture(cwd, [\"merge-base\", \"--is-ancestor\", ancestor, descendant]);\r\n if (res.status === 0) return { isAncestor: true, error: null };\r\n if (res.status === 1) return { isAncestor: false, error: null };\r\n return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };\r\n}\r\n\r\nexport type GitAncestryRelation = \"synced\" | \"merged\" | \"ahead\" | \"diverged\" | \"unknown\";\r\n\r\nexport interface GitAncestry {\r\n checked: boolean;\r\n base: string;\r\n head: string | null;\r\n baseHead: string | null;\r\n baseIsAncestorOfHead: boolean | null;\r\n headIsAncestorOfBase: boolean | null;\r\n relation: GitAncestryRelation;\r\n error?: string;\r\n}\r\n\r\nexport interface GitAncestryOptions {\r\n /** Branch or ref name (e.g. origin/main). Used when baseCommit is unset. */\r\n base?: string;\r\n /** Pinned SHA the worktree was created from \u2014 preferred over a moving branch ref. */\r\n baseCommit?: string;\r\n}\r\n\r\nexport function computeGitAncestry(worktreePath: string, baseOrOptions: string | GitAncestryOptions = \"origin/main\"): GitAncestry {\r\n const options: GitAncestryOptions =\r\n typeof baseOrOptions === \"string\" ? { base: baseOrOptions } : baseOrOptions;\r\n const baseLabel = options.baseCommit?.trim() || options.base?.trim() || \"origin/main\";\r\n const pinnedBaseCommit = options.baseCommit?.trim() || null;\r\n\r\n if (!worktreePath) {\r\n return unknownAncestry(baseLabel, \"missing worktree path\");\r\n }\r\n\r\n const head = gitCapture(worktreePath, [\"rev-parse\", \"HEAD\"]);\r\n if (head.status !== 0) {\r\n return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || \"failed to resolve HEAD\");\r\n }\r\n\r\n let baseSha: string;\r\n if (pinnedBaseCommit) {\r\n baseSha = pinnedBaseCommit;\r\n } else {\r\n const baseHead = gitCapture(worktreePath, [\"rev-parse\", baseLabel]);\r\n if (baseHead.status !== 0) {\r\n return unknownAncestry(\r\n baseLabel,\r\n baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,\r\n head.stdout.trim(),\r\n );\r\n }\r\n baseSha = baseHead.stdout.trim();\r\n }\r\n\r\n const headSha = head.stdout.trim();\r\n if (headSha === baseSha) {\r\n return {\r\n checked: true,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: true,\r\n headIsAncestorOfBase: true,\r\n relation: \"synced\",\r\n };\r\n }\r\n\r\n const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);\r\n const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);\r\n const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || undefined;\r\n if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {\r\n return {\r\n checked: false,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,\r\n headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,\r\n relation: \"unknown\",\r\n ...(error ? { error } : {}),\r\n };\r\n }\r\n\r\n const relation: GitAncestryRelation = baseIsAncestorOfHead.isAncestor\r\n ? \"ahead\"\r\n : headIsAncestorOfBase.isAncestor\r\n ? \"merged\"\r\n : \"diverged\";\r\n\r\n return {\r\n checked: true,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,\r\n headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,\r\n relation,\r\n ...(error ? { error } : {}),\r\n };\r\n}\r\n\r\nfunction unknownAncestry(base: string, error: string, head: string | null = null): GitAncestry {\r\n return {\r\n checked: false,\r\n base,\r\n head,\r\n baseHead: null,\r\n baseIsAncestorOfHead: null,\r\n headIsAncestorOfBase: null,\r\n relation: \"unknown\",\r\n error,\r\n };\r\n}\r\n\r\nexport { scrubClaudeEnv, scrubWorkerEnv } from \"./worker-env.js\";\r\n", "/**\r\n * Worker spawn env scrub \u2014 harness workers must not inherit host deployment secrets.\r\n */\r\n\r\n/** Exact env keys that must never reach a worker child process. */\r\nexport const FORBIDDEN_WORKER_ENV_KEYS = [\r\n \"ANTHROPIC_API_KEY\",\r\n \"ANALYST_API_KEY\",\r\n \"RECRUITER_API_KEY\",\r\n \"AUTH_SECRET\",\r\n \"NEXTAUTH_SECRET\",\r\n \"DATABASE_URL\",\r\n \"PRODUCTION_DATABASE_URL\",\r\n \"KYNVER_PRODUCTION_DATABASE_URL\",\r\n \"REDIS_URL\",\r\n \"GOOGLE_CLIENT_SECRET\",\r\n \"GITHUB_CLIENT_SECRET\",\r\n \"KYNVER_API_KEY\",\r\n \"KYNVER_SERVICE_SECRET\",\r\n \"KYNVER_RUNTIME_SECRET\",\r\n \"KYNVER_CRON_SECRET\",\r\n \"OPENCLAW_CRON_SECRET\",\r\n \"QSTASH_TOKEN\",\r\n \"QSTASH_CURRENT_SIGNING_KEY\",\r\n \"QSTASH_NEXT_SIGNING_KEY\",\r\n \"TOOL_SECRETS_KEK\",\r\n \"TOOL_EXECUTOR_DISPATCH_SECRET\",\r\n \"CLOUDFLARE_API_TOKEN\",\r\n \"STRIPE_SECRET_KEY\",\r\n \"STRIPE_WEBHOOK_SECRET\",\r\n \"STRIPE_IDENTITY_WEBHOOK_SECRET\",\r\n \"VOYAGE_API_KEY\",\r\n \"PERPLEXITY_API_KEY\",\r\n \"FRED_API_KEY\",\r\n \"FMP_API_KEY\",\r\n \"CURSOR_API_KEY\",\r\n] as const;\r\n\r\nconst FORBIDDEN_KEY_SET = new Set<string>(FORBIDDEN_WORKER_ENV_KEYS);\r\n\r\n/** Keys matching these suffixes are stripped (case-sensitive). */\r\nconst FORBIDDEN_SUFFIXES = [\"_SECRET\", \"_API_KEY\"] as const;\r\n\r\nexport function isForbiddenWorkerEnvKey(key: string): boolean {\r\n if (FORBIDDEN_KEY_SET.has(key)) return true;\r\n return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));\r\n}\r\n\r\nexport function listForbiddenWorkerEnvKeys(env: NodeJS.ProcessEnv): string[] {\r\n return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();\r\n}\r\n\r\nexport function scrubWorkerEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\r\n const next = { ...env };\r\n for (const key of Object.keys(next)) {\r\n if (isForbiddenWorkerEnvKey(key)) delete next[key];\r\n }\r\n return next;\r\n}\r\n\r\nexport interface WorkerEnvAudit {\r\n forbiddenPresent: string[];\r\n safe: boolean;\r\n}\r\n\r\nexport function auditWorkerEnv(env: NodeJS.ProcessEnv): WorkerEnvAudit {\r\n const forbiddenPresent = listForbiddenWorkerEnvKeys(env);\r\n return { forbiddenPresent, safe: forbiddenPresent.length === 0 };\r\n}\r\n\r\n/** @deprecated Use {@link scrubWorkerEnv} \u2014 kept for existing imports from git.ts */\r\nexport function scrubClaudeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\r\n return scrubWorkerEnv(env);\r\n}\r\n", "import { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\n\r\nexport function expandHomePath(value: string): string {\r\n if (value === \"~\") return homedir();\r\n if (value.startsWith(\"~/\") || value.startsWith(\"~\\\\\")) {\r\n return path.join(homedir(), value.slice(2));\r\n }\r\n return value;\r\n}\r\n\r\nexport function resolveUserPath(value: string): string {\r\n return path.resolve(expandHomePath(value));\r\n}\r\n\r\nexport function redactHomePath(value: string): string {\r\n const expanded = expandHomePath(value);\r\n const resolved = path.resolve(expanded);\r\n const home = path.resolve(homedir());\r\n\r\n if (resolved === home) return \"~\";\r\n if (resolved.startsWith(`${home}${path.sep}`)) {\r\n return `~/${path.relative(home, resolved).split(path.sep).join(\"/\")}`;\r\n }\r\n\r\n return resolved\r\n .replace(/^\\/home\\/[^/]+(?=\\/|$)/, \"~\")\r\n .replace(/^\\/Users\\/[^/]+(?=\\/|$)/, \"~\");\r\n}\r\n\r\n/** User-facing path strings (doctor JSON, setup output). */\r\nexport function displayUserPath(value: string): string {\r\n return redactHomePath(value);\r\n}\r\n", "import { existsSync, readFileSync, statfsSync } from \"node:fs\";\r\n\r\n/**\r\n * WSL host disk probe.\r\n *\r\n * Under WSL, the runtime VHDX (mounted at `/`) grows dynamically into the\r\n * Windows host C: drive. When Windows C: fills up, the VHDX cannot expand:\r\n * writes return SIGBUS, workers exit 135, and the Vmmem VM degrades while\r\n * the Linux-side `statfs /` still reports plenty of free space inside the\r\n * VHDX. This module gives the harness a cheap signal for that pressure so\r\n * it can block dispatch *before* a large npm install/build pushes Windows\r\n * C: into the ground.\r\n */\r\n\r\n/** Default warn threshold for the Windows host disk (25 GiB free). */\r\nexport const DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;\r\n/** Default critical threshold for the Windows host disk (12 GiB free). Below\r\n * this, large rebuilds have been observed to trigger SIGBUS / exit 135 on\r\n * WSL when the VHDX cannot grow further. */\r\nexport const DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;\r\n/** Default Windows host mount point under WSL. Overridable via env or option. */\r\nexport const DEFAULT_WSL_HOST_MOUNT = \"/mnt/c\";\r\n\r\nexport interface WslHostDiskShape {\r\n ok: boolean;\r\n /** Mount point that was probed. */\r\n path: string;\r\n freeBytes: number;\r\n totalBytes: number;\r\n usedPercent: number;\r\n warnBelowBytes: number;\r\n criticalBelowBytes: number;\r\n /** Human-readable explanation when the host disk is under pressure. */\r\n reason: string | null;\r\n /** True when the probe itself failed (mount missing, statfs error). */\r\n probeError: string | null;\r\n}\r\n\r\n/**\r\n * Cheap WSL detection. Reads `/proc/sys/kernel/osrelease` (kernel string\r\n * contains \"microsoft\" / \"WSL2\" on Microsoft's WSL kernel) and falls back\r\n * to `/proc/version`. Both reads are one-shot and gated by `existsSync`,\r\n * so non-Linux hosts return false without throwing.\r\n */\r\nexport function isWslHost(): boolean {\r\n if (process.platform !== \"linux\") return false;\r\n for (const probe of [\"/proc/sys/kernel/osrelease\", \"/proc/version\"]) {\r\n try {\r\n if (!existsSync(probe)) continue;\r\n const text = readFileSync(probe, \"utf8\");\r\n if (/microsoft|wsl/i.test(text)) return true;\r\n } catch {\r\n // ignore \u2014 try the next probe path\r\n }\r\n }\r\n return false;\r\n}\r\n\r\nexport interface ObserveWslHostDiskOptions {\r\n /** Override the Windows host mount path (e.g. `/mnt/d`). Falls back to\r\n * `KYNVER_WSL_HOST_MOUNT` env, then `/mnt/c`. */\r\n wslHostMount?: string;\r\n wslHostFreeWarnBytes?: number;\r\n wslHostFreeCriticalBytes?: number;\r\n /** Override WSL detection for tests / cross-platform CI:\r\n * `true` \u2192 treat host as WSL; `false` \u2192 treat host as non-WSL;\r\n * `undefined` \u2192 autodetect via `isWslHost()`. */\r\n forceWsl?: boolean;\r\n /** Test seam \u2014 swap in a fake statfs. */\r\n statfs?: (path: string) => { bavail: bigint | number; blocks: bigint | number; bsize: bigint | number };\r\n}\r\n\r\n/**\r\n * Probe the Windows host disk under WSL. Returns `null` when this host is\r\n * not WSL (so callers can treat the field as optional in the gate output).\r\n * When WSL but the mount is unreachable, returns a `probeError` row with\r\n * `ok = false` rather than throwing \u2014 the gate must still block dispatch\r\n * because we can't prove the host disk is healthy.\r\n */\r\nexport function observeWslHostDisk(\r\n options: ObserveWslHostDiskOptions = {},\r\n): WslHostDiskShape | null {\r\n const wsl = options.forceWsl === undefined ? isWslHost() : options.forceWsl;\r\n if (!wsl) return null;\r\n\r\n const path =\r\n options.wslHostMount?.trim() ||\r\n process.env.KYNVER_WSL_HOST_MOUNT?.trim() ||\r\n DEFAULT_WSL_HOST_MOUNT;\r\n const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;\r\n const criticalBelowBytes =\r\n options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;\r\n\r\n const statfs = options.statfs ?? statfsSync;\r\n let stats: { bavail: bigint | number; blocks: bigint | number; bsize: bigint | number };\r\n try {\r\n stats = statfs(path);\r\n } catch (error) {\r\n return {\r\n ok: false,\r\n path,\r\n freeBytes: 0,\r\n totalBytes: 0,\r\n usedPercent: 100,\r\n warnBelowBytes,\r\n criticalBelowBytes,\r\n reason: `Windows host disk probe failed at ${path}: ${(error as Error).message}`,\r\n probeError: (error as Error).message,\r\n };\r\n }\r\n\r\n const freeBytes = Number(stats.bavail) * Number(stats.bsize);\r\n const totalBytes = Number(stats.blocks) * Number(stats.bsize);\r\n const usedPercent = totalBytes > 0 ? ((totalBytes - freeBytes) / totalBytes) * 100 : 100;\r\n const lowFree = freeBytes < warnBelowBytes;\r\n const criticalFree = freeBytes < criticalBelowBytes;\r\n const ok = !lowFree && !criticalFree;\r\n\r\n const freeGiB = (freeBytes / (1024 * 1024 * 1024)).toFixed(1);\r\n let reason: string | null = null;\r\n if (!ok) {\r\n const tag = criticalFree ? \"critical\" : \"warning\";\r\n reason =\r\n `Windows host disk ${path} at ${tag}: ${freeGiB} GiB free ` +\r\n `(<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); ` +\r\n `WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;\r\n }\r\n\r\n return {\r\n ok,\r\n path,\r\n freeBytes,\r\n totalBytes,\r\n usedPercent,\r\n warnBelowBytes,\r\n criticalBelowBytes,\r\n reason,\r\n probeError: null,\r\n };\r\n}\r\n\r\n/**\r\n * Short operator recovery hint embedded in gate reasons so AgentOS evidence\r\n * surfaces actionable steps without a doc lookup. Keep terse \u2014 full runbook\r\n * lives at `docs/runbooks/wsl-disk-pressure.md`.\r\n */\r\nexport function summarizeWslRecoverySteps(): string {\r\n return (\r\n \"Recovery: \" +\r\n \"1) free Windows C: (empty Recycle Bin / Storage Sense / clear %TEMP%); \" +\r\n \"2) shut down WSL (`wsl --shutdown`) then compact the VHDX (`Optimize-VHD` or `diskpart compact vdisk`); \" +\r\n \"3) clear local node_modules / .next / harness worktrees before restarting workers. \" +\r\n \"Full runbook: docs/runbooks/wsl-disk-pressure.md.\"\r\n );\r\n}\r\n", "import { statfsSync } from \"node:fs\";\r\nimport type { DispatchNextDiskGateShape } from \"./callbacks.js\";\r\nimport {\r\n observeWslHostDisk,\r\n type ObserveWslHostDiskOptions,\r\n type WslHostDiskShape,\r\n} from \"./wsl-host.js\";\r\n\r\nconst DEFAULT_WARN_FREE_BYTES = 30 * 1024 * 1024 * 1024;\r\nconst DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;\r\nconst DEFAULT_MAX_USED_PERCENT = 80;\r\nconst DEFAULT_HARD_MAX_USED_PERCENT = 90;\r\n\r\nexport interface ObserveDiskGateInput {\r\n diskPath?: string;\r\n diskFreeWarnBytes?: number;\r\n diskFreeCriticalBytes?: number;\r\n diskMaxUsedPercent?: number;\r\n diskHardMaxUsedPercent?: number;\r\n /** Opt-out for the WSL host disk probe \u2014 leave `false`/undefined by\r\n * default so we always check `/mnt/c` under WSL. */\r\n skipWslHostCheck?: boolean;\r\n wslHost?: ObserveWslHostDiskOptions;\r\n}\r\n\r\nexport function observeRunnerDiskGate(input: ObserveDiskGateInput = {}): DispatchNextDiskGateShape {\r\n const path = input.diskPath?.trim() || \"/\";\r\n const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;\r\n const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;\r\n const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;\r\n const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;\r\n\r\n const stats = statfsSync(path);\r\n const freeBytes = Number(stats.bavail) * Number(stats.bsize);\r\n const totalBytes = Number(stats.blocks) * Number(stats.bsize);\r\n const usedPercent = totalBytes > 0 ? ((totalBytes - freeBytes) / totalBytes) * 100 : 100;\r\n const lowFree = freeBytes < warnBelowBytes;\r\n const criticalFree = freeBytes < criticalBelowBytes;\r\n const highUse = usedPercent > maxUsedPercent;\r\n const hardHighUse = usedPercent > hardMaxUsedPercent;\r\n const localOk = !lowFree && !criticalFree && !highUse && !hardHighUse;\r\n\r\n // WSL guard: the VHDX (mounted at `/`) grows into Windows C:; a healthy\r\n // local statfs is meaningless if the host disk is about to refuse writes.\r\n const wslHost: WslHostDiskShape | null = input.skipWslHostCheck\r\n ? null\r\n : observeWslHostDisk(input.wslHost);\r\n\r\n const ok = localOk && (wslHost ? wslHost.ok : true);\r\n\r\n let reason: string | null = null;\r\n if (!ok) {\r\n reason = [\r\n criticalFree ? `free space below critical ${criticalBelowBytes} bytes` : null,\r\n lowFree ? `free space below warning ${warnBelowBytes} bytes` : null,\r\n hardHighUse ? `used percent above hard cap ${hardMaxUsedPercent}%` : null,\r\n highUse ? `used percent above cap ${maxUsedPercent}%` : null,\r\n wslHost && !wslHost.ok ? wslHost.reason : null,\r\n ]\r\n .filter(Boolean)\r\n .join(\"; \");\r\n }\r\n\r\n return {\r\n ok,\r\n path,\r\n freeBytes,\r\n totalBytes,\r\n usedPercent,\r\n warnBelowBytes,\r\n criticalBelowBytes,\r\n maxUsedPercent,\r\n hardMaxUsedPercent,\r\n reason,\r\n wslHost,\r\n };\r\n}\r\n", "import { existsSync } from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { loadUserConfig } from \"./config.js\";\r\nimport { resolveUserPath } from \"./path-values.js\";\r\nimport { safeSlug } from \"./util.js\";\r\n\r\nconst LEGACY_ROOT = path.join(homedir(), \".openclaw\", \"harness\");\r\n\r\nconst HARNESS_LAYOUT_DIR_NAMES = new Set([\"runs\", \"worktrees\"]);\r\n\r\n/**\r\n * Canonical harness root: the directory that contains `runs/` and `worktrees/`.\r\n * Strips mistaken trailing layout segments (e.g. env set to `.../harness/runs`).\r\n * Server mirror: agent-os.harness-root.ts\r\n */\r\nexport function normalizeHarnessRoot(root: string): string {\r\n let resolved = path.resolve(resolveUserPath(root.trim()));\r\n while (HARNESS_LAYOUT_DIR_NAMES.has(path.basename(resolved))) {\r\n resolved = path.dirname(resolved);\r\n }\r\n return resolved;\r\n}\r\n\r\n/** Canonical harness root for CLI/workers. */\r\nexport function resolveHarnessRoot(): string {\r\n const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;\r\n if (env) return normalizeHarnessRoot(env);\r\n const configured = loadUserConfig().harnessRoot?.trim();\r\n if (configured) return normalizeHarnessRoot(configured);\r\n const kynverRoot = path.join(homedir(), \".kynver\", \"harness\");\r\n if (existsSync(kynverRoot)) return kynverRoot;\r\n if (existsSync(LEGACY_ROOT)) return LEGACY_ROOT;\r\n return kynverRoot;\r\n}\r\n\r\nexport function harnessRunsDir(harnessRoot: string): string {\r\n return path.join(normalizeHarnessRoot(harnessRoot), \"runs\");\r\n}\r\n\r\nexport function harnessWorktreesDir(harnessRoot: string): string {\r\n return path.join(normalizeHarnessRoot(harnessRoot), \"worktrees\");\r\n}\r\n\r\nexport function getHarnessPaths() {\r\n const harnessRoot = resolveHarnessRoot();\r\n return {\r\n harnessRoot,\r\n runsDir: harnessRunsDir(harnessRoot),\r\n worktreesDir: harnessWorktreesDir(harnessRoot),\r\n };\r\n}\r\n\r\nexport function runDir(runsDir: string, id: string): string {\r\n return path.join(runsDir, safeSlug(id));\r\n}\r\n", "import os from \"node:os\";\r\nimport { readMemAvailableBytes } from \"./bounded-build/meminfo.js\";\r\nimport path from \"node:path\";\r\nimport { loadUserConfig, type KynverUserConfig } from \"./config.js\";\r\nimport { resolveBoxKindFromConfig } from \"./box-identity.js\";\r\nimport type { WorkerCapSource } from \"./worker-cap-source.js\";\r\nimport { resolveWorkerCap } from \"./worker-cap-source.js\";\r\nimport type { DispatchNextDiskGateShape } from \"./callbacks.js\";\r\nimport { observeRunnerDiskGate } from \"./disk-gate.js\";\r\nimport { listRunRecords, loadRun, runDirectory, type HarnessRunRecord } from \"./run-store.js\";\r\nimport { listRunWorkerNames } from \"./run-worker-index.js\";\r\nimport { isActiveHarnessWorker, workerProcessMatchesRecord } from \"./harness-worker-active.js\";\r\nimport { readJson, safeSlug } from \"./util.js\";\r\nimport type { HarnessWorkerRecord } from \"./status.js\";\r\n\r\nexport { workerProcessMatchesRecord };\r\n\r\n/** Default RAM budget per worker (~500 MiB, dogfood measured). Internal \u2014 not a setup knob. */\r\nexport const DEFAULT_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;\r\n\r\n/** Keep headroom for OS / IDE. Internal \u2014 not a setup knob. */\r\nexport const DEFAULT_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;\r\n\r\n/** Fraction of total RAM used when auto-sizing worker cap. Internal. */\r\nexport const DEFAULT_MEM_UTILIZATION = 0.85;\r\n\r\n/** Auto cap when the user has not set maxConcurrentWorkers (safety on huge hosts). */\r\nexport const AUTO_MAX_WORKERS_CEILING = 64;\r\n\r\nexport interface RunnerResourceGateShape {\r\n ok: boolean;\r\n totalMemBytes: number;\r\n freeMemBytes: number;\r\n memReserveBytes: number;\r\n perWorkerMemBytes: number;\r\n configuredMaxWorkers: number | null;\r\n /** Where the effective worker cap was resolved (workspace override, env, config, or auto). */\r\n workerCapSource: WorkerCapSource;\r\n /** Physical pool for this host (`forge` | `ghost`). */\r\n boxKind: string;\r\n autoCap: number;\r\n capacityWorkers: number;\r\n maxConcurrentWorkers: number;\r\n activeWorkers: number;\r\n slotsAvailable: number;\r\n reason: string | null;\r\n /** Present unless `KYNVER_RESOURCE_GATE_SKIP_DISK=1`. */\r\n diskGate?: DispatchNextDiskGateShape;\r\n}\r\n\r\nexport interface ObserveResourceGateInput {\r\n runId: string;\r\n config?: KynverUserConfig;\r\n /** Command Center / workspace override \u2014 wins over local config when set. */\r\n configuredMaxWorkersOverride?: number | null;\r\n /** Override active worker count (tests). */\r\n activeWorkers?: number;\r\n totalMemBytes?: number;\r\n freeMemBytes?: number;\r\n diskPath?: string;\r\n skipDiskGate?: boolean;\r\n}\r\n\r\nfunction positiveInt(value: unknown, fallback: number): number {\r\n const n = Number(value);\r\n if (!Number.isFinite(n) || n <= 0) return fallback;\r\n return Math.floor(n);\r\n}\r\n\r\nfunction resolveResourceConfig(\r\n config: KynverUserConfig = loadUserConfig(),\r\n configuredMaxWorkersOverride?: number | null,\r\n totalMemBytes?: number,\r\n) {\r\n const perWorkerMemBytes = positiveInt(config.perWorkerMemBytes, DEFAULT_PER_WORKER_MEM_BYTES);\r\n const memReserveBytes = positiveInt(config.memReserveBytes, DEFAULT_MEM_RESERVE_BYTES);\r\n const memUtilization = Math.min(\r\n 1,\r\n Math.max(0.1, Number(config.memUtilization) > 0 ? Number(config.memUtilization) : DEFAULT_MEM_UTILIZATION),\r\n );\r\n const cap = resolveWorkerCap({\r\n config,\r\n configuredMaxWorkersOverride,\r\n totalMemBytes: totalMemBytes ?? os.totalmem(),\r\n });\r\n return {\r\n perWorkerMemBytes,\r\n memReserveBytes,\r\n memUtilization,\r\n configuredMaxWorkers: cap.configuredMaxWorkers,\r\n autoCap: cap.autoCap,\r\n workerCapSource: cap.workerCapSource,\r\n };\r\n}\r\n\r\n/** How many workers this host could run from RAM alone (before a user cap). */\r\nexport function computeAutoMaxWorkers(\r\n totalMemBytes: number,\r\n opts: { perWorkerMemBytes?: number; memReserveBytes?: number; memUtilization?: number } = {},\r\n): number {\r\n const perWorkerMemBytes = opts.perWorkerMemBytes ?? DEFAULT_PER_WORKER_MEM_BYTES;\r\n const memReserveBytes = opts.memReserveBytes ?? DEFAULT_MEM_RESERVE_BYTES;\r\n const memUtilization = opts.memUtilization ?? DEFAULT_MEM_UTILIZATION;\r\n const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);\r\n const raw = Math.max(1, Math.floor(budgetBytes / perWorkerMemBytes));\r\n return Math.min(raw, AUTO_MAX_WORKERS_CEILING);\r\n}\r\n\r\nfunction readAvailableMemBytes(): number {\r\n return readMemAvailableBytes();\r\n}\r\n\r\n/** Count alive, still-executing workers in a single run record. */\r\nfunction countActiveWorkersForRun(run: HarnessRunRecord): number {\r\n let active = 0;\r\n for (const name of listRunWorkerNames(run)) {\r\n const worker = readJson<HarnessWorkerRecord | undefined>(\r\n path.join(runDirectory(run.id), \"workers\", safeSlug(name), \"worker.json\"),\r\n undefined,\r\n );\r\n if (!worker || !isActiveHarnessWorker(worker)) continue;\r\n active++;\r\n }\r\n return active;\r\n}\r\n\r\n/** Count active workers in ONE run (kept for callers/tests scoped to a run). */\r\nexport function countActiveWorkers(runId: string): number {\r\n return countActiveWorkersForRun(loadRun(runId));\r\n}\r\n\r\n/**\r\n * Count active workers across EVERY run on disk. The harness creates a new run\r\n * per task, so a per-run count let concurrent runs each believe the machine was\r\n * idle \u2014 the configured cap was never a real global ceiling (the \"spawns 4 or\r\n * infinity, never N\" bug). This machine-wide count is what the gate must use.\r\n */\r\nexport function countActiveWorkersGlobal(): number {\r\n let active = 0;\r\n for (const run of listRunRecords()) active += countActiveWorkersForRun(run);\r\n return active;\r\n}\r\n\r\n/**\r\n * Compute how many workers this host can run and how many dispatch slots remain.\r\n * Uses total RAM for steady-state capacity and free RAM as a hard safety gate.\r\n */\r\nexport function observeRunnerResourceGate(input: ObserveResourceGateInput): RunnerResourceGateShape {\r\n const config = input.config ?? loadUserConfig();\r\n const totalMemBytes = input.totalMemBytes ?? os.totalmem();\r\n const { perWorkerMemBytes, memReserveBytes, memUtilization, configuredMaxWorkers, autoCap: resolvedAutoCap, workerCapSource } =\r\n resolveResourceConfig(config, input.configuredMaxWorkersOverride, totalMemBytes);\r\n const boxKind = resolveBoxKindFromConfig(config);\r\n const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();\r\n // Active count is GLOBAL across all runs (see countActiveWorkersGlobal), so the\r\n // cap is a true machine-wide ceiling rather than per-run.\r\n const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();\r\n\r\n const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);\r\n const capacityFromTotal = Math.max(0, Math.floor(budgetBytes / perWorkerMemBytes));\r\n const capacityFromFree = Math.max(0, Math.floor(Math.max(0, freeMemBytes - memReserveBytes) / perWorkerMemBytes));\r\n\r\n const autoCap = resolvedAutoCap;\r\n const targetCap = configuredMaxWorkers ?? autoCap;\r\n const maxConcurrentWorkers = Math.max(0, Math.min(targetCap, capacityFromTotal));\r\n const slotsByCapacity = Math.max(0, maxConcurrentWorkers - activeWorkers);\r\n // capacityFromFree is ADDITIONAL headroom: free/available RAM already excludes\r\n // memory held by running workers, so we must NOT subtract activeWorkers again.\r\n // Doing so (the old `capacityFromFree - activeWorkers`) double-counted active\r\n // workers and collapsed dispatch to a handful of slots under load.\r\n const slotsByFreeMem = capacityFromFree;\r\n let slotsAvailable = Math.min(slotsByCapacity, slotsByFreeMem);\r\n\r\n const skipDisk = input.skipDiskGate || process.env.KYNVER_RESOURCE_GATE_SKIP_DISK === \"1\";\r\n const diskGate = skipDisk\r\n ? undefined\r\n : observeRunnerDiskGate({\r\n diskPath:\r\n input.diskPath?.trim() ||\r\n process.env.KYNVER_DISK_GUARD_PATH?.trim() ||\r\n \"/\",\r\n });\r\n if (diskGate && !diskGate.ok) slotsAvailable = 0;\r\n\r\n let reason: string | null = null;\r\n if (slotsAvailable <= 0) {\r\n if (diskGate && !diskGate.ok) {\r\n reason = diskGate.reason ?? \"disk gate blocked worker admission\";\r\n } else if (activeWorkers >= maxConcurrentWorkers) {\r\n reason = `at worker limit (${activeWorkers}/${maxConcurrentWorkers} running)`;\r\n } else if (capacityFromFree <= 0) {\r\n reason = \"insufficient free memory \u2014 waiting for workers to finish\";\r\n } else {\r\n reason = \"no worker slots available\";\r\n }\r\n }\r\n\r\n return {\r\n ok: slotsAvailable > 0,\r\n totalMemBytes,\r\n freeMemBytes,\r\n memReserveBytes,\r\n perWorkerMemBytes,\r\n configuredMaxWorkers,\r\n workerCapSource,\r\n boxKind,\r\n autoCap,\r\n capacityWorkers: capacityFromTotal,\r\n maxConcurrentWorkers,\r\n activeWorkers,\r\n slotsAvailable,\r\n reason,\r\n ...(diskGate ? { diskGate } : {}),\r\n };\r\n}\r\n"],
4
+ "sourcesContent": ["import path from \"node:path\";\r\nimport { loadUserConfig, saveUserConfig, type KynverUserConfig } from \"./config.js\";\r\nimport {\r\n discoverDefaultRepo,\r\n type DefaultRepoDiscoverySource,\r\n type DiscoveredDefaultRepo,\r\n} from \"./default-repo-discovery.js\";\r\nimport { displayUserPath, redactHomePath, resolveUserPath } from \"./path-values.js\";\r\n\r\nexport type DefaultRepoSource =\r\n | \"config\"\r\n | \"env_default_repo\"\r\n | \"env_harness_repo\"\r\n | DefaultRepoDiscoverySource;\r\n\r\nexport interface ResolvedDefaultRepo {\r\n repo: string;\r\n source: DefaultRepoSource;\r\n persistedInConfig: boolean;\r\n}\r\n\r\nexport interface ResolveDefaultRepoOptions {\r\n config?: KynverUserConfig;\r\n env?: NodeJS.ProcessEnv;\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}\r\n\r\nfunction expandConfiguredRepo(value: string): string {\r\n return path.resolve(resolveUserPath(value.trim()));\r\n}\r\n\r\nfunction fromConfigured(\r\n value: string | undefined,\r\n source: Extract<DefaultRepoSource, \"config\" | \"env_default_repo\" | \"env_harness_repo\">,\r\n persistedInConfig: boolean,\r\n): ResolvedDefaultRepo | null {\r\n const trimmed = value?.trim();\r\n if (!trimmed) return null;\r\n return {\r\n repo: expandConfiguredRepo(trimmed),\r\n source,\r\n persistedInConfig,\r\n };\r\n}\r\n\r\nexport function resolveDefaultRepo(opts: ResolveDefaultRepoOptions = {}): ResolvedDefaultRepo | null {\r\n const env = opts.env ?? process.env;\r\n const config = opts.config ?? loadUserConfig();\r\n\r\n const fromConfig = fromConfigured(config.defaultRepo, \"config\", true);\r\n if (fromConfig) return fromConfig;\r\n\r\n const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, \"env_default_repo\", false);\r\n if (fromDefaultEnv) return fromDefaultEnv;\r\n\r\n const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, \"env_harness_repo\", false);\r\n if (fromHarnessEnv) return fromHarnessEnv;\r\n\r\n const discovered = discoverDefaultRepo({\r\n cwd: opts.cwd,\r\n runtimeModuleUrl: opts.runtimeModuleUrl,\r\n });\r\n if (!discovered) return null;\r\n\r\n return {\r\n repo: discovered.repo,\r\n source: discovered.source,\r\n persistedInConfig: false,\r\n };\r\n}\r\n\r\nexport function persistDefaultRepo(repo: string, existing?: KynverUserConfig): KynverUserConfig {\r\n const config: KynverUserConfig = {\r\n ...(existing ?? loadUserConfig()),\r\n defaultRepo: redactHomePath(path.resolve(repo)),\r\n };\r\n saveUserConfig(config);\r\n return config;\r\n}\r\n\r\nexport function remediateDefaultRepo(opts?: ResolveDefaultRepoOptions): {\r\n ok: true;\r\n resolved: ResolvedDefaultRepo;\r\n config: KynverUserConfig;\r\n} | {\r\n ok: false;\r\n reason: string;\r\n} {\r\n const existing = opts?.config ?? loadUserConfig();\r\n const resolved = resolveDefaultRepo({ ...opts, config: existing });\r\n if (!resolved) {\r\n return {\r\n ok: false,\r\n reason:\r\n \"No Kynver git checkout found. Clone the repo, cd into it, then run `kynver setup --repo /path/to/Kynver` (or export KYNVER_DEFAULT_REPO).\",\r\n };\r\n }\r\n\r\n if (resolved.persistedInConfig) {\r\n return { ok: true, resolved, config: existing };\r\n }\r\n\r\n const config = persistDefaultRepo(resolved.repo, existing);\r\n return {\r\n ok: true,\r\n resolved: { ...resolved, persistedInConfig: true, source: \"config\" },\r\n config,\r\n };\r\n}\r\n\r\nexport function formatResolvedDefaultRepo(resolved: ResolvedDefaultRepo): {\r\n defaultRepo: string;\r\n source: DefaultRepoSource;\r\n persistedInConfig: boolean;\r\n} {\r\n return {\r\n defaultRepo: displayUserPath(resolved.repo),\r\n source: resolved.source,\r\n persistedInConfig: resolved.persistedInConfig,\r\n };\r\n}\r\n\r\nexport type { DiscoveredDefaultRepo };\r\n", "import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\r\nimport { homedir, totalmem } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { discoverDefaultRepo } from \"./default-repo-discovery.js\";\r\nimport { displayUserPath, redactHomePath } from \"./path-values.js\";\r\nimport { trimTrailingSlash } from \"./util.js\";\r\nimport { normalizeWorkerPoolBoxKind, resolveBoxIdentity } from \"./box-identity.js\";\r\nimport { recommendSetupWorkerCap } from \"./worker-cap-source.js\";\r\nimport { observeRunnerDiskGate } from \"./disk-gate.js\";\r\nimport os from \"node:os\";\r\n\r\nexport interface KynverUserConfig {\r\n apiBaseUrl?: string;\r\n agentOsSlug?: string;\r\n agentOsId?: string;\r\n defaultRepo?: string;\r\n workerProvider?: string;\r\n /** Default Claude model when dispatch does not infer or pass `--model`. */\r\n defaultModel?: string;\r\n harnessRoot?: string;\r\n /**\r\n * Operator attestation that the hosted Kynver deployment uses this scheduler provider\r\n * (set on user runners after Vercel env cutover \u2014 scheduling is deployment-owned).\r\n */\r\n deploymentSchedulerProvider?: \"qstash\" | \"kynver-cron\" | \"openclaw-cron\";\r\n /** Physical box pool for capacity snapshots (`forge` | `ghost`). Set via `kynver setup --box-kind`. */\r\n boxKind?: \"ghost\" | \"forge\";\r\n /** Max concurrent workers on this machine. Omit to auto-size from RAM. */\r\n maxConcurrentWorkers?: number;\r\n /** Where maxConcurrentWorkers came from. */\r\n maxConcurrentWorkersSource?: \"setup-auto\" | \"setup-flag\" | \"operator\";\r\n /** @internal Advanced tuning \u2014 not required for setup. */\r\n perWorkerMemBytes?: number;\r\n /** @internal Advanced tuning \u2014 not required for setup. */\r\n memReserveBytes?: number;\r\n /** @internal Advanced tuning \u2014 not required for setup. */\r\n memUtilization?: number;\r\n}\r\n\r\nconst CONFIG_DIR = path.join(homedir(), \".kynver\");\r\nconst CONFIG_FILE = path.join(CONFIG_DIR, \"config.json\");\r\nconst CREDENTIALS_FILE = path.join(CONFIG_DIR, \"credentials\");\r\n\r\ninterface KynverCredentialsFile {\r\n apiKey?: string;\r\n /** Scoped `krc1.*` runner token for AgentOS by-id callbacks. */\r\n runnerToken?: string;\r\n runnerTokenAgentOsId?: string;\r\n}\r\n\r\nexport function loadUserConfig(): KynverUserConfig {\r\n if (!existsSync(CONFIG_FILE)) return {};\r\n try {\r\n return JSON.parse(readFileSync(CONFIG_FILE, \"utf8\")) as KynverUserConfig;\r\n } catch {\r\n return {};\r\n }\r\n}\r\n\r\nexport function saveUserConfig(config: KynverUserConfig): void {\r\n mkdirSync(CONFIG_DIR, { recursive: true });\r\n writeFileSync(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}\\n`, { mode: 0o600 });\r\n}\r\n\r\n/** Persist path fields with `~` instead of absolute home directories. */\r\nexport function normalizeConfigPaths(config: KynverUserConfig): KynverUserConfig {\r\n return {\r\n ...config,\r\n ...(config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {}),\r\n ...(config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}),\r\n };\r\n}\r\n\r\n/** Values for setup output (never emit `/home/<user>/\u2026`). */\r\nexport function presentUserConfig(config: KynverUserConfig): KynverUserConfig {\r\n return normalizeConfigPaths(config);\r\n}\r\n\r\nfunction inferSetupFields(\r\n existing: KynverUserConfig,\r\n args: Record<string, string | boolean>,\r\n): Partial<KynverUserConfig> {\r\n const creds = loadCredentialsFile();\r\n const apiBaseUrl =\r\n (typeof args.apiBaseUrl === \"string\" ? args.apiBaseUrl : undefined) ||\r\n existing.apiBaseUrl?.trim() ||\r\n process.env.KYNVER_API_URL?.trim() ||\r\n process.env.KYNVER_CRON_FIRE_BASE_URL?.trim() ||\r\n process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();\r\n const agentOsId =\r\n (typeof args.agentOsId === \"string\" ? args.agentOsId : undefined) ||\r\n existing.agentOsId?.trim() ||\r\n process.env.KYNVER_AGENT_OS_ID?.trim() ||\r\n (creds.runnerToken?.trim().startsWith(\"krc1.\") ? creds.runnerTokenAgentOsId?.trim() : undefined);\r\n const explicitRepo =\r\n typeof args.repo === \"string\"\r\n ? args.repo\r\n : args.discoverRepo === true || args.discoverRepo === \"true\"\r\n ? discoverDefaultRepo()?.repo\r\n : undefined;\r\n const defaultRepo =\r\n explicitRepo ||\r\n existing.defaultRepo?.trim() ||\r\n process.env.KYNVER_DEFAULT_REPO?.trim() ||\r\n process.env.KYNVER_HARNESS_REPO?.trim() ||\r\n discoverDefaultRepo()?.repo;\r\n const harnessRoot =\r\n (typeof args.harnessRoot === \"string\" ? args.harnessRoot : undefined) ||\r\n existing.harnessRoot?.trim() ||\r\n process.env.KYNVER_HARNESS_ROOT?.trim() ||\r\n process.env.OPUS_HARNESS_ROOT?.trim();\r\n\r\n return {\r\n ...(apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {}),\r\n ...(agentOsId ? { agentOsId } : {}),\r\n ...(defaultRepo ? { defaultRepo } : {}),\r\n ...(harnessRoot ? { harnessRoot } : {}),\r\n ...(typeof args.agentOsSlug === \"string\"\r\n ? { agentOsSlug: args.agentOsSlug }\r\n : existing.agentOsSlug\r\n ? { agentOsSlug: existing.agentOsSlug }\r\n : {}),\r\n };\r\n}\r\n\r\nconst SETUP_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;\r\nconst SETUP_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;\r\nconst SETUP_MEM_UTILIZATION = 0.85;\r\nconst SETUP_AUTO_MAX_WORKERS_CEILING = 64;\r\n\r\nfunction normalizeBoxKind(raw: unknown): \"ghost\" | \"forge\" | undefined {\r\n const kind = String(raw ?? \"\").trim().toLowerCase();\r\n if (!kind) return undefined;\r\n if (kind === \"ghost\" || kind.includes(\"ghost\") || kind.includes(\"openclaw\")) return \"ghost\";\r\n if (kind === \"forge\" || kind.includes(\"forge\")) return \"forge\";\r\n return undefined;\r\n}\r\n\r\nexport function computeSetupAutoMaxWorkers(totalMemBytes: number): number {\r\n const budgetBytes = Math.max(0, Math.floor(totalMemBytes * SETUP_MEM_UTILIZATION) - SETUP_MEM_RESERVE_BYTES);\r\n const raw = Math.max(1, Math.floor(budgetBytes / SETUP_PER_WORKER_MEM_BYTES));\r\n return Math.min(raw, SETUP_AUTO_MAX_WORKERS_CEILING);\r\n}\r\n\r\nexport function resolveSetupWorkerConfig(\r\n existing: KynverUserConfig,\r\n args: Record<string, string | boolean>,\r\n totalMemBytes = totalmem(),\r\n): Pick<KynverUserConfig, \"maxConcurrentWorkers\" | \"maxConcurrentWorkersSource\" | \"boxKind\"> {\r\n const maxWorkersRaw =\r\n typeof args.maxWorkers === \"string\"\r\n ? args.maxWorkers\r\n : typeof args.maxConcurrentWorkers === \"string\"\r\n ? args.maxConcurrentWorkers\r\n : undefined;\r\n const explicitBoxKindArg =\r\n typeof args.boxKind === \"string\"\r\n ? args.boxKind\r\n : typeof args[\"box-kind\"] === \"string\"\r\n ? String(args[\"box-kind\"])\r\n : undefined;\r\n const boxKind = resolveBoxIdentity(process.env, {\r\n ...existing,\r\n ...(explicitBoxKindArg ? { boxKind: normalizeWorkerPoolBoxKind(explicitBoxKindArg) } : {}),\r\n }).boxKind;\r\n const diskGate = observeRunnerDiskGate({\r\n diskPath: typeof args.diskPath === \"string\" ? args.diskPath : \"/\",\r\n });\r\n const capRecommendation = recommendSetupWorkerCap({\r\n totalMemBytes,\r\n diskPath: diskGate.path,\r\n diskGateOk: diskGate.ok,\r\n diskFreeBytes: diskGate.freeBytes,\r\n config: existing,\r\n });\r\n if (maxWorkersRaw) {\r\n return {\r\n maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))),\r\n maxConcurrentWorkersSource: \"setup-flag\",\r\n boxKind,\r\n };\r\n }\r\n if (existing.maxConcurrentWorkers !== undefined && existing.maxConcurrentWorkers !== null) {\r\n return {\r\n maxConcurrentWorkers: Math.max(1, Math.floor(Number(existing.maxConcurrentWorkers))),\r\n maxConcurrentWorkersSource: existing.maxConcurrentWorkersSource ?? \"operator\",\r\n boxKind,\r\n };\r\n }\r\n return {\r\n maxConcurrentWorkers: capRecommendation.recommendedMaxWorkers,\r\n maxConcurrentWorkersSource: \"setup-auto\",\r\n boxKind,\r\n };\r\n}\r\n\r\nfunction loadCredentialsFile(): KynverCredentialsFile {\r\n if (!existsSync(CREDENTIALS_FILE)) return {};\r\n try {\r\n return JSON.parse(readFileSync(CREDENTIALS_FILE, \"utf8\")) as KynverCredentialsFile;\r\n } catch {\r\n return {};\r\n }\r\n}\r\n\r\nfunction saveCredentialsFile(parsed: KynverCredentialsFile): void {\r\n mkdirSync(CONFIG_DIR, { recursive: true });\r\n writeFileSync(CREDENTIALS_FILE, `${JSON.stringify(parsed, null, 2)}\\n`, { mode: 0o600 });\r\n}\r\n\r\nexport function loadApiKey(): string | undefined {\r\n if (process.env.KYNVER_API_KEY) return process.env.KYNVER_API_KEY;\r\n return loadCredentialsFile().apiKey;\r\n}\r\n\r\nexport function saveApiKey(apiKey: string): void {\r\n saveCredentialsFile({ ...loadCredentialsFile(), apiKey });\r\n}\r\n\r\nexport function loadRunnerToken(agentOsId?: string): string | undefined {\r\n const envToken = process.env.KYNVER_RUNNER_TOKEN?.trim();\r\n if (envToken) return envToken;\r\n\r\n const creds = loadCredentialsFile();\r\n if (!creds.runnerToken) return undefined;\r\n if (agentOsId && creds.runnerTokenAgentOsId && creds.runnerTokenAgentOsId !== agentOsId) {\r\n return undefined;\r\n }\r\n return creds.runnerToken;\r\n}\r\n\r\nexport function saveRunnerToken(agentOsId: string, token: string): void {\r\n saveCredentialsFile({\r\n ...loadCredentialsFile(),\r\n runnerToken: token,\r\n runnerTokenAgentOsId: agentOsId,\r\n });\r\n}\r\n\r\nexport function resolveBaseUrl(argsBaseUrl?: string): string {\r\n const baseUrl = resolveConfiguredBaseUrl(argsBaseUrl);\r\n if (!baseUrl) failConfig(\"requires --base-url, KYNVER_API_URL, KYNVER_CRON_FIRE_BASE_URL, or ~/.kynver/config.json apiBaseUrl\");\r\n return baseUrl;\r\n}\r\n\r\nfunction resolveConfiguredBaseUrl(argsBaseUrl?: string): string | undefined {\r\n const baseUrl =\r\n argsBaseUrl ||\r\n process.env.KYNVER_API_URL ||\r\n process.env.KYNVER_CRON_FIRE_BASE_URL ||\r\n process.env.OPENCLAW_CRON_FIRE_BASE_URL ||\r\n loadUserConfig().apiBaseUrl;\r\n return baseUrl ? trimTrailingSlash(String(baseUrl)) : undefined;\r\n}\r\n\r\nfunction resolveConfiguredCallbackSecret(argsSecret?: string, agentOsId?: string): string | undefined {\r\n const scoped =\r\n argsSecret ||\r\n loadRunnerToken(agentOsId) ||\r\n (agentOsId ? undefined : loadRunnerToken(loadUserConfig().agentOsId));\r\n if (scoped) return String(scoped);\r\n\r\n const globalSecret =\r\n process.env.KYNVER_RUNTIME_SECRET ||\r\n process.env.KYNVER_CRON_SECRET ||\r\n process.env.OPENCLAW_CRON_SECRET;\r\n if (globalSecret) {\r\n console.warn(\r\n \"[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token\",\r\n );\r\n return String(globalSecret);\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nexport function resolveCallbackSecret(argsSecret?: string, agentOsId?: string): string {\r\n const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);\r\n if (configured) return configured;\r\n\r\n failConfig(\r\n \"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET\",\r\n );\r\n}\r\n\r\nexport async function resolveCallbackSecretWithMint(\r\n argsSecret?: string,\r\n agentOsId?: string,\r\n opts?: { baseUrl?: string },\r\n): Promise<string> {\r\n const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);\r\n if (configured) return configured;\r\n\r\n const apiKey = loadApiKey();\r\n const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);\r\n if (apiKey && agentOsId && baseUrl) {\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });\r\n saveRunnerToken(agentOsId, token);\r\n return token;\r\n } catch (error) {\r\n failConfig(`runner credential mint failed: ${(error as Error).message}`);\r\n }\r\n }\r\n\r\n failConfig(\r\n \"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET\",\r\n );\r\n}\r\n\r\n/**\r\n * Force-mint a fresh scoped runner token for `agentOsId`, bypassing any cached\r\n * or env token. Recovery path for a callback that 401s because the configured\r\n * token is revoked, expired, or scoped to a *different* workspace (the\r\n * self-linked repair case). Requires an API key + base URL to mint; returns\r\n * `null` when a fresh token cannot be obtained, so the caller degrades to a\r\n * structural blocker instead of papering the worker over as done.\r\n */\r\nexport async function refreshRunnerToken(\r\n agentOsId: string,\r\n opts?: { baseUrl?: string },\r\n): Promise<string | null> {\r\n const apiKey = loadApiKey();\r\n const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);\r\n if (!apiKey || !agentOsId || !baseUrl) return null;\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });\r\n saveRunnerToken(agentOsId, token);\r\n return token;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport async function refreshRunnerTokenForAuthFailure(\r\n rejectedSecret: string,\r\n agentOsId: string,\r\n opts?: { baseUrl?: string },\r\n): Promise<{ ok: true; token: string } | { ok: false; reason: string }> {\r\n const apiKey = loadApiKey();\r\n const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);\r\n if (!apiKey) return { ok: false, reason: \"KYNVER_API_KEY is required to refresh a rejected runner token\" };\r\n if (!agentOsId) return { ok: false, reason: \"agentOsId is required to refresh a rejected runner token\" };\r\n if (!baseUrl) return { ok: false, reason: \"KYNVER_API_URL or --base-url is required to refresh a rejected runner token\" };\r\n\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });\r\n if (token && token !== rejectedSecret) {\r\n saveRunnerToken(agentOsId, token);\r\n return { ok: true, token };\r\n }\r\n return { ok: false, reason: \"runner credential refresh returned the rejected token\" };\r\n } catch (error) {\r\n return { ok: false, reason: (error as Error).message };\r\n }\r\n}\r\n\r\nexport async function fetchRunnerCredential(\r\n agentOsId: string,\r\n opts?: { baseUrl?: string; apiKey?: string },\r\n): Promise<string> {\r\n const apiKey = opts?.apiKey || loadApiKey();\r\n if (!apiKey) throw new Error(\"API key required \u2014 run `kynver login` first\");\r\n\r\n const base = resolveBaseUrl(opts?.baseUrl);\r\n const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runner-credentials`;\r\n const res = await fetch(url, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n Authorization: `Bearer ${apiKey}`,\r\n },\r\n body: JSON.stringify({}),\r\n });\r\n\r\n const text = await res.text();\r\n let parsed: { token?: string; error?: string } | null = null;\r\n try {\r\n parsed = JSON.parse(text) as { token?: string; error?: string };\r\n } catch {\r\n parsed = null;\r\n }\r\n if (!res.ok || !parsed?.token) {\r\n throw new Error(\r\n `runner credential mint failed (${res.status}): ${parsed?.error ?? text.slice(0, 200)}`,\r\n );\r\n }\r\n return parsed.token;\r\n}\r\n\r\nexport async function mintRunnerCredential(args: Record<string, string | boolean>): Promise<void> {\r\n const agentOsId =\r\n (args.agentOsId ? String(args.agentOsId) : loadUserConfig().agentOsId) || \"\";\r\n if (!agentOsId) failConfig(\"runner credential requires --agent-os-id or agentOsId in ~/.kynver/config.json\");\r\n\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, {\r\n baseUrl: args.baseUrl ? String(args.baseUrl) : undefined,\r\n });\r\n saveRunnerToken(agentOsId, token);\r\n console.log(\r\n JSON.stringify(\r\n {\r\n ok: true,\r\n agentOsId,\r\n credentialsPath: displayUserPath(CREDENTIALS_FILE),\r\n tokenPrefix: `${token.slice(0, 12)}\u2026`,\r\n note: \"Scoped runner token saved; callbacks use X-Kynver-Runner-Token.\",\r\n },\r\n null,\r\n 2,\r\n ),\r\n );\r\n } catch (err) {\r\n console.error(err instanceof Error ? err.message : String(err));\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction failConfig(message: string): never {\r\n console.error(message);\r\n process.exit(1);\r\n}\r\n\r\nexport function parseArgs(argv: string[]): Record<string, string | boolean> {\r\n const args: Record<string, string | boolean> = {};\r\n for (let i = 0; i < argv.length; i++) {\r\n const item = argv[i];\r\n if (!item.startsWith(\"--\")) continue;\r\n const key = item.slice(2).replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\r\n const next = argv[i + 1];\r\n if (!next || next.startsWith(\"--\")) args[key] = true;\r\n else {\r\n args[key] = next;\r\n i++;\r\n }\r\n }\r\n return args;\r\n}\r\n\r\nexport async function runSetup(args: Record<string, string | boolean>): Promise<void> {\r\n const existing = loadUserConfig();\r\n const diskGate = observeRunnerDiskGate({\r\n diskPath: typeof args.diskPath === \"string\" ? args.diskPath : \"/\",\r\n });\r\n const capRecommendation = recommendSetupWorkerCap({\r\n totalMemBytes: os.totalmem(),\r\n diskPath: diskGate.path,\r\n diskGateOk: diskGate.ok,\r\n diskFreeBytes: diskGate.freeBytes,\r\n config: existing,\r\n });\r\n const workerConfig = resolveSetupWorkerConfig(existing, args);\r\n const config: KynverUserConfig = normalizeConfigPaths({\r\n ...existing,\r\n ...inferSetupFields(existing, args),\r\n ...workerConfig,\r\n workerProvider:\r\n typeof args.provider === \"string\"\r\n ? args.provider\r\n : existing.workerProvider || \"cursor\",\r\n });\r\n saveUserConfig(config);\r\n const boxIdentity = resolveBoxIdentity(process.env, config);\r\n\r\n let runnerCredentialNote: string | undefined;\r\n const apiKey = loadApiKey();\r\n const agentOsId = config.agentOsId;\r\n if (apiKey && agentOsId) {\r\n try {\r\n const token = await fetchRunnerCredential(agentOsId, {\r\n baseUrl: typeof args.apiBaseUrl === \"string\" ? args.apiBaseUrl : config.apiBaseUrl,\r\n apiKey,\r\n });\r\n saveRunnerToken(agentOsId, token);\r\n runnerCredentialNote = \"Scoped runner token minted and saved to ~/.kynver/credentials.\";\r\n } catch {\r\n runnerCredentialNote =\r\n \"Runner token not minted (server offline or master secret unset). Run `kynver runner credential` after deploy.\";\r\n }\r\n }\r\n\r\n console.log(\r\n JSON.stringify(\r\n {\r\n ok: true,\r\n configPath: displayUserPath(CONFIG_FILE),\r\n config: presentUserConfig(config),\r\n boxKind: config.boxKind,\r\n boxKindSource: boxIdentity.source,\r\n workerCapRecommendation: capRecommendation,\r\n ...(boxIdentity.warnings.length ? { boxIdentityWarnings: boxIdentity.warnings } : {}),\r\n note:\r\n runnerCredentialNote ??\r\n \"boxKind and maxConcurrentWorkers persisted; override with --box-kind and --max-workers. Run `kynver login` + `kynver runner credential` for scoped callbacks.\",\r\n },\r\n null,\r\n 2,\r\n ),\r\n );\r\n}\r\n\r\nexport async function runLogin(args: Record<string, string | boolean>): Promise<void> {\r\n const apiKey = typeof args.apiKey === \"string\" ? args.apiKey : process.env.KYNVER_API_KEY;\r\n if (!apiKey) failConfig(\"kynver login requires --api-key or KYNVER_API_KEY\");\r\n saveApiKey(apiKey);\r\n console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));\r\n}\r\n", "import { existsSync, readFileSync } from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport { gitCapture } from \"./git.js\";\r\nimport { resolveUserPath } from \"./path-values.js\";\r\n\r\nexport type DefaultRepoDiscoverySource =\r\n | \"cwd_git\"\r\n | \"runtime_checkout\"\r\n | \"well_known_path\";\r\n\r\nexport interface DiscoveredDefaultRepo {\r\n repo: string;\r\n source: DefaultRepoDiscoverySource;\r\n}\r\n\r\nconst WELL_KNOWN_REPO_DIRS = [\r\n \"Kynver\",\r\n \"repos/Kynver\",\r\n \"repos/kynver-source-main\",\r\n \"code/Kynver\",\r\n \"projects/Kynver\",\r\n] as const;\r\n\r\nfunction readPackageName(repoRoot: string): string | null {\r\n const pkgPath = path.join(repoRoot, \"package.json\");\r\n if (!existsSync(pkgPath)) return null;\r\n try {\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { name?: string };\r\n return typeof pkg.name === \"string\" ? pkg.name.trim() : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function isKynverMonorepoRoot(repoRoot: string): boolean {\r\n return readPackageName(repoRoot) === \"kynver\";\r\n}\r\n\r\nexport function gitRepoRoot(startDir: string): string | null {\r\n const resolvedStart = path.resolve(startDir);\r\n if (!existsSync(resolvedStart)) return null;\r\n const probe = gitCapture(resolvedStart, [\"rev-parse\", \"--show-toplevel\"]);\r\n if (probe.status !== 0) return null;\r\n const root = probe.stdout.trim();\r\n return root.length ? path.resolve(root) : null;\r\n}\r\n\r\nfunction resolveRuntimePackageRoot(moduleUrl: string = import.meta.url): string | null {\r\n let dir = path.dirname(fileURLToPath(moduleUrl));\r\n for (let depth = 0; depth < 8; depth += 1) {\r\n const pkgPath = path.join(dir, \"package.json\");\r\n if (existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { name?: string };\r\n if (pkg.name === \"@kynver-app/runtime\") return dir;\r\n } catch {\r\n // continue walking\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction pushCandidate(\r\n seen: Set<string>,\r\n out: DiscoveredDefaultRepo[],\r\n repo: string | null,\r\n source: DefaultRepoDiscoverySource,\r\n): void {\r\n if (!repo) return;\r\n const resolved = path.resolve(repo);\r\n if (seen.has(resolved)) return;\r\n if (!isKynverMonorepoRoot(resolved)) return;\r\n seen.add(resolved);\r\n out.push({ repo: resolved, source });\r\n}\r\n\r\nexport function discoverDefaultRepoCandidates(opts?: {\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}): DiscoveredDefaultRepo[] {\r\n const cwd = opts?.cwd ?? process.cwd();\r\n const seen = new Set<string>();\r\n const candidates: DiscoveredDefaultRepo[] = [];\r\n\r\n pushCandidate(seen, candidates, gitRepoRoot(cwd), \"cwd_git\");\r\n\r\n const runtimePkgRoot = resolveRuntimePackageRoot(opts?.runtimeModuleUrl ?? import.meta.url);\r\n if (runtimePkgRoot) {\r\n pushCandidate(seen, candidates, gitRepoRoot(runtimePkgRoot), \"runtime_checkout\");\r\n }\r\n\r\n const home = homedir();\r\n for (const rel of WELL_KNOWN_REPO_DIRS) {\r\n pushCandidate(seen, candidates, resolveUserPath(path.join(home, rel)), \"well_known_path\");\r\n }\r\n\r\n return candidates;\r\n}\r\n\r\nexport function discoverDefaultRepo(opts?: {\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}): DiscoveredDefaultRepo | null {\r\n return discoverDefaultRepoCandidates(opts)[0] ?? null;\r\n}\r\n", "import { spawnSync } from \"node:child_process\";\r\nimport { fail } from \"./util.js\";\r\n\r\nexport interface GitOptions {\r\n allowFailure?: boolean;\r\n throwError?: boolean;\r\n}\r\n\r\nexport function git(cwd: string, args: string[], options: GitOptions = {}): string {\r\n const res = spawnSync(\"git\", args, { cwd, encoding: \"utf8\" });\r\n if (res.status !== 0 && !options.allowFailure) {\r\n const message = `git ${args.join(\" \")} failed: ${res.stderr || res.stdout}`;\r\n if (options.throwError) throw new Error(message);\r\n fail(message);\r\n }\r\n return res.stdout || \"\";\r\n}\r\n\r\nexport function ensureGitRepo(repo: string): void {\r\n git(repo, [\"rev-parse\", \"--show-toplevel\"]);\r\n}\r\n\r\nexport function gitStatusShort(worktreePath: string): string[] {\r\n return git(worktreePath, [\"status\", \"--short\"], { allowFailure: true })\r\n .split(\"\\n\")\r\n .map((line) => line.trim())\r\n .filter(Boolean);\r\n}\r\n\r\nexport interface GitCaptureResult {\r\n status: number | null;\r\n stdout: string;\r\n stderr: string;\r\n error: string | null;\r\n}\r\n\r\nexport function gitCapture(cwd: string, args: string[]): GitCaptureResult {\r\n try {\r\n const res = spawnSync(\"git\", args, { cwd, encoding: \"utf8\" });\r\n return {\r\n status: res.status,\r\n stdout: res.stdout || \"\",\r\n stderr: res.stderr || \"\",\r\n error: res.error ? res.error.message : null,\r\n };\r\n } catch (error) {\r\n return {\r\n status: null,\r\n stdout: \"\",\r\n stderr: \"\",\r\n error: (error as Error).message,\r\n };\r\n }\r\n}\r\n\r\nexport function gitIsAncestor(\r\n cwd: string,\r\n ancestor: string,\r\n descendant: string,\r\n): { isAncestor: boolean | null; error: string | null } {\r\n const res = gitCapture(cwd, [\"merge-base\", \"--is-ancestor\", ancestor, descendant]);\r\n if (res.status === 0) return { isAncestor: true, error: null };\r\n if (res.status === 1) return { isAncestor: false, error: null };\r\n return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };\r\n}\r\n\r\nexport type GitAncestryRelation = \"synced\" | \"merged\" | \"ahead\" | \"diverged\" | \"unknown\";\r\n\r\nexport interface GitAncestry {\r\n checked: boolean;\r\n base: string;\r\n head: string | null;\r\n baseHead: string | null;\r\n baseIsAncestorOfHead: boolean | null;\r\n headIsAncestorOfBase: boolean | null;\r\n relation: GitAncestryRelation;\r\n error?: string;\r\n}\r\n\r\nexport interface GitAncestryOptions {\r\n /** Branch or ref name (e.g. origin/main). Used when baseCommit is unset. */\r\n base?: string;\r\n /** Pinned SHA the worktree was created from \u2014 preferred over a moving branch ref. */\r\n baseCommit?: string;\r\n}\r\n\r\nexport function computeGitAncestry(worktreePath: string, baseOrOptions: string | GitAncestryOptions = \"origin/main\"): GitAncestry {\r\n const options: GitAncestryOptions =\r\n typeof baseOrOptions === \"string\" ? { base: baseOrOptions } : baseOrOptions;\r\n const baseLabel = options.baseCommit?.trim() || options.base?.trim() || \"origin/main\";\r\n const pinnedBaseCommit = options.baseCommit?.trim() || null;\r\n\r\n if (!worktreePath) {\r\n return unknownAncestry(baseLabel, \"missing worktree path\");\r\n }\r\n\r\n const head = gitCapture(worktreePath, [\"rev-parse\", \"HEAD\"]);\r\n if (head.status !== 0) {\r\n return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || \"failed to resolve HEAD\");\r\n }\r\n\r\n let baseSha: string;\r\n if (pinnedBaseCommit) {\r\n baseSha = pinnedBaseCommit;\r\n } else {\r\n const baseHead = gitCapture(worktreePath, [\"rev-parse\", baseLabel]);\r\n if (baseHead.status !== 0) {\r\n return unknownAncestry(\r\n baseLabel,\r\n baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,\r\n head.stdout.trim(),\r\n );\r\n }\r\n baseSha = baseHead.stdout.trim();\r\n }\r\n\r\n const headSha = head.stdout.trim();\r\n if (headSha === baseSha) {\r\n return {\r\n checked: true,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: true,\r\n headIsAncestorOfBase: true,\r\n relation: \"synced\",\r\n };\r\n }\r\n\r\n const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);\r\n const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);\r\n const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || undefined;\r\n if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {\r\n return {\r\n checked: false,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,\r\n headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,\r\n relation: \"unknown\",\r\n ...(error ? { error } : {}),\r\n };\r\n }\r\n\r\n const relation: GitAncestryRelation = baseIsAncestorOfHead.isAncestor\r\n ? \"ahead\"\r\n : headIsAncestorOfBase.isAncestor\r\n ? \"merged\"\r\n : \"diverged\";\r\n\r\n return {\r\n checked: true,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,\r\n headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,\r\n relation,\r\n ...(error ? { error } : {}),\r\n };\r\n}\r\n\r\nfunction unknownAncestry(base: string, error: string, head: string | null = null): GitAncestry {\r\n return {\r\n checked: false,\r\n base,\r\n head,\r\n baseHead: null,\r\n baseIsAncestorOfHead: null,\r\n headIsAncestorOfBase: null,\r\n relation: \"unknown\",\r\n error,\r\n };\r\n}\r\n\r\nexport { scrubClaudeEnv, scrubWorkerEnv } from \"./worker-env.js\";\r\n", "/**\r\n * Worker spawn env scrub \u2014 harness workers must not inherit host deployment secrets.\r\n */\r\n\r\n/** Exact env keys that must never reach a worker child process. */\r\nexport const FORBIDDEN_WORKER_ENV_KEYS = [\r\n \"ANTHROPIC_API_KEY\",\r\n \"ANALYST_API_KEY\",\r\n \"RECRUITER_API_KEY\",\r\n \"AUTH_SECRET\",\r\n \"NEXTAUTH_SECRET\",\r\n \"DATABASE_URL\",\r\n \"PRODUCTION_DATABASE_URL\",\r\n \"KYNVER_PRODUCTION_DATABASE_URL\",\r\n \"REDIS_URL\",\r\n \"GOOGLE_CLIENT_SECRET\",\r\n \"GITHUB_CLIENT_SECRET\",\r\n \"KYNVER_API_KEY\",\r\n \"KYNVER_SERVICE_SECRET\",\r\n \"KYNVER_RUNTIME_SECRET\",\r\n \"KYNVER_CRON_SECRET\",\r\n \"OPENCLAW_CRON_SECRET\",\r\n \"QSTASH_TOKEN\",\r\n \"QSTASH_CURRENT_SIGNING_KEY\",\r\n \"QSTASH_NEXT_SIGNING_KEY\",\r\n \"TOOL_SECRETS_KEK\",\r\n \"TOOL_EXECUTOR_DISPATCH_SECRET\",\r\n \"CLOUDFLARE_API_TOKEN\",\r\n \"STRIPE_SECRET_KEY\",\r\n \"STRIPE_WEBHOOK_SECRET\",\r\n \"STRIPE_IDENTITY_WEBHOOK_SECRET\",\r\n \"VOYAGE_API_KEY\",\r\n \"PERPLEXITY_API_KEY\",\r\n \"FRED_API_KEY\",\r\n \"FMP_API_KEY\",\r\n \"CURSOR_API_KEY\",\r\n] as const;\r\n\r\nconst FORBIDDEN_KEY_SET = new Set<string>(FORBIDDEN_WORKER_ENV_KEYS);\r\n\r\n/** Keys matching these suffixes are stripped (case-sensitive). */\r\nconst FORBIDDEN_SUFFIXES = [\"_SECRET\", \"_API_KEY\"] as const;\r\n\r\nexport function isForbiddenWorkerEnvKey(key: string): boolean {\r\n if (FORBIDDEN_KEY_SET.has(key)) return true;\r\n return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));\r\n}\r\n\r\nexport function listForbiddenWorkerEnvKeys(env: NodeJS.ProcessEnv): string[] {\r\n return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();\r\n}\r\n\r\nexport function scrubWorkerEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\r\n const next = { ...env };\r\n for (const key of Object.keys(next)) {\r\n if (isForbiddenWorkerEnvKey(key)) delete next[key];\r\n }\r\n return next;\r\n}\r\n\r\nexport interface WorkerEnvAudit {\r\n forbiddenPresent: string[];\r\n safe: boolean;\r\n}\r\n\r\nexport function auditWorkerEnv(env: NodeJS.ProcessEnv): WorkerEnvAudit {\r\n const forbiddenPresent = listForbiddenWorkerEnvKeys(env);\r\n return { forbiddenPresent, safe: forbiddenPresent.length === 0 };\r\n}\r\n\r\n/** @deprecated Use {@link scrubWorkerEnv} \u2014 kept for existing imports from git.ts */\r\nexport function scrubClaudeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\r\n return scrubWorkerEnv(env);\r\n}\r\n", "import { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\n\r\nexport function expandHomePath(value: string): string {\r\n if (value === \"~\") return homedir();\r\n if (value.startsWith(\"~/\") || value.startsWith(\"~\\\\\")) {\r\n return path.join(homedir(), value.slice(2));\r\n }\r\n return value;\r\n}\r\n\r\nexport function resolveUserPath(value: string): string {\r\n return path.resolve(expandHomePath(value));\r\n}\r\n\r\nexport function redactHomePath(value: string): string {\r\n const expanded = expandHomePath(value);\r\n const resolved = path.resolve(expanded);\r\n const home = path.resolve(homedir());\r\n\r\n if (resolved === home) return \"~\";\r\n if (resolved.startsWith(`${home}${path.sep}`)) {\r\n return `~/${path.relative(home, resolved).split(path.sep).join(\"/\")}`;\r\n }\r\n\r\n const posix = resolved.replace(/\\\\/g, \"/\");\r\n const redacted = posix\r\n .replace(/^\\/home\\/[^/]+(?=\\/|$)/, \"~\")\r\n .replace(/^\\/Users\\/[^/]+(?=\\/|$)/, \"~\")\r\n // Windows resolves Unix-style `/home/...` paths to `C:\\home\\...`.\r\n .replace(/^[A-Za-z]:\\/home\\/[^/]+(?=\\/|$)/i, \"~\")\r\n .replace(/^[A-Za-z]:\\/Users\\/[^/]+(?=\\/|$)/i, \"~\");\r\n return redacted;\r\n}\r\n\r\n/** User-facing path strings (doctor JSON, setup output). */\r\nexport function displayUserPath(value: string): string {\r\n return redactHomePath(value);\r\n}\r\n", "import { existsSync, readFileSync, statfsSync } from \"node:fs\";\r\n\r\n/**\r\n * WSL host disk probe.\r\n *\r\n * Under WSL, the runtime VHDX (mounted at `/`) grows dynamically into the\r\n * Windows host C: drive. When Windows C: fills up, the VHDX cannot expand:\r\n * writes return SIGBUS, workers exit 135, and the Vmmem VM degrades while\r\n * the Linux-side `statfs /` still reports plenty of free space inside the\r\n * VHDX. This module gives the harness a cheap signal for that pressure so\r\n * it can block dispatch *before* a large npm install/build pushes Windows\r\n * C: into the ground.\r\n */\r\n\r\n/** Default warn threshold for the Windows host disk (25 GiB free). */\r\nexport const DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;\r\n/** Default critical threshold for the Windows host disk (12 GiB free). Below\r\n * this, large rebuilds have been observed to trigger SIGBUS / exit 135 on\r\n * WSL when the VHDX cannot grow further. */\r\nexport const DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;\r\n/** Default Windows host mount point under WSL. Overridable via env or option. */\r\nexport const DEFAULT_WSL_HOST_MOUNT = \"/mnt/c\";\r\n\r\nexport interface WslHostDiskShape {\r\n ok: boolean;\r\n /** Mount point that was probed. */\r\n path: string;\r\n freeBytes: number;\r\n totalBytes: number;\r\n usedPercent: number;\r\n warnBelowBytes: number;\r\n criticalBelowBytes: number;\r\n /** Human-readable explanation when the host disk is under pressure. */\r\n reason: string | null;\r\n /** True when the probe itself failed (mount missing, statfs error). */\r\n probeError: string | null;\r\n}\r\n\r\n/**\r\n * Cheap WSL detection. Reads `/proc/sys/kernel/osrelease` (kernel string\r\n * contains \"microsoft\" / \"WSL2\" on Microsoft's WSL kernel) and falls back\r\n * to `/proc/version`. Both reads are one-shot and gated by `existsSync`,\r\n * so non-Linux hosts return false without throwing.\r\n */\r\nexport function isWslHost(): boolean {\r\n if (process.platform !== \"linux\") return false;\r\n for (const probe of [\"/proc/sys/kernel/osrelease\", \"/proc/version\"]) {\r\n try {\r\n if (!existsSync(probe)) continue;\r\n const text = readFileSync(probe, \"utf8\");\r\n if (/microsoft|wsl/i.test(text)) return true;\r\n } catch {\r\n // ignore \u2014 try the next probe path\r\n }\r\n }\r\n return false;\r\n}\r\n\r\nexport interface ObserveWslHostDiskOptions {\r\n /** Override the Windows host mount path (e.g. `/mnt/d`). Falls back to\r\n * `KYNVER_WSL_HOST_MOUNT` env, then `/mnt/c`. */\r\n wslHostMount?: string;\r\n wslHostFreeWarnBytes?: number;\r\n wslHostFreeCriticalBytes?: number;\r\n /** Override WSL detection for tests / cross-platform CI:\r\n * `true` \u2192 treat host as WSL; `false` \u2192 treat host as non-WSL;\r\n * `undefined` \u2192 autodetect via `isWslHost()`. */\r\n forceWsl?: boolean;\r\n /** Test seam \u2014 swap in a fake statfs. */\r\n statfs?: (path: string) => { bavail: bigint | number; blocks: bigint | number; bsize: bigint | number };\r\n}\r\n\r\n/**\r\n * Probe the Windows host disk under WSL. Returns `null` when this host is\r\n * not WSL (so callers can treat the field as optional in the gate output).\r\n * When WSL but the mount is unreachable, returns a `probeError` row with\r\n * `ok = false` rather than throwing \u2014 the gate must still block dispatch\r\n * because we can't prove the host disk is healthy.\r\n */\r\nexport function observeWslHostDisk(\r\n options: ObserveWslHostDiskOptions = {},\r\n): WslHostDiskShape | null {\r\n const wsl = options.forceWsl === undefined ? isWslHost() : options.forceWsl;\r\n if (!wsl) return null;\r\n\r\n const path =\r\n options.wslHostMount?.trim() ||\r\n process.env.KYNVER_WSL_HOST_MOUNT?.trim() ||\r\n DEFAULT_WSL_HOST_MOUNT;\r\n const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;\r\n const criticalBelowBytes =\r\n options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;\r\n\r\n const statfs = options.statfs ?? statfsSync;\r\n let stats: { bavail: bigint | number; blocks: bigint | number; bsize: bigint | number };\r\n try {\r\n stats = statfs(path);\r\n } catch (error) {\r\n return {\r\n ok: false,\r\n path,\r\n freeBytes: 0,\r\n totalBytes: 0,\r\n usedPercent: 100,\r\n warnBelowBytes,\r\n criticalBelowBytes,\r\n reason: `Windows host disk probe failed at ${path}: ${(error as Error).message}`,\r\n probeError: (error as Error).message,\r\n };\r\n }\r\n\r\n const freeBytes = Number(stats.bavail) * Number(stats.bsize);\r\n const totalBytes = Number(stats.blocks) * Number(stats.bsize);\r\n const usedPercent = totalBytes > 0 ? ((totalBytes - freeBytes) / totalBytes) * 100 : 100;\r\n const lowFree = freeBytes < warnBelowBytes;\r\n const criticalFree = freeBytes < criticalBelowBytes;\r\n const ok = !lowFree && !criticalFree;\r\n\r\n const freeGiB = (freeBytes / (1024 * 1024 * 1024)).toFixed(1);\r\n let reason: string | null = null;\r\n if (!ok) {\r\n const tag = criticalFree ? \"critical\" : \"warning\";\r\n reason =\r\n `Windows host disk ${path} at ${tag}: ${freeGiB} GiB free ` +\r\n `(<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); ` +\r\n `WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;\r\n }\r\n\r\n return {\r\n ok,\r\n path,\r\n freeBytes,\r\n totalBytes,\r\n usedPercent,\r\n warnBelowBytes,\r\n criticalBelowBytes,\r\n reason,\r\n probeError: null,\r\n };\r\n}\r\n\r\n/**\r\n * Short operator recovery hint embedded in gate reasons so AgentOS evidence\r\n * surfaces actionable steps without a doc lookup. Keep terse \u2014 full runbook\r\n * lives at `docs/runbooks/wsl-disk-pressure.md`.\r\n */\r\nexport function summarizeWslRecoverySteps(): string {\r\n return (\r\n \"Recovery: \" +\r\n \"1) free Windows C: (empty Recycle Bin / Storage Sense / clear %TEMP%); \" +\r\n \"2) shut down WSL (`wsl --shutdown`) then compact the VHDX (`Optimize-VHD` or `diskpart compact vdisk`); \" +\r\n \"3) clear local node_modules / .next / harness worktrees before restarting workers. \" +\r\n \"Full runbook: docs/runbooks/wsl-disk-pressure.md.\"\r\n );\r\n}\r\n", "import { statfsSync } from \"node:fs\";\r\nimport type { DispatchNextDiskGateShape } from \"./callbacks.js\";\r\nimport {\r\n observeWslHostDisk,\r\n type ObserveWslHostDiskOptions,\r\n type WslHostDiskShape,\r\n} from \"./wsl-host.js\";\r\n\r\nconst DEFAULT_WARN_FREE_BYTES = 30 * 1024 * 1024 * 1024;\r\nconst DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;\r\nconst DEFAULT_MAX_USED_PERCENT = 80;\r\nconst DEFAULT_HARD_MAX_USED_PERCENT = 90;\r\n\r\nexport interface ObserveDiskGateInput {\r\n diskPath?: string;\r\n diskFreeWarnBytes?: number;\r\n diskFreeCriticalBytes?: number;\r\n diskMaxUsedPercent?: number;\r\n diskHardMaxUsedPercent?: number;\r\n /** Opt-out for the WSL host disk probe \u2014 leave `false`/undefined by\r\n * default so we always check `/mnt/c` under WSL. */\r\n skipWslHostCheck?: boolean;\r\n wslHost?: ObserveWslHostDiskOptions;\r\n}\r\n\r\nexport function observeRunnerDiskGate(input: ObserveDiskGateInput = {}): DispatchNextDiskGateShape {\r\n const path = input.diskPath?.trim() || \"/\";\r\n const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;\r\n const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;\r\n const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;\r\n const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;\r\n\r\n const stats = statfsSync(path);\r\n const freeBytes = Number(stats.bavail) * Number(stats.bsize);\r\n const totalBytes = Number(stats.blocks) * Number(stats.bsize);\r\n const usedPercent = totalBytes > 0 ? ((totalBytes - freeBytes) / totalBytes) * 100 : 100;\r\n const lowFree = freeBytes < warnBelowBytes;\r\n const criticalFree = freeBytes < criticalBelowBytes;\r\n const highUse = usedPercent > maxUsedPercent;\r\n const hardHighUse = usedPercent > hardMaxUsedPercent;\r\n const localOk = !lowFree && !criticalFree && !highUse && !hardHighUse;\r\n\r\n // WSL guard: the VHDX (mounted at `/`) grows into Windows C:; a healthy\r\n // local statfs is meaningless if the host disk is about to refuse writes.\r\n const wslHost: WslHostDiskShape | null = input.skipWslHostCheck\r\n ? null\r\n : observeWslHostDisk(input.wslHost);\r\n\r\n const ok = localOk && (wslHost ? wslHost.ok : true);\r\n\r\n let reason: string | null = null;\r\n if (!ok) {\r\n reason = [\r\n criticalFree ? `free space below critical ${criticalBelowBytes} bytes` : null,\r\n lowFree ? `free space below warning ${warnBelowBytes} bytes` : null,\r\n hardHighUse ? `used percent above hard cap ${hardMaxUsedPercent}%` : null,\r\n highUse ? `used percent above cap ${maxUsedPercent}%` : null,\r\n wslHost && !wslHost.ok ? wslHost.reason : null,\r\n ]\r\n .filter(Boolean)\r\n .join(\"; \");\r\n }\r\n\r\n return {\r\n ok,\r\n path,\r\n freeBytes,\r\n totalBytes,\r\n usedPercent,\r\n warnBelowBytes,\r\n criticalBelowBytes,\r\n maxUsedPercent,\r\n hardMaxUsedPercent,\r\n reason,\r\n wslHost,\r\n };\r\n}\r\n", "import { existsSync } from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { loadUserConfig } from \"./config.js\";\r\nimport { resolveUserPath } from \"./path-values.js\";\r\nimport { safeSlug } from \"./util.js\";\r\n\r\nconst LEGACY_ROOT = path.join(homedir(), \".openclaw\", \"harness\");\r\n\r\nconst HARNESS_LAYOUT_DIR_NAMES = new Set([\"runs\", \"worktrees\"]);\r\n\r\n/**\r\n * Canonical harness root: the directory that contains `runs/` and `worktrees/`.\r\n * Strips mistaken trailing layout segments (e.g. env set to `.../harness/runs`).\r\n * Server mirror: agent-os.harness-root.ts\r\n */\r\nexport function normalizeHarnessRoot(root: string): string {\r\n let resolved = path.resolve(resolveUserPath(root.trim()));\r\n while (HARNESS_LAYOUT_DIR_NAMES.has(path.basename(resolved))) {\r\n resolved = path.dirname(resolved);\r\n }\r\n return resolved;\r\n}\r\n\r\n/** Canonical harness root for CLI/workers. */\r\nexport function resolveHarnessRoot(): string {\r\n const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;\r\n if (env) return normalizeHarnessRoot(env);\r\n const configured = loadUserConfig().harnessRoot?.trim();\r\n if (configured) return normalizeHarnessRoot(configured);\r\n const kynverRoot = path.join(homedir(), \".kynver\", \"harness\");\r\n if (existsSync(kynverRoot)) return kynverRoot;\r\n if (existsSync(LEGACY_ROOT)) return LEGACY_ROOT;\r\n return kynverRoot;\r\n}\r\n\r\nexport function harnessRunsDir(harnessRoot: string): string {\r\n return path.join(normalizeHarnessRoot(harnessRoot), \"runs\");\r\n}\r\n\r\nexport function harnessWorktreesDir(harnessRoot: string): string {\r\n return path.join(normalizeHarnessRoot(harnessRoot), \"worktrees\");\r\n}\r\n\r\nexport function getHarnessPaths() {\r\n const harnessRoot = resolveHarnessRoot();\r\n return {\r\n harnessRoot,\r\n runsDir: harnessRunsDir(harnessRoot),\r\n worktreesDir: harnessWorktreesDir(harnessRoot),\r\n };\r\n}\r\n\r\nexport function runDir(runsDir: string, id: string): string {\r\n return path.join(runsDir, safeSlug(id));\r\n}\r\n", "import os from \"node:os\";\r\nimport { readMemAvailableBytes } from \"./bounded-build/meminfo.js\";\r\nimport path from \"node:path\";\r\nimport { loadUserConfig, type KynverUserConfig } from \"./config.js\";\r\nimport { resolveBoxKindFromConfig } from \"./box-identity.js\";\r\nimport type { WorkerCapSource } from \"./worker-cap-source.js\";\r\nimport { resolveWorkerCap } from \"./worker-cap-source.js\";\r\nimport type { DispatchNextDiskGateShape } from \"./callbacks.js\";\r\nimport { observeRunnerDiskGate } from \"./disk-gate.js\";\r\nimport { listRunRecords, loadRun, runDirectory, type HarnessRunRecord } from \"./run-store.js\";\r\nimport { listRunWorkerNames } from \"./run-worker-index.js\";\r\nimport { isActiveHarnessWorker, workerProcessMatchesRecord } from \"./harness-worker-active.js\";\r\nimport { readJson, safeSlug } from \"./util.js\";\r\nimport type { HarnessWorkerRecord } from \"./status.js\";\r\n\r\nexport { workerProcessMatchesRecord };\r\n\r\n/** Default RAM budget per worker (~500 MiB, dogfood measured). Internal \u2014 not a setup knob. */\r\nexport const DEFAULT_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;\r\n\r\n/** Keep headroom for OS / IDE. Internal \u2014 not a setup knob. */\r\nexport const DEFAULT_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;\r\n\r\n/** Fraction of total RAM used when auto-sizing worker cap. Internal. */\r\nexport const DEFAULT_MEM_UTILIZATION = 0.85;\r\n\r\n/** Auto cap when the user has not set maxConcurrentWorkers (safety on huge hosts). */\r\nexport const AUTO_MAX_WORKERS_CEILING = 64;\r\n\r\nexport interface RunnerResourceGateShape {\r\n ok: boolean;\r\n totalMemBytes: number;\r\n freeMemBytes: number;\r\n memReserveBytes: number;\r\n perWorkerMemBytes: number;\r\n configuredMaxWorkers: number | null;\r\n /** Where the effective worker cap was resolved (workspace override, env, config, or auto). */\r\n workerCapSource: WorkerCapSource;\r\n /** Physical pool for this host (`forge` | `ghost`). */\r\n boxKind: string;\r\n autoCap: number;\r\n capacityWorkers: number;\r\n maxConcurrentWorkers: number;\r\n activeWorkers: number;\r\n slotsAvailable: number;\r\n reason: string | null;\r\n /** Present unless `KYNVER_RESOURCE_GATE_SKIP_DISK=1`. */\r\n diskGate?: DispatchNextDiskGateShape;\r\n}\r\n\r\nexport interface ObserveResourceGateInput {\r\n runId: string;\r\n config?: KynverUserConfig;\r\n /** Command Center / workspace override \u2014 wins over local config when set. */\r\n configuredMaxWorkersOverride?: number | null;\r\n /** Override active worker count (tests). */\r\n activeWorkers?: number;\r\n totalMemBytes?: number;\r\n freeMemBytes?: number;\r\n diskPath?: string;\r\n skipDiskGate?: boolean;\r\n}\r\n\r\nfunction positiveInt(value: unknown, fallback: number): number {\r\n const n = Number(value);\r\n if (!Number.isFinite(n) || n <= 0) return fallback;\r\n return Math.floor(n);\r\n}\r\n\r\nfunction resolveResourceConfig(\r\n config: KynverUserConfig = loadUserConfig(),\r\n configuredMaxWorkersOverride?: number | null,\r\n totalMemBytes?: number,\r\n) {\r\n const perWorkerMemBytes = positiveInt(config.perWorkerMemBytes, DEFAULT_PER_WORKER_MEM_BYTES);\r\n const memReserveBytes = positiveInt(config.memReserveBytes, DEFAULT_MEM_RESERVE_BYTES);\r\n const memUtilization = Math.min(\r\n 1,\r\n Math.max(0.1, Number(config.memUtilization) > 0 ? Number(config.memUtilization) : DEFAULT_MEM_UTILIZATION),\r\n );\r\n const cap = resolveWorkerCap({\r\n config,\r\n configuredMaxWorkersOverride,\r\n totalMemBytes: totalMemBytes ?? os.totalmem(),\r\n });\r\n return {\r\n perWorkerMemBytes,\r\n memReserveBytes,\r\n memUtilization,\r\n configuredMaxWorkers: cap.configuredMaxWorkers,\r\n autoCap: cap.autoCap,\r\n workerCapSource: cap.workerCapSource,\r\n };\r\n}\r\n\r\n/** How many workers this host could run from RAM alone (before a user cap). */\r\nexport function computeAutoMaxWorkers(\r\n totalMemBytes: number,\r\n opts: { perWorkerMemBytes?: number; memReserveBytes?: number; memUtilization?: number } = {},\r\n): number {\r\n const perWorkerMemBytes = opts.perWorkerMemBytes ?? DEFAULT_PER_WORKER_MEM_BYTES;\r\n const memReserveBytes = opts.memReserveBytes ?? DEFAULT_MEM_RESERVE_BYTES;\r\n const memUtilization = opts.memUtilization ?? DEFAULT_MEM_UTILIZATION;\r\n const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);\r\n const raw = Math.max(1, Math.floor(budgetBytes / perWorkerMemBytes));\r\n return Math.min(raw, AUTO_MAX_WORKERS_CEILING);\r\n}\r\n\r\nfunction readAvailableMemBytes(): number {\r\n return readMemAvailableBytes();\r\n}\r\n\r\n/** Count alive, still-executing workers in a single run record. */\r\nfunction countActiveWorkersForRun(run: HarnessRunRecord): number {\r\n let active = 0;\r\n for (const name of listRunWorkerNames(run)) {\r\n const worker = readJson<HarnessWorkerRecord | undefined>(\r\n path.join(runDirectory(run.id), \"workers\", safeSlug(name), \"worker.json\"),\r\n undefined,\r\n );\r\n if (!worker || !isActiveHarnessWorker(worker)) continue;\r\n active++;\r\n }\r\n return active;\r\n}\r\n\r\n/** Count active workers in ONE run (kept for callers/tests scoped to a run). */\r\nexport function countActiveWorkers(runId: string): number {\r\n return countActiveWorkersForRun(loadRun(runId));\r\n}\r\n\r\n/**\r\n * Count active workers across EVERY run on disk. The harness creates a new run\r\n * per task, so a per-run count let concurrent runs each believe the machine was\r\n * idle \u2014 the configured cap was never a real global ceiling (the \"spawns 4 or\r\n * infinity, never N\" bug). This machine-wide count is what the gate must use.\r\n */\r\nexport function countActiveWorkersGlobal(): number {\r\n let active = 0;\r\n for (const run of listRunRecords()) active += countActiveWorkersForRun(run);\r\n return active;\r\n}\r\n\r\n/**\r\n * Compute how many workers this host can run and how many dispatch slots remain.\r\n * Uses total RAM for steady-state capacity and free RAM as a hard safety gate.\r\n */\r\nexport function observeRunnerResourceGate(input: ObserveResourceGateInput): RunnerResourceGateShape {\r\n const config = input.config ?? loadUserConfig();\r\n const totalMemBytes = input.totalMemBytes ?? os.totalmem();\r\n const { perWorkerMemBytes, memReserveBytes, memUtilization, configuredMaxWorkers, autoCap: resolvedAutoCap, workerCapSource } =\r\n resolveResourceConfig(config, input.configuredMaxWorkersOverride, totalMemBytes);\r\n const boxKind = resolveBoxKindFromConfig(config);\r\n const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();\r\n // Active count is GLOBAL across all runs (see countActiveWorkersGlobal), so the\r\n // cap is a true machine-wide ceiling rather than per-run.\r\n const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();\r\n\r\n const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);\r\n const capacityFromTotal = Math.max(0, Math.floor(budgetBytes / perWorkerMemBytes));\r\n const capacityFromFree = Math.max(0, Math.floor(Math.max(0, freeMemBytes - memReserveBytes) / perWorkerMemBytes));\r\n\r\n const autoCap = resolvedAutoCap;\r\n const targetCap = configuredMaxWorkers ?? autoCap;\r\n const maxConcurrentWorkers = Math.max(0, Math.min(targetCap, capacityFromTotal));\r\n const slotsByCapacity = Math.max(0, maxConcurrentWorkers - activeWorkers);\r\n // capacityFromFree is ADDITIONAL headroom: free/available RAM already excludes\r\n // memory held by running workers, so we must NOT subtract activeWorkers again.\r\n // Doing so (the old `capacityFromFree - activeWorkers`) double-counted active\r\n // workers and collapsed dispatch to a handful of slots under load.\r\n const slotsByFreeMem = capacityFromFree;\r\n let slotsAvailable = Math.min(slotsByCapacity, slotsByFreeMem);\r\n\r\n const skipDisk = input.skipDiskGate || process.env.KYNVER_RESOURCE_GATE_SKIP_DISK === \"1\";\r\n const diskGate = skipDisk\r\n ? undefined\r\n : observeRunnerDiskGate({\r\n diskPath:\r\n input.diskPath?.trim() ||\r\n process.env.KYNVER_DISK_GUARD_PATH?.trim() ||\r\n \"/\",\r\n });\r\n if (diskGate && !diskGate.ok) slotsAvailable = 0;\r\n\r\n let reason: string | null = null;\r\n if (slotsAvailable <= 0) {\r\n if (diskGate && !diskGate.ok) {\r\n reason = diskGate.reason ?? \"disk gate blocked worker admission\";\r\n } else if (activeWorkers >= maxConcurrentWorkers) {\r\n reason = `at worker limit (${activeWorkers}/${maxConcurrentWorkers} running)`;\r\n } else if (capacityFromFree <= 0) {\r\n reason = \"insufficient free memory \u2014 waiting for workers to finish\";\r\n } else {\r\n reason = \"no worker slots available\";\r\n }\r\n }\r\n\r\n return {\r\n ok: slotsAvailable > 0,\r\n totalMemBytes,\r\n freeMemBytes,\r\n memReserveBytes,\r\n perWorkerMemBytes,\r\n configuredMaxWorkers,\r\n workerCapSource,\r\n boxKind,\r\n autoCap,\r\n capacityWorkers: capacityFromTotal,\r\n maxConcurrentWorkers,\r\n activeWorkers,\r\n slotsAvailable,\r\n reason,\r\n ...(diskGate ? { diskGate } : {}),\r\n };\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": ["path", "existsSync", "readFileSync", "homedir", "path", "homedir", "path", "path", "homedir", "homedir", "path", "path", "homedir", "path", "homedir", "existsSync", "readFileSync", "path"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/installed-package-versions.ts", "../../src/memory-cost-package-version-guard.ts", "../../src/default-repo-discovery.ts", "../../src/git.ts", "../../src/worker-env.ts", "../../src/path-values.ts", "../../src/package-version.ts", "../../src/memory-cost-package-version-guard-enforce.ts"],
4
- "sourcesContent": ["import { readFile } from \"node:fs/promises\";\r\nimport { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { compareSemver } from \"./memory-cost-package-version-guard.js\";\r\n\r\nconst MANAGED_PACKAGES = [\r\n \"@kynver-app/runtime\",\r\n \"@kynver-app/openclaw-agent-os\",\r\n \"@kynver-app/mcp-agent-os\",\r\n] as const;\r\n\r\nexport type InstalledPackageVersionMap = Record<\r\n string,\r\n {\r\n version: string;\r\n observedAt: string;\r\n path: string;\r\n }\r\n>;\r\n\r\nfunction trim(value: string | undefined): string | null {\r\n const out = value?.trim();\r\n return out ? out : null;\r\n}\r\n\r\nfunction unique(values: Array<string | null | undefined>): string[] {\r\n return [...new Set(values.filter((value): value is string => Boolean(value)))];\r\n}\r\n\r\nfunction moduleRoots(): string[] {\r\n const home = homedir();\r\n const openClawPrefix =\r\n trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ??\r\n trim(process.env.OPENCLAW_NPM_ROOT) ??\r\n path.join(home, \".openclaw\", \"npm\");\r\n const npmGlobalRoot =\r\n trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ??\r\n trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ??\r\n (trim(process.env.NPM_CONFIG_PREFIX)\r\n ? path.join(trim(process.env.NPM_CONFIG_PREFIX)!, \"lib\", \"node_modules\")\r\n : path.join(home, \".npm-global\", \"lib\", \"node_modules\"));\r\n\r\n return unique([\r\n path.join(openClawPrefix, \"lib\", \"node_modules\"),\r\n path.join(openClawPrefix, \"node_modules\"),\r\n npmGlobalRoot.endsWith(\"node_modules\") ? npmGlobalRoot : path.join(npmGlobalRoot, \"lib\", \"node_modules\"),\r\n ]);\r\n}\r\n\r\nasync function readVersion(packageJsonPath: string): Promise<string | null> {\r\n try {\r\n const parsed = JSON.parse(await readFile(packageJsonPath, \"utf8\")) as { version?: unknown };\r\n return typeof parsed.version === \"string\" && parsed.version.trim() ? parsed.version.trim() : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/** Candidate install paths for one managed package across OpenClaw + global npm roots. */\r\nexport function installedPackageJsonCandidates(packageName: string): string[] {\r\n const roots = moduleRoots();\r\n const seen = new Set<string>();\r\n const out: string[] = [];\r\n for (const root of roots) {\r\n const candidate = path.join(root, packageName, \"package.json\");\r\n if (seen.has(candidate)) continue;\r\n seen.add(candidate);\r\n out.push(candidate);\r\n }\r\n return out;\r\n}\r\n\r\nexport async function collectInstalledPackageVersions(\r\n observedAt: string = new Date().toISOString(),\r\n): Promise<InstalledPackageVersionMap> {\r\n const out: InstalledPackageVersionMap = {};\r\n for (const packageName of MANAGED_PACKAGES) {\r\n let best: { version: string; path: string } | null = null;\r\n for (const packageJsonPath of installedPackageJsonCandidates(packageName)) {\r\n const version = await readVersion(packageJsonPath);\r\n if (!version) continue;\r\n if (!best || compareSemver(version, best.version) > 0) {\r\n best = { version, path: packageJsonPath };\r\n }\r\n }\r\n if (best) {\r\n out[packageName] = { version: best.version, observedAt, path: best.path };\r\n }\r\n }\r\n return out;\r\n}\r\n", "import { existsSync, readFileSync } from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { discoverDefaultRepo } from \"./default-repo-discovery.js\";\r\n\r\n/** Minimum versions that ship MARM memory cost controls (PR #1159). */\r\nexport const MEMORY_COST_PACKAGE_MIN_VERSIONS = {\r\n \"@kynver-app/runtime\": \"0.1.83\",\r\n \"@kynver-app/openclaw-agent-os\": \"0.1.43\",\r\n \"@kynver-app/mcp-agent-os\": \"0.3.34\",\r\n} as const;\r\n\r\nexport type MemoryCostManagedPackageName = keyof typeof MEMORY_COST_PACKAGE_MIN_VERSIONS;\r\n\r\nexport const MEMORY_COST_MANAGED_PACKAGES = Object.keys(\r\n MEMORY_COST_PACKAGE_MIN_VERSIONS,\r\n) as MemoryCostManagedPackageName[];\r\n\r\nexport type MemoryCostVersionSource = \"self\" | \"installed\" | \"repo\" | \"unknown\";\r\n\r\nexport interface MemoryCostPackageVersionCandidate {\r\n version: string;\r\n source: MemoryCostVersionSource;\r\n path?: string;\r\n}\r\n\r\nexport interface MemoryCostPackageGuardRow {\r\n packageName: MemoryCostManagedPackageName;\r\n displayName: string;\r\n minimumVersion: string;\r\n effectiveVersion: string | null;\r\n effectiveSource: MemoryCostVersionSource;\r\n ok: boolean;\r\n summary: string;\r\n remediation: string[];\r\n}\r\n\r\nexport interface MemoryCostPackageGuardResult {\r\n ok: boolean;\r\n summary: string;\r\n packages: MemoryCostPackageGuardRow[];\r\n}\r\n\r\nconst DISPLAY_NAMES: Record<MemoryCostManagedPackageName, string> = {\r\n \"@kynver-app/runtime\": \"Kynver runtime\",\r\n \"@kynver-app/openclaw-agent-os\": \"OpenClaw AgentOS plugin\",\r\n \"@kynver-app/mcp-agent-os\": \"AgentOS MCP server\",\r\n};\r\n\r\nconst REPO_PACKAGE_JSON_RELATIVE: Record<MemoryCostManagedPackageName, string> = {\r\n \"@kynver-app/runtime\": \"packages/kynver-runtime/package.json\",\r\n \"@kynver-app/openclaw-agent-os\": \"packages/kynver-openclaw-agent-os/package.json\",\r\n \"@kynver-app/mcp-agent-os\": \"packages/kynver-mcp-agent-os/package.json\",\r\n};\r\n\r\n/** Parse `major.minor.patch`; pre-release/build suffixes are ignored. */\r\nexport function parseSemverParts(version: string): [number, number, number] | null {\r\n const core = version.trim().split(\"-\")[0]?.split(\"+\")[0];\r\n if (!core) return null;\r\n const parts = core.split(\".\");\r\n if (parts.length < 1 || parts.length > 3) return null;\r\n const nums = parts.map((p) => Number.parseInt(p, 10));\r\n if (nums.some((n) => !Number.isFinite(n) || n < 0)) return null;\r\n while (nums.length < 3) nums.push(0);\r\n return [nums[0]!, nums[1]!, nums[2]!];\r\n}\r\n\r\n/** Returns 1 if a > b, -1 if a < b, 0 if equal or either unparsable. */\r\nexport function compareSemver(a: string, b: string): number {\r\n const pa = parseSemverParts(a);\r\n const pb = parseSemverParts(b);\r\n if (!pa || !pb) return 0;\r\n for (let i = 0; i < 3; i += 1) {\r\n if (pa[i]! > pb[i]!) return 1;\r\n if (pa[i]! < pb[i]!) return -1;\r\n }\r\n return 0;\r\n}\r\n\r\nexport function semverAtLeast(version: string, minimum: string): boolean {\r\n return compareSemver(version, minimum) >= 0;\r\n}\r\n\r\nfunction maxSemver(versions: readonly string[]): string | null {\r\n let best: string | null = null;\r\n for (const version of versions) {\r\n if (!best || compareSemver(version, best) > 0) best = version;\r\n }\r\n return best;\r\n}\r\n\r\nfunction readPackageJsonVersion(packageJsonPath: string): string | null {\r\n try {\r\n const parsed = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as { version?: unknown };\r\n return typeof parsed.version === \"string\" && parsed.version.trim() ? parsed.version.trim() : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nfunction resolveRepoRoot(cwd: string, explicitRepoRoot?: string): string | null {\r\n const candidates = [explicitRepoRoot, process.env.KYNVER_REPO, cwd].filter(\r\n (value): value is string => Boolean(value?.trim()),\r\n );\r\n for (const candidate of candidates) {\r\n const resolved = path.resolve(candidate);\r\n if (\r\n existsSync(path.join(resolved, \"packages/kynver-runtime/package.json\")) &&\r\n existsSync(path.join(resolved, \"package.json\"))\r\n ) {\r\n return resolved;\r\n }\r\n }\r\n const discovered = discoverDefaultRepo({ cwd });\r\n return discovered?.repo ?? null;\r\n}\r\n\r\nexport function probeRepoPackageVersions(input: {\r\n cwd?: string;\r\n repoRoot?: string;\r\n} = {}): Partial<Record<MemoryCostManagedPackageName, MemoryCostPackageVersionCandidate>> {\r\n const cwd = input.cwd ?? process.cwd();\r\n const repoRoot = resolveRepoRoot(cwd, input.repoRoot);\r\n if (!repoRoot) return {};\r\n\r\n const out: Partial<Record<MemoryCostManagedPackageName, MemoryCostPackageVersionCandidate>> = {};\r\n for (const packageName of MEMORY_COST_MANAGED_PACKAGES) {\r\n const packageJsonPath = path.join(repoRoot, REPO_PACKAGE_JSON_RELATIVE[packageName]);\r\n const version = readPackageJsonVersion(packageJsonPath);\r\n if (!version) continue;\r\n out[packageName] = { version, source: \"repo\", path: packageJsonPath };\r\n }\r\n return out;\r\n}\r\n\r\nfunction repoSourceCommands(packageName: MemoryCostManagedPackageName): string[] {\r\n if (packageName === \"@kynver-app/runtime\") {\r\n return [\"npm run kynver:build\", \"npm run kynver\"];\r\n }\r\n return [`npm run build -w ${packageName}`];\r\n}\r\n\r\nfunction buildRemediation(input: {\r\n packageName: MemoryCostManagedPackageName;\r\n minimumVersion: string;\r\n effectiveVersion: string | null;\r\n effectiveSource: MemoryCostVersionSource;\r\n repoVersion: string | null;\r\n}): string[] {\r\n const { packageName, minimumVersion, effectiveVersion, effectiveSource, repoVersion } = input;\r\n const lines: string[] = [];\r\n if (repoVersion && semverAtLeast(repoVersion, minimumVersion)) {\r\n lines.push(\r\n `Use the monorepo checkout (${repoVersion}) instead of a stale npm install: ${repoSourceCommands(packageName).join(\"; \")}.`,\r\n );\r\n lines.push(\"Do not publish npm packages or wait on an operator release.\");\r\n return lines;\r\n }\r\n if (effectiveVersion) {\r\n lines.push(\r\n `Upgrade ${packageName} from ${effectiveVersion} to >= ${minimumVersion} (npm install -g ${packageName}@latest or align OpenClaw npm prefix).`,\r\n );\r\n } else {\r\n lines.push(`Install ${packageName} >= ${minimumVersion} before running memory-heavy AgentOS paths.`);\r\n }\r\n if (packageName === \"@kynver-app/runtime\") {\r\n lines.push(\"Repo-source alternative: npm run kynver:build && npm run kynver\");\r\n }\r\n if (effectiveSource === \"installed\" && repoVersion) {\r\n lines.push(`Repo checkout reports ${repoVersion}; rebuild/link repo source if you develop from the monorepo.`);\r\n }\r\n return lines;\r\n}\r\n\r\nfunction pickEffectiveCandidate(\r\n candidates: MemoryCostPackageVersionCandidate[],\r\n): { version: string | null; source: MemoryCostVersionSource } {\r\n if (candidates.length === 0) return { version: null, source: \"unknown\" };\r\n const best = maxSemver(candidates.map((candidate) => candidate.version));\r\n if (!best) return { version: null, source: \"unknown\" };\r\n const winner = candidates.find((candidate) => candidate.version === best) ?? candidates[0]!;\r\n return { version: winner.version, source: winner.source };\r\n}\r\n\r\nexport function evaluateMemoryCostPackageVersionGuard(input: {\r\n installed?: Partial<Record<MemoryCostManagedPackageName, string | MemoryCostPackageVersionCandidate>>;\r\n repo?: Partial<Record<MemoryCostManagedPackageName, string | MemoryCostPackageVersionCandidate>>;\r\n self?: Partial<Record<MemoryCostManagedPackageName, string | MemoryCostPackageVersionCandidate>>;\r\n} = {}): MemoryCostPackageGuardResult {\r\n const normalize = (\r\n value: string | MemoryCostPackageVersionCandidate | undefined,\r\n fallbackSource: MemoryCostVersionSource,\r\n ): MemoryCostPackageVersionCandidate | null => {\r\n if (!value) return null;\r\n if (typeof value === \"string\") return { version: value, source: fallbackSource };\r\n return value;\r\n };\r\n\r\n const packages = MEMORY_COST_MANAGED_PACKAGES.map((packageName): MemoryCostPackageGuardRow => {\r\n const minimumVersion = MEMORY_COST_PACKAGE_MIN_VERSIONS[packageName];\r\n const candidates: MemoryCostPackageVersionCandidate[] = [];\r\n const installed = normalize(input.installed?.[packageName], \"installed\");\r\n const repo = normalize(input.repo?.[packageName], \"repo\");\r\n const self = normalize(input.self?.[packageName], \"self\");\r\n if (installed) candidates.push(installed);\r\n if (repo) candidates.push(repo);\r\n if (self) candidates.push(self);\r\n\r\n const { version: effectiveVersion, source: effectiveSource } = pickEffectiveCandidate(candidates);\r\n const repoVersion = repo?.version ?? null;\r\n const ok = effectiveVersion ? semverAtLeast(effectiveVersion, minimumVersion) : false;\r\n const remediation = ok\r\n ? []\r\n : buildRemediation({\r\n packageName,\r\n minimumVersion,\r\n effectiveVersion,\r\n effectiveSource,\r\n repoVersion,\r\n });\r\n\r\n const summary = ok\r\n ? `${DISPLAY_NAMES[packageName]} ${effectiveVersion} meets memory-cost minimum ${minimumVersion} (${effectiveSource}).`\r\n : `${DISPLAY_NAMES[packageName]} is below memory-cost minimum ${minimumVersion}` +\r\n (effectiveVersion ? ` (effective ${effectiveVersion} via ${effectiveSource})` : \" (no version detected)\") +\r\n \".\";\r\n\r\n return {\r\n packageName,\r\n displayName: DISPLAY_NAMES[packageName],\r\n minimumVersion,\r\n effectiveVersion,\r\n effectiveSource,\r\n ok,\r\n summary,\r\n remediation,\r\n };\r\n });\r\n\r\n const violations = packages.filter((row) => !row.ok);\r\n const ok = violations.length === 0;\r\n const summary = ok\r\n ? \"All managed AgentOS packages meet memory-cost minimum versions.\"\r\n : `Memory-cost package guard blocked ${violations.length} stale package(s): ${violations\r\n .map((row) => `${row.packageName} < ${row.minimumVersion}`)\r\n .join(\"; \")}.`;\r\n\r\n return { ok, summary, packages };\r\n}\r\n\r\nexport class MemoryCostPackageVersionGuardError extends Error {\r\n readonly result: MemoryCostPackageGuardResult;\r\n\r\n constructor(result: MemoryCostPackageGuardResult) {\r\n const lines = [\r\n result.summary,\r\n ...result.packages\r\n .filter((row) => !row.ok)\r\n .flatMap((row) => [`- ${row.summary}`, ...row.remediation.map((line) => ` \u2192 ${line}`)]),\r\n ];\r\n super(lines.join(\"\\n\"));\r\n this.name = \"MemoryCostPackageVersionGuardError\";\r\n this.result = result;\r\n }\r\n}\r\n\r\nexport function assertMemoryCostPackageVersionGuard(\r\n input: Parameters<typeof evaluateMemoryCostPackageVersionGuard>[0] = {},\r\n): MemoryCostPackageGuardResult {\r\n const result = evaluateMemoryCostPackageVersionGuard(input);\r\n if (!result.ok) throw new MemoryCostPackageVersionGuardError(result);\r\n return result;\r\n}\r\n\r\nexport function formatMemoryCostPackageGuardError(result: MemoryCostPackageGuardResult): string {\r\n return new MemoryCostPackageVersionGuardError(result).message;\r\n}\r\n", "import { existsSync, readFileSync } from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport { gitCapture } from \"./git.js\";\r\nimport { resolveUserPath } from \"./path-values.js\";\r\n\r\nexport type DefaultRepoDiscoverySource =\r\n | \"cwd_git\"\r\n | \"runtime_checkout\"\r\n | \"well_known_path\";\r\n\r\nexport interface DiscoveredDefaultRepo {\r\n repo: string;\r\n source: DefaultRepoDiscoverySource;\r\n}\r\n\r\nconst WELL_KNOWN_REPO_DIRS = [\r\n \"Kynver\",\r\n \"repos/Kynver\",\r\n \"repos/kynver-source-main\",\r\n \"code/Kynver\",\r\n \"projects/Kynver\",\r\n] as const;\r\n\r\nfunction readPackageName(repoRoot: string): string | null {\r\n const pkgPath = path.join(repoRoot, \"package.json\");\r\n if (!existsSync(pkgPath)) return null;\r\n try {\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { name?: string };\r\n return typeof pkg.name === \"string\" ? pkg.name.trim() : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function isKynverMonorepoRoot(repoRoot: string): boolean {\r\n return readPackageName(repoRoot) === \"kynver\";\r\n}\r\n\r\nexport function gitRepoRoot(startDir: string): string | null {\r\n const resolvedStart = path.resolve(startDir);\r\n if (!existsSync(resolvedStart)) return null;\r\n const probe = gitCapture(resolvedStart, [\"rev-parse\", \"--show-toplevel\"]);\r\n if (probe.status !== 0) return null;\r\n const root = probe.stdout.trim();\r\n return root.length ? path.resolve(root) : null;\r\n}\r\n\r\nfunction resolveRuntimePackageRoot(moduleUrl: string = import.meta.url): string | null {\r\n let dir = path.dirname(fileURLToPath(moduleUrl));\r\n for (let depth = 0; depth < 8; depth += 1) {\r\n const pkgPath = path.join(dir, \"package.json\");\r\n if (existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { name?: string };\r\n if (pkg.name === \"@kynver-app/runtime\") return dir;\r\n } catch {\r\n // continue walking\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction pushCandidate(\r\n seen: Set<string>,\r\n out: DiscoveredDefaultRepo[],\r\n repo: string | null,\r\n source: DefaultRepoDiscoverySource,\r\n): void {\r\n if (!repo) return;\r\n const resolved = path.resolve(repo);\r\n if (seen.has(resolved)) return;\r\n if (!isKynverMonorepoRoot(resolved)) return;\r\n seen.add(resolved);\r\n out.push({ repo: resolved, source });\r\n}\r\n\r\nexport function discoverDefaultRepoCandidates(opts?: {\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}): DiscoveredDefaultRepo[] {\r\n const cwd = opts?.cwd ?? process.cwd();\r\n const seen = new Set<string>();\r\n const candidates: DiscoveredDefaultRepo[] = [];\r\n\r\n pushCandidate(seen, candidates, gitRepoRoot(cwd), \"cwd_git\");\r\n\r\n const runtimePkgRoot = resolveRuntimePackageRoot(opts?.runtimeModuleUrl ?? import.meta.url);\r\n if (runtimePkgRoot) {\r\n pushCandidate(seen, candidates, gitRepoRoot(runtimePkgRoot), \"runtime_checkout\");\r\n }\r\n\r\n const home = homedir();\r\n for (const rel of WELL_KNOWN_REPO_DIRS) {\r\n pushCandidate(seen, candidates, resolveUserPath(path.join(home, rel)), \"well_known_path\");\r\n }\r\n\r\n return candidates;\r\n}\r\n\r\nexport function discoverDefaultRepo(opts?: {\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}): DiscoveredDefaultRepo | null {\r\n return discoverDefaultRepoCandidates(opts)[0] ?? null;\r\n}\r\n", "import { spawnSync } from \"node:child_process\";\r\nimport { fail } from \"./util.js\";\r\n\r\nexport interface GitOptions {\r\n allowFailure?: boolean;\r\n throwError?: boolean;\r\n}\r\n\r\nexport function git(cwd: string, args: string[], options: GitOptions = {}): string {\r\n const res = spawnSync(\"git\", args, { cwd, encoding: \"utf8\" });\r\n if (res.status !== 0 && !options.allowFailure) {\r\n const message = `git ${args.join(\" \")} failed: ${res.stderr || res.stdout}`;\r\n if (options.throwError) throw new Error(message);\r\n fail(message);\r\n }\r\n return res.stdout || \"\";\r\n}\r\n\r\nexport function ensureGitRepo(repo: string): void {\r\n git(repo, [\"rev-parse\", \"--show-toplevel\"]);\r\n}\r\n\r\nexport function gitStatusShort(worktreePath: string): string[] {\r\n return git(worktreePath, [\"status\", \"--short\"], { allowFailure: true })\r\n .split(\"\\n\")\r\n .map((line) => line.trim())\r\n .filter(Boolean);\r\n}\r\n\r\nexport interface GitCaptureResult {\r\n status: number | null;\r\n stdout: string;\r\n stderr: string;\r\n error: string | null;\r\n}\r\n\r\nexport function gitCapture(cwd: string, args: string[]): GitCaptureResult {\r\n try {\r\n const res = spawnSync(\"git\", args, { cwd, encoding: \"utf8\" });\r\n return {\r\n status: res.status,\r\n stdout: res.stdout || \"\",\r\n stderr: res.stderr || \"\",\r\n error: res.error ? res.error.message : null,\r\n };\r\n } catch (error) {\r\n return {\r\n status: null,\r\n stdout: \"\",\r\n stderr: \"\",\r\n error: (error as Error).message,\r\n };\r\n }\r\n}\r\n\r\nexport function gitIsAncestor(\r\n cwd: string,\r\n ancestor: string,\r\n descendant: string,\r\n): { isAncestor: boolean | null; error: string | null } {\r\n const res = gitCapture(cwd, [\"merge-base\", \"--is-ancestor\", ancestor, descendant]);\r\n if (res.status === 0) return { isAncestor: true, error: null };\r\n if (res.status === 1) return { isAncestor: false, error: null };\r\n return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };\r\n}\r\n\r\nexport type GitAncestryRelation = \"synced\" | \"merged\" | \"ahead\" | \"diverged\" | \"unknown\";\r\n\r\nexport interface GitAncestry {\r\n checked: boolean;\r\n base: string;\r\n head: string | null;\r\n baseHead: string | null;\r\n baseIsAncestorOfHead: boolean | null;\r\n headIsAncestorOfBase: boolean | null;\r\n relation: GitAncestryRelation;\r\n error?: string;\r\n}\r\n\r\nexport interface GitAncestryOptions {\r\n /** Branch or ref name (e.g. origin/main). Used when baseCommit is unset. */\r\n base?: string;\r\n /** Pinned SHA the worktree was created from \u2014 preferred over a moving branch ref. */\r\n baseCommit?: string;\r\n}\r\n\r\nexport function computeGitAncestry(worktreePath: string, baseOrOptions: string | GitAncestryOptions = \"origin/main\"): GitAncestry {\r\n const options: GitAncestryOptions =\r\n typeof baseOrOptions === \"string\" ? { base: baseOrOptions } : baseOrOptions;\r\n const baseLabel = options.baseCommit?.trim() || options.base?.trim() || \"origin/main\";\r\n const pinnedBaseCommit = options.baseCommit?.trim() || null;\r\n\r\n if (!worktreePath) {\r\n return unknownAncestry(baseLabel, \"missing worktree path\");\r\n }\r\n\r\n const head = gitCapture(worktreePath, [\"rev-parse\", \"HEAD\"]);\r\n if (head.status !== 0) {\r\n return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || \"failed to resolve HEAD\");\r\n }\r\n\r\n let baseSha: string;\r\n if (pinnedBaseCommit) {\r\n baseSha = pinnedBaseCommit;\r\n } else {\r\n const baseHead = gitCapture(worktreePath, [\"rev-parse\", baseLabel]);\r\n if (baseHead.status !== 0) {\r\n return unknownAncestry(\r\n baseLabel,\r\n baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,\r\n head.stdout.trim(),\r\n );\r\n }\r\n baseSha = baseHead.stdout.trim();\r\n }\r\n\r\n const headSha = head.stdout.trim();\r\n if (headSha === baseSha) {\r\n return {\r\n checked: true,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: true,\r\n headIsAncestorOfBase: true,\r\n relation: \"synced\",\r\n };\r\n }\r\n\r\n const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);\r\n const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);\r\n const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || undefined;\r\n if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {\r\n return {\r\n checked: false,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,\r\n headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,\r\n relation: \"unknown\",\r\n ...(error ? { error } : {}),\r\n };\r\n }\r\n\r\n const relation: GitAncestryRelation = baseIsAncestorOfHead.isAncestor\r\n ? \"ahead\"\r\n : headIsAncestorOfBase.isAncestor\r\n ? \"merged\"\r\n : \"diverged\";\r\n\r\n return {\r\n checked: true,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,\r\n headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,\r\n relation,\r\n ...(error ? { error } : {}),\r\n };\r\n}\r\n\r\nfunction unknownAncestry(base: string, error: string, head: string | null = null): GitAncestry {\r\n return {\r\n checked: false,\r\n base,\r\n head,\r\n baseHead: null,\r\n baseIsAncestorOfHead: null,\r\n headIsAncestorOfBase: null,\r\n relation: \"unknown\",\r\n error,\r\n };\r\n}\r\n\r\nexport { scrubClaudeEnv, scrubWorkerEnv } from \"./worker-env.js\";\r\n", "/**\r\n * Worker spawn env scrub \u2014 harness workers must not inherit host deployment secrets.\r\n */\r\n\r\n/** Exact env keys that must never reach a worker child process. */\r\nexport const FORBIDDEN_WORKER_ENV_KEYS = [\r\n \"ANTHROPIC_API_KEY\",\r\n \"ANALYST_API_KEY\",\r\n \"RECRUITER_API_KEY\",\r\n \"AUTH_SECRET\",\r\n \"NEXTAUTH_SECRET\",\r\n \"DATABASE_URL\",\r\n \"PRODUCTION_DATABASE_URL\",\r\n \"KYNVER_PRODUCTION_DATABASE_URL\",\r\n \"REDIS_URL\",\r\n \"GOOGLE_CLIENT_SECRET\",\r\n \"GITHUB_CLIENT_SECRET\",\r\n \"KYNVER_API_KEY\",\r\n \"KYNVER_SERVICE_SECRET\",\r\n \"KYNVER_RUNTIME_SECRET\",\r\n \"KYNVER_CRON_SECRET\",\r\n \"OPENCLAW_CRON_SECRET\",\r\n \"QSTASH_TOKEN\",\r\n \"QSTASH_CURRENT_SIGNING_KEY\",\r\n \"QSTASH_NEXT_SIGNING_KEY\",\r\n \"TOOL_SECRETS_KEK\",\r\n \"TOOL_EXECUTOR_DISPATCH_SECRET\",\r\n \"CLOUDFLARE_API_TOKEN\",\r\n \"STRIPE_SECRET_KEY\",\r\n \"STRIPE_WEBHOOK_SECRET\",\r\n \"STRIPE_IDENTITY_WEBHOOK_SECRET\",\r\n \"VOYAGE_API_KEY\",\r\n \"PERPLEXITY_API_KEY\",\r\n \"FRED_API_KEY\",\r\n \"FMP_API_KEY\",\r\n \"CURSOR_API_KEY\",\r\n] as const;\r\n\r\nconst FORBIDDEN_KEY_SET = new Set<string>(FORBIDDEN_WORKER_ENV_KEYS);\r\n\r\n/** Keys matching these suffixes are stripped (case-sensitive). */\r\nconst FORBIDDEN_SUFFIXES = [\"_SECRET\", \"_API_KEY\"] as const;\r\n\r\nexport function isForbiddenWorkerEnvKey(key: string): boolean {\r\n if (FORBIDDEN_KEY_SET.has(key)) return true;\r\n return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));\r\n}\r\n\r\nexport function listForbiddenWorkerEnvKeys(env: NodeJS.ProcessEnv): string[] {\r\n return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();\r\n}\r\n\r\nexport function scrubWorkerEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\r\n const next = { ...env };\r\n for (const key of Object.keys(next)) {\r\n if (isForbiddenWorkerEnvKey(key)) delete next[key];\r\n }\r\n return next;\r\n}\r\n\r\nexport interface WorkerEnvAudit {\r\n forbiddenPresent: string[];\r\n safe: boolean;\r\n}\r\n\r\nexport function auditWorkerEnv(env: NodeJS.ProcessEnv): WorkerEnvAudit {\r\n const forbiddenPresent = listForbiddenWorkerEnvKeys(env);\r\n return { forbiddenPresent, safe: forbiddenPresent.length === 0 };\r\n}\r\n\r\n/** @deprecated Use {@link scrubWorkerEnv} \u2014 kept for existing imports from git.ts */\r\nexport function scrubClaudeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\r\n return scrubWorkerEnv(env);\r\n}\r\n", "import { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\n\r\nexport function expandHomePath(value: string): string {\r\n if (value === \"~\") return homedir();\r\n if (value.startsWith(\"~/\") || value.startsWith(\"~\\\\\")) {\r\n return path.join(homedir(), value.slice(2));\r\n }\r\n return value;\r\n}\r\n\r\nexport function resolveUserPath(value: string): string {\r\n return path.resolve(expandHomePath(value));\r\n}\r\n\r\nexport function redactHomePath(value: string): string {\r\n const expanded = expandHomePath(value);\r\n const resolved = path.resolve(expanded);\r\n const home = path.resolve(homedir());\r\n\r\n if (resolved === home) return \"~\";\r\n if (resolved.startsWith(`${home}${path.sep}`)) {\r\n return `~/${path.relative(home, resolved).split(path.sep).join(\"/\")}`;\r\n }\r\n\r\n return resolved\r\n .replace(/^\\/home\\/[^/]+(?=\\/|$)/, \"~\")\r\n .replace(/^\\/Users\\/[^/]+(?=\\/|$)/, \"~\");\r\n}\r\n\r\n/** User-facing path strings (doctor JSON, setup output). */\r\nexport function displayUserPath(value: string): string {\r\n return redactHomePath(value);\r\n}\r\n", "import { existsSync, readFileSync } from \"node:fs\";\r\nimport { dirname, join } from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\n\r\nfunction resolvePackageRoot(moduleUrl: string): string {\r\n let dir = dirname(fileURLToPath(moduleUrl));\r\n for (let depth = 0; depth < 6; depth += 1) {\r\n if (existsSync(join(dir, \"package.json\"))) return dir;\r\n const parent = dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n throw new Error(`package.json not found above ${dirname(fileURLToPath(moduleUrl))}`);\r\n}\r\n\r\nexport function readOwnPackageVersion(moduleUrl: string = import.meta.url): string {\r\n const pkgPath = join(resolvePackageRoot(moduleUrl), \"package.json\");\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { version?: string };\r\n if (typeof pkg.version !== \"string\" || !pkg.version.trim()) {\r\n throw new Error(`Missing package.json version at ${pkgPath}`);\r\n }\r\n return pkg.version;\r\n}\r\n\r\nexport const PACKAGE_VERSION = readOwnPackageVersion();\r\n\r\nexport function wantsCliVersion(argv: string[]): boolean {\r\n return argv.some((arg) => arg === \"--version\" || arg === \"-v\");\r\n}\r\n\r\nexport function printCliVersionAndExit(version: string, binName?: string): never {\r\n console.log(binName ? `${binName} ${version}` : version);\r\n process.exit(0);\r\n}\r\n\r\nexport function handleCliVersionFlag(\r\n argv: string[],\r\n moduleUrl: string = import.meta.url,\r\n binName?: string,\r\n): boolean {\r\n if (!wantsCliVersion(argv)) return false;\r\n printCliVersionAndExit(readOwnPackageVersion(moduleUrl), binName);\r\n return true;\r\n}\r\n", "import { collectInstalledPackageVersions } from \"./installed-package-versions.js\";\r\nimport {\r\n assertMemoryCostPackageVersionGuard,\r\n evaluateMemoryCostPackageVersionGuard,\r\n probeRepoPackageVersions,\r\n type MemoryCostManagedPackageName,\r\n type MemoryCostPackageGuardResult,\r\n} from \"./memory-cost-package-version-guard.js\";\r\nimport { PACKAGE_VERSION } from \"./package-version.js\";\r\n\r\nfunction installedVersionMap(\r\n observed: Awaited<ReturnType<typeof collectInstalledPackageVersions>>,\r\n): Partial<Record<MemoryCostManagedPackageName, { version: string; source: \"installed\"; path: string }>> {\r\n const out: Partial<\r\n Record<MemoryCostManagedPackageName, { version: string; source: \"installed\"; path: string }>\r\n > = {};\r\n for (const [packageName, row] of Object.entries(observed)) {\r\n if (!row?.version) continue;\r\n out[packageName as MemoryCostManagedPackageName] = {\r\n version: row.version,\r\n source: \"installed\",\r\n path: row.path,\r\n };\r\n }\r\n return out;\r\n}\r\n\r\nexport async function buildMemoryCostPackageGuardInput(input: {\r\n cwd?: string;\r\n repoRoot?: string;\r\n selfPackageName?: MemoryCostManagedPackageName;\r\n selfVersion?: string;\r\n} = {}): Promise<Parameters<typeof evaluateMemoryCostPackageVersionGuard>[0]> {\r\n const [installed, repo] = await Promise.all([\r\n collectInstalledPackageVersions(),\r\n Promise.resolve(probeRepoPackageVersions({ cwd: input.cwd, repoRoot: input.repoRoot })),\r\n ]);\r\n\r\n const self: Partial<Record<MemoryCostManagedPackageName, { version: string; source: \"self\" }>> = {};\r\n const runtimeSelfVersion =\r\n input.selfPackageName === \"@kynver-app/runtime\" && input.selfVersion\r\n ? input.selfVersion\r\n : PACKAGE_VERSION;\r\n self[\"@kynver-app/runtime\"] = { version: runtimeSelfVersion, source: \"self\" };\r\n if (\r\n input.selfPackageName &&\r\n input.selfVersion &&\r\n input.selfPackageName !== \"@kynver-app/runtime\"\r\n ) {\r\n self[input.selfPackageName] = { version: input.selfVersion, source: \"self\" };\r\n }\r\n\r\n return {\r\n installed: installedVersionMap(installed),\r\n repo,\r\n self,\r\n };\r\n}\r\n\r\nexport async function evaluateMemoryCostPackageGuardAtStartup(\r\n input: Parameters<typeof buildMemoryCostPackageGuardInput>[0] = {},\r\n): Promise<MemoryCostPackageGuardResult> {\r\n const guardInput = await buildMemoryCostPackageGuardInput(input);\r\n return evaluateMemoryCostPackageVersionGuard(guardInput);\r\n}\r\n\r\nexport async function enforceMemoryCostPackageGuardAtStartup(\r\n input: Parameters<typeof buildMemoryCostPackageGuardInput>[0] = {},\r\n): Promise<MemoryCostPackageGuardResult> {\r\n const guardInput = await buildMemoryCostPackageGuardInput(input);\r\n return assertMemoryCostPackageVersionGuard(guardInput);\r\n}\r\n\r\nexport function shouldEnforceMemoryCostPackageGuardCli(scope?: string, action?: string): boolean {\r\n if (!scope) return false;\r\n if (scope === \"daemon\") return true;\r\n if (scope === \"worker\") return true;\r\n if (scope === \"monitor\") return true;\r\n if (scope === \"run\" && (action === \"dispatch\" || action === \"sweep\" || action === \"reconcile\" || action === \"unblock\")) {\r\n return true;\r\n }\r\n if (scope === \"cron\" && action === \"tick\") return true;\r\n return false;\r\n}\r\n"],
4
+ "sourcesContent": ["import { readFile } from \"node:fs/promises\";\r\nimport { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { compareSemver } from \"./memory-cost-package-version-guard.js\";\r\n\r\nconst MANAGED_PACKAGES = [\r\n \"@kynver-app/runtime\",\r\n \"@kynver-app/openclaw-agent-os\",\r\n \"@kynver-app/mcp-agent-os\",\r\n] as const;\r\n\r\nexport type InstalledPackageVersionMap = Record<\r\n string,\r\n {\r\n version: string;\r\n observedAt: string;\r\n path: string;\r\n }\r\n>;\r\n\r\nfunction trim(value: string | undefined): string | null {\r\n const out = value?.trim();\r\n return out ? out : null;\r\n}\r\n\r\nfunction unique(values: Array<string | null | undefined>): string[] {\r\n return [...new Set(values.filter((value): value is string => Boolean(value)))];\r\n}\r\n\r\nfunction moduleRoots(): string[] {\r\n const home = homedir();\r\n const openClawPrefix =\r\n trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ??\r\n trim(process.env.OPENCLAW_NPM_ROOT) ??\r\n path.join(home, \".openclaw\", \"npm\");\r\n const npmGlobalRoot =\r\n trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ??\r\n trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ??\r\n (trim(process.env.NPM_CONFIG_PREFIX)\r\n ? path.join(trim(process.env.NPM_CONFIG_PREFIX)!, \"lib\", \"node_modules\")\r\n : path.join(home, \".npm-global\", \"lib\", \"node_modules\"));\r\n\r\n return unique([\r\n path.join(openClawPrefix, \"lib\", \"node_modules\"),\r\n path.join(openClawPrefix, \"node_modules\"),\r\n npmGlobalRoot.endsWith(\"node_modules\") ? npmGlobalRoot : path.join(npmGlobalRoot, \"lib\", \"node_modules\"),\r\n ]);\r\n}\r\n\r\nasync function readVersion(packageJsonPath: string): Promise<string | null> {\r\n try {\r\n const parsed = JSON.parse(await readFile(packageJsonPath, \"utf8\")) as { version?: unknown };\r\n return typeof parsed.version === \"string\" && parsed.version.trim() ? parsed.version.trim() : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/** Candidate install paths for one managed package across OpenClaw + global npm roots. */\r\nexport function installedPackageJsonCandidates(packageName: string): string[] {\r\n const roots = moduleRoots();\r\n const seen = new Set<string>();\r\n const out: string[] = [];\r\n for (const root of roots) {\r\n const candidate = path.join(root, packageName, \"package.json\");\r\n if (seen.has(candidate)) continue;\r\n seen.add(candidate);\r\n out.push(candidate);\r\n }\r\n return out;\r\n}\r\n\r\nexport async function collectInstalledPackageVersions(\r\n observedAt: string = new Date().toISOString(),\r\n): Promise<InstalledPackageVersionMap> {\r\n const out: InstalledPackageVersionMap = {};\r\n for (const packageName of MANAGED_PACKAGES) {\r\n let best: { version: string; path: string } | null = null;\r\n for (const packageJsonPath of installedPackageJsonCandidates(packageName)) {\r\n const version = await readVersion(packageJsonPath);\r\n if (!version) continue;\r\n if (!best || compareSemver(version, best.version) > 0) {\r\n best = { version, path: packageJsonPath };\r\n }\r\n }\r\n if (best) {\r\n out[packageName] = { version: best.version, observedAt, path: best.path };\r\n }\r\n }\r\n return out;\r\n}\r\n", "import { existsSync, readFileSync } from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { discoverDefaultRepo } from \"./default-repo-discovery.js\";\r\n\r\n/** Minimum versions that ship MARM memory cost controls (PR #1159). */\r\nexport const MEMORY_COST_PACKAGE_MIN_VERSIONS = {\r\n \"@kynver-app/runtime\": \"0.1.83\",\r\n \"@kynver-app/openclaw-agent-os\": \"0.1.43\",\r\n \"@kynver-app/mcp-agent-os\": \"0.3.34\",\r\n} as const;\r\n\r\nexport type MemoryCostManagedPackageName = keyof typeof MEMORY_COST_PACKAGE_MIN_VERSIONS;\r\n\r\nexport const MEMORY_COST_MANAGED_PACKAGES = Object.keys(\r\n MEMORY_COST_PACKAGE_MIN_VERSIONS,\r\n) as MemoryCostManagedPackageName[];\r\n\r\nexport type MemoryCostVersionSource = \"self\" | \"installed\" | \"repo\" | \"unknown\";\r\n\r\nexport interface MemoryCostPackageVersionCandidate {\r\n version: string;\r\n source: MemoryCostVersionSource;\r\n path?: string;\r\n}\r\n\r\nexport interface MemoryCostPackageGuardRow {\r\n packageName: MemoryCostManagedPackageName;\r\n displayName: string;\r\n minimumVersion: string;\r\n effectiveVersion: string | null;\r\n effectiveSource: MemoryCostVersionSource;\r\n ok: boolean;\r\n summary: string;\r\n remediation: string[];\r\n}\r\n\r\nexport interface MemoryCostPackageGuardResult {\r\n ok: boolean;\r\n summary: string;\r\n packages: MemoryCostPackageGuardRow[];\r\n}\r\n\r\nconst DISPLAY_NAMES: Record<MemoryCostManagedPackageName, string> = {\r\n \"@kynver-app/runtime\": \"Kynver runtime\",\r\n \"@kynver-app/openclaw-agent-os\": \"OpenClaw AgentOS plugin\",\r\n \"@kynver-app/mcp-agent-os\": \"AgentOS MCP server\",\r\n};\r\n\r\nconst REPO_PACKAGE_JSON_RELATIVE: Record<MemoryCostManagedPackageName, string> = {\r\n \"@kynver-app/runtime\": \"packages/kynver-runtime/package.json\",\r\n \"@kynver-app/openclaw-agent-os\": \"packages/kynver-openclaw-agent-os/package.json\",\r\n \"@kynver-app/mcp-agent-os\": \"packages/kynver-mcp-agent-os/package.json\",\r\n};\r\n\r\n/** Parse `major.minor.patch`; pre-release/build suffixes are ignored. */\r\nexport function parseSemverParts(version: string): [number, number, number] | null {\r\n const core = version.trim().split(\"-\")[0]?.split(\"+\")[0];\r\n if (!core) return null;\r\n const parts = core.split(\".\");\r\n if (parts.length < 1 || parts.length > 3) return null;\r\n const nums = parts.map((p) => Number.parseInt(p, 10));\r\n if (nums.some((n) => !Number.isFinite(n) || n < 0)) return null;\r\n while (nums.length < 3) nums.push(0);\r\n return [nums[0]!, nums[1]!, nums[2]!];\r\n}\r\n\r\n/** Returns 1 if a > b, -1 if a < b, 0 if equal or either unparsable. */\r\nexport function compareSemver(a: string, b: string): number {\r\n const pa = parseSemverParts(a);\r\n const pb = parseSemverParts(b);\r\n if (!pa || !pb) return 0;\r\n for (let i = 0; i < 3; i += 1) {\r\n if (pa[i]! > pb[i]!) return 1;\r\n if (pa[i]! < pb[i]!) return -1;\r\n }\r\n return 0;\r\n}\r\n\r\nexport function semverAtLeast(version: string, minimum: string): boolean {\r\n return compareSemver(version, minimum) >= 0;\r\n}\r\n\r\nfunction maxSemver(versions: readonly string[]): string | null {\r\n let best: string | null = null;\r\n for (const version of versions) {\r\n if (!best || compareSemver(version, best) > 0) best = version;\r\n }\r\n return best;\r\n}\r\n\r\nfunction readPackageJsonVersion(packageJsonPath: string): string | null {\r\n try {\r\n const parsed = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as { version?: unknown };\r\n return typeof parsed.version === \"string\" && parsed.version.trim() ? parsed.version.trim() : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nfunction resolveRepoRoot(cwd: string, explicitRepoRoot?: string): string | null {\r\n const candidates = [explicitRepoRoot, process.env.KYNVER_REPO, cwd].filter(\r\n (value): value is string => Boolean(value?.trim()),\r\n );\r\n for (const candidate of candidates) {\r\n const resolved = path.resolve(candidate);\r\n if (\r\n existsSync(path.join(resolved, \"packages/kynver-runtime/package.json\")) &&\r\n existsSync(path.join(resolved, \"package.json\"))\r\n ) {\r\n return resolved;\r\n }\r\n }\r\n const discovered = discoverDefaultRepo({ cwd });\r\n return discovered?.repo ?? null;\r\n}\r\n\r\nexport function probeRepoPackageVersions(input: {\r\n cwd?: string;\r\n repoRoot?: string;\r\n} = {}): Partial<Record<MemoryCostManagedPackageName, MemoryCostPackageVersionCandidate>> {\r\n const cwd = input.cwd ?? process.cwd();\r\n const repoRoot = resolveRepoRoot(cwd, input.repoRoot);\r\n if (!repoRoot) return {};\r\n\r\n const out: Partial<Record<MemoryCostManagedPackageName, MemoryCostPackageVersionCandidate>> = {};\r\n for (const packageName of MEMORY_COST_MANAGED_PACKAGES) {\r\n const packageJsonPath = path.join(repoRoot, REPO_PACKAGE_JSON_RELATIVE[packageName]);\r\n const version = readPackageJsonVersion(packageJsonPath);\r\n if (!version) continue;\r\n out[packageName] = { version, source: \"repo\", path: packageJsonPath };\r\n }\r\n return out;\r\n}\r\n\r\nfunction repoSourceCommands(packageName: MemoryCostManagedPackageName): string[] {\r\n if (packageName === \"@kynver-app/runtime\") {\r\n return [\"npm run kynver:build\", \"npm run kynver\"];\r\n }\r\n return [`npm run build -w ${packageName}`];\r\n}\r\n\r\nfunction buildRemediation(input: {\r\n packageName: MemoryCostManagedPackageName;\r\n minimumVersion: string;\r\n effectiveVersion: string | null;\r\n effectiveSource: MemoryCostVersionSource;\r\n repoVersion: string | null;\r\n}): string[] {\r\n const { packageName, minimumVersion, effectiveVersion, effectiveSource, repoVersion } = input;\r\n const lines: string[] = [];\r\n if (repoVersion && semverAtLeast(repoVersion, minimumVersion)) {\r\n lines.push(\r\n `Use the monorepo checkout (${repoVersion}) instead of a stale npm install: ${repoSourceCommands(packageName).join(\"; \")}.`,\r\n );\r\n lines.push(\"Do not publish npm packages or wait on an operator release.\");\r\n return lines;\r\n }\r\n if (effectiveVersion) {\r\n lines.push(\r\n `Upgrade ${packageName} from ${effectiveVersion} to >= ${minimumVersion} (npm install -g ${packageName}@latest or align OpenClaw npm prefix).`,\r\n );\r\n } else {\r\n lines.push(`Install ${packageName} >= ${minimumVersion} before running memory-heavy AgentOS paths.`);\r\n }\r\n if (packageName === \"@kynver-app/runtime\") {\r\n lines.push(\"Repo-source alternative: npm run kynver:build && npm run kynver\");\r\n }\r\n if (effectiveSource === \"installed\" && repoVersion) {\r\n lines.push(`Repo checkout reports ${repoVersion}; rebuild/link repo source if you develop from the monorepo.`);\r\n }\r\n return lines;\r\n}\r\n\r\nfunction pickEffectiveCandidate(\r\n candidates: MemoryCostPackageVersionCandidate[],\r\n): { version: string | null; source: MemoryCostVersionSource } {\r\n if (candidates.length === 0) return { version: null, source: \"unknown\" };\r\n const best = maxSemver(candidates.map((candidate) => candidate.version));\r\n if (!best) return { version: null, source: \"unknown\" };\r\n const winner = candidates.find((candidate) => candidate.version === best) ?? candidates[0]!;\r\n return { version: winner.version, source: winner.source };\r\n}\r\n\r\nexport function evaluateMemoryCostPackageVersionGuard(input: {\r\n installed?: Partial<Record<MemoryCostManagedPackageName, string | MemoryCostPackageVersionCandidate>>;\r\n repo?: Partial<Record<MemoryCostManagedPackageName, string | MemoryCostPackageVersionCandidate>>;\r\n self?: Partial<Record<MemoryCostManagedPackageName, string | MemoryCostPackageVersionCandidate>>;\r\n} = {}): MemoryCostPackageGuardResult {\r\n const normalize = (\r\n value: string | MemoryCostPackageVersionCandidate | undefined,\r\n fallbackSource: MemoryCostVersionSource,\r\n ): MemoryCostPackageVersionCandidate | null => {\r\n if (!value) return null;\r\n if (typeof value === \"string\") return { version: value, source: fallbackSource };\r\n return value;\r\n };\r\n\r\n const packages = MEMORY_COST_MANAGED_PACKAGES.map((packageName): MemoryCostPackageGuardRow => {\r\n const minimumVersion = MEMORY_COST_PACKAGE_MIN_VERSIONS[packageName];\r\n const candidates: MemoryCostPackageVersionCandidate[] = [];\r\n const installed = normalize(input.installed?.[packageName], \"installed\");\r\n const repo = normalize(input.repo?.[packageName], \"repo\");\r\n const self = normalize(input.self?.[packageName], \"self\");\r\n if (installed) candidates.push(installed);\r\n if (repo) candidates.push(repo);\r\n if (self) candidates.push(self);\r\n\r\n const { version: effectiveVersion, source: effectiveSource } = pickEffectiveCandidate(candidates);\r\n const repoVersion = repo?.version ?? null;\r\n const ok = effectiveVersion ? semverAtLeast(effectiveVersion, minimumVersion) : false;\r\n const remediation = ok\r\n ? []\r\n : buildRemediation({\r\n packageName,\r\n minimumVersion,\r\n effectiveVersion,\r\n effectiveSource,\r\n repoVersion,\r\n });\r\n\r\n const summary = ok\r\n ? `${DISPLAY_NAMES[packageName]} ${effectiveVersion} meets memory-cost minimum ${minimumVersion} (${effectiveSource}).`\r\n : `${DISPLAY_NAMES[packageName]} is below memory-cost minimum ${minimumVersion}` +\r\n (effectiveVersion ? ` (effective ${effectiveVersion} via ${effectiveSource})` : \" (no version detected)\") +\r\n \".\";\r\n\r\n return {\r\n packageName,\r\n displayName: DISPLAY_NAMES[packageName],\r\n minimumVersion,\r\n effectiveVersion,\r\n effectiveSource,\r\n ok,\r\n summary,\r\n remediation,\r\n };\r\n });\r\n\r\n const violations = packages.filter((row) => !row.ok);\r\n const ok = violations.length === 0;\r\n const summary = ok\r\n ? \"All managed AgentOS packages meet memory-cost minimum versions.\"\r\n : `Memory-cost package guard blocked ${violations.length} stale package(s): ${violations\r\n .map((row) => `${row.packageName} < ${row.minimumVersion}`)\r\n .join(\"; \")}.`;\r\n\r\n return { ok, summary, packages };\r\n}\r\n\r\nexport class MemoryCostPackageVersionGuardError extends Error {\r\n readonly result: MemoryCostPackageGuardResult;\r\n\r\n constructor(result: MemoryCostPackageGuardResult) {\r\n const lines = [\r\n result.summary,\r\n ...result.packages\r\n .filter((row) => !row.ok)\r\n .flatMap((row) => [`- ${row.summary}`, ...row.remediation.map((line) => ` \u2192 ${line}`)]),\r\n ];\r\n super(lines.join(\"\\n\"));\r\n this.name = \"MemoryCostPackageVersionGuardError\";\r\n this.result = result;\r\n }\r\n}\r\n\r\nexport function assertMemoryCostPackageVersionGuard(\r\n input: Parameters<typeof evaluateMemoryCostPackageVersionGuard>[0] = {},\r\n): MemoryCostPackageGuardResult {\r\n const result = evaluateMemoryCostPackageVersionGuard(input);\r\n if (!result.ok) throw new MemoryCostPackageVersionGuardError(result);\r\n return result;\r\n}\r\n\r\nexport function formatMemoryCostPackageGuardError(result: MemoryCostPackageGuardResult): string {\r\n return new MemoryCostPackageVersionGuardError(result).message;\r\n}\r\n", "import { existsSync, readFileSync } from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport { gitCapture } from \"./git.js\";\r\nimport { resolveUserPath } from \"./path-values.js\";\r\n\r\nexport type DefaultRepoDiscoverySource =\r\n | \"cwd_git\"\r\n | \"runtime_checkout\"\r\n | \"well_known_path\";\r\n\r\nexport interface DiscoveredDefaultRepo {\r\n repo: string;\r\n source: DefaultRepoDiscoverySource;\r\n}\r\n\r\nconst WELL_KNOWN_REPO_DIRS = [\r\n \"Kynver\",\r\n \"repos/Kynver\",\r\n \"repos/kynver-source-main\",\r\n \"code/Kynver\",\r\n \"projects/Kynver\",\r\n] as const;\r\n\r\nfunction readPackageName(repoRoot: string): string | null {\r\n const pkgPath = path.join(repoRoot, \"package.json\");\r\n if (!existsSync(pkgPath)) return null;\r\n try {\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { name?: string };\r\n return typeof pkg.name === \"string\" ? pkg.name.trim() : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport function isKynverMonorepoRoot(repoRoot: string): boolean {\r\n return readPackageName(repoRoot) === \"kynver\";\r\n}\r\n\r\nexport function gitRepoRoot(startDir: string): string | null {\r\n const resolvedStart = path.resolve(startDir);\r\n if (!existsSync(resolvedStart)) return null;\r\n const probe = gitCapture(resolvedStart, [\"rev-parse\", \"--show-toplevel\"]);\r\n if (probe.status !== 0) return null;\r\n const root = probe.stdout.trim();\r\n return root.length ? path.resolve(root) : null;\r\n}\r\n\r\nfunction resolveRuntimePackageRoot(moduleUrl: string = import.meta.url): string | null {\r\n let dir = path.dirname(fileURLToPath(moduleUrl));\r\n for (let depth = 0; depth < 8; depth += 1) {\r\n const pkgPath = path.join(dir, \"package.json\");\r\n if (existsSync(pkgPath)) {\r\n try {\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { name?: string };\r\n if (pkg.name === \"@kynver-app/runtime\") return dir;\r\n } catch {\r\n // continue walking\r\n }\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n return null;\r\n}\r\n\r\nfunction pushCandidate(\r\n seen: Set<string>,\r\n out: DiscoveredDefaultRepo[],\r\n repo: string | null,\r\n source: DefaultRepoDiscoverySource,\r\n): void {\r\n if (!repo) return;\r\n const resolved = path.resolve(repo);\r\n if (seen.has(resolved)) return;\r\n if (!isKynverMonorepoRoot(resolved)) return;\r\n seen.add(resolved);\r\n out.push({ repo: resolved, source });\r\n}\r\n\r\nexport function discoverDefaultRepoCandidates(opts?: {\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}): DiscoveredDefaultRepo[] {\r\n const cwd = opts?.cwd ?? process.cwd();\r\n const seen = new Set<string>();\r\n const candidates: DiscoveredDefaultRepo[] = [];\r\n\r\n pushCandidate(seen, candidates, gitRepoRoot(cwd), \"cwd_git\");\r\n\r\n const runtimePkgRoot = resolveRuntimePackageRoot(opts?.runtimeModuleUrl ?? import.meta.url);\r\n if (runtimePkgRoot) {\r\n pushCandidate(seen, candidates, gitRepoRoot(runtimePkgRoot), \"runtime_checkout\");\r\n }\r\n\r\n const home = homedir();\r\n for (const rel of WELL_KNOWN_REPO_DIRS) {\r\n pushCandidate(seen, candidates, resolveUserPath(path.join(home, rel)), \"well_known_path\");\r\n }\r\n\r\n return candidates;\r\n}\r\n\r\nexport function discoverDefaultRepo(opts?: {\r\n cwd?: string;\r\n runtimeModuleUrl?: string;\r\n}): DiscoveredDefaultRepo | null {\r\n return discoverDefaultRepoCandidates(opts)[0] ?? null;\r\n}\r\n", "import { spawnSync } from \"node:child_process\";\r\nimport { fail } from \"./util.js\";\r\n\r\nexport interface GitOptions {\r\n allowFailure?: boolean;\r\n throwError?: boolean;\r\n}\r\n\r\nexport function git(cwd: string, args: string[], options: GitOptions = {}): string {\r\n const res = spawnSync(\"git\", args, { cwd, encoding: \"utf8\" });\r\n if (res.status !== 0 && !options.allowFailure) {\r\n const message = `git ${args.join(\" \")} failed: ${res.stderr || res.stdout}`;\r\n if (options.throwError) throw new Error(message);\r\n fail(message);\r\n }\r\n return res.stdout || \"\";\r\n}\r\n\r\nexport function ensureGitRepo(repo: string): void {\r\n git(repo, [\"rev-parse\", \"--show-toplevel\"]);\r\n}\r\n\r\nexport function gitStatusShort(worktreePath: string): string[] {\r\n return git(worktreePath, [\"status\", \"--short\"], { allowFailure: true })\r\n .split(\"\\n\")\r\n .map((line) => line.trim())\r\n .filter(Boolean);\r\n}\r\n\r\nexport interface GitCaptureResult {\r\n status: number | null;\r\n stdout: string;\r\n stderr: string;\r\n error: string | null;\r\n}\r\n\r\nexport function gitCapture(cwd: string, args: string[]): GitCaptureResult {\r\n try {\r\n const res = spawnSync(\"git\", args, { cwd, encoding: \"utf8\" });\r\n return {\r\n status: res.status,\r\n stdout: res.stdout || \"\",\r\n stderr: res.stderr || \"\",\r\n error: res.error ? res.error.message : null,\r\n };\r\n } catch (error) {\r\n return {\r\n status: null,\r\n stdout: \"\",\r\n stderr: \"\",\r\n error: (error as Error).message,\r\n };\r\n }\r\n}\r\n\r\nexport function gitIsAncestor(\r\n cwd: string,\r\n ancestor: string,\r\n descendant: string,\r\n): { isAncestor: boolean | null; error: string | null } {\r\n const res = gitCapture(cwd, [\"merge-base\", \"--is-ancestor\", ancestor, descendant]);\r\n if (res.status === 0) return { isAncestor: true, error: null };\r\n if (res.status === 1) return { isAncestor: false, error: null };\r\n return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };\r\n}\r\n\r\nexport type GitAncestryRelation = \"synced\" | \"merged\" | \"ahead\" | \"diverged\" | \"unknown\";\r\n\r\nexport interface GitAncestry {\r\n checked: boolean;\r\n base: string;\r\n head: string | null;\r\n baseHead: string | null;\r\n baseIsAncestorOfHead: boolean | null;\r\n headIsAncestorOfBase: boolean | null;\r\n relation: GitAncestryRelation;\r\n error?: string;\r\n}\r\n\r\nexport interface GitAncestryOptions {\r\n /** Branch or ref name (e.g. origin/main). Used when baseCommit is unset. */\r\n base?: string;\r\n /** Pinned SHA the worktree was created from \u2014 preferred over a moving branch ref. */\r\n baseCommit?: string;\r\n}\r\n\r\nexport function computeGitAncestry(worktreePath: string, baseOrOptions: string | GitAncestryOptions = \"origin/main\"): GitAncestry {\r\n const options: GitAncestryOptions =\r\n typeof baseOrOptions === \"string\" ? { base: baseOrOptions } : baseOrOptions;\r\n const baseLabel = options.baseCommit?.trim() || options.base?.trim() || \"origin/main\";\r\n const pinnedBaseCommit = options.baseCommit?.trim() || null;\r\n\r\n if (!worktreePath) {\r\n return unknownAncestry(baseLabel, \"missing worktree path\");\r\n }\r\n\r\n const head = gitCapture(worktreePath, [\"rev-parse\", \"HEAD\"]);\r\n if (head.status !== 0) {\r\n return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || \"failed to resolve HEAD\");\r\n }\r\n\r\n let baseSha: string;\r\n if (pinnedBaseCommit) {\r\n baseSha = pinnedBaseCommit;\r\n } else {\r\n const baseHead = gitCapture(worktreePath, [\"rev-parse\", baseLabel]);\r\n if (baseHead.status !== 0) {\r\n return unknownAncestry(\r\n baseLabel,\r\n baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,\r\n head.stdout.trim(),\r\n );\r\n }\r\n baseSha = baseHead.stdout.trim();\r\n }\r\n\r\n const headSha = head.stdout.trim();\r\n if (headSha === baseSha) {\r\n return {\r\n checked: true,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: true,\r\n headIsAncestorOfBase: true,\r\n relation: \"synced\",\r\n };\r\n }\r\n\r\n const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);\r\n const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);\r\n const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || undefined;\r\n if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {\r\n return {\r\n checked: false,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,\r\n headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,\r\n relation: \"unknown\",\r\n ...(error ? { error } : {}),\r\n };\r\n }\r\n\r\n const relation: GitAncestryRelation = baseIsAncestorOfHead.isAncestor\r\n ? \"ahead\"\r\n : headIsAncestorOfBase.isAncestor\r\n ? \"merged\"\r\n : \"diverged\";\r\n\r\n return {\r\n checked: true,\r\n base: baseLabel,\r\n head: headSha,\r\n baseHead: baseSha,\r\n baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,\r\n headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,\r\n relation,\r\n ...(error ? { error } : {}),\r\n };\r\n}\r\n\r\nfunction unknownAncestry(base: string, error: string, head: string | null = null): GitAncestry {\r\n return {\r\n checked: false,\r\n base,\r\n head,\r\n baseHead: null,\r\n baseIsAncestorOfHead: null,\r\n headIsAncestorOfBase: null,\r\n relation: \"unknown\",\r\n error,\r\n };\r\n}\r\n\r\nexport { scrubClaudeEnv, scrubWorkerEnv } from \"./worker-env.js\";\r\n", "/**\r\n * Worker spawn env scrub \u2014 harness workers must not inherit host deployment secrets.\r\n */\r\n\r\n/** Exact env keys that must never reach a worker child process. */\r\nexport const FORBIDDEN_WORKER_ENV_KEYS = [\r\n \"ANTHROPIC_API_KEY\",\r\n \"ANALYST_API_KEY\",\r\n \"RECRUITER_API_KEY\",\r\n \"AUTH_SECRET\",\r\n \"NEXTAUTH_SECRET\",\r\n \"DATABASE_URL\",\r\n \"PRODUCTION_DATABASE_URL\",\r\n \"KYNVER_PRODUCTION_DATABASE_URL\",\r\n \"REDIS_URL\",\r\n \"GOOGLE_CLIENT_SECRET\",\r\n \"GITHUB_CLIENT_SECRET\",\r\n \"KYNVER_API_KEY\",\r\n \"KYNVER_SERVICE_SECRET\",\r\n \"KYNVER_RUNTIME_SECRET\",\r\n \"KYNVER_CRON_SECRET\",\r\n \"OPENCLAW_CRON_SECRET\",\r\n \"QSTASH_TOKEN\",\r\n \"QSTASH_CURRENT_SIGNING_KEY\",\r\n \"QSTASH_NEXT_SIGNING_KEY\",\r\n \"TOOL_SECRETS_KEK\",\r\n \"TOOL_EXECUTOR_DISPATCH_SECRET\",\r\n \"CLOUDFLARE_API_TOKEN\",\r\n \"STRIPE_SECRET_KEY\",\r\n \"STRIPE_WEBHOOK_SECRET\",\r\n \"STRIPE_IDENTITY_WEBHOOK_SECRET\",\r\n \"VOYAGE_API_KEY\",\r\n \"PERPLEXITY_API_KEY\",\r\n \"FRED_API_KEY\",\r\n \"FMP_API_KEY\",\r\n \"CURSOR_API_KEY\",\r\n] as const;\r\n\r\nconst FORBIDDEN_KEY_SET = new Set<string>(FORBIDDEN_WORKER_ENV_KEYS);\r\n\r\n/** Keys matching these suffixes are stripped (case-sensitive). */\r\nconst FORBIDDEN_SUFFIXES = [\"_SECRET\", \"_API_KEY\"] as const;\r\n\r\nexport function isForbiddenWorkerEnvKey(key: string): boolean {\r\n if (FORBIDDEN_KEY_SET.has(key)) return true;\r\n return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));\r\n}\r\n\r\nexport function listForbiddenWorkerEnvKeys(env: NodeJS.ProcessEnv): string[] {\r\n return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();\r\n}\r\n\r\nexport function scrubWorkerEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\r\n const next = { ...env };\r\n for (const key of Object.keys(next)) {\r\n if (isForbiddenWorkerEnvKey(key)) delete next[key];\r\n }\r\n return next;\r\n}\r\n\r\nexport interface WorkerEnvAudit {\r\n forbiddenPresent: string[];\r\n safe: boolean;\r\n}\r\n\r\nexport function auditWorkerEnv(env: NodeJS.ProcessEnv): WorkerEnvAudit {\r\n const forbiddenPresent = listForbiddenWorkerEnvKeys(env);\r\n return { forbiddenPresent, safe: forbiddenPresent.length === 0 };\r\n}\r\n\r\n/** @deprecated Use {@link scrubWorkerEnv} \u2014 kept for existing imports from git.ts */\r\nexport function scrubClaudeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\r\n return scrubWorkerEnv(env);\r\n}\r\n", "import { homedir } from \"node:os\";\r\nimport path from \"node:path\";\r\n\r\nexport function expandHomePath(value: string): string {\r\n if (value === \"~\") return homedir();\r\n if (value.startsWith(\"~/\") || value.startsWith(\"~\\\\\")) {\r\n return path.join(homedir(), value.slice(2));\r\n }\r\n return value;\r\n}\r\n\r\nexport function resolveUserPath(value: string): string {\r\n return path.resolve(expandHomePath(value));\r\n}\r\n\r\nexport function redactHomePath(value: string): string {\r\n const expanded = expandHomePath(value);\r\n const resolved = path.resolve(expanded);\r\n const home = path.resolve(homedir());\r\n\r\n if (resolved === home) return \"~\";\r\n if (resolved.startsWith(`${home}${path.sep}`)) {\r\n return `~/${path.relative(home, resolved).split(path.sep).join(\"/\")}`;\r\n }\r\n\r\n const posix = resolved.replace(/\\\\/g, \"/\");\r\n const redacted = posix\r\n .replace(/^\\/home\\/[^/]+(?=\\/|$)/, \"~\")\r\n .replace(/^\\/Users\\/[^/]+(?=\\/|$)/, \"~\")\r\n // Windows resolves Unix-style `/home/...` paths to `C:\\home\\...`.\r\n .replace(/^[A-Za-z]:\\/home\\/[^/]+(?=\\/|$)/i, \"~\")\r\n .replace(/^[A-Za-z]:\\/Users\\/[^/]+(?=\\/|$)/i, \"~\");\r\n return redacted;\r\n}\r\n\r\n/** User-facing path strings (doctor JSON, setup output). */\r\nexport function displayUserPath(value: string): string {\r\n return redactHomePath(value);\r\n}\r\n", "import { existsSync, readFileSync } from \"node:fs\";\r\nimport { dirname, join } from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\n\r\nfunction resolvePackageRoot(moduleUrl: string): string {\r\n let dir = dirname(fileURLToPath(moduleUrl));\r\n for (let depth = 0; depth < 6; depth += 1) {\r\n if (existsSync(join(dir, \"package.json\"))) return dir;\r\n const parent = dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n throw new Error(`package.json not found above ${dirname(fileURLToPath(moduleUrl))}`);\r\n}\r\n\r\nexport function readOwnPackageVersion(moduleUrl: string = import.meta.url): string {\r\n const pkgPath = join(resolvePackageRoot(moduleUrl), \"package.json\");\r\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { version?: string };\r\n if (typeof pkg.version !== \"string\" || !pkg.version.trim()) {\r\n throw new Error(`Missing package.json version at ${pkgPath}`);\r\n }\r\n return pkg.version;\r\n}\r\n\r\nexport const PACKAGE_VERSION = readOwnPackageVersion();\r\n\r\nexport function wantsCliVersion(argv: string[]): boolean {\r\n return argv.some((arg) => arg === \"--version\" || arg === \"-v\");\r\n}\r\n\r\nexport function printCliVersionAndExit(version: string, binName?: string): never {\r\n console.log(binName ? `${binName} ${version}` : version);\r\n process.exit(0);\r\n}\r\n\r\nexport function handleCliVersionFlag(\r\n argv: string[],\r\n moduleUrl: string = import.meta.url,\r\n binName?: string,\r\n): boolean {\r\n if (!wantsCliVersion(argv)) return false;\r\n printCliVersionAndExit(readOwnPackageVersion(moduleUrl), binName);\r\n return true;\r\n}\r\n", "import { collectInstalledPackageVersions } from \"./installed-package-versions.js\";\r\nimport {\r\n assertMemoryCostPackageVersionGuard,\r\n evaluateMemoryCostPackageVersionGuard,\r\n probeRepoPackageVersions,\r\n type MemoryCostManagedPackageName,\r\n type MemoryCostPackageGuardResult,\r\n} from \"./memory-cost-package-version-guard.js\";\r\nimport { PACKAGE_VERSION } from \"./package-version.js\";\r\n\r\nfunction installedVersionMap(\r\n observed: Awaited<ReturnType<typeof collectInstalledPackageVersions>>,\r\n): Partial<Record<MemoryCostManagedPackageName, { version: string; source: \"installed\"; path: string }>> {\r\n const out: Partial<\r\n Record<MemoryCostManagedPackageName, { version: string; source: \"installed\"; path: string }>\r\n > = {};\r\n for (const [packageName, row] of Object.entries(observed)) {\r\n if (!row?.version) continue;\r\n out[packageName as MemoryCostManagedPackageName] = {\r\n version: row.version,\r\n source: \"installed\",\r\n path: row.path,\r\n };\r\n }\r\n return out;\r\n}\r\n\r\nexport async function buildMemoryCostPackageGuardInput(input: {\r\n cwd?: string;\r\n repoRoot?: string;\r\n selfPackageName?: MemoryCostManagedPackageName;\r\n selfVersion?: string;\r\n} = {}): Promise<Parameters<typeof evaluateMemoryCostPackageVersionGuard>[0]> {\r\n const [installed, repo] = await Promise.all([\r\n collectInstalledPackageVersions(),\r\n Promise.resolve(probeRepoPackageVersions({ cwd: input.cwd, repoRoot: input.repoRoot })),\r\n ]);\r\n\r\n const self: Partial<Record<MemoryCostManagedPackageName, { version: string; source: \"self\" }>> = {};\r\n const runtimeSelfVersion =\r\n input.selfPackageName === \"@kynver-app/runtime\" && input.selfVersion\r\n ? input.selfVersion\r\n : PACKAGE_VERSION;\r\n self[\"@kynver-app/runtime\"] = { version: runtimeSelfVersion, source: \"self\" };\r\n if (\r\n input.selfPackageName &&\r\n input.selfVersion &&\r\n input.selfPackageName !== \"@kynver-app/runtime\"\r\n ) {\r\n self[input.selfPackageName] = { version: input.selfVersion, source: \"self\" };\r\n }\r\n\r\n return {\r\n installed: installedVersionMap(installed),\r\n repo,\r\n self,\r\n };\r\n}\r\n\r\nexport async function evaluateMemoryCostPackageGuardAtStartup(\r\n input: Parameters<typeof buildMemoryCostPackageGuardInput>[0] = {},\r\n): Promise<MemoryCostPackageGuardResult> {\r\n const guardInput = await buildMemoryCostPackageGuardInput(input);\r\n return evaluateMemoryCostPackageVersionGuard(guardInput);\r\n}\r\n\r\nexport async function enforceMemoryCostPackageGuardAtStartup(\r\n input: Parameters<typeof buildMemoryCostPackageGuardInput>[0] = {},\r\n): Promise<MemoryCostPackageGuardResult> {\r\n const guardInput = await buildMemoryCostPackageGuardInput(input);\r\n return assertMemoryCostPackageVersionGuard(guardInput);\r\n}\r\n\r\nexport function shouldEnforceMemoryCostPackageGuardCli(scope?: string, action?: string): boolean {\r\n if (!scope) return false;\r\n if (scope === \"daemon\") return true;\r\n if (scope === \"worker\") return true;\r\n if (scope === \"monitor\") return true;\r\n if (scope === \"run\" && (action === \"dispatch\" || action === \"sweep\" || action === \"reconcile\" || action === \"unblock\")) {\r\n return true;\r\n }\r\n if (scope === \"cron\" && action === \"tick\") return true;\r\n return false;\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": ["homedir", "path", "existsSync", "readFileSync", "path", "homedir", "path", "path", "homedir", "readFileSync", "path", "existsSync", "ok", "summary", "homedir", "path", "existsSync", "readFileSync", "fileURLToPath"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ /** Instruction block appended to verify_live harness dispatch tasks. */
2
+ export declare function formatVerifyLiveCompletionContract(): string;
@@ -86,14 +86,14 @@ var WORKER_PERSONA_CATALOG = [
86
86
  {
87
87
  slug: "lorentz",
88
88
  displayName: "Lorentz",
89
- description: "Review / testing \u2014 pre-landing and post-landing verification, report + deep review.",
89
+ description: "Deep/adversarial review lane expert for risk, correctness, and safety gates. Run adversarial review and validation gating.",
90
90
  dispatchLane: "review",
91
91
  defaultRoleLane: "report_reviewer"
92
92
  },
93
93
  {
94
94
  slug: "dalton",
95
95
  displayName: "Dalton",
96
- description: "Landing / merge execution \u2014 merge-ready PR landing and merge evidence only (no implementation).",
96
+ description: "Landing-only \u2014 merge-ready handoff and final verification evidence; no implementation ownership.",
97
97
  dispatchLane: "landing",
98
98
  defaultRoleLane: "implementer"
99
99
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/worker-persona-catalog.ts"],
4
- "sourcesContent": ["// Canonical Command Center worker persona catalog \u2014 single source of truth for\r\n// harness dispatch lanes, default plan role lanes, and persona slug validation.\r\n// AgentOS re-exports this module; do not duplicate lane maps elsewhere.\r\n\r\nexport const WORKER_PERSONA_DISPATCH_LANES = [\r\n \"implementation\",\r\n \"review\",\r\n \"landing\",\r\n] as const;\r\n\r\nexport type WorkerPersonaDispatchLane = (typeof WORKER_PERSONA_DISPATCH_LANES)[number];\r\n\r\nexport const WORKER_PERSONA_DEFAULT_ROLE_LANES = [\r\n \"plan_author\",\r\n \"implementer\",\r\n \"report_reviewer\",\r\n \"deep_reviewer\",\r\n \"runtime_verifier\",\r\n \"system\",\r\n] as const;\r\n\r\nexport type WorkerPersonaDefaultRoleLane = (typeof WORKER_PERSONA_DEFAULT_ROLE_LANES)[number];\r\n\r\nexport interface WorkerPersonaCatalogEntry {\r\n slug: string;\r\n displayName: string;\r\n description: string;\r\n /** Harness dispatch lane reserved for this persona (null = orchestration / no dispatch). */\r\n dispatchLane: WorkerPersonaDispatchLane | null;\r\n /** Default plan progress role when enriching harness tasks. */\r\n defaultRoleLane: WorkerPersonaDefaultRoleLane;\r\n}\r\n\r\nexport const WORKER_PERSONA_CATALOG: readonly WorkerPersonaCatalogEntry[] = [\r\n {\r\n slug: \"ghost\",\r\n displayName: \"Ghost\",\r\n description: \"Orchestration \u2014 coordinates lanes, handoffs, and operator decisions.\",\r\n dispatchLane: null,\r\n defaultRoleLane: \"system\",\r\n },\r\n {\r\n slug: \"astra\",\r\n displayName: \"Astra\",\r\n description: \"Planning \u2014 plan authoring, milestones, and operational flow design.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"plan_author\",\r\n },\r\n {\r\n slug: \"rhea\",\r\n displayName: \"Rhea\",\r\n description: \"Runtime / control-plane implementation \u2014 harness, dispatch, and AgentOS plumbing.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"mnemo\",\r\n displayName: \"Mnemo\",\r\n description: \"Memory-quality implementation \u2014 MARM lanes, retrieval hygiene, and consolidation.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"sentinel\",\r\n displayName: \"Sentinel\",\r\n description: \"Security / privacy implementation and security verification gates.\",\r\n dispatchLane: \"review\",\r\n defaultRoleLane: \"deep_reviewer\",\r\n },\r\n {\r\n slug: \"pixel\",\r\n displayName: \"Pixel\",\r\n description: \"Frontend \u2014 Command Center UI, dashboards, and client surfaces.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"schema\",\r\n displayName: \"Schema\",\r\n description: \"Data / migrations \u2014 Prisma schema, seeds, and persistence contracts.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"atlas\",\r\n displayName: \"Atlas\",\r\n description: \"Infra / reliability \u2014 deploy pipelines, observability, and runtime health.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"runtime_verifier\",\r\n },\r\n {\r\n slug: \"bridge\",\r\n displayName: \"Bridge\",\r\n description: \"Integrations / MCP \u2014 tool manifests, OpenClaw bridges, and cross-service wiring.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"catalyst\",\r\n displayName: \"Catalyst\",\r\n description: \"Product / domain \u2014 business rules, domain packs, and feature semantics.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"lorentz\",\r\n displayName: \"Lorentz\",\r\n description: \"Review / testing \u2014 pre-landing and post-landing verification, report + deep review.\",\r\n dispatchLane: \"review\",\r\n defaultRoleLane: \"report_reviewer\",\r\n },\r\n {\r\n slug: \"dalton\",\r\n displayName: \"Dalton\",\r\n description: \"Landing / merge execution \u2014 merge-ready PR landing and merge evidence only (no implementation).\",\r\n dispatchLane: \"landing\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n] as const;\r\n\r\nexport type WorkerPersonaCatalogSlug = (typeof WORKER_PERSONA_CATALOG)[number][\"slug\"];\r\n\r\nconst BY_SLUG = new Map(WORKER_PERSONA_CATALOG.map((entry) => [entry.slug, entry]));\r\n\r\nexport function getWorkerPersonaCatalogEntry(\r\n slug: string | null | undefined,\r\n): WorkerPersonaCatalogEntry | null {\r\n if (!slug) return null;\r\n const key = slug.trim().toLowerCase();\r\n return BY_SLUG.get(key) ?? null;\r\n}\r\n\r\n/** Dispatch lane reserved for a persona; null when orchestration-only or unknown. */\r\nexport function getPersonaDispatchLane(\r\n slug: string | null | undefined,\r\n): WorkerPersonaDispatchLane | null {\r\n return getWorkerPersonaCatalogEntry(slug)?.dispatchLane ?? null;\r\n}\r\n\r\nexport function getPersonaDefaultRoleLane(\r\n slug: string | null | undefined,\r\n): WorkerPersonaDefaultRoleLane | null {\r\n return getWorkerPersonaCatalogEntry(slug)?.defaultRoleLane ?? null;\r\n}\r\n\r\nexport function isKnownWorkerPersonaSlug(slug: string | null | undefined): boolean {\r\n if (!slug) return false;\r\n return BY_SLUG.has(slug.trim().toLowerCase());\r\n}\r\n\r\n/** Personas whose harness dispatch lane is `review` (derived \u2014 do not hardcode slugs). */\r\nexport function workerPersonaReviewSlugs(): readonly WorkerPersonaCatalogSlug[] {\r\n return WORKER_PERSONA_CATALOG.filter((entry) => entry.dispatchLane === \"review\").map(\r\n (entry) => entry.slug,\r\n );\r\n}\r\n\r\n/** Personas whose harness dispatch lane is `landing` (derived \u2014 do not hardcode slugs). */\r\nexport function workerPersonaLandingSlugs(): readonly WorkerPersonaCatalogSlug[] {\r\n return WORKER_PERSONA_CATALOG.filter((entry) => entry.dispatchLane === \"landing\").map(\r\n (entry) => entry.slug,\r\n );\r\n}\r\n"],
5
- "mappings": ";AAIO,IAAM,gCAAgC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AACF;AAIO,IAAM,oCAAoC;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAcO,IAAM,yBAA+D;AAAA,EAC1E;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AACF;AAIA,IAAM,UAAU,IAAI,IAAI,uBAAuB,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;AAE3E,SAAS,6BACd,MACkC;AAClC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,MAAM,KAAK,KAAK,EAAE,YAAY;AACpC,SAAO,QAAQ,IAAI,GAAG,KAAK;AAC7B;AAGO,SAAS,uBACd,MACkC;AAClC,SAAO,6BAA6B,IAAI,GAAG,gBAAgB;AAC7D;AAEO,SAAS,0BACd,MACqC;AACrC,SAAO,6BAA6B,IAAI,GAAG,mBAAmB;AAChE;AAEO,SAAS,yBAAyB,MAA0C;AACjF,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,QAAQ,IAAI,KAAK,KAAK,EAAE,YAAY,CAAC;AAC9C;AAGO,SAAS,2BAAgE;AAC9E,SAAO,uBAAuB,OAAO,CAAC,UAAU,MAAM,iBAAiB,QAAQ,EAAE;AAAA,IAC/E,CAAC,UAAU,MAAM;AAAA,EACnB;AACF;AAGO,SAAS,4BAAiE;AAC/E,SAAO,uBAAuB,OAAO,CAAC,UAAU,MAAM,iBAAiB,SAAS,EAAE;AAAA,IAChF,CAAC,UAAU,MAAM;AAAA,EACnB;AACF;",
4
+ "sourcesContent": ["// Canonical Command Center worker persona catalog \u2014 single source of truth for\r\n// harness dispatch lanes, default plan role lanes, and persona slug validation.\r\n// AgentOS re-exports this module; do not duplicate lane maps elsewhere.\r\n\r\nexport const WORKER_PERSONA_DISPATCH_LANES = [\r\n \"implementation\",\r\n \"review\",\r\n \"landing\",\r\n] as const;\r\n\r\nexport type WorkerPersonaDispatchLane = (typeof WORKER_PERSONA_DISPATCH_LANES)[number];\r\n\r\nexport const WORKER_PERSONA_DEFAULT_ROLE_LANES = [\r\n \"plan_author\",\r\n \"implementer\",\r\n \"report_reviewer\",\r\n \"deep_reviewer\",\r\n \"runtime_verifier\",\r\n \"system\",\r\n] as const;\r\n\r\nexport type WorkerPersonaDefaultRoleLane = (typeof WORKER_PERSONA_DEFAULT_ROLE_LANES)[number];\r\n\r\nexport interface WorkerPersonaCatalogEntry {\r\n slug: string;\r\n displayName: string;\r\n description: string;\r\n /** Harness dispatch lane reserved for this persona (null = orchestration / no dispatch). */\r\n dispatchLane: WorkerPersonaDispatchLane | null;\r\n /** Default plan progress role when enriching harness tasks. */\r\n defaultRoleLane: WorkerPersonaDefaultRoleLane;\r\n}\r\n\r\nexport const WORKER_PERSONA_CATALOG: readonly WorkerPersonaCatalogEntry[] = [\r\n {\r\n slug: \"ghost\",\r\n displayName: \"Ghost\",\r\n description: \"Orchestration \u2014 coordinates lanes, handoffs, and operator decisions.\",\r\n dispatchLane: null,\r\n defaultRoleLane: \"system\",\r\n },\r\n {\r\n slug: \"astra\",\r\n displayName: \"Astra\",\r\n description: \"Planning \u2014 plan authoring, milestones, and operational flow design.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"plan_author\",\r\n },\r\n {\r\n slug: \"rhea\",\r\n displayName: \"Rhea\",\r\n description: \"Runtime / control-plane implementation \u2014 harness, dispatch, and AgentOS plumbing.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"mnemo\",\r\n displayName: \"Mnemo\",\r\n description: \"Memory-quality implementation \u2014 MARM lanes, retrieval hygiene, and consolidation.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"sentinel\",\r\n displayName: \"Sentinel\",\r\n description: \"Security / privacy implementation and security verification gates.\",\r\n dispatchLane: \"review\",\r\n defaultRoleLane: \"deep_reviewer\",\r\n },\r\n {\r\n slug: \"pixel\",\r\n displayName: \"Pixel\",\r\n description: \"Frontend \u2014 Command Center UI, dashboards, and client surfaces.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"schema\",\r\n displayName: \"Schema\",\r\n description: \"Data / migrations \u2014 Prisma schema, seeds, and persistence contracts.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"atlas\",\r\n displayName: \"Atlas\",\r\n description: \"Infra / reliability \u2014 deploy pipelines, observability, and runtime health.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"runtime_verifier\",\r\n },\r\n {\r\n slug: \"bridge\",\r\n displayName: \"Bridge\",\r\n description: \"Integrations / MCP \u2014 tool manifests, OpenClaw bridges, and cross-service wiring.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"catalyst\",\r\n displayName: \"Catalyst\",\r\n description: \"Product / domain \u2014 business rules, domain packs, and feature semantics.\",\r\n dispatchLane: \"implementation\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n {\r\n slug: \"lorentz\",\r\n displayName: \"Lorentz\",\r\n description:\r\n \"Deep/adversarial review lane expert for risk, correctness, and safety gates. Run adversarial review and validation gating.\",\r\n dispatchLane: \"review\",\r\n defaultRoleLane: \"report_reviewer\",\r\n },\r\n {\r\n slug: \"dalton\",\r\n displayName: \"Dalton\",\r\n description:\r\n \"Landing-only \u2014 merge-ready handoff and final verification evidence; no implementation ownership.\",\r\n dispatchLane: \"landing\",\r\n defaultRoleLane: \"implementer\",\r\n },\r\n] as const;\r\n\r\nexport type WorkerPersonaCatalogSlug = (typeof WORKER_PERSONA_CATALOG)[number][\"slug\"];\r\n\r\nconst BY_SLUG = new Map(WORKER_PERSONA_CATALOG.map((entry) => [entry.slug, entry]));\r\n\r\nexport function getWorkerPersonaCatalogEntry(\r\n slug: string | null | undefined,\r\n): WorkerPersonaCatalogEntry | null {\r\n if (!slug) return null;\r\n const key = slug.trim().toLowerCase();\r\n return BY_SLUG.get(key) ?? null;\r\n}\r\n\r\n/** Dispatch lane reserved for a persona; null when orchestration-only or unknown. */\r\nexport function getPersonaDispatchLane(\r\n slug: string | null | undefined,\r\n): WorkerPersonaDispatchLane | null {\r\n return getWorkerPersonaCatalogEntry(slug)?.dispatchLane ?? null;\r\n}\r\n\r\nexport function getPersonaDefaultRoleLane(\r\n slug: string | null | undefined,\r\n): WorkerPersonaDefaultRoleLane | null {\r\n return getWorkerPersonaCatalogEntry(slug)?.defaultRoleLane ?? null;\r\n}\r\n\r\nexport function isKnownWorkerPersonaSlug(slug: string | null | undefined): boolean {\r\n if (!slug) return false;\r\n return BY_SLUG.has(slug.trim().toLowerCase());\r\n}\r\n\r\n/** Personas whose harness dispatch lane is `review` (derived \u2014 do not hardcode slugs). */\r\nexport function workerPersonaReviewSlugs(): readonly WorkerPersonaCatalogSlug[] {\r\n return WORKER_PERSONA_CATALOG.filter((entry) => entry.dispatchLane === \"review\").map(\r\n (entry) => entry.slug,\r\n );\r\n}\r\n\r\n/** Personas whose harness dispatch lane is `landing` (derived \u2014 do not hardcode slugs). */\r\nexport function workerPersonaLandingSlugs(): readonly WorkerPersonaCatalogSlug[] {\r\n return WORKER_PERSONA_CATALOG.filter((entry) => entry.dispatchLane === \"landing\").map(\r\n (entry) => entry.slug,\r\n );\r\n}\r\n"],
5
+ "mappings": ";AAIO,IAAM,gCAAgC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AACF;AAIO,IAAM,oCAAoC;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAcO,IAAM,yBAA+D;AAAA,EAC1E;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aACE;AAAA,IACF,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aACE;AAAA,IACF,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB;AACF;AAIA,IAAM,UAAU,IAAI,IAAI,uBAAuB,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;AAE3E,SAAS,6BACd,MACkC;AAClC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,MAAM,KAAK,KAAK,EAAE,YAAY;AACpC,SAAO,QAAQ,IAAI,GAAG,KAAK;AAC7B;AAGO,SAAS,uBACd,MACkC;AAClC,SAAO,6BAA6B,IAAI,GAAG,gBAAgB;AAC7D;AAEO,SAAS,0BACd,MACqC;AACrC,SAAO,6BAA6B,IAAI,GAAG,mBAAmB;AAChE;AAEO,SAAS,yBAAyB,MAA0C;AACjF,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,QAAQ,IAAI,KAAK,KAAK,EAAE,YAAY,CAAC;AAC9C;AAGO,SAAS,2BAAgE;AAC9E,SAAO,uBAAuB,OAAO,CAAC,UAAU,MAAM,iBAAiB,QAAQ,EAAE;AAAA,IAC/E,CAAC,UAAU,MAAM;AAAA,EACnB;AACF;AAGO,SAAS,4BAAiE;AAC/E,SAAO,uBAAuB,OAAO,CAAC,UAAU,MAAM,iBAAiB,SAAS,EAAE;AAAA,IAChF,CAAC,UAAU,MAAM;AAAA,EACnB;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kynver-app/runtime",
3
- "version": "0.1.105",
3
+ "version": "0.1.108",
4
4
  "description": "Kynver AgentOS local execution runtime and CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",