@serviceme/devtools-core 0.2.0 → 0.3.0

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 +1 @@
1
- {"version":3,"sources":["../src/agents/AgentCatalogClient.ts","../src/permissions/agent-permissions.ts","../src/agents/AgentReconciler.ts","../src/agents/AgentStore.ts","../src/auth/AccessControl.ts","../src/auth/AuthStateManager.ts","../src/auth/ProviderRegistry.ts","../src/auth/AuthCore.ts","../src/auth/KeychainAuthTokenStore.ts","../src/auth/utils/githubUserEmail.ts","../src/auth/providers/GitHubAuthProvider.ts","../src/auth/providers/MicrosoftAuthProvider.ts","../src/copilot/doctor.ts","../src/process/runCommand.ts","../src/copilot/prompt.ts","../src/device/deviceAuth.ts","../src/device/Enroller.ts","../src/device/InstallationId.ts","../src/device/IdentityStore.ts","../src/paths/userHome.ts","../src/device/types.ts","../src/device/DeviceCore.ts","../src/drafts/index.ts","../src/skill-store/index.ts","../src/repo-layout/index.ts","../src/env/environmentInspector.ts","../src/git-client/index.ts","../src/git-client/types.ts","../src/image/imageTools.ts","../src/json/jsonTools.ts","../src/logger.ts","../src/phase5/bootstrap.ts","../src/project/projectTools.ts","../src/utils/fileUtils.ts","../src/repo-manager/index.ts","../src/repos/types.ts","../src/repos/default-repos.ts","../src/repos/loader.ts","../src/repos/store.ts","../src/scheduled-tasks/daemon/DaemonLogger.ts","../src/scheduled-tasks/daemon/PidManager.ts","../src/scheduled-tasks/daemon/SchedulerDaemon.ts","../src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts","../src/scheduled-tasks/executors/timeout.ts","../src/scheduled-tasks/executors/HttpRequestExecutor.ts","../src/scheduled-tasks/executors/ShellExecutor.ts","../src/scheduled-tasks/executors/types.ts","../src/scheduled-tasks/executors/index.ts","../src/scheduled-tasks/TaskConfigManager.ts","../src/scheduled-tasks/TaskExecutionEngine.ts","../src/scheduled-tasks/TaskLogManager.ts","../src/scheduled-tasks/daemon/SchedulerDaemonV2.ts","../src/scheduled-tasks/migration/MigrateToGlobal.ts","../src/scheduled-tasks/workspace-probe/WorkspaceProbe.ts","../src/skills/SkillCatalogClient.ts","../src/skills/SkillReconciler.ts","../src/skills/SkillStore.ts","../src/submit/index.ts","../src/submit/types.ts","../src/toolbox/sort.ts","../src/toolbox/ToolboxStore.ts","../src/toolbox/types.ts","../src/toolbox/ToolboxCore.ts"],"sourcesContent":["import type { AgentMarketplaceEntry } from \"@serviceme/devtools-protocol\";\nimport { createServicemeError } from \"@serviceme/devtools-protocol\";\nimport type { AgentDownloadFile } from \"./types\";\n\nexport interface AgentCatalog {\n\tagents: AgentMarketplaceEntry[];\n\tfetchedAt: string;\n}\n\nexport interface AgentCatalogClientOptions {\n\tfetchImpl?: typeof fetch;\n\tbaseUrl?: string;\n}\n\nexport class AgentCatalogClient {\n\tprivate readonly fetchImpl: typeof fetch;\n\tprivate readonly baseUrl?: string;\n\n\tconstructor(options: AgentCatalogClientOptions = {}) {\n\t\tthis.fetchImpl = options.fetchImpl ?? fetch;\n\t\tthis.baseUrl = options.baseUrl;\n\t}\n\n\tasync getCatalog(): Promise<AgentCatalog> {\n\t\tif (!this.baseUrl) {\n\t\t\tthrow createServicemeError(\"workspace_not_found\", \"Agent catalog baseUrl is not configured.\");\n\t\t}\n\n\t\tconst response = await this.fetchImpl(`${this.baseUrl}/api/v1/marketplace/agents`);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to fetch agents catalog: ${response.status}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as {\n\t\t\tagents?: AgentMarketplaceEntry[];\n\t\t};\n\t\treturn {\n\t\t\tagents: data.agents ?? [],\n\t\t\tfetchedAt: new Date().toISOString(),\n\t\t};\n\t}\n\n\tasync downloadAgent(remoteId: string): Promise<AgentDownloadFile[]> {\n\t\tif (!this.baseUrl) {\n\t\t\tthrow createServicemeError(\"workspace_not_found\", \"Agent catalog baseUrl is not configured.\");\n\t\t}\n\n\t\tconst response = await this.fetchImpl(\n\t\t\t`${this.baseUrl}/api/v1/marketplace/agents/download/${remoteId}`\n\t\t);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 404) {\n\t\t\t\tthrow createServicemeError(\"not_found\", `Agent '${remoteId}' not found`);\n\t\t\t}\n\t\t\tthrow new Error(`Failed to download agent ${remoteId}: ${response.status}`);\n\t\t}\n\n\t\tconst payload = (await response.json()) as {\n\t\t\tdata?: { files?: AgentDownloadFile[] };\n\t\t};\n\t\treturn payload.data?.files ?? [];\n\t}\n}\n","import type { AgentToolPermission, AgentToolRiskLevel } from \"@serviceme/devtools-protocol\";\n\nexport const TOOL_RISK_MAP: Record<string, AgentToolRiskLevel> = {\n\tshell: \"high\",\n\tterminal: \"high\",\n\trun_in_terminal: \"high\",\n\texecution_subagent: \"high\",\n\tfilesystem: \"medium\",\n\tfetch: \"medium\",\n\tfetch_webpage: \"medium\",\n\tcreate_file: \"medium\",\n\treplace_string_in_file: \"medium\",\n\tmulti_replace_string_in_file: \"medium\",\n\tread_file: \"low\",\n\tsearch: \"low\",\n\tgrep_search: \"low\",\n\tfile_search: \"low\",\n\tsemantic_search: \"low\",\n\tlist_dir: \"low\",\n};\n\nconst FRONTMATTER_REGEX = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/;\nconst TOOLS_LINE_REGEX = /^tools:\\s*$/m;\nconst TOOLS_INLINE_REGEX = /^tools:\\s*\\[([^\\]]*)\\]/m;\nconst LIST_ITEM_REGEX = /^\\s*-\\s+(.+)$/;\n\nexport function parseAgentToolPermissions(content: string): AgentToolPermission[] {\n\tconst fmMatch = content.match(FRONTMATTER_REGEX);\n\tif (!fmMatch?.[1]) return [];\n\n\tconst frontmatter = fmMatch[1];\n\n\tconst inlineMatch = frontmatter.match(TOOLS_INLINE_REGEX);\n\tif (inlineMatch?.[1] != null) {\n\t\tconst raw = inlineMatch[1];\n\t\treturn raw\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean)\n\t\t\t.map((tool) => ({\n\t\t\t\ttool,\n\t\t\t\triskLevel: TOOL_RISK_MAP[tool] ?? \"medium\",\n\t\t\t}));\n\t}\n\n\tconst blockMatch = frontmatter.match(TOOLS_LINE_REGEX);\n\tif (!blockMatch?.[0]) return [];\n\n\tconst toolsStartIndex = frontmatter.indexOf(blockMatch[0]) + blockMatch[0].length;\n\tconst remaining = frontmatter.slice(toolsStartIndex);\n\tconst lines = remaining.split(/\\r?\\n/);\n\tconst tools: AgentToolPermission[] = [];\n\n\tfor (const line of lines) {\n\t\tconst itemMatch = line.match(LIST_ITEM_REGEX);\n\t\tif (itemMatch?.[1]) {\n\t\t\tconst tool = itemMatch[1].trim();\n\t\t\ttools.push({ tool, riskLevel: TOOL_RISK_MAP[tool] ?? \"medium\" });\n\t\t} else if (line.trim() !== \"\" && !line.startsWith(\" \") && !line.startsWith(\"\\t\")) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn tools;\n}\n","import type {\n\tAgentMarketplaceEntry,\n\tAgentMutationRequest,\n\tAgentPermissionSummary,\n} from \"@serviceme/devtools-protocol\";\nimport { parseAgentToolPermissions, TOOL_RISK_MAP } from \"../permissions\";\n\ninterface AgentStoreLike {\n\tnormalizeRemoteAgentId(remoteId: string): string;\n\tlistWorkspaceAgentIds(): Promise<string[]>;\n\tlistUserAgentIds(): Promise<string[]>;\n}\n\ninterface AgentCatalogLike {\n\tagents: AgentMarketplaceEntry[];\n\tfetchedAt: string;\n}\n\ninterface AgentCatalogClientLike {\n\tgetCatalog(): Promise<AgentCatalogLike>;\n}\n\nexport interface AgentReconcilerDependencies {\n\tagentStore: AgentStoreLike;\n\tcatalogClient: AgentCatalogClientLike;\n}\n\nexport interface AgentMutateResult {\n\tstatus: \"success\" | \"blocked\" | \"requires_confirmation\";\n\tchanged: boolean;\n\tmessage?: string;\n\ttools?: string[];\n}\n\nexport class AgentReconciler {\n\tconstructor(private readonly deps: AgentReconcilerDependencies) {}\n\n\tasync mutate(request: AgentMutationRequest): Promise<AgentMutateResult> {\n\t\tif (request.targetScope !== \"workspace\" && request.targetScope !== \"user\") {\n\t\t\tthrow new Error(`Invalid target scope: ${String(request.targetScope)}`);\n\t\t}\n\n\t\tif (\n\t\t\trequest.action === \"uninstall\" ||\n\t\t\trequest.action === \"move\" ||\n\t\t\trequest.action === \"removeExternal\"\n\t\t) {\n\t\t\treturn {\n\t\t\t\tstatus: \"success\",\n\t\t\t\tchanged: true,\n\t\t\t\tmessage: `Agent ${request.action} completed.`,\n\t\t\t};\n\t\t}\n\n\t\tif (request.action !== \"install\") {\n\t\t\treturn {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: `Agent action is not supported by bridge reconciler: ${request.action}`,\n\t\t\t};\n\t\t}\n\n\t\tconst catalog = await this.deps.catalogClient.getCatalog();\n\t\tconst remoteAgent = catalog.agents.find(\n\t\t\t(agent) => this.deps.agentStore.normalizeRemoteAgentId(agent.id) === request.agentId\n\t\t);\n\n\t\tif (!remoteAgent) {\n\t\t\treturn {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: \"Agent not found in catalog.\",\n\t\t\t};\n\t\t}\n\n\t\tif (!request.confirmed && this.hasHighRiskTool(remoteAgent.tools)) {\n\t\t\treturn {\n\t\t\t\tstatus: \"requires_confirmation\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: \"This agent uses high-risk tools that require confirmation.\",\n\t\t\t\ttools: remoteAgent.tools,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: \"success\",\n\t\t\tchanged: true,\n\t\t\tmessage: \"Agent installed.\",\n\t\t};\n\t}\n\n\tgetPermissionSummary(\n\t\tagentId: string,\n\t\tagentName: string,\n\t\tcontent: string\n\t): AgentPermissionSummary {\n\t\tconst tools = parseAgentToolPermissions(content);\n\t\treturn {\n\t\t\tagentId,\n\t\t\tagentName,\n\t\t\ttools,\n\t\t\thighRiskCount: tools.filter((tool) => tool.riskLevel === \"high\").length,\n\t\t\tmediumRiskCount: tools.filter((tool) => tool.riskLevel === \"medium\").length,\n\t\t\tlowRiskCount: tools.filter((tool) => tool.riskLevel === \"low\").length,\n\t\t};\n\t}\n\n\tprivate hasHighRiskTool(tools: string[]): boolean {\n\t\treturn tools.some((tool) => TOOL_RISK_MAP[tool] === \"high\");\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type {\n\tAgentDownloadFile,\n\tAgentStoreFileSystem,\n\tAgentStoreOptions,\n\tAgentsStateFile,\n\tInstalledAgent,\n} from \"./types\";\n\nconst WORKSPACE_AGENTS_ROOT_RELATIVE = \".github/agents\";\nconst WORKSPACE_AGENTS_STATE_RELATIVE = \".github/.serviceme-agents.yml\";\nconst LEGACY_WORKSPACE_AGENTS_STATE_RELATIVE = \".github/.ms-devtools-agents.yml\";\nconst SAFE_LOCAL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\n\nfunction assertSafeLocalAgentId(agentId: string): string {\n\tif (\n\t\ttypeof agentId !== \"string\" ||\n\t\tagentId.length === 0 ||\n\t\tagentId === \".\" ||\n\t\tagentId === \"..\" ||\n\t\tagentId.includes(\"/\") ||\n\t\tagentId.includes(\"\\\\\") ||\n\t\t!SAFE_LOCAL_ID_PATTERN.test(agentId)\n\t) {\n\t\tthrow new Error(`Invalid agent id: ${agentId}`);\n\t}\n\n\treturn agentId;\n}\n\nexport class AgentStore {\n\tprivate readonly workspacePath: string;\n\tprivate readonly userAgentsRoot: string;\n\tprivate readonly fileSystem: AgentStoreFileSystem;\n\tprivate readonly schemaVersion: number;\n\n\tconstructor(options: AgentStoreOptions) {\n\t\tthis.workspacePath = options.workspacePath;\n\t\tthis.userAgentsRoot = options.userAgentsRoot;\n\t\tthis.fileSystem = options.fileSystem ?? fs;\n\t\tthis.schemaVersion = options.schemaVersion ?? 1;\n\t}\n\n\tnormalizeRemoteAgentId(remoteId: string): string {\n\t\tif (remoteId.startsWith(\"official/\")) {\n\t\t\treturn assertSafeLocalAgentId(remoteId.slice(\"official/\".length));\n\t\t}\n\t\tif (remoteId.startsWith(\"community/\")) {\n\t\t\tconst lastSlash = remoteId.lastIndexOf(\"/\");\n\t\t\treturn assertSafeLocalAgentId(remoteId.slice(lastSlash + 1));\n\t\t}\n\t\treturn assertSafeLocalAgentId(remoteId);\n\t}\n\n\tgetWorkspaceAgentsRootPath(): string {\n\t\treturn WORKSPACE_AGENTS_ROOT_RELATIVE;\n\t}\n\n\tgetWorkspaceStateFilePath(): string {\n\t\treturn WORKSPACE_AGENTS_STATE_RELATIVE;\n\t}\n\n\tgetUserAgentsRootPath(): string {\n\t\treturn this.userAgentsRoot;\n\t}\n\n\tasync listWorkspaceAgentIds(): Promise<string[]> {\n\t\treturn this.listAgentIds(path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE));\n\t}\n\n\tasync listUserAgentIds(): Promise<string[]> {\n\t\treturn this.listAgentIds(this.userAgentsRoot);\n\t}\n\n\tasync readState(): Promise<AgentsStateFile | null> {\n\t\tawait this.migrateLegacyState();\n\t\ttry {\n\t\t\tconst raw = await this.fileSystem.readFile(\n\t\t\t\tpath.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE),\n\t\t\t\t\"utf-8\"\n\t\t\t);\n\t\t\tconst parsed = JSON.parse(raw) as AgentsStateFile;\n\t\t\tif (typeof parsed.schemaVersion !== \"number\" || !Array.isArray(parsed.installedAgents)) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn parsed;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * One-time migration: the workspace agents state file used to be named\n\t * `.github/.ms-devtools-agents.yml`. If the new `.serviceme-agents.yml`\n\t * doesn't exist yet but the legacy file does, copy its content forward\n\t * so existing installed-agent state isn't silently lost.\n\t */\n\tprivate async migrateLegacyState(): Promise<void> {\n\t\tconst newPath = path.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE);\n\t\tconst legacyPath = path.join(this.workspacePath, LEGACY_WORKSPACE_AGENTS_STATE_RELATIVE);\n\t\ttry {\n\t\t\tawait this.fileSystem.readFile(newPath, \"utf-8\");\n\t\t\treturn;\n\t\t} catch {\n\t\t\t// new file missing — check the legacy path below\n\t\t}\n\t\ttry {\n\t\t\tconst legacyContent = await this.fileSystem.readFile(legacyPath, \"utf-8\");\n\t\t\tawait this.fileSystem.mkdir(path.dirname(newPath), { recursive: true });\n\t\t\tawait this.fileSystem.writeFile(newPath, legacyContent, \"utf-8\");\n\t\t} catch {\n\t\t\t// legacy file doesn't exist either — nothing to migrate\n\t\t}\n\t}\n\n\tasync writeState(state: AgentsStateFile): Promise<void> {\n\t\tconst statePath = path.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE);\n\t\tawait this.fileSystem.mkdir(path.dirname(statePath), { recursive: true });\n\t\tawait this.fileSystem.writeFile(statePath, JSON.stringify(state, null, 2), \"utf-8\");\n\t}\n\n\tasync addInstalledAgent(entry: InstalledAgent): Promise<void> {\n\t\tconst state =\n\t\t\t(await this.readState()) ??\n\t\t\t({\n\t\t\t\tschemaVersion: this.schemaVersion,\n\t\t\t\tinstalledAgents: [],\n\t\t\t} as AgentsStateFile);\n\t\tstate.installedAgents = state.installedAgents.filter((agent) => agent.id !== entry.id);\n\t\tstate.installedAgents.push(entry);\n\t\tawait this.writeState(state);\n\t}\n\n\tasync removeInstalledAgent(agentId: string): Promise<void> {\n\t\tconst state = await this.readState();\n\t\tif (!state) {\n\t\t\treturn;\n\t\t}\n\t\tstate.installedAgents = state.installedAgents.filter((agent) => agent.id !== agentId);\n\t\tawait this.writeState(state);\n\t}\n\n\tasync writeAgentFiles(\n\t\tagentId: string,\n\t\tscope: \"workspace\" | \"user\",\n\t\tfiles: AgentDownloadFile[]\n\t): Promise<void> {\n\t\tconst root =\n\t\t\tscope === \"workspace\"\n\t\t\t\t? path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE)\n\t\t\t\t: this.userAgentsRoot;\n\t\tconst firstFile = files[0];\n\t\tconst isSingleFlatFile =\n\t\t\tfiles.length === 1 &&\n\t\t\tfirstFile !== undefined &&\n\t\t\tfirstFile.path === `${agentId}.agent.md` &&\n\t\t\t!firstFile.path.includes(\"/\");\n\t\tconst targetDir = isSingleFlatFile ? root : path.join(root, agentId);\n\t\tawait this.fileSystem.mkdir(targetDir, { recursive: true });\n\n\t\tfor (const file of files) {\n\t\t\tconst filePath = path.join(targetDir, file.path);\n\t\t\tawait this.fileSystem.mkdir(path.dirname(filePath), { recursive: true });\n\t\t\tawait this.fileSystem.writeFile(filePath, file.content, \"utf-8\");\n\t\t\tif (file.executable) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.fileSystem.chmod(filePath, 0o755);\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore chmod failures on unsupported environments\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async listAgentIds(dir: string): Promise<string[]> {\n\t\ttry {\n\t\t\tconst entries = await this.fileSystem.readdir(dir, {\n\t\t\t\twithFileTypes: true,\n\t\t\t});\n\t\t\tconst ids: string[] = [];\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tids.push(entry.name);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (entry.isFile() && entry.name.endsWith(\".agent.md\")) {\n\t\t\t\t\tids.push(entry.name.replace(/\\.agent\\.md$/, \"\"));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ids.sort();\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n}\n","/**\n * AccessControl — Pure logic for org-membership gating.\n *\n * Ported from `apps/extension/src/services/auth/AccessControlService.ts`\n * but stripped of all VSCode dependencies:\n * - No `vscode.window.showWarningMessage` — callers render the notice.\n * - No `vscode.context.globalState` — callers inject a `KeyValueStore`.\n * - No `vscode.Event` — uses a plain `onDidChange` callback.\n *\n * The class is generic over the membership-fetcher function so it can\n * run identically with the CLI's `@serviceme/shared` `getGitHubOrgMembership`\n * or with the Extension's bundled copy. ADL-003 forbids Core from\n * importing `@serviceme/shared` directly, so the org membership\n * result type is defined locally and the runtime adapter wraps the\n * shared helper at the boundary (Phase 5.3+).\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AccessControl.ts 从 AccessControlService 改写`\n * - ADL-003 — `@serviceme/shared` boundary (Core MUST NOT import shared)\n */\n\nimport type { AuthAccountMeta, AuthProvider } from \"@serviceme/devtools-protocol\";\n\n/** Core-local mirror of `GitHubOrgMembershipCheckResult` (defined in `@serviceme/shared`). */\nexport interface CoreOrgMembershipResult {\n\tstatus: \"active\" | \"pending\" | \"not_member\" | \"unknown\";\n\thttpStatus?: number;\n\trole?: string;\n\tdirectMembership?: boolean;\n}\n\n/**\n * Upstream org-membership fetcher. CLI / Extension adapters wrap\n * `getGitHubOrgMembership` (or the local extension copy) to match\n * this signature.\n */\nexport type OrgMembershipFetcher = (token: string, org: string) => Promise<CoreOrgMembershipResult>;\n\n/** Pluggable key/value store. Extension injects `globalState`-backed impl; CLI injects a file-backed impl. */\nexport interface KeyValueStore {\n\tget<T>(key: string): T | undefined;\n\tupdate<T>(key: string, value: T): Promise<void>;\n}\n\nexport interface AccessCheckResult {\n\tallowed: boolean;\n\treason?: \"not_authenticated\" | \"not_member\";\n\tusername?: string;\n}\n\nexport interface AccessControlOptions {\n\torg: string;\n\t/** Domains that auto-qualify Microsoft users without a GitHub link. */\n\tmicrosoftEmailDomains?: readonly string[];\n\t/** TTL for in-memory membership cache (ms). */\n\tcacheTtlMs?: number;\n\t/** TTL for persisted membership cache (ms). */\n\tpersistentCacheTtlMs?: number;\n\t/** Tokens whose bearer can be supplied to `OrgMembershipFetcher`. */\n\ttokenFetcher: (provider: AuthProvider) => Promise<string | null>;\n\t/** Upstream org-membership fetcher (typically `getGitHubOrgMembership` from `@serviceme/shared`). */\n\torgFetcher: OrgMembershipFetcher;\n\t/** Clock seam for tests. */\n\tnow?: () => number;\n}\n\nconst DEFAULT_CACHE_TTL_MS = 10 * 60 * 1000;\nconst DEFAULT_PERSISTENT_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\nconst KEY_GRANTED_USERS = \"msDevTools.accessControl.grantedUsers\";\nconst KEY_MEMBERSHIP_CACHE = \"msDevTools.accessControl.membershipCache\";\nconst KEY_SERVER_IN_ORG = \"msDevTools.accessControl.serverInOrg\";\n\ninterface CacheEntry {\n\tisMember: boolean;\n\tat: number;\n}\n\ninterface PersistentCache {\n\t[username: string]: CacheEntry;\n}\n\nexport class AccessControl {\n\tprivate readonly memCache = new Map<string, CacheEntry>();\n\tprivate readonly serverInOrgMem = new Map<string, boolean>();\n\tprivate readonly listeners = new Set<() => void>();\n\tprivate readonly opts: Required<\n\t\tOmit<AccessControlOptions, \"now\" | \"tokenFetcher\" | \"microsoftEmailDomains\" | \"orgFetcher\">\n\t> & {\n\t\ttokenFetcher: AccessControlOptions[\"tokenFetcher\"];\n\t\torgFetcher: OrgMembershipFetcher;\n\t\tmicrosoftEmailDomains: readonly string[];\n\t\tnow: () => number;\n\t};\n\n\tconstructor(\n\t\tprivate readonly kv: KeyValueStore,\n\t\toptions: AccessControlOptions\n\t) {\n\t\tthis.opts = {\n\t\t\torg: options.org,\n\t\t\ttokenFetcher: options.tokenFetcher,\n\t\t\torgFetcher: options.orgFetcher,\n\t\t\tmicrosoftEmailDomains: options.microsoftEmailDomains ?? [],\n\t\t\tcacheTtlMs: options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,\n\t\t\tpersistentCacheTtlMs: options.persistentCacheTtlMs ?? DEFAULT_PERSISTENT_CACHE_TTL_MS,\n\t\t\tnow: options.now ?? (() => Date.now()),\n\t\t};\n\t\t// Hydrate `serverInOrg` from persistent storage so the gate can run instantly on restart.\n\t\tconst persistedServerInOrg = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG);\n\t\tif (persistedServerInOrg) {\n\t\t\tfor (const [key, value] of Object.entries(persistedServerInOrg)) {\n\t\t\t\tthis.serverInOrgMem.set(key, value);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Subscribe to inOrg changes (used by extension to refresh access-gated UI). */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\t/** Full check: requires authentication + org membership / admin grant. */\n\tasync checkAccess(user: AuthAccountMeta | null): Promise<AccessCheckResult> {\n\t\tif (!user) {\n\t\t\treturn { allowed: false, reason: \"not_authenticated\" };\n\t\t}\n\n\t\t// Microsoft users: server-confirmed inOrg takes priority over GitHub link.\n\t\tif (user.provider === \"microsoft\" && user.login) {\n\t\t\tconst inOrg = this.serverInOrgMem.get(user.login.toLowerCase());\n\t\t\tif (inOrg === true) return { allowed: true };\n\t\t\tif (inOrg === false) {\n\t\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t\t}\n\t\t}\n\n\t\t// Find a GitHub account if available (linked for Microsoft users).\n\t\tconst githubAccount = user.provider === \"github\" ? user : null;\n\t\tif (!githubAccount && user.provider === \"microsoft\" && user.login) {\n\t\t\tconst [, domain = \"\"] = user.login.split(\"@\");\n\t\t\tconst isDomainMember =\n\t\t\t\tdomain.length > 0 && this.opts.microsoftEmailDomains.includes(domain.toLowerCase());\n\t\t\tif (isDomainMember) return { allowed: true };\n\t\t\t// No GitHub link + unknown domain — deny until the next server-confirmed claim.\n\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t}\n\t\tif (!githubAccount) {\n\t\t\t// No identity at all — treat as authenticated for now (caller may have other gating).\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst accessUsername = githubAccount.login ?? user.login ?? \"\";\n\t\tif (accessUsername.length === 0) {\n\t\t\treturn { allowed: false, reason: \"not_member\", username: \"\" };\n\t\t}\n\n\t\t// Admin-granted users bypass the org check.\n\t\tconst granted = this.getGrantedUsers();\n\t\tif (granted.includes(accessUsername.toLowerCase())) {\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst isMember = await this.checkOrgMembership(accessUsername);\n\t\treturn isMember\n\t\t\t? { allowed: true }\n\t\t\t: { allowed: false, reason: \"not_member\", username: accessUsername };\n\t}\n\n\t/** Admin: grant access to a GitHub username (bypasses org check). */\n\tasync grantAccess(username: string): Promise<void> {\n\t\tconst list = this.getGrantedUsers();\n\t\tconst key = username.toLowerCase();\n\t\tif (!list.includes(key)) {\n\t\t\tlist.push(key);\n\t\t\tawait this.kv.update(KEY_GRANTED_USERS, list);\n\t\t}\n\t}\n\n\t/** Admin: revoke a previously granted access. */\n\tasync revokeAccess(username: string): Promise<void> {\n\t\tconst updated = this.getGrantedUsers().filter((u) => u !== username.toLowerCase());\n\t\tawait this.kv.update(KEY_GRANTED_USERS, updated);\n\t}\n\n\t/** Snapshot of all admin-granted usernames (lowercased). */\n\tgetGrantedUsers(): string[] {\n\t\treturn this.kv.get<string[]>(KEY_GRANTED_USERS) ?? [];\n\t}\n\n\t/** Cached membership lookup with both in-memory + persistent layers. */\n\tasync checkOrgMembership(username: string): Promise<boolean> {\n\t\tconst now = this.opts.now();\n\n\t\t// 1. In-memory cache.\n\t\tconst inMem = this.memCache.get(username);\n\t\tif (inMem && now - inMem.at < this.opts.cacheTtlMs) {\n\t\t\treturn inMem.isMember;\n\t\t}\n\n\t\t// 2. Persistent cache.\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tconst persistentEntry = persistent[username];\n\t\tif (persistentEntry && now - persistentEntry.at < this.opts.persistentCacheTtlMs) {\n\t\t\tthis.memCache.set(username, persistentEntry);\n\t\t\treturn persistentEntry.isMember;\n\t\t}\n\n\t\t// 3. Hit upstream GitHub API via the injected fetcher.\n\t\tconst token = await this.opts.tokenFetcher(\"github\");\n\t\tif (!token) return false;\n\n\t\ttry {\n\t\t\tconst result = await this.opts.orgFetcher(token, this.opts.org);\n\t\t\tconst decision = this.resolveDecision(username, result);\n\t\t\tif (typeof decision === \"boolean\") {\n\t\t\t\tthis.updateCache(username, decision);\n\t\t\t\treturn decision;\n\t\t\t}\n\t\t\t// Indeterminate — fail-open.\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// Fail-open on transient network errors.\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/** Server-confirmed inOrg status (set by device claim flow). */\n\tsetServerInOrg(username: string, inOrg: boolean): void {\n\t\tconst key = username.toLowerCase();\n\t\tthis.serverInOrgMem.set(key, inOrg);\n\t\tconst persisted = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG) ?? {};\n\t\tpersisted[key] = inOrg;\n\t\tvoid this.kv.update(KEY_SERVER_IN_ORG, persisted);\n\t\tthis.notifyListeners();\n\t}\n\n\t/** Inspect server-confirmed inOrg for a username; returns undefined when unknown. */\n\tgetServerInOrg(username: string): boolean | undefined {\n\t\treturn this.serverInOrgMem.get(username.toLowerCase());\n\t}\n\n\tprivate resolveDecision(\n\t\t_username: string,\n\t\tmembership: CoreOrgMembershipResult\n\t): boolean | undefined {\n\t\tif (membership.status === \"active\" || membership.status === \"pending\") return true;\n\t\tif (membership.status === \"not_member\") return false;\n\t\treturn undefined;\n\t}\n\n\tprivate updateCache(username: string, isMember: boolean): void {\n\t\tconst at = this.opts.now();\n\t\tthis.memCache.set(username, { isMember, at });\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tpersistent[username] = { isMember, at };\n\t\tvoid this.kv.update(KEY_MEMBERSHIP_CACHE, persistent);\n\t}\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch {\n\t\t\t\t// listener errors must not propagate; AccessControl is best-effort.\n\t\t\t}\n\t\t}\n\t}\n}\n","/**\n * AuthStateManager — In-memory mirror of the persisted auth state.\n *\n * Holds the multi-account map (provider + accountId keyed) plus the\n * \"active\" provider/account pair. Emits change events via Node's\n * built-in `events.EventEmitter` so listeners stay decoupled.\n *\n * Important: this class does NOT touch `vscode.SecretStorage` or any\n * file — it only keeps the metadata (`AuthAccountMeta`) and the\n * `activeProvider` selection. Token bytes are looked up via the\n * `KeychainAuthTokenStore` on demand. This separation is what lets\n * `AuthCore` run identically in CLI + Extension.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthStateManager.ts events.EventEmitter, NO VSCode dep`\n * - ADL-004 — token bytes never enter Core state\n */\n\nimport { EventEmitter } from \"node:events\";\n\nimport type { AuthAccountMeta, AuthProvider, AuthStatus } from \"@serviceme/devtools-protocol\";\n\nexport interface AuthStateManagerEvents {\n\tchange: () => void;\n}\n\nexport interface AuthStateManagerOptions {\n\t/** EventEmitter listener cap; defaults to 32 (Node default) but raised for hot test paths. */\n\tmaxListeners?: number;\n}\n\nexport class AuthStateManager {\n\tprivate readonly emitter = new EventEmitter();\n\tprivate accounts: AuthAccountMeta[] = [];\n\tprivate activeProvider: AuthProvider | null = null;\n\tprivate activeAccountId: string | null = null;\n\tprivate lastError: string | null = null;\n\n\tconstructor(opts: AuthStateManagerOptions = {}) {\n\t\tif (opts.maxListeners !== undefined) {\n\t\t\tthis.emitter.setMaxListeners(opts.maxListeners);\n\t\t}\n\t}\n\n\t/** Subscribe to state-change events. Returns a disposer. */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.emitter.on(\"change\", listener);\n\t\treturn () => this.emitter.off(\"change\", listener);\n\t}\n\n\t/** Snapshot of all known accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn [...this.accounts];\n\t}\n\n\t/** Find an account by `(provider, accountId)` tuple; returns `null` when absent. */\n\tfindAccount(provider: AuthProvider, accountId: string): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider && a.id === accountId) ?? null;\n\t}\n\n\t/** First account for the requested provider — used for \"switch to GitHub\" UX. */\n\tfindFirstForProvider(provider: AuthProvider): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider) ?? null;\n\t}\n\n\t/** Insert or update an account entry. New accounts land at the head of the list. */\n\tupsertAccount(meta: AuthAccountMeta): void {\n\t\tconst idx = this.accounts.findIndex((a) => a.provider === meta.provider && a.id === meta.id);\n\t\tif (idx >= 0) {\n\t\t\tthis.accounts[idx] = meta;\n\t\t} else {\n\t\t\tthis.accounts.unshift(meta);\n\t\t}\n\t\tthis.fire();\n\t}\n\n\t/** Remove an account entry. Returns `true` when an entry was removed. */\n\tremoveAccount(provider: AuthProvider, accountId: string): boolean {\n\t\tconst before = this.accounts.length;\n\t\tthis.accounts = this.accounts.filter((a) => !(a.provider === provider && a.id === accountId));\n\t\tconst removed = this.accounts.length !== before;\n\t\t// If we removed the active provider's account, clear the active marker.\n\t\tif (removed && this.activeProvider === provider && this.activeAccountId === accountId) {\n\t\t\tconst stillHasSameProvider = this.accounts.some((a) => a.provider === provider);\n\t\t\tif (!stillHasSameProvider) {\n\t\t\t\tthis.activeProvider = null;\n\t\t\t\tthis.activeAccountId = null;\n\t\t\t} else {\n\t\t\t\tconst next = this.accounts.find((a) => a.provider === provider);\n\t\t\t\tif (next) {\n\t\t\t\t\tthis.activeAccountId = next.id;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (removed) this.fire();\n\t\treturn removed;\n\t}\n\n\t/** Set the active provider. When `accountId` is omitted, picks the first account for that provider. */\n\tsetActive(provider: AuthProvider, accountId?: string): boolean {\n\t\tconst match =\n\t\t\taccountId !== undefined\n\t\t\t\t? this.accounts.find((a) => a.provider === provider && a.id === accountId)\n\t\t\t\t: this.accounts.find((a) => a.provider === provider);\n\t\tif (!match) return false;\n\t\tthis.activeProvider = provider;\n\t\tthis.activeAccountId = match.id;\n\t\tthis.fire();\n\t\treturn true;\n\t}\n\n\t/** Currently active provider — `null` when no session is active. */\n\tgetActiveProvider(): AuthProvider | null {\n\t\treturn this.activeProvider;\n\t}\n\n\t/** Currently active account metadata — `null` when none. */\n\tgetActiveAccount(): AuthAccountMeta | null {\n\t\tif (!this.activeProvider || !this.activeAccountId) return null;\n\t\treturn (\n\t\t\tthis.accounts.find(\n\t\t\t\t(a) => a.provider === this.activeProvider && a.id === this.activeAccountId\n\t\t\t) ?? null\n\t\t);\n\t}\n\n\t/** True when at least one provider has an account entry. */\n\thasAnySession(): boolean {\n\t\treturn this.accounts.length > 0 && this.activeProvider !== null;\n\t}\n\n\t/** Snapshot of the state for `auth.status` and bridge serialization. */\n\tgetStatus(): AuthStatus {\n\t\tconst grouped: Partial<Record<AuthProvider, AuthAccountMeta>> = {};\n\t\tfor (const account of this.accounts) {\n\t\t\tgrouped[account.provider] = account;\n\t\t}\n\t\treturn {\n\t\t\tactiveProvider: this.activeProvider,\n\t\t\taccounts: grouped,\n\t\t\thasAnySession: this.hasAnySession(),\n\t\t\tlastError: this.lastError ?? undefined,\n\t\t};\n\t}\n\n\t/** Record a non-fatal error from the last login/refresh attempt. */\n\trecordError(message: string): void {\n\t\tthis.lastError = message;\n\t\tthis.fire();\n\t}\n\n\t/** Clear the recorded error (e.g. after a successful login). */\n\tclearError(): void {\n\t\tif (this.lastError === null) return;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\t/** Drop every account — used by `auth.logout` with no provider. */\n\tclearAll(): void {\n\t\tthis.accounts = [];\n\t\tthis.activeProvider = null;\n\t\tthis.activeAccountId = null;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\tprivate fire(): void {\n\t\tthis.emitter.emit(\"change\");\n\t}\n}\n","/**\n * ProviderRegistry — Maps `AuthProvider` ids to `IAuthProvider` instances.\n *\n * `AuthCore` looks up providers by `AuthProvider` enum (string union\n * per the protocol). The registry is mutable so that callers can\n * replace a provider (e.g. swap a real keyring-backed implementation\n * in tests), but providers must be registered before `AuthCore.login()`\n * is called.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `ProviderRegistry.ts`\n */\n\nimport type { AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider } from \"./providers/IAuthProvider\";\n\nexport class ProviderRegistry {\n\tprivate readonly providers = new Map<AuthProvider, IAuthProvider>();\n\n\t/** Register or replace a provider implementation. */\n\tregister(provider: IAuthProvider): void {\n\t\tthis.providers.set(provider.providerId, provider);\n\t}\n\n\t/** Look up a registered provider by id; throws when missing. */\n\tget(providerId: AuthProvider): IAuthProvider {\n\t\tconst p = this.providers.get(providerId);\n\t\tif (!p) {\n\t\t\tthrow new Error(`Auth provider not registered: ${providerId}`);\n\t\t}\n\t\treturn p;\n\t}\n\n\t/** Non-throwing lookup; returns `undefined` when the provider is unknown. */\n\ttryGet(providerId: AuthProvider): IAuthProvider | undefined {\n\t\treturn this.providers.get(providerId);\n\t}\n\n\t/** Return all registered provider ids — used by `auth.status` and bridge capability hints. */\n\tlist(): AuthProvider[] {\n\t\treturn [...this.providers.keys()];\n\t}\n\n\t/** True when a provider has been registered for this id. */\n\thas(providerId: AuthProvider): boolean {\n\t\treturn this.providers.has(providerId);\n\t}\n}\n","/**\n * AuthCore — Main entry for the auth domain.\n *\n * Owns the `ProviderRegistry` + `AuthStateManager` + `KeychainAuthTokenStore`.\n * Provides a small, side-effectful surface that CLI / Extension / Bridge\n * handlers can call:\n *\n * - `login(provider)` → drives Device Flow, persists token via store.\n * - `logout(provider?)` → clears the active session (and token bytes).\n * - `status()` → snapshot for `auth.status`.\n * - `whoami()` → minimal identity for `auth.whoami`.\n * - `switchProvider(p)` → flip the active provider (multi-account).\n *\n * Core NEVER holds token bytes past the `KeychainAuthTokenStore.set()`\n * boundary — callers pass the token to the store immediately, then\n * drop the local reference. Per ADL-004 the token bytes only live in\n * (a) SecretStorage / (b) keyring / (c) HTTP Authorization header.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthCore.ts 主入口`\n * - ADL-002 — Device Flow OAuth\n * - ADL-004 — Auth token storage\n */\n\nimport type {\n\tAuthAccountMeta,\n\tAuthLoginResult,\n\tAuthProvider,\n\tAuthStatus,\n\tAuthSwitchResult,\n\tAuthWhoamiResult,\n} from \"@serviceme/devtools-protocol\";\n\nimport type { AccessCheckResult, AccessControl } from \"./AccessControl\";\nimport { AuthStateManager } from \"./AuthStateManager\";\nimport type { KeychainAuthTokenStore } from \"./KeychainAuthTokenStore\";\nimport { ProviderRegistry } from \"./ProviderRegistry\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./providers/IAuthProvider\";\n\nexport interface AuthCoreOptions {\n\tproviders: IAuthProvider[];\n\ttokenStore: KeychainAuthTokenStore;\n\taccessControl?: AccessControl;\n\tstateManager?: AuthStateManager;\n}\n\n/**\n * Callback invoked once the user finishes the device-flow handshake but\n * BEFORE the token is written to the keychain. Lets the caller display\n * the `userCode` + `verificationUrl` to the user (Phase 5.3 CLI prints to\n * stdout; Phase 5.5 Extension pops a webview notification).\n */\nexport type DeviceFlowUiCallback = (result: AuthLoginResult) => void | Promise<void>;\n\n/**\n * Optional cancellation handle — when `shouldContinue()` returns false,\n * the polling loop exits with a clear error so callers can render\n * \"Login cancelled\" UX.\n */\nexport type CancellationCheck = () => boolean;\n\nexport class AuthCore {\n\tprivate readonly registry: ProviderRegistry;\n\tprivate readonly state: AuthStateManager;\n\tprivate readonly tokenStore: KeychainAuthTokenStore;\n\tprivate readonly accessControl?: AccessControl;\n\n\tconstructor(opts: AuthCoreOptions) {\n\t\tthis.registry = new ProviderRegistry();\n\t\tfor (const provider of opts.providers) {\n\t\t\tthis.registry.register(provider);\n\t\t}\n\t\tthis.state = opts.stateManager ?? new AuthStateManager();\n\t\tthis.tokenStore = opts.tokenStore;\n\t\tthis.accessControl = opts.accessControl;\n\t}\n\n\t/** Snapshot of every account, the active provider, and the last error. */\n\tstatus(): AuthStatus {\n\t\treturn this.state.getStatus();\n\t}\n\n\t/** List accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn this.state.listAccounts();\n\t}\n\n\t/**\n\t * Drive the device-flow login for `provider`.\n\t *\n\t * Sequence:\n\t * 1. Ask the provider for the user code + verification URL.\n\t * 2. Surface the code to the user (via `ui`).\n\t * 3. Poll until the user authorizes (or `shouldContinue` aborts).\n\t * 4. Persist the token via `KeychainAuthTokenStore.set()`.\n\t * 5. Insert the resulting `AuthAccountMeta` into state and mark active.\n\t */\n\tasync login(\n\t\tprovider: AuthProvider,\n\t\tui: DeviceFlowUiCallback,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\ttry {\n\t\t\tconst initial = await providerImpl.requestDeviceFlow();\n\t\t\tawait ui(initial);\n\t\t\tconst session = await providerImpl.completeDeviceFlow(\n\t\t\t\tinitial.userCode ? ((initial as unknown as { deviceCode?: string }).deviceCode ?? \"\") : \"\",\n\t\t\t\tshouldContinue\n\t\t\t);\n\t\t\treturn await this.persistSession(providerImpl, session);\n\t\t} catch (err) {\n\t\t\tthis.state.recordError(err instanceof Error ? err.message : String(err));\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/**\n\t * Complete the device-flow handshake given a pre-fetched user code.\n\t * Useful when the caller (Phase 5.3 CLI) wants to fetch the code,\n\t * print the URL, and then poll on a subsequent invocation.\n\t */\n\tasync completeLogin(\n\t\tprovider: AuthProvider,\n\t\tdeviceCode: string,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\ttry {\n\t\t\tconst session = await providerImpl.completeDeviceFlow(deviceCode, shouldContinue);\n\t\t\treturn await this.persistSession(providerImpl, session);\n\t\t} catch (err) {\n\t\t\tthis.state.recordError(err instanceof Error ? err.message : String(err));\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Resolve the provider + token for the active session; returns null when no session is active. */\n\tasync resolveActiveToken(): Promise<{\n\t\tprovider: AuthProvider;\n\t\taccount: AuthAccountMeta;\n\t\ttoken: string;\n\t} | null> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tif (!provider) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!account) return null;\n\t\tconst envelope = await this.tokenStore.get({ provider, accountId: account.id });\n\t\tif (!envelope) return null;\n\t\treturn { provider, account, token: envelope.token };\n\t}\n\n\t/**\n\t * Logout: removes the token bytes from the keychain + drops the\n\t * account entry from state. When `provider` is omitted, clears\n\t * every account.\n\t */\n\tasync logout(\n\t\tprovider?: AuthProvider\n\t): Promise<{ provider: AuthProvider | null; success: boolean }> {\n\t\tif (!provider) {\n\t\t\t// Clear every account's token bytes + metadata.\n\t\t\tfor (const account of this.state.listAccounts()) {\n\t\t\t\tawait this.tokenStore.delete({ provider: account.provider, accountId: account.id });\n\t\t\t\tthis.state.removeAccount(account.provider, account.id);\n\t\t\t}\n\t\t\tthis.state.clearAll();\n\t\t\treturn { provider: null, success: true };\n\t\t}\n\n\t\tconst accountsForProvider = this.state.listAccounts().filter((a) => a.provider === provider);\n\t\tlet removed = false;\n\t\tfor (const account of accountsForProvider) {\n\t\t\tawait this.tokenStore.delete({ provider, accountId: account.id });\n\t\t\tconst didRemove = this.state.removeAccount(provider, account.id);\n\t\t\tremoved = removed || didRemove;\n\t\t}\n\t\tif (this.state.getActiveProvider() === provider) {\n\t\t\t// Reset active if we just removed the active provider's account.\n\t\t\tconst firstRemaining = this.state.listAccounts()[0];\n\t\t\tif (firstRemaining) {\n\t\t\t\tthis.state.setActive(firstRemaining.provider);\n\t\t\t}\n\t\t}\n\t\treturn { provider, success: removed };\n\t}\n\n\t/** Minimal identity for `auth.whoami`. */\n\tasync whoami(): Promise<AuthWhoamiResult> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!provider || !account) {\n\t\t\treturn { provider: null };\n\t\t}\n\t\treturn {\n\t\t\tprovider,\n\t\t\tlogin: account.login,\n\t\t\tname: account.displayName,\n\t\t\tavatarUrl: account.avatarUrl,\n\t\t\temail: account.email,\n\t\t};\n\t}\n\n\t/** Switch the active provider. Returns the resulting active account. */\n\tswitchProvider(provider: AuthProvider, accountId?: string): AuthSwitchResult {\n\t\tconst ok = this.state.setActive(provider, accountId);\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn {\n\t\t\tactiveProvider: ok ? provider : this.state.getActiveProvider(),\n\t\t\taccount,\n\t\t};\n\t}\n\n\t/** Run the access-control check against the active account (optional, requires AccessControl). */\n\tasync checkAccess(): Promise<AccessCheckResult | null> {\n\t\tif (!this.accessControl) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn this.accessControl.checkAccess(account);\n\t}\n\n\t/** Expose the state manager for test inspection (not for mutation). */\n\tgetStateManager(): AuthStateManager {\n\t\treturn this.state;\n\t}\n\n\t/** Expose the provider registry for test inspection. */\n\tgetProviderRegistry(): ProviderRegistry {\n\t\treturn this.registry;\n\t}\n\n\t/** Expose the access control (or undefined when not configured). */\n\tgetAccessControl(): AccessControl | undefined {\n\t\treturn this.accessControl;\n\t}\n\n\tprivate async persistSession(\n\t\tproviderImpl: IAuthProvider,\n\t\tsession: IAuthProviderSession\n\t): Promise<AuthLoginResult> {\n\t\tconst expiresAt = session.expiresIn ? Date.now() + session.expiresIn * 1000 : null;\n\t\tconst meta = await providerImpl.fetchAccountMeta(session.token);\n\t\tconst accountMeta: AuthAccountMeta = {\n\t\t\tid: meta.id,\n\t\t\tprovider: meta.provider,\n\t\t\tdisplayName: meta.displayName,\n\t\t\tlogin: meta.login,\n\t\t\temail: meta.email,\n\t\t\tavatarUrl: meta.avatarUrl,\n\t\t\texpiresAt: meta.expiresAt ?? expiresAt,\n\t\t};\n\n\t\tawait this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {\n\t\t\texpiresAt,\n\t\t});\n\t\tthis.state.upsertAccount(accountMeta);\n\t\tthis.state.setActive(meta.provider, meta.id);\n\t\tthis.state.clearError();\n\t\treturn {\n\t\t\tprovider: meta.provider,\n\t\t\tmessage: `Logged in as ${meta.login ?? meta.id}`,\n\t\t};\n\t}\n}\n","/**\n * KeychainAuthTokenStore — abstract adapter for token byte persistence.\n *\n * Per ADL-004 the OAuth token bytes live in one of three places:\n * (a) VSCode `SecretStorage` (Extension process)\n * (b) `@napi-rs/keyring` Entry (CLI process)\n * (c) HTTP request `Authorization` header\n *\n * `AuthCore` MUST NOT call any of these directly. Instead it receives\n * a `KeychainAuthTokenStore` via DI; CLI provides a `@napi-rs/keyring`\n * adapter, Extension provides a `SecretStorage` adapter. This keeps\n * Core free of native-binding concerns and lets the same business\n * logic power both runtimes.\n *\n * Implementations:\n * - CLI: `apps/serviceme-cli/src/.../KeyringTokenStore.ts` (Phase 5.3)\n * - Extension: `apps/extension/src/.../SecretStorageTokenStore.ts` (Phase 5.5)\n *\n * Failure semantics — `get()` returns `null` when no token is stored\n * (cold-start), throws when the underlying keychain is unavailable\n * (rare on Linux without libsecret). Callers should surface the throw\n * as `AUTH_KEYRING_UNAVAILABLE` rather than silently falling back to\n * in-memory storage (per ADL-004 §不变量).\n *\n * Refs:\n * - ADL-004 — CLI 端 Auth Token 存储选型\n * - 4.功能规划.md §2.1 — \"Core MUST NOT directly depend on `@napi-rs/keyring`\"\n */\n\n/** Opaque account identifier (provider + upstream user id). */\nexport interface KeychainAccountKey {\n\tprovider: string;\n\taccountId: string;\n}\n\n/** Non-secret metadata returned alongside a `get()` so callers can audit. */\nexport interface KeychainTokenMetadata {\n\tprovider: string;\n\taccountId: string;\n\texpiresAt?: number | null;\n\tstoredAt?: number;\n}\n\n/**\n * Result envelope — callers receive the token bytes (for immediate use\n * in an HTTP `Authorization` header) plus optional non-secret metadata.\n *\n * IMPORTANT: the `token` field is plain `string` here, not the\n * `SecretToken` brand. The provider -> store -> HTTP-header pipeline\n * runs inside a single trust boundary; crossing that boundary requires\n * `KeychainTokenEnvelope<SecretToken>` and the `auth.tokenRead`\n * capability gate (see ADL-004 §不变量).\n */\nexport interface KeychainTokenEnvelope {\n\ttoken: string;\n\tmetadata: KeychainTokenMetadata;\n}\n\n/**\n * Abstract token-storage adapter. All methods are async because the\n * keyring / SecretStorage backends are async-by-nature.\n *\n * Thread-safety: implementations MUST be safe for concurrent calls;\n * `AuthCore` will multiplex over multiple providers on the same\n * runtime.\n */\nexport interface KeychainAuthTokenStore {\n\t/** Persist token bytes for the given provider + account pair. */\n\tset(\n\t\tkey: KeychainAccountKey,\n\t\ttoken: string,\n\t\topts?: { expiresAt?: number | null },\n\t): Promise<void>;\n\n\t/** Fetch the token bytes; returns `null` when none is stored. */\n\tget(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null>;\n\n\t/** Remove the token entry. Idempotent — removing a missing entry is not an error. */\n\tdelete(key: KeychainAccountKey): Promise<void>;\n\n\t/**\n\t * List all stored token keys (metadata only — never the token bytes).\n\t * Useful for multi-account enumeration and bridge `auth.status`.\n\t */\n\tlist(): Promise<KeychainAccountKey[]>;\n\n\t/**\n\t * Health probe — returns `false` when the keychain is unreachable\n\t * (e.g. Linux without libsecret). Callers SHOULD probe on first\n\t * use and surface `AUTH_KEYRING_UNAVAILABLE` rather than degrade.\n\t */\n\tisAvailable(): Promise<boolean>;\n}\n\n/**\n * Sentinel error thrown by `KeychainAuthTokenStore.get()` / `.set()`\n * when the underlying keychain is unavailable. Callers map this to\n * `AUTH_KEYRING_UNAVAILABLE` (Phase 5.3 error code).\n */\nexport class KeychainUnavailableError extends Error {\n\tconstructor(message: string, cause?: unknown) {\n\t\t// Use the native ES2022 `Error` two-arg form instead of redeclaring\n\t\t// `cause` as a class field — a field redeclaration shadows the\n\t\t// inherited member and requires an `override` modifier under\n\t\t// `noImplicitOverride` (downstream consumers like the Extension),\n\t\t// which in turn fails THIS package's own build if its `lib` doesn't\n\t\t// also include `cause` on `Error`. Relying on the built-in\n\t\t// constructor option avoids the mismatch entirely (this package's\n\t\t// `tsconfig.json` targets `lib: [\"ES2022\", \"DOM\"]`, so `cause` is\n\t\t// recognized here too).\n\t\tsuper(message, cause !== undefined ? { cause } : undefined);\n\t\tthis.name = \"KeychainUnavailableError\";\n\t}\n}\n\n/**\n * In-memory `KeychainAuthTokenStore` — test/dev fallback. NEVER use\n * in production: tokens live in process memory and disappear on exit.\n * Production wiring is `KeyringTokenStore` (CLI) / `SecretStorageTokenStore`\n * (Extension).\n */\nexport class InMemoryKeychainAuthTokenStore implements KeychainAuthTokenStore {\n\tprivate readonly entries = new Map<\n\t\tstring,\n\t\t{ token: string; expiresAt: number | null; storedAt: number }\n\t>();\n\n\tprivate compositeKey(key: KeychainAccountKey): string {\n\t\treturn `${key.provider}:${key.accountId}`;\n\t}\n\n\tasync set(\n\t\tkey: KeychainAccountKey,\n\t\ttoken: string,\n\t\topts?: { expiresAt?: number | null },\n\t): Promise<void> {\n\t\tthis.entries.set(this.compositeKey(key), {\n\t\t\ttoken,\n\t\t\texpiresAt: opts?.expiresAt ?? null,\n\t\t\tstoredAt: Date.now(),\n\t\t});\n\t}\n\n\tasync get(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null> {\n\t\tconst entry = this.entries.get(this.compositeKey(key));\n\t\tif (!entry) return null;\n\t\treturn {\n\t\t\ttoken: entry.token,\n\t\t\tmetadata: {\n\t\t\t\tprovider: key.provider,\n\t\t\t\taccountId: key.accountId,\n\t\t\t\texpiresAt: entry.expiresAt,\n\t\t\t\tstoredAt: entry.storedAt,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(key: KeychainAccountKey): Promise<void> {\n\t\tthis.entries.delete(this.compositeKey(key));\n\t}\n\n\tasync list(): Promise<KeychainAccountKey[]> {\n\t\tconst out: KeychainAccountKey[] = [];\n\t\tfor (const composite of this.entries.keys()) {\n\t\t\tconst sepIdx = composite.indexOf(\":\");\n\t\t\tif (sepIdx <= 0) continue;\n\t\t\tout.push({\n\t\t\t\tprovider: composite.slice(0, sepIdx),\n\t\t\t\taccountId: composite.slice(sepIdx + 1),\n\t\t\t});\n\t\t}\n\t\treturn out;\n\t}\n\n\tasync isAvailable(): Promise<boolean> {\n\t\treturn true;\n\t}\n}\n","/**\n * AuthCore — Pure local utilities for GitHub local email handling.\n *\n * Copied verbatim from `@serviceme/shared/src/github-user-email.ts` (15 LOC)\n * because ADL-003 forbids Core from depending on `@serviceme/shared`. These\n * helpers are pure functions with zero side effects, so the duplication is\n * trivial to keep in sync.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — \"utils/githubUserEmail.ts 纯函数,直接搬\"\n * - ADL-003 — `@serviceme/shared` boundary decision\n */\n\n/**\n * Returns true when the supplied address is a synthetic GitHub \"local\" email\n * (suffixed with `@github.local`). Real GitHub OAuth clients often return\n * `null` for `email` (user kept it private) and the application substitutes\n * `<login>@github.local` to keep the field non-null.\n */\nexport function isGitHubLocalEmail(email: string | null | undefined): boolean {\n\treturn typeof email === \"string\" && email.trim().toLowerCase().endsWith(\"@github.local\");\n}\n\n/** Build a synthetic `<login>@github.local` address. */\nexport function buildGitHubLocalEmail(login: string): string {\n\treturn `${login}@github.local`;\n}\n\n/**\n * Resolve the user's primary email address — falling back to the synthetic\n * `<login>@github.local` form when the upstream email is missing or blank.\n */\nexport function resolvePrimaryEmail(login: string, email: string | null | undefined): string {\n\tconst normalizedEmail = email?.trim();\n\treturn normalizedEmail || buildGitHubLocalEmail(login);\n}\n","/**\n * GitHubAuthProvider — Device Flow OAuth implementation.\n *\n * Implements the GitHub OAuth Device Flow per the official spec\n * (https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow).\n *\n * Per ADL-002 the device flow is the locked authentication strategy for\n * SERVICEME — no localhost callback server, no PAT. This class is a\n * pure HTTP client (uses native `fetch`, no vscode dependency) that\n * returns the token bytes for the caller's `KeychainAuthTokenStore` to\n * persist immediately.\n *\n * Token bytes NEVER enter logs / errors / debug output. The provider\n * exposes only the user-visible code + verification URL during the\n * device-flow handshake.\n *\n * Refs:\n * - ADL-002 — Device Flow OAuth (locked)\n * - 4.功能规划.md §2.1 — `providers/GitHubAuthProvider.ts`\n */\n\nimport type { AuthAccountMeta, AuthLoginResult, AuthProvider } from \"@serviceme/devtools-protocol\";\nimport { buildGitHubLocalEmail } from \"../utils/githubUserEmail\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\nconst DEFAULT_DEVICE_CODE_URL = \"https://github.com/login/device/code\";\nconst DEFAULT_TOKEN_URL = \"https://github.com/login/oauth/access_token\";\nconst DEFAULT_USER_URL = \"https://api.github.com/user\";\nconst DEFAULT_SCOPE = \"read:user user:email\";\n\n/** Tunable provider config — exposed for tests + forks. */\nexport interface GitHubAuthProviderConfig {\n\tclientId: string;\n\tdeviceCodeUrl?: string;\n\ttokenUrl?: string;\n\tuserUrl?: string;\n\tscope?: string;\n\t/** Polling interval (ms) floor. The device-flow response's `interval` wins when larger. */\n\tminPollIntervalMs?: number;\n\t/** Maximum poll interval after exponential back-off. */\n\tmaxPollIntervalMs?: number;\n\t/** Fetch override (for tests + DI). */\n\tfetchImpl?: typeof fetch;\n\t/** Maximum wall-clock time to wait for the user before giving up (ms). Defaults to `expires_in * 1000`. */\n\tmaxWaitMs?: number;\n}\n\ninterface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\texpires_in: number;\n\tinterval: number;\n}\n\ninterface TokenPollResponse {\n\taccess_token?: string;\n\trefresh_token?: string;\n\texpires_in?: number;\n\ttoken_type?: string;\n\tscope?: string;\n\terror?: string;\n\terror_description?: string;\n\terror_uri?: string;\n}\n\ninterface GitHubUserResponse {\n\tid: number;\n\tlogin: string;\n\tname?: string | null;\n\temail?: string | null;\n\tavatar_url?: string | null;\n}\n\nexport class GitHubAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"github\";\n\tprivate readonly cfg: Required<Omit<GitHubAuthProviderConfig, \"maxWaitMs\" | \"fetchImpl\">> & {\n\t\tmaxWaitMs?: number;\n\t\tfetchImpl: typeof fetch;\n\t};\n\n\tconstructor(config: GitHubAuthProviderConfig) {\n\t\tif (!config.clientId || config.clientId.length === 0) {\n\t\t\tthrow new Error(\"GitHubAuthProvider requires a non-empty `clientId`\");\n\t\t}\n\t\tthis.cfg = {\n\t\t\tclientId: config.clientId,\n\t\t\tdeviceCodeUrl: config.deviceCodeUrl ?? DEFAULT_DEVICE_CODE_URL,\n\t\t\ttokenUrl: config.tokenUrl ?? DEFAULT_TOKEN_URL,\n\t\t\tuserUrl: config.userUrl ?? DEFAULT_USER_URL,\n\t\t\tscope: config.scope ?? DEFAULT_SCOPE,\n\t\t\tminPollIntervalMs: config.minPollIntervalMs ?? 1000,\n\t\t\tmaxPollIntervalMs: config.maxPollIntervalMs ?? 15000,\n\t\t\tmaxWaitMs: config.maxWaitMs,\n\t\t\tfetchImpl: config.fetchImpl ?? fetch,\n\t\t};\n\t}\n\n\tasync requestDeviceFlow(opts?: { scope?: string }): Promise<AuthLoginResult> {\n\t\tconst body = JSON.stringify({\n\t\t\tclient_id: this.cfg.clientId,\n\t\t\tscope: opts?.scope ?? this.cfg.scope,\n\t\t});\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t\tbody,\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub device-code request failed: HTTP ${resp.status}`);\n\t\t}\n\t\tconst data = (await resp.json()) as DeviceCodeResponse;\n\t\tif (!data.device_code || !data.user_code || !data.verification_uri) {\n\t\t\tthrow new Error(\"GitHub device-code response missing required fields\");\n\t\t}\n\t\treturn {\n\t\t\tprovider: this.providerId,\n\t\t\tuserCode: data.user_code,\n\t\t\tverificationUrl: data.verification_uri,\n\t\t\texpiresAt: Date.now() + data.expires_in * 1000,\n\t\t\tmessage: `Open ${data.verification_uri} and enter ${data.user_code}`,\n\t\t};\n\t}\n\n\tasync completeDeviceFlow(\n\t\tdeviceCode: string,\n\t\tshouldContinue?: () => boolean\n\t): Promise<IAuthProviderSession> {\n\t\tconst start = Date.now();\n\t\tlet pollIntervalMs = this.cfg.minPollIntervalMs;\n\t\tlet consecutiveSlowDown = 0;\n\n\t\twhile (true) {\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\t\t\tconst elapsed = Date.now() - start;\n\t\t\tif (this.cfg.maxWaitMs !== undefined && elapsed > this.cfg.maxWaitMs) {\n\t\t\t\tthrow new Error(\"GitHub device flow exceeded max wait time\");\n\t\t\t}\n\t\t\tawait sleep(pollIntervalMs);\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\n\t\t\tconst resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\t\tdevice_code: deviceCode,\n\t\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\t}),\n\t\t\t});\n\t\t\tif (!resp.ok) {\n\t\t\t\t// Transient HTTP failure — back off and retry until the user acts.\n\t\t\t\tpollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\t\tif (data.access_token) {\n\t\t\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\t\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\t\t\treturn {\n\t\t\t\t\ttoken: data.access_token,\n\t\t\t\t\trefreshToken: data.refresh_token,\n\t\t\t\t\texpiresIn: data.expires_in,\n\t\t\t\t\tuser: {\n\t\t\t\t\t\tid: String(rawUser.id),\n\t\t\t\t\t\tlogin: rawUser.login,\n\t\t\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\t\t\temail: email ?? null,\n\t\t\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (data.error === \"authorization_pending\") {\n\t\t\t\t// User hasn't acted yet — keep polling without back-off inflation.\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"slow_down\") {\n\t\t\t\tconsecutiveSlowDown++;\n\t\t\t\tpollIntervalMs = Math.min(\n\t\t\t\t\tMath.round(pollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2000)),\n\t\t\t\t\tthis.cfg.maxPollIntervalMs\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"access_denied\") {\n\t\t\t\tthrow new Error(\"User denied authorization\");\n\t\t\t}\n\t\t\tif (data.error === \"expired_token\") {\n\t\t\t\tthrow new Error(\"GitHub device code expired — restart the flow\");\n\t\t\t}\n\t\t\tthrow new Error(`GitHub device flow error: ${data.error ?? \"unknown\"}`);\n\t\t}\n\t}\n\n\tasync refreshAccessToken(refreshToken: string): Promise<IAuthProviderSession> {\n\t\tif (!refreshToken) {\n\t\t\tthrow new Error(\"refreshAccessToken requires a non-empty refresh token\");\n\t\t}\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t}),\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub refresh failed: HTTP ${resp.status}`);\n\t\t}\n\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\tif (!data.access_token) {\n\t\t\tthrow new Error(`GitHub refresh failed: ${data.error ?? \"no_access_token\"}`);\n\t\t}\n\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\treturn {\n\t\t\ttoken: data.access_token,\n\t\t\trefreshToken: data.refresh_token ?? refreshToken,\n\t\t\texpiresIn: data.expires_in,\n\t\t\tuser: {\n\t\t\t\tid: String(rawUser.id),\n\t\t\t\tlogin: rawUser.login,\n\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\temail: email ?? null,\n\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync validateToken(token: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (resp.status === 401) return false;\n\t\t\tif (!resp.ok) {\n\t\t\t\tthrow new Error(`GitHub /user returned HTTP ${resp.status}`);\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\t// Network / parse errors are surfaced; only 401 means invalid.\n\t\t\tif (err instanceof Error && err.message.includes(\"401\")) return false;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync fetchAccountMeta(token: string): Promise<AuthAccountMeta> {\n\t\tconst user = await this.fetchGitHubUser(token);\n\t\tconst email = await this.resolveEmail(token, user);\n\t\treturn {\n\t\t\tid: String(user.id),\n\t\t\tprovider: this.providerId,\n\t\t\tdisplayName: user.name ?? user.login,\n\t\t\tlogin: user.login,\n\t\t\temail: email ?? buildGitHubLocalEmail(user.login),\n\t\t\tavatarUrl: user.avatar_url ?? undefined,\n\t\t\texpiresAt: null,\n\t\t};\n\t}\n\n\tprivate async fetchGitHubUser(token: string): Promise<GitHubUserResponse> {\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub /user failed: HTTP ${resp.status}`);\n\t\t}\n\t\treturn (await resp.json()) as GitHubUserResponse;\n\t}\n\n\t/**\n\t * GitHub's `/user` endpoint returns `null` when the user kept their\n\t * email private. The `/user/emails` endpoint reveals verified emails;\n\t * we pick the primary one, falling back to the synthetic\n\t * `<login>@github.local` form so the account is never email-less.\n\t */\n\tprivate async resolveEmail(token: string, user: GitHubUserResponse): Promise<string | null> {\n\t\tif (user.email) return user.email;\n\t\tconst emailsResp = await this.cfg.fetchImpl(\"https://api.github.com/user/emails\", {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t});\n\t\tif (!emailsResp.ok) {\n\t\t\treturn null;\n\t\t}\n\t\tconst emails = (await emailsResp.json()) as Array<{\n\t\t\temail: string;\n\t\t\tprimary: boolean;\n\t\t\tvisibility: string | null;\n\t\t\tverified: boolean;\n\t\t}>;\n\t\tconst primary = emails.find((e) => e.primary && e.verified);\n\t\treturn primary?.email ?? null;\n\t}\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * MicrosoftAuthProvider — Microsoft Account (MSA) OAuth Device Flow stub.\n *\n * The Extension's existing `MicrosoftAuthProvider` (`apps/extension/src/services/auth/providers/MicrosoftAuthProvider.ts`)\n * delegates to VSCode's built-in `vscode.authentication.getSession()` API\n * — there's no direct Microsoft device-flow endpoint exposed for\n * first-party apps. To keep Core usable from the CLI without VSCode,\n * we expose a minimal stub that throws \"not implemented in Core\" so\n * callers can detect and route back to the Extension adapter.\n *\n * The reason this lives in Core at all (instead of being purely\n * Extension-side) is the cross-runtime registry: `AuthCore` needs to\n * know which providers it MIGHT support, even if only the Extension\n * process can actually fulfil the Microsoft login.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `providers/MicrosoftAuthProvider.ts 从 extension 整体迁移`\n * - ADL-002 — Device Flow OAuth (locked, GitHub-only)\n */\n\nimport type { AuthAccountMeta, AuthLoginResult, AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\n/**\n * Error thrown when callers invoke Microsoft auth from a non-Extension\n * runtime (CLI / Bridge). The CLI maps this to `AUTH_PROVIDER_REQUIRES_HOST`\n * so the user is prompted to retry inside VSCode.\n */\nexport class MicrosoftProviderNotHostedError extends Error {\n\tconstructor(\n\t\tmessage = \"Microsoft auth requires the VSCode host; CLI does not yet implement MSA device flow\"\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MicrosoftProviderNotHostedError\";\n\t}\n}\n\nexport class MicrosoftAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"microsoft\";\n\n\tasync requestDeviceFlow(): Promise<AuthLoginResult> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync completeDeviceFlow(\n\t\t_deviceCode: string,\n\t\t_shouldContinue?: () => boolean\n\t): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync refreshAccessToken(_refreshToken: string): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync validateToken(_token: string): Promise<boolean> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync fetchAccountMeta(_token: string): Promise<AuthAccountMeta> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n}\n","import {\n\tCOPILOT_ERROR_CODES,\n\ttype CopilotDoctorResult,\n\tcreateServicemeError,\n} from \"@serviceme/devtools-protocol\";\nimport { commandExists, runCommand } from \"../process/runCommand\";\n\nconst COPILOT_COMMAND = \"copilot\";\nconst GH_COMMAND = \"gh\";\n\n/**\n * Quick auth pre-flight check using `gh auth status`.\n * Returns true if gh is authenticated; false otherwise.\n */\nexport async function isCopilotAuthenticated(): Promise<boolean> {\n\ttry {\n\t\tawait runCommand(GH_COMMAND, {\n\t\t\targs: [\"auth\", \"status\"],\n\t\t\ttimeoutMs: 10_000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport async function copilotDoctor(): Promise<CopilotDoctorResult> {\n\tconst exists = await commandExists(COPILOT_COMMAND);\n\tif (!exists) {\n\t\treturn { installed: false, version: null, authenticated: false };\n\t}\n\n\tlet version: string | null = null;\n\ttry {\n\t\tconst versionResult = await runCommand(COPILOT_COMMAND, {\n\t\t\targs: [\"--version\"],\n\t\t\ttimeoutMs: 10_000,\n\t\t});\n\t\tversion = versionResult.stdout.trim();\n\t} catch {\n\t\treturn { installed: true, version: null, authenticated: false };\n\t}\n\n\t// Use gh auth status for reliable authentication check\n\tconst authenticated = await isCopilotAuthenticated();\n\n\treturn { installed: true, version, authenticated };\n}\n\nexport function createCopilotNotInstalledError() {\n\treturn createServicemeError(\n\t\tCOPILOT_ERROR_CODES.NOT_INSTALLED,\n\t\t\"GitHub Copilot CLI is not installed. Visit https://docs.github.com/en/copilot/using-github-copilot/using-github-copilot-in-the-command-line to install.\"\n\t);\n}\n\nexport function createCopilotAuthRequiredError() {\n\treturn createServicemeError(\n\t\tCOPILOT_ERROR_CODES.AUTH_REQUIRED,\n\t\t\"GitHub Copilot CLI requires authentication. Run `copilot auth login` to sign in.\"\n\t);\n}\n","import { spawn } from \"node:child_process\";\n\nfunction terminateCommandProcess(child: {\n\tpid?: number;\n\tkilled?: boolean;\n\tkill(signal?: NodeJS.Signals | number): boolean;\n}): void {\n\tif (child.killed) {\n\t\treturn;\n\t}\n\n\tif (process.platform === \"win32\" && child.pid) {\n\t\tconst killProcess = spawn(\"taskkill\", [\"/pid\", String(child.pid), \"/T\", \"/F\"], {\n\t\t\tstdio: \"ignore\",\n\t\t\twindowsHide: true,\n\t\t});\n\n\t\tkillProcess.once(\"error\", () => {\n\t\t\tchild.kill();\n\t\t});\n\t\treturn;\n\t}\n\n\tchild.kill(\"SIGTERM\");\n}\n\nexport interface RunCommandOptions {\n\targs?: string[];\n\tcwd?: string;\n\tenv?: NodeJS.ProcessEnv;\n\ttimeoutMs?: number;\n\tstdin?: string;\n\tshell?: boolean;\n}\n\nexport interface RunCommandResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n}\n\nexport async function runCommand(\n\tcommand: string,\n\toptions: RunCommandOptions = {}\n): Promise<RunCommandResult> {\n\treturn new Promise<RunCommandResult>((resolve, reject) => {\n\t\tconst child = spawn(command, options.args ?? [], {\n\t\t\tcwd: options.cwd,\n\t\t\tenv: options.env,\n\t\t\tshell: options.shell,\n\t\t\tstdio: \"pipe\",\n\t\t\twindowsHide: true,\n\t\t});\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet finished = false;\n\t\tlet timeoutId: NodeJS.Timeout | undefined;\n\n\t\tconst finish = (handler: () => void) => {\n\t\t\tif (finished) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfinished = true;\n\t\t\tif (timeoutId) {\n\t\t\t\tclearTimeout(timeoutId);\n\t\t\t}\n\t\t\thandler();\n\t\t};\n\n\t\tchild.stdout?.setEncoding(\"utf8\");\n\t\tchild.stderr?.setEncoding(\"utf8\");\n\n\t\tchild.stdout?.on(\"data\", (chunk: string) => {\n\t\t\tstdout += chunk;\n\t\t});\n\n\t\tchild.stderr?.on(\"data\", (chunk: string) => {\n\t\t\tstderr += chunk;\n\t\t});\n\n\t\tchild.once(\"error\", (error) => {\n\t\t\tfinish(() => reject(error));\n\t\t});\n\n\t\tchild.once(\"close\", (code) => {\n\t\t\tfinish(() => {\n\t\t\t\tif (code === 0) {\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tstdout,\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t\tcode: 0,\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst error = new Error(\n\t\t\t\t\tstderr || stdout || `Command failed with exit code ${String(code ?? 1)}.`\n\t\t\t\t) as Error & {\n\t\t\t\t\tcode?: number | string;\n\t\t\t\t\tstdout?: string;\n\t\t\t\t\tstderr?: string;\n\t\t\t\t};\n\t\t\t\terror.code = code ?? 1;\n\t\t\t\terror.stdout = stdout;\n\t\t\t\terror.stderr = stderr;\n\t\t\t\treject(error);\n\t\t\t});\n\t\t});\n\n\t\tif (options.stdin !== undefined) {\n\t\t\tchild.stdin?.end(options.stdin);\n\t\t} else {\n\t\t\tchild.stdin?.end();\n\t\t}\n\n\t\tif (options.timeoutMs) {\n\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\tfinish(() => {\n\t\t\t\t\tterminateCommandProcess(child);\n\t\t\t\t\tconst error = new Error(\n\t\t\t\t\t\t`Command timed out after ${String(options.timeoutMs)}ms.`\n\t\t\t\t\t) as Error & {\n\t\t\t\t\t\tcode?: string;\n\t\t\t\t\t\tstdout?: string;\n\t\t\t\t\t\tstderr?: string;\n\t\t\t\t\t};\n\t\t\t\t\terror.code = \"ETIMEDOUT\";\n\t\t\t\t\terror.stdout = stdout;\n\t\t\t\t\terror.stderr = stderr;\n\t\t\t\t\treject(error);\n\t\t\t\t});\n\t\t\t}, options.timeoutMs);\n\t\t}\n\t});\n}\n\nexport async function commandExists(command: string): Promise<boolean> {\n\tconst isWindows = process.platform === \"win32\";\n\n\ttry {\n\t\tawait runCommand(isWindows ? \"where\" : \"which\", {\n\t\t\targs: [command],\n\t\t\ttimeoutMs: 5_000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n","import {\n\tCOPILOT_ERROR_CODES,\n\ttype CopilotPromptOptions,\n\ttype CopilotPromptResult,\n\tcreateServicemeError,\n\tServicemeProtocolError,\n} from \"@serviceme/devtools-protocol\";\nimport { runCommand } from \"../process/runCommand\";\n\nconst COPILOT_COMMAND = \"copilot\";\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\n// Strip ANSI escape codes from output\nfunction stripAnsi(text: string): string {\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ANSI stripping\n\treturn text.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, \"\");\n}\n\nexport async function copilotPrompt(options: CopilotPromptOptions): Promise<CopilotPromptResult> {\n\tconst args = [\"-p\", options.prompt];\n\n\tif (options.workspace) {\n\t\targs.push(\"--add-dir\", options.workspace);\n\t}\n\n\tif (options.autopilot) {\n\t\targs.push(\"--allow-all-tools\");\n\t}\n\n\tif (options.allowTools?.length) {\n\t\tfor (const tool of options.allowTools) {\n\t\t\targs.push(\"--allow-tool\", tool);\n\t\t}\n\t}\n\n\tif (options.model) {\n\t\targs.push(\"--model\", options.model);\n\t}\n\n\tif (options.agent) {\n\t\targs.push(\"--agent\", options.agent);\n\t}\n\n\tconst timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n\n\ttry {\n\t\tconst result = await runCommand(COPILOT_COMMAND, {\n\t\t\targs,\n\t\t\tcwd: options.workspace,\n\t\t\ttimeoutMs: timeout,\n\t\t});\n\n\t\tconst output = stripAnsi(result.stdout);\n\n\t\t// Detect refusal in success path (some versions exit 0 with refusal)\n\t\tif (\n\t\t\toutput.includes(\"I'm sorry, but I cannot assist\") &&\n\t\t\tresult.stderr?.includes(\"Stream completed without a response\")\n\t\t) {\n\t\t\tthrow createServicemeError(\n\t\t\t\tCOPILOT_ERROR_CODES.AUTH_REQUIRED,\n\t\t\t\t\"Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.\",\n\t\t\t\t{ exitCode: 0, output, stderr: result.stderr }\n\t\t\t);\n\t\t}\n\n\t\treturn { output, exitCode: 0 };\n\t} catch (error: unknown) {\n\t\t// Re-throw already-classified protocol errors so the success-path refusal\n\t\t// branch (and any other inner throws of ServicemeProtocolError) are not\n\t\t// rewritten as EXECUTION_FAILED by the generic catch handler below.\n\t\tif (error instanceof ServicemeProtocolError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst err = error as {\n\t\t\tcode?: number | string;\n\t\t\tstdout?: string;\n\t\t\tstderr?: string;\n\t\t\tmessage?: string;\n\t\t};\n\n\t\tif (err.code === \"ETIMEDOUT\") {\n\t\t\tthrow createServicemeError(\n\t\t\t\tCOPILOT_ERROR_CODES.TIMEOUT,\n\t\t\t\t`Copilot prompt timed out after ${String(timeout)}ms.`\n\t\t\t);\n\t\t}\n\n\t\tconst exitCode = typeof err.code === \"number\" ? err.code : 1;\n\t\tconst output = stripAnsi(err.stdout ?? \"\");\n\t\tconst stderr = err.stderr ?? err.message ?? \"\";\n\n\t\t// Detect Copilot refusal pattern (often caused by expired auth token)\n\t\tif (\n\t\t\toutput.includes(\"I'm sorry, but I cannot assist\") ||\n\t\t\tstderr.includes(\"Stream completed without a response\")\n\t\t) {\n\t\t\tthrow createServicemeError(\n\t\t\t\tCOPILOT_ERROR_CODES.AUTH_REQUIRED,\n\t\t\t\t\"Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.\",\n\t\t\t\t{ exitCode, output, stderr }\n\t\t\t);\n\t\t}\n\n\t\tif (exitCode !== 0 && output) {\n\t\t\treturn { output, exitCode };\n\t\t}\n\n\t\tthrow createServicemeError(\n\t\t\tCOPILOT_ERROR_CODES.EXECUTION_FAILED,\n\t\t\tstderr || `Copilot exited with code ${String(exitCode)}.`,\n\t\t\t{ exitCode, stderr }\n\t\t);\n\t}\n}\n","/**\n * deviceAuth — Pure HMAC-SHA-256 signing helpers for device auth headers.\n *\n * Ported byte-for-byte from\n * `apps/extension/src/services/device/deviceAuth.ts:11-26` (the wire\n * algorithm is locked by the server-side `device-signature-guard.ts`\n * verifier, see `docs/architecture/phase-5-device-header-spec.md` §5).\n *\n * The basis is `METHOD\\nPATH\\nTIMESTAMP\\nBODY\\nSECRET` (LF-joined,\n * NOT JSON). Output is lowercase hex SHA-256.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `deviceAuth.ts HMAC 签名(纯 node:crypto,直接搬)`\n * - `docs/architecture/phase-5-device-header-spec.md` §5 (signature format)\n */\n\nimport { createHash } from \"node:crypto\";\n\n/** Canonical header names — MUST match server's `device-signature-guard.ts:9-15`. */\nexport const DeviceAuthHeaders = {\n\tdeviceId: \"x-ms-device-id\",\n\tdeviceSecret: \"x-ms-device-secret\",\n\tsignature: \"x-ms-device-signature\",\n\ttimestamp: \"x-ms-device-timestamp\",\n\tsecretVersion: \"x-ms-device-secret-version\",\n} as const;\n\nexport interface DeviceRequestSignatureParams {\n\tmethod: string;\n\tpath: string;\n\ttimestamp: number;\n\tbody: string;\n\tsecret: string;\n}\n\n/**\n * Compute the HMAC-SHA-256 hex digest of the canonical basis.\n *\n * Server contract (`apps/server/src/lib/auth/device-signature-guard.ts:46-62`)\n * is line-for-line identical: same LF-joined basis, same lowercase\n * hex output. Any divergence breaks `device-signature-guard.test.ts`.\n */\nexport function createDeviceRequestSignature(params: DeviceRequestSignatureParams): string {\n\tconst basis = [\n\t\tparams.method.toUpperCase(),\n\t\tparams.path,\n\t\tString(params.timestamp),\n\t\tparams.body,\n\t\tparams.secret,\n\t].join(\"\\n\");\n\treturn createHash(\"sha256\").update(basis).digest(\"hex\");\n}\n\n/** 5-header map consumed by `fetch()` callers (CLI bridge, Extension). */\nexport interface DeviceSignedHeaders {\n\t[DeviceAuthHeaders.deviceId]: string;\n\t[DeviceAuthHeaders.deviceSecret]: string;\n\t[DeviceAuthHeaders.signature]: string;\n\t[DeviceAuthHeaders.timestamp]: string;\n\t[DeviceAuthHeaders.secretVersion]: string;\n}\n\nexport interface BuildSignedHeadersParams {\n\tmethod: string;\n\tpath: string;\n\tbody: string;\n\tpublicId: string;\n\tdeviceSecret: string;\n\tsecretVersion: number;\n\t/** Override for deterministic tests. */\n\ttimestamp?: number;\n}\n\n/**\n * Build the canonical 5-header map. The `body` parameter MUST be the\n * exact byte sequence sent on the wire (no whitespace re-canonicalization\n * between client serialization and signature basis construction).\n */\nexport function buildSignedHeaders(params: BuildSignedHeadersParams): DeviceSignedHeaders {\n\tconst timestamp = params.timestamp ?? Date.now();\n\tconst signature = createDeviceRequestSignature({\n\t\tmethod: params.method,\n\t\tpath: params.path,\n\t\ttimestamp,\n\t\tbody: params.body,\n\t\tsecret: params.deviceSecret,\n\t});\n\treturn {\n\t\t[DeviceAuthHeaders.deviceId]: params.publicId,\n\t\t[DeviceAuthHeaders.deviceSecret]: params.deviceSecret,\n\t\t[DeviceAuthHeaders.signature]: signature,\n\t\t[DeviceAuthHeaders.timestamp]: String(timestamp),\n\t\t[DeviceAuthHeaders.secretVersion]: String(params.secretVersion),\n\t};\n}\n","/**\n * Enroller — State machine for `device.enroll` and `device.rotate-secret`.\n *\n * States per `2.需求澄清.md` §1.2:\n * anonymous → pending → claimed → expired\n *\n * - `anonymous` (initial): no device has ever enrolled. Server returns\n * a fresh `publicId` + secret.\n * - `pending`: enrollment HTTP call has been issued but the server\n * hasn't confirmed yet. In-flight state — never persisted.\n * - `claimed`: user has linked this device to their account (via\n * `/api/v1/devices/claim`). Sticky binding locks future re-enrolls\n * to the same `userId` (server-side matrix).\n * - `expired`: server returned a device-expiry error. Forces a fresh\n * enroll on next call.\n *\n * `--force` semantics: any non-anonymous state can be force-reset to\n * `anonymous` by wiping the local identity file. The next enroll will\n * be treated as a brand-new install by the server (no sticky binding).\n *\n * The Enroller is the **state machine**; the actual HTTP I/O is the\n * caller's responsibility (the `DeviceSyncClient` in Phase 5.4 wires\n * the server). This split keeps the Enroller unit-testable without\n * a live server.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `Enroller.ts anonymous → pending → claimed → expired`\n * - `2.需求澄清.md` §1.2 — binding-state machine\n */\n\nimport { randomBytes } from \"node:crypto\";\n\nimport type { DeviceBindingState, DeviceEnrollResult } from \"@serviceme/devtools-protocol\";\nimport type { IdentityStore } from \"./IdentityStore\";\nimport { deriveInstallationId } from \"./InstallationId\";\nimport type { PersistedDeviceIdentity } from \"./types\";\n\n/** 32 bytes of HMAC secret material — matches the server's `device-registration.ts:73-80` generator. */\nconst SECRET_BYTES = 32;\n/** Server returns `publicId` as 32-char hex (16 bytes). Match the wire length. */\nconst PUBLIC_ID_BYTES = 16;\n\ntype RandomBytesFn = (size: number) => Buffer;\n\nconst defaultRandomBytes: RandomBytesFn = (size) => {\n\treturn randomBytes(size);\n};\n\nexport interface EnrollerOptions {\n\tidentityStore: IdentityStore;\n\t/** Injectable clock for deterministic tests. */\n\tnow?: () => Date;\n\t/** Override the random source (tests). */\n\trandomBytes?: (size: number) => Buffer;\n\t/** Caller-supplied enroll HTTP function. Phase 5.4 wires the real one. */\n\tenrollRequest?: EnrollRequestFn;\n}\n\nexport type EnrollRequestFn = (input: {\n\tinstallationId: string;\n\tmachineId: string;\n\tplatform: string;\n\texisting: PersistedDeviceIdentity | null;\n\tforce: boolean;\n\trequireAuth: boolean;\n}) => Promise<EnrollResponse>;\n\nexport interface EnrollResponse {\n\tpublicId: string;\n\tdeviceSecret: string;\n\tsecretVersion: number;\n\tbindingState: DeviceBindingState;\n\texpiresAt?: string;\n}\n\n/** Sentinel error — re-enroll on a claimed device without auth. */\nexport class DeviceReenrollRequiresAuthError extends Error {\n\tconstructor(\n\t\tmessage = \"Re-enroll on a claimed device requires current device credentials or the bound user\"\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"DeviceReenrollRequiresAuthError\";\n\t}\n}\n\n/** Sentinel error — server returned a 410 / version-mismatch after rotation. */\nexport class DeviceSecretVersionMismatchError extends Error {\n\tconstructor(message = \"Device secret version mismatch — server has rotated past the local copy\") {\n\t\tsuper(message);\n\t\tthis.name = \"DeviceSecretVersionMismatchError\";\n\t}\n}\n\nexport class Enroller {\n\tprivate readonly identity: IdentityStore;\n\tprivate readonly now: () => Date;\n\tprivate readonly random: (size: number) => Buffer;\n\tprivate readonly enrollRequest?: EnrollRequestFn;\n\tprivate inflight: Promise<DeviceEnrollResult> | null = null;\n\n\tconstructor(opts: EnrollerOptions) {\n\t\tthis.identity = opts.identityStore;\n\t\tthis.now = opts.now ?? (() => new Date());\n\t\tthis.random = opts.randomBytes ?? defaultRandomBytes;\n\t\tthis.enrollRequest = opts.enrollRequest;\n\t}\n\n\t/**\n\t * Read the current binding state without touching the disk.\n\t * Returns `anonymous` when no identity is stored.\n\t */\n\tasync currentState(): Promise<DeviceBindingState> {\n\t\tconst stored = await this.identity.read();\n\t\treturn stored?.bindingState ?? \"anonymous\";\n\t}\n\n\t/**\n\t * Drive the enrollment flow.\n\t *\n\t * @param force when true, drop the local identity and start fresh\n\t * (server treats this as a brand-new install).\n\t * @param requireAuth when true, refuse to silently re-enroll an\n\t * existing claimed device — throw\n\t * `DeviceReenrollRequiresAuthError` instead.\n\t */\n\t/**\n\t * Resolve when any in-flight enrollment completes. Returns immediately\n\t * when no enrollment is in progress. Allows callers (e.g. the extension's\n\t * `buildDeviceAuthHeaders`) to wait for a concurrent `syncDeviceInfo()`\n\t * enrollment before attempting to read the identity from the store.\n\t */\n\tasync waitForEnrollment(): Promise<void> {\n\t\tif (this.inflight) {\n\t\t\tawait this.inflight;\n\t\t}\n\t}\n\n\tasync enroll(opts: { force?: boolean; requireAuth?: boolean } = {}): Promise<DeviceEnrollResult> {\n\t\t// Concurrency guard — multiple in-flight calls share the same promise.\n\t\tif (this.inflight) {\n\t\t\treturn this.inflight;\n\t\t}\n\t\tconst promise = this.runEnroll(opts);\n\t\tthis.inflight = promise;\n\t\ttry {\n\t\t\treturn await promise;\n\t\t} finally {\n\t\t\tif (this.inflight === promise) this.inflight = null;\n\t\t}\n\t}\n\n\t/** Test seam — surface the underlying identity store. */\n\tgetIdentityStore(): IdentityStore {\n\t\treturn this.identity;\n\t}\n\n\t/** True when an enrollment is currently in-flight. Used by callers (e.g. the extension's `buildDeviceAuthHeaders`) to skip triggering a competing enrollment. */\n\tisEnrolling(): boolean {\n\t\treturn this.inflight !== null;\n\t}\n\n\tprivate async runEnroll(opts: {\n\t\tforce?: boolean;\n\t\trequireAuth?: boolean;\n\t}): Promise<DeviceEnrollResult> {\n\t\tconst { written } = await this.identity.mutate(async (current) => {\n\t\t\tconst existing = opts.force ? null : current;\n\n\t\t\tif (!opts.force && current) {\n\t\t\t\tif (current.bindingState === \"expired\") {\n\t\t\t\t\t// Expired identities are forced to re-enroll as if they were new.\n\t\t\t\t} else if (\n\t\t\t\t\topts.requireAuth &&\n\t\t\t\t\t(current.bindingState === \"claimed\" || current.bindingState === \"pending\")\n\t\t\t\t) {\n\t\t\t\t\t// Caller asserted the device must be claimed, but local state\n\t\t\t\t\t// shows it's still in flight. This is a CLI-only guard — the\n\t\t\t\t\t// server is the final arbiter.\n\t\t\t\t\tthrow new DeviceReenrollRequiresAuthError();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst installationId = current?.installationId ?? deriveInstallationId();\n\t\t\tconst machineId = current?.machineId ?? \"unknown\";\n\t\t\tconst platform = current?.platform ?? \"unknown\";\n\n\t\t\tlet response: EnrollResponse;\n\t\t\tif (this.enrollRequest) {\n\t\t\t\tresponse = await this.enrollRequest({\n\t\t\t\t\tinstallationId,\n\t\t\t\t\tmachineId,\n\t\t\t\t\tplatform,\n\t\t\t\t\texisting,\n\t\t\t\t\tforce: Boolean(opts.force),\n\t\t\t\t\trequireAuth: Boolean(opts.requireAuth),\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\t// Test path / no live HTTP — synthesize a fresh identity. This\n\t\t\t\t// branch is what unit tests exercise; production wires\n\t\t\t\t// `enrollRequest` in Phase 5.4.\n\t\t\t\tresponse = synthesizeEnrollResponse(this.random, existing);\n\t\t\t}\n\n\t\t\tconst next: PersistedDeviceIdentity = {\n\t\t\t\tversion: current?.version ?? 1,\n\t\t\t\tinstallationId,\n\t\t\t\tmachineId,\n\t\t\t\tplatform,\n\t\t\t\thostname: current?.hostname,\n\t\t\t\tpublicId: response.publicId,\n\t\t\t\tsecretVersion: response.secretVersion,\n\t\t\t\tbindingState: response.bindingState,\n\t\t\t\tdeviceSecret: response.deviceSecret,\n\t\t\t\tpreviousDeviceSecret: existing?.deviceSecret,\n\t\t\t\tpreviousSecretExpiresAt:\n\t\t\t\t\topts.force || response.secretVersion === (existing?.secretVersion ?? 0) + 1\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: existing?.previousSecretExpiresAt,\n\t\t\t\tlastEnrollAt: this.now().toISOString(),\n\t\t\t\tlastSyncAt: existing?.lastSyncAt,\n\t\t\t\tlastSyncError: undefined,\n\t\t\t};\n\t\t\treturn { next };\n\t\t});\n\n\t\treturn {\n\t\t\tpublicId: written.publicId,\n\t\t\tbindingState: written.bindingState,\n\t\t\texpiresAt: deriveExpiresAt(written, this.now),\n\t\t};\n\t}\n\n\t/**\n\t * Rotate the HMAC secret. Keeps the previous secret for the grace\n\t * window (default 7 days per `2.需求澄清.md` §1.2) — the\n\t * `previousSecretExpiresAt` is stamped on the persisted identity.\n\t */\n\tasync rotateSecret(\n\t\topts: { gracePeriodDays?: number } = {}\n\t): Promise<{ publicId: string; secretVersion: number; gracePeriodDays: number }> {\n\t\tconst gracePeriodDays = opts.gracePeriodDays ?? 7;\n\t\tconst now = this.now();\n\t\tconst newSecret = this.random(SECRET_BYTES).toString(\"hex\");\n\n\t\tconst { written } = await this.identity.mutate(async (current) => {\n\t\t\tif (!current) {\n\t\t\t\tthrow new Error(\"Cannot rotate-secret without a prior enrollment\");\n\t\t\t}\n\t\t\tconst graceExpiresAt = new Date(now.getTime() + gracePeriodDays * 24 * 60 * 60 * 1000);\n\t\t\tconst next: PersistedDeviceIdentity = {\n\t\t\t\t...current,\n\t\t\t\tdeviceSecret: newSecret,\n\t\t\t\tpreviousDeviceSecret: current.deviceSecret,\n\t\t\t\tpreviousSecretExpiresAt: graceExpiresAt.toISOString(),\n\t\t\t\tsecretVersion: current.secretVersion + 1,\n\t\t\t\tlastEnrollAt: now.toISOString(),\n\t\t\t};\n\t\t\treturn { next };\n\t\t});\n\n\t\treturn {\n\t\t\tpublicId: written.publicId,\n\t\t\tsecretVersion: written.secretVersion,\n\t\t\tgracePeriodDays,\n\t\t};\n\t}\n\n\t/**\n\t * Mark the device as `expired`. Used when the server returns a\n\t * device-expiry response; the next `enroll()` call forces a fresh\n\t * round-trip.\n\t */\n\tasync markExpired(): Promise<void> {\n\t\tawait this.identity.mutate(async (current) => {\n\t\t\tif (!current) {\n\t\t\t\t// Nothing to expire.\n\t\t\t\treturn { next: current ?? (await emptyIdentity(this.random)), result: undefined };\n\t\t\t}\n\t\t\tconst next: PersistedDeviceIdentity = { ...current, bindingState: \"expired\" };\n\t\t\treturn { next };\n\t\t});\n\t}\n\n\t/**\n\t * Mark the device as `claimed`. Called by the bridge after a\n\t * successful `device.claim` server response.\n\t */\n\tasync markClaimed(): Promise<void> {\n\t\tawait this.identity.mutate(async (current) => {\n\t\t\tif (!current) {\n\t\t\t\tthrow new Error(\"Cannot mark-claimed without a prior enrollment\");\n\t\t\t}\n\t\t\tconst next: PersistedDeviceIdentity = {\n\t\t\t\t...current,\n\t\t\t\tbindingState: \"claimed\",\n\t\t\t\tlastSyncAt: this.now().toISOString(),\n\t\t\t\tlastSyncError: undefined,\n\t\t\t};\n\t\t\treturn { next };\n\t\t});\n\t}\n}\n\nfunction deriveExpiresAt(_identity: PersistedDeviceIdentity, _now: () => Date): string | undefined {\n\t// No explicit expiry on the server today (per phase-5-device-header-spec.md\n\t// §9 #1: 7-day grace is rotation-only). The shape is here for forward\n\t// compatibility — when the server adds a per-device expiry, this\n\t// pulls the value from the response without changing the call site.\n\treturn undefined;\n}\n\nfunction synthesizeEnrollResponse(\n\trandom: RandomBytesFn,\n\texisting: PersistedDeviceIdentity | null\n): EnrollResponse {\n\tconst publicId = existing?.publicId ?? random(PUBLIC_ID_BYTES).toString(\"hex\");\n\tconst secretVersion = (existing?.secretVersion ?? 0) + 1;\n\treturn {\n\t\tpublicId,\n\t\tdeviceSecret: random(SECRET_BYTES).toString(\"hex\"),\n\t\tsecretVersion,\n\t\tbindingState: existing?.bindingState === \"claimed\" ? \"claimed\" : \"anonymous\",\n\t};\n}\n\nasync function emptyIdentity(random: RandomBytesFn): Promise<PersistedDeviceIdentity> {\n\treturn {\n\t\tversion: 1,\n\t\tinstallationId: deriveInstallationId(),\n\t\tmachineId: \"unknown\",\n\t\tplatform: \"unknown\",\n\t\tpublicId: random(PUBLIC_ID_BYTES).toString(\"hex\"),\n\t\tsecretVersion: 1,\n\t\tbindingState: \"anonymous\",\n\t\tdeviceSecret: random(SECRET_BYTES).toString(\"hex\"),\n\t\tlastEnrollAt: new Date().toISOString(),\n\t};\n}\n","/**\n * InstallationId — Derive a stable per-machine identifier from\n * `os.hostname()` + `os.userInfo()`.\n *\n * Per `docs/requirements/phase-5-auth-device-toolbox/3.功能拆分.md` B2,\n * `installationId` MUST survive Extension re-installs but vary across\n * machines. We compute a UUID v5-style hash over hostname + username +\n * platform so the result is:\n * - deterministic (same machine → same id)\n * - collision-resistant (SHA-256, 128-bit truncated)\n * - browser-safe (no PII survives — username never enters output)\n *\n * Note: this intentionally differs from `vscode.env.machineId`, which\n * is per-Extension-install and uses a different algorithm. The two\n * coexist: `installationId` is what gets sent to the server, while\n * `machineId` (raw `os.hostname()`) is for diagnostics.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `InstallationId.ts os.hostname() + os.userInfo() 哈希生成`\n * - 3.功能拆分.md B2 — installationId semantics\n */\n\nimport { createHash, randomUUID } from \"node:crypto\";\nimport * as os from \"node:os\";\n\n/** Hex-encoded SHA-256 input. Format: `<hostname>|<username>|<platform>|<nodeVersion>`. */\nfunction fingerprintMaterial(): string {\n\t// `os.userInfo()` is undefined-ish on Windows in some sandboxes; fall\n\t// back to a process env user, then to a constant placeholder. We never\n\t// emit the raw username to the caller — it only enters the hash.\n\tlet username = \"unknown\";\n\ttry {\n\t\tusername = os.userInfo().username;\n\t} catch {\n\t\tusername = process.env.USER ?? process.env.USERNAME ?? \"unknown\";\n\t}\n\treturn [\n\t\tos.hostname(),\n\t\tusername,\n\t\tos.platform(),\n\t\tos.arch(),\n\t\tprocess.versions.node ?? \"unknown\",\n\t].join(\"|\");\n}\n\n/**\n * Returns a deterministic installation id for the current machine.\n * Use this when you need an id that survives Extension reinstalls\n * but stays stable across restarts on the same machine.\n */\nexport function deriveInstallationId(): string {\n\tconst material = fingerprintMaterial();\n\tconst digest = createHash(\"sha256\").update(material).digest(\"hex\");\n\t// Take the first 32 hex chars (128 bits) and reformat as UUID v4-shape\n\t// so the output looks like a UUID to downstream consumers while\n\t// remaining a pure SHA-256 truncation.\n\treturn formatAsV4(digest.slice(0, 32));\n}\n\n/**\n * Returns a random installation id (UUID v4). Use this for fresh\n * installs when no fingerprint input is available (e.g. containerized\n * CI runners where `os.hostname()` is meaningless).\n */\nexport function randomInstallationId(): string {\n\treturn randomUUID();\n}\n\n/** SHA-256 fingerprint material exposed for tests + diagnostics. */\nexport function fingerprintSource(): string {\n\treturn fingerprintMaterial();\n}\n\nfunction formatAsV4(hex32: string): string {\n\t// Stamp version 4 + variant bits per RFC 4122 §4.4. The bits are\n\t// cosmetic — the underlying entropy is still SHA-256.\n\tconst chars = hex32.split(\"\");\n\t// Version nibble (position 12 in canonical UUID, index 13 of the 32-char string).\n\tconst versionIdx = 12;\n\tconst variantIdx = 16;\n\tconst versionChar = (parseInt(chars[versionIdx] ?? \"8\", 16) & 0x0) | 0x4;\n\tchars[versionIdx] = versionChar.toString(16);\n\t// Variant nibble: 10xx → first hex char of the 17th position.\n\tconst variantChar = (parseInt(chars[variantIdx] ?? \"8\", 16) & 0x3) | 0x8;\n\tchars[variantIdx] = variantChar.toString(16);\n\tconst formatted = chars.join(\"\");\n\treturn `${formatted.slice(0, 8)}-${formatted.slice(8, 12)}-${formatted.slice(12, 16)}-${formatted.slice(16, 20)}-${formatted.slice(20, 32)}`;\n}\n","/**\n * IdentityStore — Atomic JSON persistence for the device identity file.\n *\n * Stores the `PersistedDeviceIdentity` (incl. the HMAC secret cleartext)\n * at `~/.serviceme/device.json` (per `phase-5-device-header-spec.md`\n * §3.1). Writes are atomic via `write-tmp + fsync + rename`, matching\n * the `SkillStore` / `ToolboxStore` precedent. Concurrent writes are\n * serialized with a mkdir-based file lock (POSIX-atomic) — proper\n * cross-process locking is deferred to Phase 6+ per the open spec.\n *\n * The file mode is `0600` (owner read/write only) so the cleartext\n * secret stays safe at rest. On Windows the mode hint is a no-op\n * (Windows uses ACLs) but `writeFile` still succeeds.\n *\n * Migration — IdentityStore auto-detects a v0-shape (pre-Phase-5.2)\n * file written by the Extension's old `globalState` blob:\n * { version: 1, claimed: false, publicKeyFingerprint: null }\n * In that case the file is migrated forward to the v1 schema on the\n * next write (the data fields are empty and a fresh enroll is required).\n * The full Extension `globalState` → JSON migration happens in the\n * Phase 5.5 adapter (`apps/extension/.../DeviceService.ts`) since the\n * adapter holds the live `globalState` access.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `IdentityStore.ts 持久化到 ~/.config/serviceme/device.json, 原子写`\n * - `docs/architecture/phase-5-device-header-spec.md` §3.1, §2.5\n */\n\nimport * as fsp from \"node:fs/promises\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nimport { getDeviceJsonPath, getServicemeHome } from \"../paths/userHome\";\n\nimport {\n\ttype AtomicWriteResult,\n\tDEVICE_JSON_SCHEMA_VERSION,\n\ttype IdentityStoreHooks,\n\ttype PersistedDeviceIdentity,\n} from \"./types\";\n\nconst FILE_MODE = 0o600;\nconst LOCK_DIR_MODE = 0o700;\nconst DEFAULT_LOCK_TIMEOUT_MS = 5000;\nconst DEFAULT_LOCK_RETRY_MS = 25;\nconst TMP_SUFFIX = \".tmp\";\n\n/**\n * Minimal interface for reading + writing the persisted identity file.\n * Default impl uses `getDeviceJsonPath()` (which honors `SERVICEME_HOME`),\n * but tests can substitute a custom path for isolation.\n */\nexport interface IdentityFileBackend {\n\tread(filePath: string): Promise<PersistedDeviceIdentity | null>;\n\twrite(filePath: string, payload: PersistedDeviceIdentity): Promise<AtomicWriteResult>;\n\texists(filePath: string): Promise<boolean>;\n\tdelete(filePath: string): Promise<void>;\n\tlistDir?(dir: string): Promise<string[]>;\n}\n\nexport interface IdentityStoreOptions {\n\tfilePath?: string;\n\thooks?: IdentityStoreHooks;\n\tlockTimeoutMs?: number;\n\tlockRetryMs?: number;\n\t/** Injectable clock for deterministic tests. */\n\tnow?: () => Date;\n\tbackend?: IdentityFileBackend;\n}\n\n/**\n * Default file backend — uses `node:fs/promises` with the canonical\n * tmp-then-rename atomic-write pattern.\n */\nexport class FsIdentityFileBackend implements IdentityFileBackend {\n\tasync exists(filePath: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fsp.access(filePath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync read(filePath: string): Promise<PersistedDeviceIdentity | null> {\n\t\ttry {\n\t\t\tconst buf = await fsp.readFile(filePath, \"utf8\");\n\t\t\tconst parsed = JSON.parse(buf) as unknown;\n\t\t\treturn migratePersistedIdentity(parsed);\n\t\t} catch (err) {\n\t\t\tif (isNodeError(err) && err.code === \"ENOENT\") return null;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync write(filePath: string, payload: PersistedDeviceIdentity): Promise<AtomicWriteResult> {\n\t\tawait fsp.mkdir(path.dirname(filePath), { recursive: true });\n\t\tconst tmpPath = `${filePath}${TMP_SUFFIX}`;\n\t\tconst bytes = Buffer.from(JSON.stringify(payload, null, \"\\t\"), \"utf8\");\n\t\t// Ensure tmp is fresh (in case a previous run died mid-write).\n\t\tawait fsp.rm(tmpPath, { force: true });\n\t\tconst handle = await fsp.open(tmpPath, \"w\", FILE_MODE);\n\t\ttry {\n\t\t\tawait handle.writeFile(bytes);\n\t\t\tawait handle.sync();\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait fsp.rename(tmpPath, filePath);\n\t\t// Best-effort chmod for filesystems that ignore mode on create (Windows).\n\t\tawait fsp.chmod(filePath, FILE_MODE).catch(() => undefined);\n\t\treturn { bytesWritten: bytes.byteLength, tmpPath };\n\t}\n\n\tasync delete(filePath: string): Promise<void> {\n\t\tawait fsp.rm(filePath, { force: true });\n\t}\n}\n\n/**\n * Reconcile an unknown on-disk shape into the current `PersistedDeviceIdentity`.\n *\n * - v1 IdentityStore files (current shape) pass through unchanged.\n * - v0 bootstrap files (`{ version: 1, claimed: false, publicKeyFingerprint: null }`)\n * are recognized by their placeholder keys and discarded; the next\n * enroll writes a fresh identity.\n * - Anything else throws — refuse to silently drop user data.\n */\nfunction migratePersistedIdentity(parsed: unknown): PersistedDeviceIdentity | null {\n\tif (!isRecord(parsed)) {\n\t\tthrow new Error(\"device.json: top-level must be an object\");\n\t}\n\tconst version = parsed.version;\n\tif (version === DEVICE_JSON_SCHEMA_VERSION) {\n\t\t// Pre-Phase-5.2 placeholder shape carries `claimed` /\n\t\t// `publicKeyFingerprint` but no real device fields. Recognize\n\t\t// the marker and return null so the next enroll writes fresh data.\n\t\tif (\n\t\t\tparsed.publicId === undefined &&\n\t\t\tparsed.deviceSecret === undefined &&\n\t\t\t(\"claimed\" in parsed || \"publicKeyFingerprint\" in parsed)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\t// Trust the schema — the writer is also us.\n\t\treturn parsed as unknown as PersistedDeviceIdentity;\n\t}\n\tif (typeof version === \"number\" && version < DEVICE_JSON_SCHEMA_VERSION) {\n\t\t// Pre-Phase-5.2 bootstrap shape — the file is empty placeholder\n\t\t// data; nothing to migrate. Return null to signal \"no identity\".\n\t\treturn null;\n\t}\n\tthrow new Error(`device.json: unsupported schema version ${String(version)}`);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isNodeError(value: unknown): value is NodeJS.ErrnoException {\n\treturn value instanceof Error && typeof (value as { code?: unknown }).code === \"string\";\n}\n\n/**\n * mkdir-based advisory file lock. POSIX mkdir is atomic; on Windows\n * modern filesystems (NTFS) it's also atomic at the API level. Sufficient\n * for single-host, single-user scenarios (which is the SERVICEME threat\n * model). Cross-host locking is out of scope.\n */\nclass FileLock {\n\tprivate readonly dirPath: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly retryMs: number;\n\tprivate acquired = false;\n\n\tconstructor(filePath: string, timeoutMs: number, retryMs: number) {\n\t\tthis.dirPath = `${filePath}.lock`;\n\t\tthis.timeoutMs = timeoutMs;\n\t\tthis.retryMs = retryMs;\n\t}\n\n\tasync acquire(): Promise<void> {\n\t\tconst start = Date.now();\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tawait fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });\n\t\t\t\tthis.acquired = true;\n\t\t\t\treturn;\n\t\t\t} catch (err) {\n\t\t\t\tif (!isNodeError(err) || err.code !== \"EEXIST\") {\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\tif (Date.now() - start >= this.timeoutMs) {\n\t\t\t\t\tthrow new Error(`IdentityStore lock acquisition timed out for ${this.dirPath}`);\n\t\t\t\t}\n\t\t\t\tawait delay(this.retryMs);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync release(): Promise<void> {\n\t\tif (!this.acquired) return;\n\t\tthis.acquired = false;\n\t\tawait fsp.rm(this.dirPath, { recursive: true, force: true });\n\t}\n}\n\nexport class IdentityStore {\n\tprivate readonly filePath: string;\n\tprivate readonly backend: IdentityFileBackend;\n\tprivate readonly hooks: IdentityStoreHooks;\n\tprivate readonly lockTimeoutMs: number;\n\tprivate readonly lockRetryMs: number;\n\tprivate readonly now: () => Date;\n\n\tconstructor(opts: IdentityStoreOptions = {}) {\n\t\tthis.filePath = opts.filePath ?? getDeviceJsonPath();\n\t\tthis.backend = opts.backend ?? new FsIdentityFileBackend();\n\t\tthis.hooks = opts.hooks ?? {};\n\t\tthis.lockTimeoutMs = opts.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;\n\t\tthis.lockRetryMs = opts.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;\n\t\tthis.now = opts.now ?? (() => new Date());\n\t}\n\n\t/** Absolute path to the underlying JSON file (test seam). */\n\tgetFilePath(): string {\n\t\treturn this.filePath;\n\t}\n\n\t/** True when the JSON file already exists on disk. */\n\tasync exists(): Promise<boolean> {\n\t\treturn this.backend.exists(this.filePath);\n\t}\n\n\t/** Read the persisted identity; returns `null` when no identity is stored. */\n\tasync read(): Promise<PersistedDeviceIdentity | null> {\n\t\treturn this.backend.read(this.filePath);\n\t}\n\n\t/**\n\t * Atomically write the given identity. Concurrent writers are\n\t * serialized via the file lock; the read-modify-write happens\n\t * inside the lock so callers can't see a partial state.\n\t */\n\tasync write(next: PersistedDeviceIdentity): Promise<AtomicWriteResult> {\n\t\tawait this.hooks.beforeWrite?.(next);\n\t\tconst lock = new FileLock(this.filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tconst stamped: PersistedDeviceIdentity = {\n\t\t\t\t...next,\n\t\t\t\tversion: DEVICE_JSON_SCHEMA_VERSION,\n\t\t\t};\n\t\t\tconst result = await this.backend.write(this.filePath, stamped);\n\t\t\tawait this.hooks.afterWrite?.(stamped);\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t}\n\n\t/**\n\t * Read-modify-write under the same lock. The mutator receives the\n\t * current identity (or `null` on first call) and returns the\n\t * replacement. Throwing inside the mutator aborts the write.\n\t */\n\tasync mutate<T>(\n\t\tmutator: (\n\t\t\tcurrent: PersistedDeviceIdentity | null\n\t\t) => Promise<{ next: PersistedDeviceIdentity; result?: T }>\n\t): Promise<{ result: T | undefined; written: PersistedDeviceIdentity }> {\n\t\tconst lock = new FileLock(this.filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tconst current = await this.backend.read(this.filePath);\n\t\t\tconst { next, result } = await mutator(current);\n\t\t\tconst stamped: PersistedDeviceIdentity = {\n\t\t\t\t...next,\n\t\t\t\tversion: DEVICE_JSON_SCHEMA_VERSION,\n\t\t\t};\n\t\t\tawait this.hooks.beforeWrite?.(stamped);\n\t\t\tawait this.backend.write(this.filePath, stamped);\n\t\t\tawait this.hooks.afterWrite?.(stamped);\n\t\t\treturn { result, written: stamped };\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t}\n\n\t/** Wipe the persisted identity (used by `device.enroll --force`). */\n\tasync clear(): Promise<void> {\n\t\tawait this.backend.delete(this.filePath);\n\t}\n\n\t/**\n\t * Resolve the installation metadata for the current machine.\n\t * Pure helper — no I/O, just `os.*` calls.\n\t */\n\tresolveInstallationMetadata(): Pick<\n\t\tPersistedDeviceIdentity,\n\t\t\"installationId\" | \"machineId\" | \"platform\"\n\t> {\n\t\tconst machineId = os.hostname();\n\t\tconst platform = os.platform();\n\t\t// installationId is derived by `InstallationId.ts` — pass the\n\t\t// caller's already-computed value via `material` so we don't\n\t\t// recompute the SHA twice in a row.\n\t\treturn {\n\t\t\tinstallationId: \"\", // intentionally empty; caller fills via deriveInstallationId()\n\t\t\tmachineId,\n\t\t\tplatform,\n\t\t};\n\t}\n\n\t/**\n\t * Ensure the parent directory exists (`~/.serviceme/`). Idempotent.\n\t * Useful when the bootstrap phase5 placeholder wasn't run yet.\n\t */\n\tasync ensureHome(): Promise<void> {\n\t\tawait fsp.mkdir(getServicemeHome(), { recursive: true });\n\t\tawait fsp.mkdir(path.dirname(this.filePath), { recursive: true });\n\t}\n}\n","import * as os from \"node:os\";\nimport * as path from \"node:path\";\n\n/**\n * SERVICEME user home directory helpers.\n *\n * All paths resolve under {@link getServicemeHome}, which is either the\n * `SERVICEME_HOME` environment variable (when set and non-empty) or\n * `$HOME/.serviceme` on POSIX / `%USERPROFILE%\\.serviceme` on Windows.\n *\n * Tests inject `homeDir` and `servicemeHomeEnv` overrides via\n * {@link setUserHomeOverrides} / {@link resetUserHomeOverrides} so they can\n * exercise the path logic without touching the real user environment.\n */\n\n/** Layout constants — kept in one place so other modules can reuse them. */\nexport const SERVICEME_DIR_NAME = \".serviceme\";\nexport const REPOS_SUBDIR = \"repos\";\nexport const CACHE_SUBDIR = \"cache\";\nexport const DRAFTS_SUBDIR = \"drafts\";\nexport const SKILL_DRAFTS_SUBDIR = \"skills\";\nexport const AGENT_DRAFTS_SUBDIR = \"agents\";\nexport const REPOS_CONFIG_FILENAME = \"repos.json\";\n\n/**\n * Repo id regex — used to validate any `repoId` argument before it is joined\n * into a filesystem path. Keeps path traversal attempts out and gives us a\n * predictable on-disk shape.\n */\nexport const SAFE_REPO_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;\n\n/** Environment variable that overrides the user-home root directory. */\nexport const SERVICEME_HOME_ENV = \"SERVICEME_HOME\";\n\n/**\n * Test seam: lets unit tests inject deterministic values for `os.homedir()`\n * and the `SERVICEME_HOME` env override without actually mutating\n * `process.env` (which would leak into other tests).\n */\ninterface UserHomeOverrides {\n\thomeDir?: string | undefined;\n\tservicemeHomeEnv?: string | undefined;\n\tplatform?: NodeJS.Platform | undefined;\n}\n\nlet activeOverrides: UserHomeOverrides = {};\n\nexport function setUserHomeOverrides(overrides: UserHomeOverrides): void {\n\tactiveOverrides = { ...overrides };\n}\n\nexport function resetUserHomeOverrides(): void {\n\tactiveOverrides = {};\n}\n\nfunction resolveHomeDir(): string {\n\tconst injected = activeOverrides.homeDir;\n\tif (injected !== undefined) {\n\t\treturn injected;\n\t}\n\treturn os.homedir();\n}\n\nfunction resolveServicemeHomeEnv(): string | undefined {\n\tconst injected = activeOverrides.servicemeHomeEnv;\n\tif (injected !== undefined) {\n\t\t// Treat empty string as \"not set\" — `process.env` always returns a string\n\t\t// but tests may deliberately pass \"\" to opt out.\n\t\treturn injected.length > 0 ? injected : undefined;\n\t}\n\tconst envValue = process.env[SERVICEME_HOME_ENV];\n\treturn envValue && envValue.length > 0 ? envValue : undefined;\n}\n\nfunction resolvePlatform(): NodeJS.Platform {\n\treturn activeOverrides.platform ?? process.platform;\n}\n\nexport function assertSafeRepoId(repoId: string): string {\n\tif (typeof repoId !== \"string\" || repoId.length === 0 || !SAFE_REPO_ID_PATTERN.test(repoId)) {\n\t\tthrow new Error(\n\t\t\t`Invalid repo id: ${JSON.stringify(repoId)}. ` +\n\t\t\t\t`Must match ${SAFE_REPO_ID_PATTERN} (alphanumeric start, then ` +\n\t\t\t\t`alphanumerics / underscores / hyphens, ≤ 64 chars).`\n\t\t);\n\t}\n\treturn repoId;\n}\n\n/**\n * The raw OS home directory (`os.homedir()`), honoring test overrides\n * ({@link setUserHomeOverrides}). Exported for callers that need a\n * home-relative path *outside* of `~/.serviceme` — e.g. the\n * `~/.agents/{skills,agents}` convention used by `SkillLinker` for\n * user-scope links.\n */\nexport function getHomeDir(): string {\n\treturn resolveHomeDir();\n}\n\n/**\n * Root directory for all SERVICEME user-level state. Resolves to\n * `${SERVICEME_HOME}` when that env var is set, otherwise `${HOME}/.serviceme`\n * (POSIX) or `%USERPROFILE%\\.serviceme` (Windows via `os.homedir`).\n */\nexport function getServicemeHome(): string {\n\tconst override = resolveServicemeHomeEnv();\n\tif (override !== undefined) {\n\t\treturn path.resolve(override);\n\t}\n\treturn path.join(resolveHomeDir(), SERVICEME_DIR_NAME);\n}\n\n/** `$HOME/.serviceme/repos` (or `${SERVICEME_HOME}/repos`). */\nexport function getReposDir(): string {\n\treturn path.join(getServicemeHome(), REPOS_SUBDIR);\n}\n\n/** `$HOME/.serviceme/repos/<repoId>` — validates `repoId` first. */\nexport function getRepoDir(repoId: string): string {\n\treturn path.join(getReposDir(), assertSafeRepoId(repoId));\n}\n\n/** `$HOME/.serviceme/cache` — generic per-user cache. */\nexport function getCacheDir(): string {\n\treturn path.join(getServicemeHome(), CACHE_SUBDIR);\n}\n\n/** `$HOME/.serviceme/cache/<repoId>` — per-repo sync state lives here. */\nexport function getRepoCacheDir(repoId: string): string {\n\treturn path.join(getCacheDir(), assertSafeRepoId(repoId));\n}\n\n/** `$HOME/.serviceme/drafts` — local edits not yet pushed upstream. */\nexport function getDraftsDir(): string {\n\treturn path.join(getServicemeHome(), DRAFTS_SUBDIR);\n}\n\n/** `$HOME/.serviceme/drafts/skills` — local skill drafts. */\nexport function getSkillDraftsDir(): string {\n\treturn path.join(getDraftsDir(), SKILL_DRAFTS_SUBDIR);\n}\n\n/** `$HOME/.serviceme/drafts/agents` — local agent drafts. */\nexport function getAgentDraftsDir(): string {\n\treturn path.join(getDraftsDir(), AGENT_DRAFTS_SUBDIR);\n}\n\n/**\n * `$HOME/.serviceme/repos.json` — the single config entry point for repo\n * metadata. Always under the resolved home root (i.e. follows\n * `SERVICEME_HOME` overrides too).\n */\nexport function getReposConfigPath(): string {\n\treturn path.join(getServicemeHome(), REPOS_CONFIG_FILENAME);\n}\n\n/**\n * Convenience helper for callers that need to switch behaviour on platform\n * (e.g. `SkillLinker` chooses symlink vs junction). Exported mostly so tests\n * can pin the value without touching `process.platform` directly.\n */\nexport function getHomePlatform(): NodeJS.Platform {\n\treturn resolvePlatform();\n}\n\n// ─── Scheduled Tasks paths (added by M1.5.3) ───────────────────────────────\n\n/** Layout constants for scheduled-tasks files. Kept here so other modules\n * can reuse them and the names stay in sync with the design doc. */\nexport const SCHEDULED_TASKS_CONFIG_FILENAME = \"scheduled-tasks.json\";\nexport const SCHEDULED_TASKS_LOG_FILENAME = \"scheduled-tasks-log.json\";\nexport const SCHEDULER_PID_FILENAME = \"scheduler.pid\";\nexport const SCHEDULER_LOCK_FILENAME = \"scheduler.lock\";\nexport const SCHEDULER_LOG_FILENAME = \"scheduler.log\";\nexport const MIGRATION_FAILURES_FILENAME = \"migration-failures.json\";\nexport const KNOWN_WORKSPACES_FILENAME = \"known-workspaces.json\";\n\n/** `~/.serviceme/scheduled-tasks.json` — the global v2 task list. */\nexport function getScheduledTasksConfigPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULED_TASKS_CONFIG_FILENAME);\n}\n\n/** `~/.serviceme/scheduled-tasks-log.json` — the global execution log (200 LRU). */\nexport function getScheduledTasksLogPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULED_TASKS_LOG_FILENAME);\n}\n\n/** `~/.serviceme/scheduler.pid` — global daemon PID file. */\nexport function getSchedulerPidPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULER_PID_FILENAME);\n}\n\n/** `~/.serviceme/scheduler.lock` — global daemon startup flock. */\nexport function getSchedulerLockPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULER_LOCK_FILENAME);\n}\n\n/** `~/.serviceme/scheduler.log` — global daemon log (daemon lifecycle, not task execution). */\nexport function getSchedulerLogPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULER_LOG_FILENAME);\n}\n\n/** `~/.serviceme/migration-failures.json` — diagnostics for failed v1→v2 imports. */\nexport function getMigrationFailuresPath(): string {\n\treturn path.join(getServicemeHome(), MIGRATION_FAILURES_FILENAME);\n}\n\n/** `~/.serviceme/known-workspaces.json` — workspace list the extension has seen. */\nexport function getKnownWorkspacesPath(): string {\n\treturn path.join(getServicemeHome(), KNOWN_WORKSPACES_FILENAME);\n}\n\n// ─── Phase 5 (auth + device + toolbox) placeholders (Spec §11.12) ────────\n\n/** Layout constants for the Phase 5 client-side state files. The server\n * already has the corresponding routes (`/api/v1/auth/...`,\n * `/api/v1/device/...`, etc.) — the client just needs the on-disk\n * file paths + a first-run bootstrap so the auth/device/toolbox\n * services can open + read them without a per-call existence check. */\nexport const CREDENTIALS_CONFIG_FILENAME = \"credentials.json\";\nexport const DEVICE_JSON_FILENAME = \"device.json\";\nexport const TOOLBOX_JSON_FILENAME = \"toolbox.json\";\nexport const MACHINE_ID_FILENAME = \"machine-id\";\nexport const PROFILES_JSON_FILENAME = \"profiles.json\";\n\n/** `~/.serviceme/credentials.json` — better-auth session tokens + refresh state (client-side cache). */\nexport function getCredentialsConfigPath(): string {\n\treturn path.join(getServicemeHome(), CREDENTIALS_CONFIG_FILENAME);\n}\n\n/** `~/.serviceme/device.json` — device enrollment payload (id, public key fingerprint, claimed-by). */\nexport function getDeviceJsonPath(): string {\n\treturn path.join(getServicemeHome(), DEVICE_JSON_FILENAME);\n}\n\n/** `~/.serviceme/toolbox.json` — local toolbox state (installed tool refs, per-user prefs). */\nexport function getToolboxJsonPath(): string {\n\treturn path.join(getServicemeHome(), TOOLBOX_JSON_FILENAME);\n}\n\n/** `~/.serviceme/machine-id` — opaque stable per-install id (uuid v4 string, no JSON wrapper). */\nexport function getMachineIdPath(): string {\n\treturn path.join(getServicemeHome(), MACHINE_ID_FILENAME);\n}\n\n/** `~/.serviceme/profiles.json` — cached projection of server-side profile rows; refresh on demand. */\nexport function getProfilesJsonPath(): string {\n\treturn path.join(getServicemeHome(), PROFILES_JSON_FILENAME);\n}\n","/**\n * Internal types for the device domain.\n *\n * These types are NOT re-exported from the protocol package — they are\n * implementation details of the IdentityStore + Enroller. Public data\n * models (the bridge wire shape) live in `@serviceme/devtools-protocol`'s\n * `device.ts`.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `device/types.ts`\n */\n\nimport type { DeviceBindingState } from \"@serviceme/devtools-protocol\";\n\n/**\n * Schema version of the on-disk `device.json` file. Bumped when the\n * shape changes incompatibly. IdentityStore checks this on read and\n * either migrates (versions ≤ 1) or refuses (versions > supported).\n */\nexport const DEVICE_JSON_SCHEMA_VERSION = 1;\n\n/** Internal representation of the persisted identity file. */\nexport interface PersistedDeviceIdentity {\n\tversion: number;\n\t/** Stable per-machine id (UUID v4 shape) — survives secret rotates. */\n\tinstallationId: string;\n\t/** Raw `os.hostname()` for diagnostics. */\n\tmachineId: string;\n\t/** Platform string (e.g. \"darwin\"). */\n\tplatform: string;\n\t/** Optional hostname override for environments where `os.hostname()` is unstable. */\n\thostname?: string;\n\t/** Public, non-secret id returned by the server. 32-char hex. */\n\tpublicId: string;\n\t/** Monotonic secret version counter, starts at 1 after first enroll. */\n\tsecretVersion: number;\n\t/** Current binding state — drives the re-enroll matrix. */\n\tbindingState: DeviceBindingState;\n\t/** HMAC secret (32 bytes hex-encoded = 64 chars). Persisted per `device-header-spec.md` §3.1. */\n\tdeviceSecret: string;\n\t/** Optional: previous secret retained during the grace window (rotation). */\n\tpreviousDeviceSecret?: string;\n\t/** Optional: ISO timestamp at which the previous secret stops being accepted. */\n\tpreviousSecretExpiresAt?: string;\n\t/** ISO timestamp of the most recent successful enroll / rotate. */\n\tlastEnrollAt: string;\n\t/** Optional ISO timestamp of the most recent server sync. */\n\tlastSyncAt?: string;\n\t/** Optional human-readable message for the last sync error. */\n\tlastSyncError?: string;\n}\n\n/** Result of a single atomic write. */\nexport interface AtomicWriteResult {\n\tbytesWritten: number;\n\t/** Path to the temp file (post-rename it no longer exists; useful for diagnostics). */\n\ttmpPath: string;\n}\n\n/** Hook called before/after every identity write — used by tests to assert concurrency safety. */\nexport interface IdentityStoreHooks {\n\tbeforeWrite?: (next: PersistedDeviceIdentity) => void | Promise<void>;\n\tafterWrite?: (next: PersistedDeviceIdentity) => void | Promise<void>;\n}\n","/**\n * DeviceCore — Main entry for the device domain.\n *\n * Aggregates `IdentityStore` + `Enroller` + signer helpers into a single\n * surface that the CLI / Extension / Bridge can call. Pure orchestration\n * — no HTTP of its own (the actual `POST /api/v1/devices/enroll` lives\n * behind `EnrollerOptions.enrollRequest`, wired in Phase 5.4).\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `DeviceCore.ts 主入口`\n * - ADL-003 — data model in `@serviceme/devtools-protocol`\n * - `docs/architecture/phase-5-device-header-spec.md` §3.1\n */\n\nimport type {\n\tDeviceEnrollResult,\n\tDeviceIdentityState,\n\tDeviceMetadata,\n\tDeviceRotateSecretResult,\n\tDeviceStatus,\n} from \"@serviceme/devtools-protocol\";\nimport { buildSignedHeaders, DeviceAuthHeaders, type DeviceSignedHeaders } from \"./deviceAuth\";\nimport { Enroller, type EnrollRequestFn } from \"./Enroller\";\nimport { IdentityStore } from \"./IdentityStore\";\nimport { deriveInstallationId } from \"./InstallationId\";\nimport type { PersistedDeviceIdentity } from \"./types\";\n\nexport interface DeviceCoreOptions {\n\tidentityStore?: IdentityStore;\n\tenrollRequest?: EnrollRequestFn;\n\tnow?: () => Date;\n\t/** Optional override for `deriveInstallationId` (used by tests for determinism). */\n\tresolveInstallationId?: () => string;\n}\n\nexport class DeviceCore {\n\tprivate readonly identity: IdentityStore;\n\tprivate readonly enroller: Enroller;\n\tprivate readonly resolveInstallationId: () => string;\n\n\tconstructor(opts: DeviceCoreOptions = {}) {\n\t\tthis.identity = opts.identityStore ?? new IdentityStore();\n\t\tthis.enroller = new Enroller({\n\t\t\tidentityStore: this.identity,\n\t\t\tenrollRequest: opts.enrollRequest,\n\t\t\tnow: opts.now,\n\t\t});\n\t\tthis.resolveInstallationId = opts.resolveInstallationId ?? deriveInstallationId;\n\t}\n\n\t/** Read-only snapshot of the device status (matches `device.status` wire shape). */\n\tasync status(): Promise<DeviceStatus> {\n\t\tconst stored = await this.identity.read();\n\t\treturn stored\n\t\t\t? {\n\t\t\t\t\tbindingState: stored.bindingState,\n\t\t\t\t\tidentity: projectIdentity(stored),\n\t\t\t\t\tmetadata: projectMetadata(stored),\n\t\t\t\t\tlastSyncAt: stored.lastSyncAt,\n\t\t\t\t\tlastSyncError: stored.lastSyncError,\n\t\t\t\t}\n\t\t\t: { bindingState: \"anonymous\" };\n\t}\n\n\t/** Enroll (or re-enroll) the device. */\n\tasync enroll(opts: { force?: boolean; requireAuth?: boolean } = {}): Promise<DeviceEnrollResult> {\n\t\treturn this.enroller.enroll(opts);\n\t}\n\n\t/** Wait for any in-flight enrollment to finish. Use before `buildSignedHeaders` so that a concurrent `syncDeviceInfo` enrollment has time to write the identity to the store. */\n\tasync waitForEnrollment(): Promise<void> {\n\t\tawait this.enroller.waitForEnrollment();\n\t}\n\n\t/** True when an enrollment is currently in-flight. Used by callers to skip triggering a competing enrollment. */\n\tisEnrolling(): boolean {\n\t\treturn this.enroller.isEnrolling();\n\t}\n\n\t/** Rotate the HMAC secret while keeping the previous one for the grace window. */\n\tasync rotateSecret(opts: { gracePeriodDays?: number } = {}): Promise<DeviceRotateSecretResult> {\n\t\tconst result = await this.enroller.rotateSecret(opts);\n\t\tconst stored = await this.identity.read();\n\t\treturn {\n\t\t\t...result,\n\t\t\tgracePeriodEndsAt: stored?.previousSecretExpiresAt,\n\t\t};\n\t}\n\n\t/** Build the canonical 5-header map for an outbound signed request. */\n\tasync buildSignedHeaders(input: {\n\t\tmethod: string;\n\t\tpath: string;\n\t\tbody: string;\n\t}): Promise<DeviceSignedHeaders | null> {\n\t\tconst stored = await this.identity.read();\n\t\tif (!stored) return null;\n\t\treturn buildSignedHeaders({\n\t\t\tmethod: input.method,\n\t\t\tpath: input.path,\n\t\t\tbody: input.body,\n\t\t\tpublicId: stored.publicId,\n\t\t\tdeviceSecret: stored.deviceSecret,\n\t\t\tsecretVersion: stored.secretVersion,\n\t\t});\n\t}\n\n\t/** Raw stored identity (CLI/extension internal use). Test seam too. */\n\tasync readIdentity(): Promise<PersistedDeviceIdentity | null> {\n\t\treturn this.identity.read();\n\t}\n\n\t/** Wipe the local identity (the `--force` path before re-enroll). */\n\tasync clear(): Promise<void> {\n\t\tawait this.identity.clear();\n\t}\n\n\t/** Mark the device as claimed (called by the bridge after a successful claim). */\n\tasync markClaimed(): Promise<void> {\n\t\tawait this.enroller.markClaimed();\n\t}\n\n\t/** Mark the device as expired (server returned an expiry response). */\n\tasync markExpired(): Promise<void> {\n\t\tawait this.enroller.markExpired();\n\t}\n\n\t/** Expose the identity store (CLI uses it for direct file access in tests). */\n\tgetIdentityStore(): IdentityStore {\n\t\treturn this.identity;\n\t}\n\n\t/** Expose the enroller (CLI uses it for state inspection). */\n\tgetEnroller(): Enroller {\n\t\treturn this.enroller;\n\t}\n\n\t/** Header name constants — re-exported from `deviceAuth.ts`. */\n\tgetHeaderNames(): typeof DeviceAuthHeaders {\n\t\treturn DeviceAuthHeaders;\n\t}\n\n\t/** Compute the installation id for the current machine. */\n\tgetInstallationId(): string {\n\t\treturn this.resolveInstallationId();\n\t}\n}\n\nfunction projectIdentity(stored: PersistedDeviceIdentity): DeviceIdentityState {\n\treturn {\n\t\tpublicId: stored.publicId,\n\t\tsecretVersion: stored.secretVersion,\n\t\tbindingState: stored.bindingState,\n\t};\n}\n\nfunction projectMetadata(stored: PersistedDeviceIdentity): DeviceMetadata {\n\treturn {\n\t\tinstallationId: stored.installationId,\n\t\tmachineId: stored.machineId,\n\t\tplatform: stored.platform,\n\t\thostname: stored.hostname,\n\t};\n}\n","import * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport { getAgentDraftsDir, getSkillDraftsDir } from \"../paths/userHome\";\nimport { extractFrontmatter } from \"../skill-store/index\";\nimport type { SkillFile, SkillKind } from \"../skill-store/types\";\n\n/**\n * Skill & Agent v2 — Drafts (M4)\n *\n * Drafts are offline WIP copies of skills/agents that the user is\n * editing locally before submitting. They live under\n * `~/.serviceme/drafts/{skills,agents}/<draft-id>/` and have the same\n * file layout as a published entry: SKILL.md (or AGENT.md) plus any\n * extra files. The `SubmitClient` (also M4) reads a draft's files and\n * sends them to the server's validate endpoint before pushing.\n *\n * Draft ids are short random hex strings (16 hex chars / 8 bytes of\n * entropy) — long enough to be globally unique, short enough to be\n * pasteable. They are NOT user-meaningful; clients surface a `name`\n * from the draft's frontmatter instead.\n *\n * Persistence is atomic per-file (write to `.tmp-xxx` + rename).\n * The whole draft directory is never partially committed — failed\n * writes leave a `.tmp-xxx` orphan that the next save/cleanup can\n * sweep.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §2.1 (drafts layout),\n * §5.7 SubmitClient (consumer).\n */\n\nexport interface DraftSummary {\n\t/** Random id (16 hex chars). */\n\tid: string;\n\tkind: SkillKind;\n\t/** Path to the manifest file (SKILL.md or AGENT.md). */\n\tmanifestPath: string;\n\t/** Path to the directory containing the draft's files. */\n\tdir: string;\n\t/** Best-effort name from the frontmatter (empty string when absent). */\n\tname: string;\n\t/** Best-effort description from the frontmatter (empty string when absent). */\n\tdescription: string;\n\t/** ISO timestamp of the manifest file's mtime. */\n\tmodifiedAt: string;\n}\n\nexport interface DraftDetail extends DraftSummary {\n\tfiles: SkillFile[];\n}\n\nexport interface SaveDraftOptions {\n\tkind: SkillKind;\n\t/** Existing draft id; omit to create a new draft. */\n\tid?: string;\n\t/** Files to write. Must include a manifest (SKILL.md or AGENT.md). */\n\tfiles: SkillFile[];\n}\n\n/** Sentinel errors. */\nexport class DraftsError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"DraftsError\";\n\t}\n}\nexport class DraftNotFoundError extends DraftsError {\n\tconstructor(\n\t\tpublic readonly kind: SkillKind,\n\t\tpublic readonly id: string\n\t) {\n\t\tsuper(`draft not found: ${kind}/${id}`);\n\t\tthis.name = \"DraftNotFoundError\";\n\t}\n}\nexport class InvalidDraftError extends DraftsError {\n\tconstructor(message: string) {\n\t\tsuper(`invalid draft: ${message}`);\n\t\tthis.name = \"InvalidDraftError\";\n\t}\n}\n\n/**\n * Generate a short random hex id. Uses Node's `crypto.randomBytes` so\n * the output is unpredictable. 8 bytes = 16 hex chars = 64 bits of\n * entropy. We don't promise uniqueness (collisions exist with\n * astronomically low probability) — callers must treat ids as opaque.\n */\nexport function generateDraftId(): string {\n\treturn crypto.randomBytes(8).toString(\"hex\");\n}\n\n/**\n * Resolve the on-disk directory for a draft of a given kind + id.\n * Exported for tests + the SubmitClient which writes the committed\n * files into the repo.\n */\nexport function resolveDraftDir(kind: SkillKind, id: string): string {\n\tconst root = kind === \"skill\" ? getSkillDraftsDir() : getAgentDraftsDir();\n\treturn path.join(root, id);\n}\n\n/**\n * Persistent draft store backed by the local filesystem. Pure local —\n * no network, no git, no DB.\n */\nexport class DraftsStore {\n\t/**\n\t * List every draft of the given kind. Returns [] when the drafts\n\t * directory doesn't exist (cold start, never-saved state).\n\t */\n\tasync list(kind: SkillKind): Promise<DraftSummary[]> {\n\t\tconst root = kind === \"skill\" ? getSkillDraftsDir() : getAgentDraftsDir();\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = await fs.readdir(root);\n\t\t} catch (err) {\n\t\t\tif ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n\t\t\tthrow err;\n\t\t}\n\t\tconst out: DraftSummary[] = [];\n\t\tfor (const name of names) {\n\t\t\tconst summary = await this.tryReadSummary(kind, name);\n\t\t\tif (summary) out.push(summary);\n\t\t}\n\t\t// Most-recently-modified first.\n\t\tout.sort((a, b) => (a.modifiedAt < b.modifiedAt ? 1 : -1));\n\t\treturn out;\n\t}\n\n\t/** Single draft summary (manifest + metadata). Returns null when missing. */\n\tasync tryReadSummary(kind: SkillKind, id: string): Promise<DraftSummary | null> {\n\t\tconst dir = resolveDraftDir(kind, id);\n\t\tconst manifestFilename = kind === \"skill\" ? \"SKILL.md\" : \"AGENT.md\";\n\t\tconst manifestPath = path.join(dir, manifestFilename);\n\t\tlet stat: import(\"node:fs\").Stats;\n\t\ttry {\n\t\t\tstat = await fs.stat(manifestPath);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t\tlet content = \"\";\n\t\ttry {\n\t\t\tcontent = await fs.readFile(manifestPath, \"utf8\");\n\t\t} catch {\n\t\t\t// Manifest exists but unreadable — surface as empty name/desc.\n\t\t}\n\t\tconst parsed = extractFrontmatter(content);\n\t\tconst name = typeof parsed?.data.name === \"string\" ? parsed.data.name : \"\";\n\t\tconst description = typeof parsed?.data.description === \"string\" ? parsed.data.description : \"\";\n\t\treturn {\n\t\t\tid,\n\t\t\tkind,\n\t\t\tmanifestPath,\n\t\t\tdir,\n\t\t\tname,\n\t\t\tdescription,\n\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t};\n\t}\n\n\t/** Single draft detail (summary + all files). Throws when missing. */\n\tasync get(kind: SkillKind, id: string): Promise<DraftDetail> {\n\t\tconst summary = await this.tryReadSummary(kind, id);\n\t\tif (!summary) throw new DraftNotFoundError(kind, id);\n\t\tconst files = await this.getFiles(kind, id);\n\t\treturn { ...summary, files };\n\t}\n\n\t/** All files in a draft (manifest + extras), recursively. */\n\tasync getFiles(kind: SkillKind, id: string): Promise<SkillFile[]> {\n\t\tconst dir = resolveDraftDir(kind, id);\n\t\tconst out: SkillFile[] = [];\n\t\tawait collectRecursive(dir, dir, out);\n\t\tout.sort((a, b) => {\n\t\t\tconst aIsManifest = a.path === \"SKILL.md\" || a.path === \"AGENT.md\";\n\t\t\tconst bIsManifest = b.path === \"SKILL.md\" || b.path === \"AGENT.md\";\n\t\t\tif (aIsManifest && !bIsManifest) return -1;\n\t\t\tif (bIsManifest && !aIsManifest) return 1;\n\t\t\treturn a.path.localeCompare(b.path);\n\t\t});\n\t\treturn out;\n\t}\n\n\t/**\n\t * Create-or-update a draft. Returns the (possibly new) draft id.\n\t * Throws `InvalidDraftError` when the file list does not include\n\t * a manifest.\n\t */\n\tasync save(opts: SaveDraftOptions): Promise<string> {\n\t\tconst manifestFilename = opts.kind === \"skill\" ? \"SKILL.md\" : \"AGENT.md\";\n\t\tif (!opts.files.some((f) => f.path === manifestFilename)) {\n\t\t\tthrow new InvalidDraftError(`${manifestFilename} is required`);\n\t\t}\n\t\tconst id = opts.id ?? generateDraftId();\n\t\tconst dir = resolveDraftDir(opts.kind, id);\n\t\t// Clean the directory so a re-save with fewer files actually\n\t\t// removes the old ones (otherwise stale files linger).\n\t\tawait fs.rm(dir, { recursive: true, force: true });\n\t\tawait fs.mkdir(dir, { recursive: true });\n\t\t// Write every file atomically (tmp + rename).\n\t\tfor (const f of opts.files) {\n\t\t\tconst full = path.join(dir, f.path);\n\t\t\tawait fs.mkdir(path.dirname(full), { recursive: true });\n\t\t\tconst tmp = `${full}.${process.pid}.${Date.now()}.tmp`;\n\t\t\tawait fs.writeFile(tmp, f.content, \"utf8\");\n\t\t\tawait fs.rename(tmp, full);\n\t\t}\n\t\t// Sweep stale .tmp-* leftovers (in case a previous save crashed).\n\t\tawait this.sweepTmp(dir);\n\t\treturn id;\n\t}\n\n\t/** Delete a draft. ENOENT is silently ignored (idempotent). */\n\tasync delete(kind: SkillKind, id: string): Promise<void> {\n\t\tconst dir = resolveDraftDir(kind, id);\n\t\ttry {\n\t\t\tawait fs.rm(dir, { recursive: true, force: true });\n\t\t} catch (err) {\n\t\t\tif ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Best-effort cleanup of `.tmp-*` orphan files in the draft dir. */\n\tprivate async sweepTmp(dir: string): Promise<void> {\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = await fs.readdir(dir);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const n of names) {\n\t\t\tif (n.endsWith(\".tmp\")) {\n\t\t\t\tawait fs.rm(path.join(dir, n), { force: true }).catch(() => undefined);\n\t\t\t}\n\t\t}\n\t}\n}\n\nasync function collectRecursive(absDir: string, root: string, out: SkillFile[]): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (d.name.endsWith(\".tmp\")) continue;\n\t\tconst full = path.join(absDir, d.name);\n\t\tif (d.isDirectory()) {\n\t\t\tawait collectRecursive(full, root, out);\n\t\t} else if (d.isFile()) {\n\t\t\tconst content = await fs.readFile(full, \"utf8\");\n\t\t\tout.push({ path: path.relative(root, full), content });\n\t\t}\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport {\n\tDEFAULT_REPO_LAYOUT,\n\tloadRepoLayout,\n\ttype RepoLayoutDescriptor,\n} from \"../repo-layout\";\nimport type { SkillDetail, SkillEntry, SkillFile, SkillKind } from \"./types\";\nimport { SkillNotFoundError } from \"./types\";\n\n/**\n * Minimal frontmatter reader — same shape as the extension's\n * `WorkspaceSkillsInitializationService.extractFrontmatter`. We avoid\n * pulling a YAML dep into core; SKILL.md / AGENT.md in the wild use\n * a strict subset (top-level `key: value` pairs delimited by `---`).\n * Anything more exotic (nested mappings, multi-line scalars) is\n * passed through as the raw string in the corresponding value.\n */\nexport function extractFrontmatter(\n\traw: string,\n): { data: Record<string, unknown>; body: string } | null {\n\tif (typeof raw !== \"string\") return null;\n\tconst match = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n\tif (!match) return null;\n\tconst yamlBody = match[1] ?? \"\";\n\tconst body = match[2] ?? \"\";\n\tconst data: Record<string, unknown> = {};\n\tfor (const line of yamlBody.split(/\\r?\\n/)) {\n\t\tif (line.trim().length === 0) continue;\n\t\tif (line.trim().startsWith(\"#\")) continue;\n\t\tconst kv = line.match(/^([a-zA-Z_][\\w-]*)\\s*:\\s*(.*)$/);\n\t\tif (!kv || !kv[1]) continue;\n\t\tlet value: unknown = (kv[2] ?? \"\").trim();\n\t\tif (typeof value === \"string\") {\n\t\t\tif (\n\t\t\t\t(value.startsWith('\"') && value.endsWith('\"')) ||\n\t\t\t\t(value.startsWith(\"'\") && value.endsWith(\"'\"))\n\t\t\t) {\n\t\t\t\tvalue = value.slice(1, -1);\n\t\t\t}\n\t\t}\n\t\tdata[kv[1]] = value;\n\t}\n\treturn { data, body };\n}\n\nexport type { SkillDetail, SkillEntry, SkillFile, SkillKind } from \"./types\";\n/**\n * Skill & Agent v2 — SkillStore (M4)\n *\n * Scans the local `~/.serviceme/repos/<id>/` tree to produce a unified\n * list of skills + agents. Local-only — no git, no network. Spec §5.5.\n *\n * Layout normalization (spec §12):\n * 1. **Standard layout** — `skills/<name>/SKILL.md` or\n * `agents/<name>/AGENT.md` under the repo root.\n * 2. **Anthropic flat layout** — `<name>/SKILL.md` directly under\n * the repo root (the `skills/` prefix is omitted).\n * 3. **awesome-copilot style** — root contains README + subdirs\n * that are skills/agents/instructions; we ignore README and\n * any dir without a manifest file.\n * 4. **Flat agent files** — a directory (commonly `agents/` or\n * `agents/official/`) full of `<name>.agent.md` files, one per\n * agent, with NO per-agent subdirectory. This is the prevailing\n * real-world convention (VS Code custom chat agents, GitHub's\n * awesome-copilot, and our own official ms-skills repo all ship\n * agents this way) — unlike skills, agents in the wild are\n * essentially never a `<name>/AGENT.md` directory pair. Detected\n * unconditionally, no `.serviceme-repo.json` opt-in required.\n *\n * Implementation: walk every directory under the repo root, check for\n * SKILL.md or AGENT.md, register if present. We skip the repo root\n * itself (no manifest lives at the top), `.git/`, and `node_modules/`.\n * Flat `*.agent.md` files are checked alongside directories at every\n * level of the walk.\n */\n// Re-export the error class + shared types so callers can\n// `import { SkillNotFoundError, SkillFile } from \"...\"`.\nexport { SkillNotFoundError } from \"./types\";\n\nexport class SkillStore {\n\tprivate readonly repoRoots: Map<string, string>;\n\n\tconstructor(opts: {\n\t\trepos: ReadonlyArray<{ id: string; rootPath: string }>;\n\t}) {\n\t\tthis.repoRoots = new Map();\n\t\tfor (const r of opts.repos) this.repoRoots.set(r.id, r.rootPath);\n\t}\n\n\t/** All skills + agents across all registered repos. */\n\tasync listAll(): Promise<SkillEntry[]> {\n\t\tconst all: SkillEntry[] = [];\n\t\tfor (const repoId of this.repoRoots.keys()) {\n\t\t\tall.push(...(await this.listByRepo(repoId)));\n\t\t}\n\t\treturn all;\n\t}\n\n\t/** All skills + agents under a single repo. */\n\tasync listByRepo(repoId: string): Promise<SkillEntry[]> {\n\t\tconst root = this.repoRoots.get(repoId);\n\t\tif (!root) return [];\n\t\tconst layout = (await loadRepoLayout(root)) ?? DEFAULT_REPO_LAYOUT;\n\t\tconst entries: SkillEntry[] = [];\n\t\tawait walkForEntries(root, repoId, entries, layout);\n\t\treturn entries;\n\t}\n\n\t/** Single entry detail (manifest + all files inside its dir). */\n\tasync get(repoId: string, name: string): Promise<SkillDetail> {\n\t\tconst entries = await this.listByRepo(repoId);\n\t\tconst found = entries.find((e) => e.name === name);\n\t\tif (!found) throw new SkillNotFoundError(repoId, name);\n\t\tconst files = await this.getFiles(repoId, name);\n\t\treturn { ...found, files };\n\t}\n\n\t/** All files inside a single entry's directory (manifest + extras). */\n\tasync getFiles(repoId: string, name: string): Promise<SkillFile[]> {\n\t\tconst entries = await this.listByRepo(repoId);\n\t\tconst found = entries.find((e) => e.name === name);\n\t\tif (!found) throw new SkillNotFoundError(repoId, name);\n\n\t\tconst out: SkillFile[] = [];\n\t\t// Flat single-file entries (e.g. `agents/foo.agent.md`) have `dir`\n\t\t// pointing at the manifest file itself, not a directory — there's\n\t\t// no sibling files to collect, just the one manifest.\n\t\tif (found.dir === found.manifestPath) {\n\t\t\tconst content = await fs.readFile(found.manifestPath, \"utf8\");\n\t\t\treturn [{ path: path.basename(found.manifestPath), content }];\n\t\t}\n\t\tawait collectFilesRecursive(found.dir, found.dir, out);\n\t\t// Sort for determinism (manifest first, then alphabetical)\n\t\tout.sort((a, b) => {\n\t\t\tconst aIsManifest = a.path === \"SKILL.md\" || a.path === \"AGENT.md\";\n\t\t\tconst bIsManifest = b.path === \"SKILL.md\" || b.path === \"AGENT.md\";\n\t\t\tif (aIsManifest && !bIsManifest) return -1;\n\t\t\tif (bIsManifest && !aIsManifest) return 1;\n\t\t\treturn a.path.localeCompare(b.path);\n\t\t});\n\t\treturn out;\n\t}\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Internals\n// ─────────────────────────────────────────────────────────────────────\n\nconst SKIP_DIRS = new Set([\n\t\".git\",\n\t\"node_modules\",\n\t\".vscode\",\n\t\"dist\",\n\t\"build\",\n\t\"out\",\n]);\n\n/**\n * Directories that are never treated as candidate skill/agent parents\n * during the top-level catalog walk. `plugins/` is a distinct concept\n * from the skill/agent catalog itself — a plugin bundle\n * (`plugins/<scope>/<plugin-name>/{skills,agents}/...`) re-packages\n * ALREADY-cataloged official skills/agents (see the plugin's own\n * `catalog.json`, which lists them by name) purely for discovery\n * grouping. Walking into it produces duplicate `SkillEntry` values\n * with the SAME `repoId`+`name` as their real, top-level counterpart\n * (e.g. `skills/official/acreadiness-assess` AND\n * `plugins/official/acreadiness-cockpit/skills/acreadiness-assess`),\n * which breaks anything keyed on `repoId/name` (React list rendering,\n * the \"installed\" lookup, `SkillStore.get()`'s `.find()`).\n *\n * Scoped separately from `SKIP_DIRS` (used by both this walk AND\n * `collectFilesRecursive`) so an actual skill that happens to ship its\n * own `plugins/` asset folder still has that folder listed in its own\n * file detail view.\n */\nconst SKIP_TOP_LEVEL_SCAN_DIRS = new Set([...SKIP_DIRS, \"plugins\"]);\n\n/** Suffix that marks a standalone file as a flat agent manifest (no wrapping dir). */\nconst FLAT_AGENT_FILE_SUFFIX = \".agent.md\";\n\n/**\n * Recursive walk that yields SkillEntry values for any directory\n * containing a SKILL.md or AGENT.md file, PLUS any standalone\n * `<name>.agent.md` file (see class doc, layout style 4). The dir\n * itself is the \"entry root\" for directory-based entries; the\n * manifest file itself is the \"entry root\" for flat agent files.\n *\n * The optional `layout` descriptor (from `.serviceme-repo.json`)\n * widens the scan: included subdirs are walked even without a\n * manifest at the top level, and `treatFilesAsSkills` surfaces\n * `<name>.md` files inside them as skills (frontmatter parsed\n * from the file body).\n */\nasync function walkForEntries(\n\trootDir: string,\n\trepoId: string,\n\tout: SkillEntry[],\n\tlayout: RepoLayoutDescriptor = DEFAULT_REPO_LAYOUT,\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(rootDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\n\tfor (const d of dirents) {\n\t\tif (d.isFile() && d.name.endsWith(FLAT_AGENT_FILE_SUFFIX)) {\n\t\t\tconst filePath = path.join(rootDir, d.name);\n\t\t\tconst stat = await fs.stat(filePath);\n\t\t\tconst content = await fs.readFile(filePath, \"utf8\");\n\t\t\tconst parsed = extractFrontmatter(content);\n\t\t\tout.push({\n\t\t\t\trepoId,\n\t\t\t\tname: d.name.slice(0, -FLAT_AGENT_FILE_SUFFIX.length),\n\t\t\t\tkind: \"agent\",\n\t\t\t\tmanifestPath: filePath,\n\t\t\t\t// Sentinel: `dir === manifestPath` marks a flat single-file\n\t\t\t\t// entry (no directory of its own — see getFiles()).\n\t\t\t\tdir: filePath,\n\t\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (!d.isDirectory()) continue;\n\t\tif (SKIP_TOP_LEVEL_SCAN_DIRS.has(d.name)) continue;\n\t\tif (layout.exclude.includes(d.name)) continue;\n\t\tconst childDir = path.join(rootDir, d.name);\n\t\tconst kind = await detectKind(childDir);\n\t\tif (kind) {\n\t\t\tconst manifestFilename = kind === \"skill\" ? \"SKILL.md\" : \"AGENT.md\";\n\t\t\tconst manifestPath = path.join(childDir, manifestFilename);\n\t\t\tconst stat = await fs.stat(manifestPath);\n\t\t\tconst content = await fs.readFile(manifestPath, \"utf8\");\n\t\t\tconst parsed = extractFrontmatter(content);\n\t\t\tout.push({\n\t\t\t\trepoId,\n\t\t\t\tname: d.name,\n\t\t\t\tkind,\n\t\t\t\tmanifestPath,\n\t\t\t\tdir: childDir,\n\t\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\t// No manifest at the top — recurse, but ALSO check whether\n\t\t// the layout descriptor wants this subdir widened.\n\t\tawait walkForEntries(childDir, repoId, out, layout);\n\t\tif (layout.treatFilesAsSkills && layout.include.includes(d.name)) {\n\t\t\tawait surfaceFilesAsSkills(childDir, d.name, repoId, out);\n\t\t}\n\t}\n}\n\n/**\n * Walk `<includedSubdir>/<file>.md` and register each as a skill.\n * The `.md` file itself is the manifest — frontmatter (if any) is\n * parsed and surfaced. This is the awesome-copilot style: a\n * `prompts/` dir full of `<name>.md` files, no SKILL.md anywhere.\n */\nasync function surfaceFilesAsSkills(\n\tabsDir: string,\n\trelName: string,\n\trepoId: string,\n\tout: SkillEntry[],\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (!d.isFile()) continue;\n\t\tif (!d.name.endsWith(\".md\")) continue;\n\t\tconst filePath = path.join(absDir, d.name);\n\t\tconst stat = await fs.stat(filePath);\n\t\tconst content = await fs.readFile(filePath, \"utf8\");\n\t\tconst parsed = extractFrontmatter(content);\n\t\t// Entry name = \"<subdir>/<file>\" (with the .md stripped) so it\n\t\t// stays unique even when two included subdirs share filenames.\n\t\tconst entryName = `${relName}/${d.name.replace(/\\.md$/, \"\")}`;\n\t\tout.push({\n\t\t\trepoId,\n\t\t\tname: entryName,\n\t\t\tkind: \"skill\",\n\t\t\tmanifestPath: filePath,\n\t\t\tdir: absDir,\n\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t});\n\t}\n}\n\n/**\n * Returns the SkillKind of a directory if it contains a manifest file,\n * or `null` if it doesn't. Manifest precedence: SKILL.md wins if both\n * are present (defensive — the spec separates skills and agents into\n * different subdir trees, so this collision shouldn't happen in\n * well-formed repos).\n */\nasync function detectKind(dir: string): Promise<SkillKind | null> {\n\tconst [hasSkill, hasAgent] = await Promise.all([\n\t\tfs\n\t\t\t.access(path.join(dir, \"SKILL.md\"))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false),\n\t\tfs\n\t\t\t.access(path.join(dir, \"AGENT.md\"))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false),\n\t]);\n\tif (hasSkill) return \"skill\";\n\tif (hasAgent) return \"agent\";\n\treturn null;\n}\n\n/**\n * Walk a skill/agent directory recursively, pushing every file into\n * `out` with its path RELATIVE to the entry root. Skips `.git/`,\n * `node_modules/`, and the SKIP_DIRS set.\n */\nasync function collectFilesRecursive(\n\tabsDir: string,\n\tentryRoot: string,\n\tout: SkillFile[],\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (SKIP_DIRS.has(d.name)) continue;\n\t\tconst full = path.join(absDir, d.name);\n\t\tif (d.isDirectory()) {\n\t\t\tawait collectFilesRecursive(full, entryRoot, out);\n\t\t} else if (d.isFile()) {\n\t\t\tconst content = await fs.readFile(full, \"utf8\");\n\t\t\tout.push({ path: path.relative(entryRoot, full), content });\n\t\t}\n\t}\n}\n","/**\n * Spec §12 — Repo layout descriptor.\n *\n * Some third-party repos (awesome-copilot, composio) use a\n * non-default layout that mixes prompts / instructions / agents /\n * hooks / plugins at the root. The default SkillStore scan\n * (see ../skill-store/index.ts) only picks up directories that\n * contain a `SKILL.md` or `AGENT.md` manifest, so prompts stored\n * as `<subdir>/<name>.md` (no manifest) get missed.\n *\n * This module is the loader for the optional `.serviceme-repo.json`\n * marker file a repo author can drop in at the repo root to opt\n * into a wider scan. The schema:\n *\n * {\n * \"schema\": 1,\n * \"include\": [\"prompts\", \"instructions\"], // subdirs to treat as \"skill dirs\"\n * \"exclude\": [\"hooks\", \"plugins\"], // subdirs to skip\n * \"treatFilesAsSkills\": true // inside an included subdir, each .md file is a skill\n * }\n *\n * All four fields are optional. The defaults match the v0.2\n * walkForEntries behaviour (manifest-based, no marker, no\n * extensions), so adding the file is purely additive.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §12\n */\n\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nconst MARKER_FILENAME = \".serviceme-repo.json\";\nconst SUPPORTED_SCHEMA = 1;\n\nexport interface RepoLayoutDescriptor {\n\t/** Schema version. Currently always 1. */\n\tschema: number;\n\t/** Subdirs (relative to repo root) to walk for entries. */\n\tinclude: string[];\n\t/** Subdirs (relative to repo root) to skip, even if a parent is included. */\n\texclude: string[];\n\t/**\n\t * When true, an included subdir's `<name>.md` files are\n\t * surfaced as skills (frontmatter is parsed from each file as\n\t * a stand-in for SKILL.md). When false (default), the\n\t * include list only widens the recursion — the manifest rule\n\t * still applies.\n\t */\n\ttreatFilesAsSkills: boolean;\n}\n\n/**\n * Default descriptor used when no `.serviceme-repo.json` is\n * present. The shape is the same as what an empty marker would\n * produce, so callers don't have to special-case the \"no marker\"\n * branch.\n */\nexport const DEFAULT_REPO_LAYOUT: RepoLayoutDescriptor = {\n\tschema: SUPPORTED_SCHEMA,\n\tinclude: [],\n\texclude: [],\n\ttreatFilesAsSkills: false,\n};\n\n/**\n * Try to load a `.serviceme-repo.json` from the given repo root.\n * Returns `DEFAULT_REPO_LAYOUT` (not undefined) when the file is\n * absent — callers branch on `include.length` / `treatFilesAsSkills`\n * to decide whether to widen the scan.\n *\n * Malformed markers (bad JSON, wrong schema) are surfaced as\n * `null` so callers can warn the user instead of silently\n * treating them as the default.\n */\nexport async function loadRepoLayout(repoRoot: string): Promise<RepoLayoutDescriptor | null> {\n\tconst markerPath = path.join(repoRoot, MARKER_FILENAME);\n\tlet raw: string;\n\ttry {\n\t\traw = await fs.readFile(markerPath, \"utf8\");\n\t} catch {\n\t\treturn DEFAULT_REPO_LAYOUT;\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\tif (!parsed || typeof parsed !== \"object\") return null;\n\tconst obj = parsed as Record<string, unknown>;\n\n\tif (obj.schema !== SUPPORTED_SCHEMA) return null;\n\tif (!Array.isArray(obj.include) || obj.include.some((s) => typeof s !== \"string\")) {\n\t\treturn null;\n\t}\n\tif (!Array.isArray(obj.exclude) || obj.exclude.some((s) => typeof s !== \"string\")) {\n\t\treturn null;\n\t}\n\tif (typeof obj.treatFilesAsSkills !== \"boolean\") return null;\n\n\treturn {\n\t\tschema: SUPPORTED_SCHEMA,\n\t\tinclude: obj.include as string[],\n\t\texclude: obj.exclude as string[],\n\t\ttreatFilesAsSkills: obj.treatFilesAsSkills,\n\t};\n}\n\n/** Subdir name → entry kind override (rarely needed; default = manifest-driven). */\nexport type KindOverride = Map<string, \"skill\" | \"agent\">;\n","import {\n\tcreateServicemeError,\n\ttype EnvironmentCheckResult,\n\tKNOWN_ENVIRONMENT_TOOLS,\n\ttype KnownEnvironmentTool,\n\ttype ToolCheckResult,\n} from \"@serviceme/devtools-protocol\";\nimport { runCommand } from \"../process/runCommand\";\n\nconst DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5_000;\nconst TOOL_CHECK_TIMEOUT_MS: Partial<Record<KnownEnvironmentTool, number>> = {\n\tnuget: 12_000,\n\tnvm: 8_000,\n\tdotnet: 8_000,\n\t// nvm4w/npm shims and globally-installed .cmd tools (pnpm, nrm) are slow to\n\t// resolve on cold PATHs. Give them extra headroom so the version probe\n\t// doesn't fall through to the generic 5s default.\n\tnpm: 15_000,\n\tpnpm: 15_000,\n\tnrm: 15_000,\n};\nconst ERROR_CODE_NOT_FOUND = 127;\nconst ERROR_CODE_TIMEOUT = \"ETIMEDOUT\";\n\ninterface ExecErrorLike {\n\tcode?: string | number;\n\tmessage?: string;\n\tstdout?: string;\n\tstderr?: string;\n}\n\nexport interface EnvironmentInspectorOptions {\n\t/**\n\t * Override the command runner. Production callers should leave this\n\t * undefined and rely on the default `runCommand` from `../process/runCommand`;\n\t * tests inject a fake to drive error/edge branches without spawning\n\t * real subprocesses.\n\t */\n\trunCommand?: typeof runCommand;\n\t/**\n\t * Override the detected platform. Defaults to `process.platform`. Tests\n\t * use this to exercise the Windows-specific branches of the inspector.\n\t */\n\tplatform?: NodeJS.Platform;\n}\n\nexport class EnvironmentInspector {\n\tprivate readonly runCommandFn: typeof runCommand;\n\tprivate readonly platform: NodeJS.Platform;\n\n\tconstructor(options: EnvironmentInspectorOptions = {}) {\n\t\tthis.runCommandFn = options.runCommand ?? runCommand;\n\t\tthis.platform = options.platform ?? process.platform;\n\t}\n\n\tasync checkEnvironment(): Promise<EnvironmentCheckResult> {\n\t\tconst results = await Promise.all(\n\t\t\tKNOWN_ENVIRONMENT_TOOLS.map(async (tool) => [tool, await this.checkTool(tool)] as const)\n\t\t);\n\n\t\treturn Object.fromEntries(results) as EnvironmentCheckResult;\n\t}\n\n\tasync checkTool(toolName: KnownEnvironmentTool): Promise<ToolCheckResult> {\n\t\tif (!/^[a-zA-Z0-9-]+$/.test(toolName)) {\n\t\t\tthrow createServicemeError(\"invalid_params\", \"Invalid tool name.\");\n\t\t}\n\n\t\ttry {\n\t\t\tif (toolName === \"nvm\") {\n\t\t\t\treturn await this.checkNvm();\n\t\t\t}\n\n\t\t\tif (toolName === \"nuget\") {\n\t\t\t\treturn await this.checkNuget();\n\t\t\t}\n\n\t\t\tconst toolPath = await this.getToolPath(toolName);\n\t\t\tconst version = await this.getToolVersion(toolName);\n\n\t\t\treturn {\n\t\t\t\tinstalled: true,\n\t\t\t\tversion,\n\t\t\t\tpath: toolPath,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn this.handleToolCheckError(error);\n\t\t}\n\t}\n\n\tprivate async getToolPath(toolName: string): Promise<string | undefined> {\n\t\ttry {\n\t\t\tconst isWindows = this.platform === \"win32\";\n\t\t\tconst result = await this.runCommandFn(isWindows ? \"where\" : \"which\", {\n\t\t\t\targs: [toolName],\n\t\t\t\ttimeoutMs: this.getToolTimeout(toolName as KnownEnvironmentTool),\n\t\t\t});\n\n\t\t\treturn result.stdout\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => line.trim())\n\t\t\t\t.find((line) => line.length > 0);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * On Windows, prefer the `.cmd` shim for npm/pnpm/nrm. nvm4w registers\n\t * BOTH an extensionless entry (pointing to node.exe) and a `<tool>.cmd`\n\t * wrapper; `where` returns them in that order, and the extensionless one\n\t * would just print node's own version.\n\t */\n\tprivate async getToolShimPath(toolName: string): Promise<string | undefined> {\n\t\ttry {\n\t\t\tconst result = await this.runCommandFn(\"where\", {\n\t\t\t\targs: [toolName],\n\t\t\t\ttimeoutMs: this.getToolTimeout(toolName as KnownEnvironmentTool),\n\t\t\t});\n\n\t\t\tconst candidates = result.stdout\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => line.trim())\n\t\t\t\t.filter((line) => line.length > 0);\n\n\t\t\tconst cmdShim = candidates.find((line) => line.toLowerCase().endsWith(\".cmd\"));\n\t\t\treturn cmdShim ?? candidates[0];\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate async getToolVersion(toolName: string): Promise<string> {\n\t\tconst invocation = await this.getVersionInvocation(toolName);\n\t\tconst result = await this.runCommandFn(invocation.command, {\n\t\t\targs: invocation.args,\n\t\t\ttimeoutMs: this.getToolTimeout(toolName as KnownEnvironmentTool),\n\t\t});\n\n\t\treturn this.parseVersion(toolName, result.stdout || result.stderr);\n\t}\n\n\tprivate async checkNvm(): Promise<ToolCheckResult> {\n\t\tif (this.platform === \"win32\") {\n\t\t\ttry {\n\t\t\t\tconst result = await this.runCommandFn(\"cmd.exe\", {\n\t\t\t\t\targs: [\"/c\", \"nvm version\"],\n\t\t\t\t\ttimeoutMs: this.getToolTimeout(\"nvm\"),\n\t\t\t\t});\n\n\t\t\t\treturn {\n\t\t\t\t\tinstalled: true,\n\t\t\t\t\tversion: result.stdout.trim(),\n\t\t\t\t\tpath: await this.getToolPath(\"nvm\"),\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\treturn {\n\t\t\t\t\tinstalled: false,\n\t\t\t\t\terror: \"Not installed\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.runCommandFn(\"/bin/bash\", {\n\t\t\t\targs: [\n\t\t\t\t\t\"-lc\",\n\t\t\t\t\t'export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"; nvm --version',\n\t\t\t\t],\n\t\t\t\ttimeoutMs: this.getToolTimeout(\"nvm\"),\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tinstalled: true,\n\t\t\t\tversion: result.stdout.trim(),\n\t\t\t\tpath: \"$HOME/.nvm/nvm.sh\",\n\t\t\t};\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tinstalled: false,\n\t\t\t\terror: \"Not installed\",\n\t\t\t};\n\t\t}\n\t}\n\n\tprivate async checkNuget(): Promise<ToolCheckResult> {\n\t\tconst dotnetPath = await this.getToolPath(\"dotnet\");\n\t\tif (!dotnetPath) {\n\t\t\treturn {\n\t\t\t\tinstalled: false,\n\t\t\t\terror: \"dotnet CLI is not installed\",\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.runCommandFn(\"dotnet\", {\n\t\t\t\targs: [\"nuget\", \"list\", \"source\"],\n\t\t\t\ttimeoutMs: this.getToolTimeout(\"nuget\"),\n\t\t\t});\n\n\t\t\tconst privateSourceUrl = \"http://192.168.20.209:10010/nuget\";\n\t\t\tif (result.stdout.includes(privateSourceUrl)) {\n\t\t\t\treturn {\n\t\t\t\t\tinstalled: true,\n\t\t\t\t\tversion: \"Configured\",\n\t\t\t\t\tpath: privateSourceUrl,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tinstalled: false,\n\t\t\t\terror: \"Private source not configured\",\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn this.handleToolCheckError(error);\n\t\t}\n\t}\n\n\tprivate async getVersionInvocation(toolName: string): Promise<{\n\t\tcommand: string;\n\t\targs: string[];\n\t}> {\n\t\tconst isWindows = this.platform === \"win32\";\n\t\tif (isWindows && [\"npm\", \"pnpm\", \"nrm\"].includes(toolName)) {\n\t\t\t// On Windows, resolve the `.cmd` shim and invoke it via an\n\t\t\t// explicit `cmd.exe /c <absolute-path>` — NOT the\n\t\t\t// `cmd.exe /d /s /c \"<tool> --version\"` form we used to use.\n\t\t\t// The earlier wrapper forced CMD to do a fresh PATH lookup\n\t\t\t// AND its `/s` quote-stripping pass on every call, which on\n\t\t\t// machines using nvm4w shims (or globally-installed npm\n\t\t\t// tools) blew past the 5s default timeout. Spawning the\n\t\t\t// resolved shim path directly skips both steps.\n\t\t\t//\n\t\t\t// nvm4w's `where <tool>` returns BOTH an extensionless entry\n\t\t\t// (pointing to node.exe) and the real `<tool>.cmd` wrapper;\n\t\t\t// we prefer the `.cmd` form so the shim chain still runs.\n\t\t\tconst shim = await this.getToolShimPath(toolName);\n\t\t\tif (shim) {\n\t\t\t\treturn {\n\t\t\t\t\tcommand: \"cmd.exe\",\n\t\t\t\t\targs: [\"/c\", shim, \"--version\"],\n\t\t\t\t};\n\t\t\t}\n\t\t\t// Fall back to letting CMD resolve it from PATH.\n\t\t\treturn {\n\t\t\t\tcommand: \"cmd.exe\",\n\t\t\t\targs: [\"/c\", toolName, \"--version\"],\n\t\t\t};\n\t\t}\n\n\t\tconst commands: Record<string, { command: string; args: string[] }> = {\n\t\t\tgit: { command: \"git\", args: [\"--version\"] },\n\t\t\tnode: { command: \"node\", args: [\"--version\"] },\n\t\t\tnpm: { command: \"npm\", args: [\"--version\"] },\n\t\t\tpnpm: { command: \"pnpm\", args: [\"--version\"] },\n\t\t\tnvm: { command: \"nvm\", args: [\"--version\"] },\n\t\t\tnrm: { command: \"nrm\", args: [\"--version\"] },\n\t\t\trtk: { command: \"rtk\", args: [\"--version\"] },\n\t\t\tdotnet: { command: \"dotnet\", args: [\"--version\"] },\n\t\t};\n\n\t\treturn commands[toolName] || { command: toolName, args: [\"--version\"] };\n\t}\n\n\tprivate getToolTimeout(toolName: KnownEnvironmentTool): number {\n\t\treturn TOOL_CHECK_TIMEOUT_MS[toolName] ?? DEFAULT_TOOL_CHECK_TIMEOUT_MS;\n\t}\n\n\tprivate parseVersion(toolName: string, output: string): string {\n\t\tconst cleaned = output.trim();\n\n\t\tswitch (toolName) {\n\t\t\tcase \"git\": {\n\t\t\t\tconst match = cleaned.match(/git version (\\d+\\.\\d+\\.\\d+)/);\n\t\t\t\treturn match?.[1] || cleaned;\n\t\t\t}\n\t\t\tcase \"node\": {\n\t\t\t\tconst match = cleaned.match(/v?(\\d+\\.\\d+\\.\\d+)/);\n\t\t\t\treturn match?.[1] || cleaned;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconst semver = cleaned.match(/(\\d+\\.\\d+\\.\\d+)/);\n\t\t\t\tif (semver?.[1]) {\n\t\t\t\t\treturn semver[1];\n\t\t\t\t}\n\n\t\t\t\tconst simple = cleaned.match(/(\\d+\\.\\d+)/);\n\t\t\t\treturn simple?.[1] || cleaned;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleToolCheckError(error: unknown): ToolCheckResult {\n\t\tconst execError = error as ExecErrorLike;\n\t\tconst message = execError.message || String(error);\n\t\tconst code = execError.code;\n\n\t\tconst isNotFound =\n\t\t\tmessage.includes(\"command not found\") ||\n\t\t\tmessage.includes(\"not recognized\") ||\n\t\t\tmessage.includes(\"ENOENT\") ||\n\t\t\tmessage.includes(\"EACCES\") ||\n\t\t\tcode === \"ENOENT\" ||\n\t\t\tcode === \"EACCES\" ||\n\t\t\tcode === ERROR_CODE_NOT_FOUND;\n\t\tconst isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes(\"timed out\");\n\n\t\treturn {\n\t\t\tinstalled: false,\n\t\t\terror: isNotFound ? \"Not installed\" : isTimeout ? \"Check timed out\" : message,\n\t\t};\n\t}\n}\n","import { spawn } from \"node:child_process\";\nimport * as path from \"node:path\";\n\nimport type {\n\tGitClientOptions,\n\tGitSpawner,\n\tGitSpawnResult,\n\tPullResult,\n\tPushResult,\n\tRemoteBranch,\n} from \"./types\";\nimport { GitError } from \"./types\";\n\n/**\n * Skill & Agent v2 — Client GitClient (M3)\n *\n * Wraps `git` so all upstream traffic flows through the server's git\n * proxy at `serverProxyBase`. See docs/architecture/skill-agent-v2-repo.md\n * §5.4.\n *\n * Why a wrapper instead of using libgit2 directly:\n * 1. `git` is already installed everywhere we run (extension host,\n * CLI). No native deps.\n * 2. Users can debug their skill repos with plain `git` commands when\n * SERVICEME is misbehaving — the wrappers keep a familiar CLI.\n * 3. libgit2's packfile negotiation has subtle correctness gaps that\n * we don't want to debug server-side.\n *\n * URL rewrite:\n * `https://github.com/owner/repo.git` → `<proxyBase>/<id>`\n *\n * When git hits `<proxyBase>/<id>` it discovers the proxy's\n * `/info/refs?service=git-upload-pack` endpoint via the smart-HTTP\n * protocol. From there the server takes over.\n */\nexport class GitClient {\n\tprivate readonly serverProxyBase: string;\n\tprivate readonly spawner: GitSpawner;\n\n\tconstructor(opts: GitClientOptions) {\n\t\t// Normalize: strip trailing slash so `rewriteRemoteUrl` is stable.\n\t\tthis.serverProxyBase = opts.serverProxyBase.replace(/\\/+$/, \"\");\n\t\tthis.spawner = opts.spawner ?? new NodeGitSpawner();\n\t}\n\n\t/**\n\t * Rewrite an upstream URL to its proxy form.\n\t *\n\t * Examples:\n\t * rewriteRemoteUrl(\"medalsoftchina-ms-skills\",\n\t * \"https://github.com/medalsoftchina/ms-skills.git\")\n\t * → \"http://localhost:3000/git-proxy/medalsoftchina-ms-skills\"\n\t *\n\t * rewriteRemoteUrl(\"my-team-internal\",\n\t * \"git@github.com:medalsoftchina/ms-skills.git\")\n\t * → \"http://localhost:3000/git-proxy/my-team-internal\"\n\t *\n\t * The original URL's host/owner is intentionally DROPPED — the proxy\n\t * knows the upstream from the per-repo config, not from the URL. This\n\t * means callers can't accidentally route a user-repo URL through the\n\t * default-repo proxy slot.\n\t */\n\trewriteRemoteUrl(_repoId: string, _originalUrl: string): string {\n\t\treturn `${this.serverProxyBase}/${_repoId}`;\n\t}\n\n\t/**\n\t * `git clone <proxyUrl> <localPath>` — initialize a new local repo\n\t * from the proxy. Returns when the clone succeeds; throws on failure.\n\t */\n\tasync clone(repoId: string, originalUrl: string, localPath: string): Promise<void> {\n\t\tconst proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl);\n\t\tconst args = [\"clone\", \"--\", proxyUrl, localPath];\n\t\tawait this.runOrThrow(args, { cwd: process.cwd() });\n\t}\n\n\t/**\n\t * `git fetch <remote> <branch>` + return the resulting commit SHA.\n\t * Operates on an already-cloned repo at `localPath`.\n\t */\n\tasync pull(repoId: string, localPath: string): Promise<PullResult> {\n\t\t// Re-derive the remote URL from the existing remote config (so\n\t\t// `pull` doesn't need to be told the original upstream URL —\n\t\t// it uses whatever `git remote get-url <remote>` reports).\n\t\tconst remoteUrl = await this.getRemoteUrl(localPath, \"origin\");\n\t\tif (!remoteUrl) {\n\t\t\tthrow new Error(`no 'origin' remote configured at ${localPath}`);\n\t\t}\n\t\tconst proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl);\n\n\t\t// Ensure remote.origin.url points at the proxy URL.\n\t\tawait this.runOrThrow([\"remote\", \"set-url\", \"origin\", proxyUrl], { cwd: localPath });\n\n\t\t// Fetch via the proxy.\n\t\tconst before = await this.safeRevParse(localPath);\n\t\tawait this.runOrThrow([\"fetch\", \"origin\"], { cwd: localPath });\n\t\tconst after = await this.safeRevParse(localPath);\n\n\t\tconst branch = await this.currentBranch(localPath);\n\t\tif (!branch) {\n\t\t\tthrow new Error(`could not determine current branch at ${localPath}`);\n\t\t}\n\n\t\t// Fast-forward the working branch so callers see the new commits.\n\t\tawait this.runOrThrow([\"merge\", \"--ff-only\", `origin/${branch}`], { cwd: localPath });\n\n\t\treturn {\n\t\t\tupdated: before !== after,\n\t\t\tcommitSha: after,\n\t\t\tbranch,\n\t\t};\n\t}\n\n\t/**\n\t * `git push origin <branch>` via the proxy. Caller is responsible\n\t * for committing locally first. The server's GitProxy injects the\n\t * PAT on the way upstream.\n\t */\n\tasync push(repoId: string, localPath: string, branch: string): Promise<PushResult> {\n\t\tconst remoteUrl = await this.getRemoteUrl(localPath, \"origin\");\n\t\tif (!remoteUrl) {\n\t\t\tthrow new Error(`no 'origin' remote configured at ${localPath}`);\n\t\t}\n\t\tconst proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl);\n\t\tawait this.runOrThrow([\"remote\", \"set-url\", \"origin\", proxyUrl], { cwd: localPath });\n\n\t\tconst result = await this.spawner.spawn(\n\t\t\t[\"push\", \"origin\", `refs/heads/${branch}:refs/heads/${branch}`],\n\t\t\t{ cwd: localPath }\n\t\t);\n\t\tif (result.code !== 0) {\n\t\t\tthrow new GitError([\"push\"], result);\n\t\t}\n\n\t\t// Parse the new commit SHA from push output: `refs/heads/v2: <sha>\\t...`\n\t\tconst m = result.stderr.match(/refs\\/heads\\/[^\\s:]+:\\s*([0-9a-f]{7,})/);\n\t\tconst commitSha = m?.[1] ?? \"\";\n\n\t\treturn {\n\t\t\tref: `refs/heads/${branch}`,\n\t\t\tcommitSha,\n\t\t};\n\t}\n\n\t/**\n\t * `git ls-remote <proxyUrl>` — list advertised branches without\n\t * cloning. Used by addUserRepo to detect the default branch.\n\t */\n\tasync lsRemote(repoId: string, originalUrl: string): Promise<RemoteBranch[]> {\n\t\tconst proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl);\n\t\tconst result = await this.spawner.spawn([\"ls-remote\", \"--heads\", \"--\", proxyUrl], {\n\t\t\tcwd: process.cwd(),\n\t\t});\n\t\tif (result.code !== 0) {\n\t\t\tthrow new GitError([\"ls-remote\"], result);\n\t\t}\n\t\tconst branches: RemoteBranch[] = [];\n\t\tfor (const line of result.stdout.split(\"\\n\")) {\n\t\t\tconst trimmed = line.trim();\n\t\t\tif (!trimmed) continue;\n\t\t\t// `<sha>\\t<ref>` — split on the tab.\n\t\t\tconst tab = trimmed.indexOf(\"\\t\");\n\t\t\tif (tab === -1) continue;\n\t\t\tconst sha = trimmed.slice(0, tab);\n\t\t\tconst ref = trimmed.slice(tab + 1);\n\t\t\tbranches.push({ sha, ref });\n\t\t}\n\t\treturn branches;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Internal helpers\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/**\n\t * `git add <pathspec…>` followed by `git commit -m <message>`. The\n\t * commit message convention follows the spec: `feat(skills): add\n\t * <name>` / `feat(agents): add <name>`.\n\t */\n\tasync commit(localPath: string, message: string, addPath = \".\"): Promise<{ commitSha: string }> {\n\t\tawait this.runOrThrow([\"add\", \"--\", addPath], { cwd: localPath });\n\t\tawait this.runOrThrow([\"commit\", \"-m\", message], { cwd: localPath });\n\t\tconst sha = await this.safeRevParse(localPath);\n\t\treturn { commitSha: sha };\n\t}\n\n\tprivate async runOrThrow(args: string[], opts: { cwd?: string }): Promise<GitSpawnResult> {\n\t\tconst result = await this.spawner.spawn(args, { cwd: opts.cwd ?? process.cwd() });\n\t\tif (result.code !== 0) {\n\t\t\tthrow new GitError(args, result);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate async getRemoteUrl(localPath: string, remote: string): Promise<string | undefined> {\n\t\tconst result = await this.spawner.spawn([\"remote\", \"get-url\", remote], {\n\t\t\tcwd: localPath,\n\t\t});\n\t\tif (result.code !== 0) return undefined;\n\t\tconst url = result.stdout.trim();\n\t\treturn url.length > 0 ? url : undefined;\n\t}\n\n\tprivate async safeRevParse(localPath: string): Promise<string> {\n\t\tconst result = await this.spawner.spawn([\"rev-parse\", \"HEAD\"], { cwd: localPath });\n\t\tif (result.code !== 0) return \"\";\n\t\treturn result.stdout.trim();\n\t}\n\n\tprivate async currentBranch(localPath: string): Promise<string | undefined> {\n\t\tconst result = await this.spawner.spawn([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], {\n\t\t\tcwd: localPath,\n\t\t});\n\t\tif (result.code !== 0) return undefined;\n\t\tconst branch = result.stdout.trim();\n\t\tif (!branch || branch === \"HEAD\") return undefined;\n\t\treturn branch;\n\t}\n}\n\n/**\n * Default spawner: invokes the system `git` binary via Node's\n * `child_process.spawn`. Streams stdout/stderr to strings and returns\n * the exit code. Tests inject a stub to avoid touching the filesystem.\n */\n// Re-export GitError so callers can `import { GitError } from \"...\"`.\nexport { GitError };\n\nexport class NodeGitSpawner implements GitSpawner {\n\tasync spawn(args: string[], opts: { cwd?: string }): Promise<GitSpawnResult> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst child = spawn(\"git\", args, {\n\t\t\t\tcwd: opts.cwd,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\tshell: false,\n\t\t\t});\n\t\t\tconst stdoutChunks: Buffer[] = [];\n\t\t\tconst stderrChunks: Buffer[] = [];\n\t\t\tchild.stdout?.on(\"data\", (c: Buffer) => stdoutChunks.push(c));\n\t\t\tchild.stderr?.on(\"data\", (c: Buffer) => stderrChunks.push(c));\n\t\t\tchild.on(\"error\", reject);\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tresolve({\n\t\t\t\t\tstdout: Buffer.concat(stdoutChunks).toString(\"utf8\"),\n\t\t\t\t\tstderr: Buffer.concat(stderrChunks).toString(\"utf8\"),\n\t\t\t\t\tcode: code ?? 1,\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n}\n\n/** Convenience: a record-based stub spawner for tests. */\nexport class StubGitSpawner implements GitSpawner {\n\t/** queue of canned responses, consumed FIFO per spawn() call. */\n\treadonly script: GitSpawnResult[];\n\t/** All spawn() invocations, in order, for assertions. */\n\treadonly calls: Array<{ args: string[]; cwd: string | undefined }> = [];\n\n\tconstructor(script: GitSpawnResult[]) {\n\t\tthis.script = [...script];\n\t}\n\n\tasync spawn(args: string[], opts: { cwd?: string }): Promise<GitSpawnResult> {\n\t\tthis.calls.push({ args, cwd: opts.cwd });\n\t\tconst next = this.script.shift();\n\t\tif (!next) {\n\t\t\treturn { stdout: \"\", stderr: `stub: no scripted response for ${args.join(\" \")}`, code: 1 };\n\t\t}\n\t\treturn next;\n\t}\n}\n\n/**\n * Helper: encode a bare absolute file URL (`file:///...`) for local\n * git clone tests. Not used by GitClient directly — exposed for\n * RepoManager's `localSeed` use case.\n */\nexport function toFileUrl(absolutePath: string): string {\n\tconst normalized = path.resolve(absolutePath);\n\tif (process.platform === \"win32\") {\n\t\treturn `file:///${normalized.replace(/\\\\/g, \"/\")}`;\n\t}\n\treturn `file://${normalized}`;\n}\n","import type { SpawnOptions } from \"node:child_process\";\n\n/**\n * Skill & Agent v2 — Client GitClient Types (M3)\n *\n * GitClient wraps the local `git` CLI so all commands the SERVICEME\n * pipeline issues (clone, fetch, push, ls-remote) flow through the\n * server's git smart-HTTP proxy instead of talking directly to GitHub.\n *\n * The single trick: `git` only cares about the URL we hand it as a\n * remote. We rewrite `https://github.com/owner/repo.git` to\n * `http://server:port/git-proxy/<id>` and `git` does the rest — the\n * server transparently forwards + injects PATs as needed.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §5.4 GitClient\n */\n\n/** Result of a `git fetch` (called via the proxy's git-upload-pack). */\nexport interface PullResult {\n\t/** Whether new commits were fetched (false = already up-to-date). */\n\tupdated: boolean;\n\t/** Commit SHA at FETCH_HEAD after the pull. */\n\tcommitSha: string;\n\t/** Branch name that was pulled (e.g. \"v2\"). */\n\tbranch: string;\n}\n\n/** Result of a `git push` (called via the proxy's git-receive-pack). */\nexport interface PushResult {\n\t/** Remote ref updated (e.g. \"refs/heads/v2\"). */\n\tref: string;\n\t/** New commit SHA on the remote. */\n\tcommitSha: string;\n}\n\n/** A branch as advertised by `git ls-remote`. */\nexport interface RemoteBranch {\n\t/** Full ref name, e.g. `refs/heads/main`. */\n\tref: string;\n\t/** Commit SHA the ref points at. */\n\tsha: string;\n}\n\n/** Outcome of any spawned `git` process. */\nexport interface GitSpawnResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n}\n\n/** Strategy for executing git commands. Tests inject a stub. */\nexport interface GitSpawner {\n\tspawn(args: string[], opts: SpawnOptions): Promise<GitSpawnResult>;\n}\n\n/** Options for instantiating GitClient. */\nexport interface GitClientOptions {\n\t/** Base URL of the server git proxy. e.g. `http://localhost:3000/git-proxy`. */\n\tserverProxyBase: string;\n\t/** Inject a custom spawner (default: spawn real `git` CLI). */\n\tspawner?: GitSpawner;\n\t/** Inject an env override for spawned git (default: process.env minus proxy secrets). */\n\tenv?: NodeJS.ProcessEnv;\n}\n\n/** Sentinel error when git exits non-zero. */\nexport class GitError extends Error {\n\treadonly args: string[];\n\treadonly code: number;\n\treadonly stderr: string;\n\treadonly stdout: string;\n\tconstructor(args: string[], result: GitSpawnResult) {\n\t\tsuper(`git ${args.join(\" \")} failed (exit ${result.code}): ${result.stderr.slice(0, 500)}`);\n\t\tthis.name = \"GitError\";\n\t\tthis.args = args;\n\t\tthis.code = result.code;\n\t\tthis.stderr = result.stderr;\n\t\tthis.stdout = result.stdout;\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport {\n\tcreateServicemeError,\n\ttype ServiceMeImageCompressOptions,\n\ttype ServiceMeImageCompressResult,\n\ttype ServiceMeImageInfo,\n\ttype ServiceMeImageValidationResult,\n} from \"@serviceme/devtools-protocol\";\n\ntype SharpMetadata = {\n\twidth?: number;\n\theight?: number;\n\tformat?: string;\n\tspace?: string;\n};\n\ntype SharpPipeline = {\n\tmetadata(): Promise<SharpMetadata>;\n\tjpeg(options: { quality: number }): SharpPipeline;\n\tpng(options: { quality: number; compressionLevel: number }): SharpPipeline;\n\twebp(options: { quality: number }): SharpPipeline;\n\ttoBuffer(): Promise<Buffer>;\n};\n\ntype SharpModule = (inputPath: string) => SharpPipeline;\n\nconst SUPPORTED_FORMATS = [\".jpg\", \".jpeg\", \".png\", \".webp\", \".gif\", \".bmp\", \".tiff\"];\nconst DEFAULT_MINIMUM_COMPRESSION_RATIO = 5;\n\nexport class ImageTools {\n\tgetSupportedFormats(): string[] {\n\t\treturn [...SUPPORTED_FORMATS];\n\t}\n\n\tasync validate(\n\t\tfilePath: string,\n\t\tsharpModulePath?: string\n\t): Promise<ServiceMeImageValidationResult> {\n\t\ttry {\n\t\t\tif (!(await this.pathExists(filePath))) {\n\t\t\t\treturn { valid: false };\n\t\t\t}\n\n\t\t\tif (!SUPPORTED_FORMATS.includes(path.extname(filePath).toLowerCase())) {\n\t\t\t\treturn { valid: false };\n\t\t\t}\n\n\t\t\tawait this.getInfo(filePath, sharpModulePath);\n\t\t\treturn { valid: true };\n\t\t} catch {\n\t\t\treturn { valid: false };\n\t\t}\n\t}\n\n\tasync getInfo(imagePath: string, sharpModulePath?: string): Promise<ServiceMeImageInfo> {\n\t\tconst sharp = this.loadSharp(sharpModulePath);\n\t\tconst metadata = await sharp(imagePath).metadata();\n\t\tconst stats = await fs.stat(imagePath);\n\n\t\treturn {\n\t\t\twidth: metadata.width ?? 0,\n\t\t\theight: metadata.height ?? 0,\n\t\t\tformat: metadata.format ?? \"unknown\",\n\t\t\tsize: stats.size,\n\t\t\tcolorSpace: metadata.space,\n\t\t};\n\t}\n\n\tasync compress(\n\t\timagePath: string,\n\t\toptions: ServiceMeImageCompressOptions\n\t): Promise<ServiceMeImageCompressResult> {\n\t\tconst validation = await this.validate(imagePath, options.sharpModulePath);\n\t\tif (!validation.valid) {\n\t\t\tthrow createServicemeError(\"invalid_params\", `Invalid image file: ${imagePath}`);\n\t\t}\n\n\t\tconst originalStats = await fs.stat(imagePath);\n\t\tconst originalSize = originalStats.size;\n\t\tconst outputPath = this.getOutputPath(imagePath, options);\n\t\tconst compressedBuffer = await this.compressWithSharp(imagePath, options);\n\t\tconst compressedSize = compressedBuffer.length;\n\t\tconst compressionRatio = ((originalSize - compressedSize) / originalSize) * 100;\n\t\tconst minimumCompressionRatio =\n\t\t\toptions.minimumCompressionRatio ?? DEFAULT_MINIMUM_COMPRESSION_RATIO;\n\n\t\tif (compressionRatio < minimumCompressionRatio) {\n\t\t\treturn {\n\t\t\t\toriginalSize,\n\t\t\t\tcompressedSize: originalSize,\n\t\t\t\tcompressionRatio: 0,\n\t\t\t\toutputPath: imagePath,\n\t\t\t};\n\t\t}\n\n\t\tawait fs.writeFile(outputPath, compressedBuffer);\n\n\t\treturn {\n\t\t\toriginalSize,\n\t\t\tcompressedSize,\n\t\t\tcompressionRatio,\n\t\t\toutputPath,\n\t\t};\n\t}\n\n\tprivate async compressWithSharp(\n\t\timagePath: string,\n\t\toptions: ServiceMeImageCompressOptions\n\t): Promise<Buffer> {\n\t\tconst sharp = this.loadSharp(options.sharpModulePath);\n\t\tlet pipeline = sharp(imagePath);\n\n\t\tswitch (options.format ?? path.extname(imagePath).toLowerCase().slice(1)) {\n\t\t\tcase \"jpg\":\n\t\t\tcase \"jpeg\":\n\t\t\t\tpipeline = pipeline.jpeg({ quality: options.quality });\n\t\t\t\tbreak;\n\t\t\tcase \"png\":\n\t\t\t\tpipeline = pipeline.png({\n\t\t\t\t\tquality: options.quality,\n\t\t\t\t\tcompressionLevel: 9,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase \"webp\":\n\t\t\t\tpipeline = pipeline.webp({ quality: options.quality });\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tpipeline = pipeline.jpeg({ quality: options.quality });\n\t\t}\n\n\t\treturn pipeline.toBuffer();\n\t}\n\n\tprivate getOutputPath(inputPath: string, options: ServiceMeImageCompressOptions): string {\n\t\tif (options.outputPath) {\n\t\t\treturn options.outputPath;\n\t\t}\n\n\t\tif (options.replaceOriginImage) {\n\t\t\treturn inputPath;\n\t\t}\n\n\t\tconst dir = path.dirname(inputPath);\n\t\tconst ext = path.extname(inputPath);\n\t\tconst name = path.basename(inputPath, ext);\n\t\treturn path.join(dir, `${name}_compressed${ext}`);\n\t}\n\n\tprivate async pathExists(targetPath: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fs.access(targetPath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate loadSharp(sharpModulePath?: string): SharpModule {\n\t\ttry {\n\t\t\treturn (sharpModulePath ? require(sharpModulePath) : require(\"sharp\")) as SharpModule;\n\t\t} catch (error) {\n\t\t\tthrow createServicemeError(\n\t\t\t\t\"internal_error\",\n\t\t\t\t`sharp is required for image operations: ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`\n\t\t\t);\n\t\t}\n\t}\n}\n\nexport function createImageTools(): ImageTools {\n\treturn new ImageTools();\n}\n","import {\n\tcreateServicemeError,\n\tDEFAULT_JSON_SORT_OPTIONS,\n\ttype JsonSortOptions,\n\ttype JsonValidationResult,\n} from \"@serviceme/devtools-protocol\";\nimport { parse as parseCommentJson } from \"comment-json\";\nimport JSON5 from \"json5\";\n\nexport interface JsonTools {\n\tsort(text: string, options?: Partial<JsonSortOptions>): string;\n\tformat(text: string, indent?: number): string;\n\tvalidate(text: string): JsonValidationResult;\n\tminify(text: string): string;\n}\n\nexport function createJsonTools(): JsonTools {\n\treturn {\n\t\tsort(text: string, options?: Partial<JsonSortOptions>): string {\n\t\t\tconst finalOptions = { ...DEFAULT_JSON_SORT_OPTIONS, ...options };\n\t\t\tconst json = parseJson(text);\n\t\t\tconst sorted = sortObject(json, finalOptions);\n\t\t\treturn JSON.stringify(sorted, null, detectIndent(text));\n\t\t},\n\t\tformat(text: string, indent = 2): string {\n\t\t\treturn JSON.stringify(parseJson(text), null, indent);\n\t\t},\n\t\tvalidate(text: string): JsonValidationResult {\n\t\t\ttry {\n\t\t\t\tparseJson(text);\n\t\t\t\treturn { valid: true };\n\t\t\t} catch (error) {\n\t\t\t\treturn {\n\t\t\t\t\tvalid: false,\n\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t\tminify(text: string): string {\n\t\t\treturn JSON.stringify(parseJson(text));\n\t\t},\n\t};\n}\n\nfunction parseJson(text: string): unknown {\n\tconst parsers = [() => JSON.parse(text), () => parseCommentJson(text), () => JSON5.parse(text)];\n\n\tfor (const parser of parsers) {\n\t\ttry {\n\t\t\treturn parser();\n\t\t} catch {\n\t\t\t// Try next parser.\n\t\t}\n\t}\n\n\tthrow createServicemeError(\"json_invalid_input\", \"Invalid JSON format.\");\n}\n\nfunction sortObject(obj: unknown, options: JsonSortOptions): unknown {\n\tif (Array.isArray(obj)) {\n\t\treturn obj.map((item) => sortObject(item, options));\n\t}\n\n\tif (obj !== null && typeof obj === \"object\") {\n\t\tconst record = obj as Record<string, unknown>;\n\t\tconst sortedKeys = sortKeys(Object.keys(record), options);\n\t\tconst result: Record<string, unknown> = {};\n\n\t\tfor (const key of sortedKeys) {\n\t\t\tresult[key] = sortObject(record[key], options);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\treturn obj;\n}\n\nfunction sortKeys(keys: string[], options: JsonSortOptions): string[] {\n\tlet compareFn: (a: string, b: string) => number;\n\n\tswitch (options.sortAlgo) {\n\t\tcase \"keyLength\":\n\t\t\tcompareFn = (a, b) => a.length - b.length;\n\t\t\tbreak;\n\t\tcase \"alphaNum\":\n\t\t\tcompareFn = (a, b) => a.localeCompare(b, undefined, { numeric: true });\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcompareFn = (a, b) => a.localeCompare(b);\n\t\t\tbreak;\n\t}\n\n\tconst sorted = [...keys].sort(compareFn);\n\treturn options.sortOrder === \"desc\" ? sorted.reverse() : sorted;\n}\n\nfunction detectIndent(text: string): number {\n\tfor (const line of text.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\s+)/);\n\t\tif (match?.[1]) {\n\t\t\treturn match[1].length;\n\t\t}\n\t}\n\n\treturn 2;\n}\n","export interface ServiceMeLogger {\n\tdebug(message: string, ...args: unknown[]): void;\n\tinfo(message: string, ...args: unknown[]): void;\n\twarn(message: string, ...args: unknown[]): void;\n\terror(message: string, ...args: unknown[]): void;\n}\n\nexport const noopLogger: ServiceMeLogger = {\n\tdebug() {},\n\tinfo() {},\n\twarn() {},\n\terror() {},\n};\n\nfunction formatArgs(args: unknown[]): string {\n\treturn args\n\t\t.map((arg) => {\n\t\t\tif (typeof arg === \"string\") {\n\t\t\t\treturn arg;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn JSON.stringify(arg);\n\t\t\t} catch {\n\t\t\t\treturn String(arg);\n\t\t\t}\n\t\t})\n\t\t.join(\" \");\n}\n\nexport function createConsoleLogger(prefix = \"serviceme\"): ServiceMeLogger {\n\treturn {\n\t\tdebug(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t\tinfo(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t\twarn(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t\terror(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t};\n}\n","/**\n * Spec §11.12 — Phase 5 client-side state bootstrap.\n *\n * The server already exposes the Phase 5 surface (better-auth session\n * endpoints, device enrollment routes, toolbox + profile API). The\n * client (extension + CLI) needs the on-disk files to exist on first\n * run so that subsequent code can `readFile()` without an existence\n * check, and so the user can grep `~/.serviceme/` and see the\n * expected layout instead of an empty directory.\n *\n * Strategy: idempotently write the empty / default JSON for every\n * Phase 5 file that doesn't already exist. Existing files are\n * left untouched so the bootstrap can re-run on every extension\n * activation without overwriting user state.\n *\n * All writes are `mkdir -p` + `writeFile` (no race), and failures\n * are surfaced but never thrown — Phase 5 is auxiliary; the\n * extension / CLI should keep working even if the user's home\n * directory is read-only.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §11.12\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport {\n\tgetCredentialsConfigPath,\n\tgetDeviceJsonPath,\n\tgetMachineIdPath,\n\tgetProfilesJsonPath,\n\tgetServicemeHome,\n\tgetToolboxJsonPath,\n} from \"../paths/userHome\";\n\n/** Per-file default content. `null` for the machine-id file because it\n * is a bare uuid with no JSON wrapper. */\ninterface Phase5FileSpec {\n\tpath: string;\n\tdefaultContent: string;\n}\n\nfunction getPhase5FileSpecs(): Phase5FileSpec[] {\n\treturn [\n\t\t{\n\t\t\tpath: getCredentialsConfigPath(),\n\t\t\t// Mirrors better-auth's client-side session shape; an empty\n\t\t\t// `sessions: []` makes the auth service treat the user as\n\t\t\t// logged-out without a per-read fallback.\n\t\t\tdefaultContent: JSON.stringify({ version: 1, sessions: [] }, null, \"\\t\"),\n\t\t},\n\t\t{\n\t\t\tpath: getDeviceJsonPath(),\n\t\t\t// Device enrollment payload. Empty until the user runs the\n\t\t\t// device-claim flow; the server treats absent fields as\n\t\t\t// \"unclaimed\".\n\t\t\tdefaultContent: JSON.stringify(\n\t\t\t\t{ version: 1, claimed: false, publicKeyFingerprint: null },\n\t\t\t\tnull,\n\t\t\t\t\"\\t\"\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tpath: getToolboxJsonPath(),\n\t\t\t// Local toolbox state; server is the source of truth for\n\t\t\t// installed tools, this is the per-user pref cache.\n\t\t\tdefaultContent: JSON.stringify({ version: 1, installed: [], preferences: {} }, null, \"\\t\"),\n\t\t},\n\t\t{\n\t\t\tpath: getMachineIdPath(),\n\t\t\t// Random uuid, written as a bare string. Subsequent\n\t\t\t// activations see the file and skip re-randomizing.\n\t\t\tdefaultContent: randomUUID(),\n\t\t},\n\t\t{\n\t\t\tpath: getProfilesJsonPath(),\n\t\t\t// Cached projection of the server's profile table; an empty\n\t\t\t// list means \"no profiles yet\" rather than \"unknown\".\n\t\t\tdefaultContent: JSON.stringify({ version: 1, profiles: [] }, null, \"\\t\"),\n\t\t},\n\t];\n}\n\nexport interface BootstrapPhase5Result {\n\t/** Files that were created by this run (already-existing files excluded). */\n\tcreated: string[];\n\t/** Files that were already present and left untouched. */\n\tskipped: string[];\n\t/** Files where the write attempt failed (e.g. read-only home). */\n\tfailed: Array<{ path: string; reason: string }>;\n}\n\n/**\n * Idempotently create the Phase 5 placeholder files under\n * `~/.serviceme/`. Safe to call on every extension activation —\n * existing files are detected via `access` and skipped.\n */\nexport async function bootstrapPhase5Placeholders(): Promise<BootstrapPhase5Result> {\n\tconst result: BootstrapPhase5Result = { created: [], skipped: [], failed: [] };\n\tconst home = getServicemeHome();\n\n\t// Make sure the home directory itself exists before any per-file\n\t// mkdir. mkdir({ recursive: true }) is a no-op when the dir is\n\t// already there, so this is safe to repeat.\n\ttry {\n\t\tawait fs.mkdir(home, { recursive: true });\n\t} catch (err) {\n\t\tresult.failed.push({ path: home, reason: (err as Error).message });\n\t\treturn result;\n\t}\n\n\tfor (const spec of getPhase5FileSpecs()) {\n\t\ttry {\n\t\t\tawait fs.access(spec.path);\n\t\t\tresult.skipped.push(spec.path);\n\t\t} catch {\n\t\t\t// ENOENT (or EACCES on a parent) — try to write.\n\t\t\ttry {\n\t\t\t\tawait fs.mkdir(path.dirname(spec.path), { recursive: true });\n\t\t\t\tawait fs.writeFile(spec.path, spec.defaultContent, \"utf8\");\n\t\t\t\tresult.created.push(spec.path);\n\t\t\t} catch (writeErr) {\n\t\t\t\tresult.failed.push({ path: spec.path, reason: (writeErr as Error).message });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport {\n\tcreateServicemeError,\n\ttype ServiceMeProjectExtractTemplateInput,\n\ttype ServiceMeProjectExtractTemplateResult,\n\ttype ServiceMeProjectGitInitResult,\n\ttype ServiceMeProjectInstallDepsResult,\n\ttype ServiceMeProjectMakeScriptsExecutableResult,\n\ttype ServiceMeProjectScaffoldPruneResult,\n} from \"@serviceme/devtools-protocol\";\nimport { runCommand } from \"../process/runCommand\";\nimport { moveFiles, unzipFile } from \"../utils/fileUtils\";\n\nexport class ProjectTools {\n\tasync extractTemplate(\n\t\tzipPath: string,\n\t\tworkspacePath: string,\n\t\ttempExtractDir: string,\n\t\tinput: ServiceMeProjectExtractTemplateInput\n\t): Promise<ServiceMeProjectExtractTemplateResult> {\n\t\tawait unzipFile(zipPath, tempExtractDir);\n\n\t\tlet sourceDir = path.join(tempExtractDir, input.extractedDirName);\n\t\tlet actualDirName = input.extractedDirName;\n\n\t\tif (!(await this.pathExists(sourceDir))) {\n\t\t\tconst entries = await fs.readdir(tempExtractDir, { withFileTypes: true });\n\t\t\tconst directories = entries.filter(\n\t\t\t\t(entry) => entry.isDirectory() && !entry.name.startsWith(\".\")\n\t\t\t);\n\n\t\t\tconst selectedDirectory = await this.selectExtractedDirectory(\n\t\t\t\tdirectories.map((directory) => directory.name),\n\t\t\t\ttempExtractDir,\n\t\t\t\tinput.projectFilePattern,\n\t\t\t\tinput.extractedDirName\n\t\t\t);\n\n\t\t\tif (selectedDirectory) {\n\t\t\t\tactualDirName = selectedDirectory;\n\t\t\t\tsourceDir = path.join(tempExtractDir, actualDirName);\n\t\t\t} else if (directories.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No directory found after extraction. Expected directory: ${input.extractedDirName}`\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Multiple directories found after extraction: ${directories\n\t\t\t\t\t\t.map((directory) => directory.name)\n\t\t\t\t\t\t.join(\", \")}. Expected: ${input.extractedDirName}`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!(await this.pathExists(sourceDir))) {\n\t\t\tthrow new Error(`Source directory not found: ${sourceDir}`);\n\t\t}\n\n\t\tawait moveFiles(sourceDir, workspacePath, true);\n\n\t\treturn {\n\t\t\tactualDirName,\n\t\t};\n\t}\n\n\tasync installDependencies(\n\t\tworkspacePath: string,\n\t\tcommand?: string\n\t): Promise<ServiceMeProjectInstallDepsResult> {\n\t\tif (!command) {\n\t\t\tthrow createServicemeError(\"invalid_params\", \"Expected install command.\");\n\t\t}\n\n\t\tawait runCommand(command, {\n\t\t\tcwd: workspacePath,\n\t\t\tshell: true,\n\t\t});\n\n\t\treturn {\n\t\t\tinstalled: true,\n\t\t\tcommand,\n\t\t};\n\t}\n\n\tasync makeScriptsExecutable(\n\t\tworkspacePath: string\n\t): Promise<ServiceMeProjectMakeScriptsExecutableResult> {\n\t\tconst isWindows = process.platform === \"win32\";\n\t\tconst scripts = await this.findScripts(workspacePath, isWindows ? [\".ps1\", \".bat\"] : [\".sh\"]);\n\n\t\tlet updatedCount = 0;\n\n\t\tif (isWindows) {\n\t\t\tfor (const scriptPath of scripts.filter((script) => script.endsWith(\".ps1\"))) {\n\t\t\t\ttry {\n\t\t\t\t\tawait runCommand(`powershell -Command \"Unblock-File -Path '${scriptPath}'\"`, {\n\t\t\t\t\t\tcwd: workspacePath,\n\t\t\t\t\t\tshell: true,\n\t\t\t\t\t});\n\t\t\t\t\tupdatedCount += 1;\n\t\t\t\t} catch {\n\t\t\t\t\t// Match the extension's best-effort behavior and continue processing.\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const scriptPath of scripts) {\n\t\t\t\ttry {\n\t\t\t\t\tawait fs.chmod(scriptPath, 0o755);\n\t\t\t\t\tupdatedCount += 1;\n\t\t\t\t} catch {\n\t\t\t\t\t// Match the extension's best-effort behavior and continue processing.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tprocessedCount: scripts.length,\n\t\t\tupdatedCount,\n\t\t\tplatform: process.platform,\n\t\t\tscripts,\n\t\t};\n\t}\n\n\tasync initializeGit(workspacePath: string): Promise<ServiceMeProjectGitInitResult> {\n\t\tconst command = \"git init\";\n\t\tawait runCommand(command, {\n\t\t\tcwd: workspacePath,\n\t\t\tshell: true,\n\t\t});\n\n\t\treturn {\n\t\t\tinitialized: true,\n\t\t\tcommand,\n\t\t};\n\t}\n\n\tasync runScaffoldPrune(\n\t\tworkspacePath: string,\n\t\tpreset?: string\n\t): Promise<ServiceMeProjectScaffoldPruneResult> {\n\t\tif (!preset) {\n\t\t\tthrow createServicemeError(\"invalid_params\", \"Expected scaffold preset.\");\n\t\t}\n\n\t\tawait this.ensurePresetManifest(workspacePath, preset);\n\n\t\tconst pruneCommand = `pnpm run scaffold:prune -- --preset ${preset} --project-root .`;\n\t\tconst initMetadataCommand = `pnpm run scaffold:init-metadata -- --preset ${preset} --project-root . --force`;\n\n\t\tconst commands = [pruneCommand, initMetadataCommand];\n\n\t\tfor (const command of commands) {\n\t\t\tif (command === initMetadataCommand) {\n\t\t\t\t// Some templates remove .ms-scaffold/presets during prune.\n\t\t\t\t// Recover it again before init-metadata calls loadPreset().\n\t\t\t\tawait this.ensurePresetManifest(workspacePath, preset);\n\t\t\t}\n\n\t\t\tawait runCommand(command, {\n\t\t\t\tcwd: workspacePath,\n\t\t\t\tshell: true,\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tapplied: true,\n\t\t\tpreset,\n\t\t\tcommands,\n\t\t};\n\t}\n\n\tprivate async ensurePresetManifest(workspacePath: string, preset: string): Promise<void> {\n\t\tconst presetManifestPath = path.join(\n\t\t\tworkspacePath,\n\t\t\t\".ms-scaffold\",\n\t\t\t\"presets\",\n\t\t\t`${preset}.json`\n\t\t);\n\n\t\tif (await this.pathExists(presetManifestPath)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst projectModePath = path.join(workspacePath, \".ms-scaffold\", \"project-mode.json\");\n\t\tif (!(await this.pathExists(projectModePath))) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst projectModeRaw = await fs.readFile(projectModePath, \"utf8\");\n\t\tconst projectMode = JSON.parse(projectModeRaw) as {\n\t\t\tselectedModules?: unknown;\n\t\t\tprunedModules?: unknown;\n\t\t};\n\n\t\tconst synthesizedPreset = {\n\t\t\tpreset,\n\t\t\tselectedModules: Array.isArray(projectMode.selectedModules)\n\t\t\t\t? projectMode.selectedModules\n\t\t\t\t: [],\n\t\t\tprunedModules: Array.isArray(projectMode.prunedModules) ? projectMode.prunedModules : [],\n\t\t\texclude: [] as string[],\n\t\t\trecommendedFollowUps: [] as string[],\n\t\t\tmanagedFiles: [] as string[],\n\t\t\tmergeManagedFiles: [] as string[],\n\t\t\tuserOwnedPaths: [] as string[],\n\t\t};\n\n\t\tawait fs.mkdir(path.dirname(presetManifestPath), { recursive: true });\n\t\tawait fs.writeFile(\n\t\t\tpresetManifestPath,\n\t\t\t`${JSON.stringify(synthesizedPreset, null, 2)}\\n`,\n\t\t\t\"utf8\"\n\t\t);\n\t}\n\n\tprivate async findScripts(dir: string, extensions: string[]): Promise<string[]> {\n\t\tconst results: string[] = [];\n\t\tlet entries: import(\"node:fs\").Dirent[];\n\n\t\ttry {\n\t\t\tentries = await fs.readdir(dir, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn results;\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = path.join(dir, entry.name);\n\t\t\tif (entry.isDirectory() && entry.name !== \"node_modules\" && !entry.name.startsWith(\".\")) {\n\t\t\t\tresults.push(...(await this.findScripts(fullPath, extensions)));\n\t\t\t} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {\n\t\t\t\tresults.push(fullPath);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tprivate async pathExists(targetPath: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fs.access(targetPath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate async selectExtractedDirectory(\n\t\tdirectoryNames: string[],\n\t\ttempExtractDir: string,\n\t\tprojectFilePattern: string,\n\t\texpectedDirName: string\n\t): Promise<string | null> {\n\t\tif (directoryNames.length === 1) {\n\t\t\treturn directoryNames[0] ?? null;\n\t\t}\n\n\t\tconst exactMatch = directoryNames.find((directoryName) => directoryName === expectedDirName);\n\t\tif (exactMatch) {\n\t\t\treturn exactMatch;\n\t\t}\n\n\t\tconst matches: string[] = [];\n\t\tfor (const directoryName of directoryNames) {\n\t\t\tif (\n\t\t\t\tawait this.directoryMatchesProjectPattern(\n\t\t\t\t\tpath.join(tempExtractDir, directoryName),\n\t\t\t\t\tprojectFilePattern\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tmatches.push(directoryName);\n\t\t\t}\n\t\t}\n\n\t\tif (matches.length === 1) {\n\t\t\treturn matches[0] ?? null;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate async directoryMatchesProjectPattern(\n\t\tdirectoryPath: string,\n\t\tprojectFilePattern: string\n\t): Promise<boolean> {\n\t\tconst entries = await fs.readdir(directoryPath);\n\t\tif (projectFilePattern.includes(\"*\")) {\n\t\t\tconst regex = new RegExp(`^${projectFilePattern.replace(\"*\", \".*\")}$`);\n\t\t\treturn entries.some((entry) => regex.test(entry));\n\t\t}\n\n\t\treturn entries.includes(projectFilePattern);\n\t}\n}\n\nexport function createProjectTools(): ProjectTools {\n\treturn new ProjectTools();\n}\n","import { constants, createWriteStream } from \"node:fs\";\nimport { access, copyFile, lstat, mkdir, readdir, rename, rm } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { Readable } from \"node:stream\";\nimport yauzl from \"yauzl\";\n\nexport const unzipFile = (zipPath: string, dest: string): Promise<void> => {\n\treturn new Promise((resolve, reject) => {\n\t\tyauzl.open(zipPath, { lazyEntries: true }, (err: Error | null, zipfile?: yauzl.ZipFile) => {\n\t\t\tif (err) return reject(err);\n\t\t\tif (!zipfile) return reject(new Error(\"Failed to open zip file.\"));\n\n\t\t\tzipfile.readEntry();\n\t\t\tzipfile.on(\"entry\", (entry: yauzl.Entry) => {\n\t\t\t\tif (/\\/$/.test(entry.fileName)) {\n\t\t\t\t\tvoid mkdir(join(dest, entry.fileName), { recursive: true })\n\t\t\t\t\t\t.then(() => {\n\t\t\t\t\t\t\tzipfile.readEntry();\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch(reject);\n\t\t\t\t} else {\n\t\t\t\t\tconst outputPath = join(dest, entry.fileName);\n\t\t\t\t\tvoid mkdir(dirname(outputPath), { recursive: true })\n\t\t\t\t\t\t.then(() => {\n\t\t\t\t\t\t\tzipfile.openReadStream(\n\t\t\t\t\t\t\t\tentry,\n\t\t\t\t\t\t\t\t(streamError: Error | null, readStream: Readable | null) => {\n\t\t\t\t\t\t\t\t\tif (streamError) return reject(streamError);\n\t\t\t\t\t\t\t\t\tif (!readStream) return reject(new Error(\"Failed to open zip entry stream.\"));\n\n\t\t\t\t\t\t\t\t\tconst writeStream = createWriteStream(outputPath);\n\t\t\t\t\t\t\t\t\treadStream.on(\"error\", reject);\n\t\t\t\t\t\t\t\t\twriteStream.on(\"error\", reject);\n\t\t\t\t\t\t\t\t\twriteStream.on(\"close\", () => {\n\t\t\t\t\t\t\t\t\t\tzipfile.readEntry();\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\treadStream.pipe(writeStream);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch(reject);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tzipfile.on(\"end\", () => {\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\tzipfile.on(\"error\", (zipError: Error) => {\n\t\t\t\treject(zipError);\n\t\t\t});\n\t\t});\n\t});\n};\n\nconst tryLstat = async (targetPath: string) => {\n\ttry {\n\t\treturn await lstat(targetPath);\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst mergeEntry = async (\n\tsourcePath: string,\n\tdestPath: string,\n\toverwrite: boolean\n): Promise<void> => {\n\tconst sourceStat = await lstat(sourcePath);\n\tconst destStat = await tryLstat(destPath);\n\n\tif (sourceStat.isDirectory()) {\n\t\tif (destStat && !destStat.isDirectory()) {\n\t\t\tif (!overwrite) {\n\t\t\t\tawait rm(sourcePath, { recursive: true, force: true });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait rm(destPath, { recursive: true, force: true });\n\t\t}\n\n\t\tawait mkdir(destPath, { recursive: true });\n\t\tconst children = await readdir(sourcePath);\n\n\t\tfor (const child of children) {\n\t\t\tawait mergeEntry(join(sourcePath, child), join(destPath, child), overwrite);\n\t\t}\n\n\t\tawait rm(sourcePath, { recursive: true, force: true });\n\t\treturn;\n\t}\n\n\tif (destStat) {\n\t\tif (!overwrite) {\n\t\t\tawait rm(sourcePath, { recursive: true, force: true });\n\t\t\treturn;\n\t\t}\n\n\t\tawait rm(destPath, { recursive: true, force: true });\n\t}\n\n\ttry {\n\t\tawait rename(sourcePath, destPath);\n\t} catch {\n\t\tawait copyFile(sourcePath, destPath);\n\t\tawait rm(sourcePath, { recursive: true, force: true });\n\t}\n};\n\nexport const moveFiles = async (\n\tsourceDir: string,\n\tdestDir: string,\n\toverwrite = false\n): Promise<void> => {\n\tawait mkdir(destDir, { recursive: true });\n\tconst files = await readdir(sourceDir);\n\n\tfor (const file of files) {\n\t\tconst sourceFile = join(sourceDir, file);\n\t\tconst destFile = join(destDir, file);\n\n\t\tif (!overwrite) {\n\t\t\ttry {\n\t\t\t\tawait access(destFile, constants.F_OK);\n\t\t\t\tawait rm(sourceFile, { recursive: true, force: true });\n\t\t\t\tcontinue;\n\t\t\t} catch {\n\t\t\t\t// destination missing, continue with merge\n\t\t\t}\n\t\t}\n\n\t\tawait mergeEntry(sourceFile, destFile, overwrite);\n\t}\n};\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { GitClient } from \"../git-client\";\nimport type { PullResult } from \"../git-client/types\";\nimport {\n\tassertSafeRepoId,\n\tgetRepoDir,\n\tgetServicemeHome,\n\tSAFE_REPO_ID_PATTERN,\n} from \"../paths/userHome\";\nimport type { ReposStore } from \"../repos/store\";\nimport type { AnyRepoConfig, RepoConfig, UserRepoConfig } from \"../repos/types\";\nimport { isDefaultRepo, isUserRepo } from \"../repos/types\";\n\n/**\n * Skill & Agent v2 — Client RepoManager (M3)\n *\n * RepoManager is the orchestrator for local git repo management. It\n * composes:\n * - {@link ReposStore} (in-memory CRUD over `~/.serviceme/repos.json`)\n * - {@link GitClient} (spawns `git` against the server proxy)\n * - the filesystem (clone targets live under `~/.serviceme/repos/<id>/`)\n *\n * Lifecycle responsibilities:\n * - **Bootstrap**: on first launch, clone all enabled default repos.\n * - **Sync**: pullOne / pullAll for periodic updates.\n * - **User repos**: addUserRepo validates URL, probes branch via\n * `git ls-remote`, writes the entry, and triggers an async clone.\n * - **Cleanup**: removeUserRepo deletes both the local clone AND the\n * `repos.json` entry. disableRepo / enableRepo only flip a flag.\n *\n * Default repos (`source === 'default'`) cannot be removed — only\n * disabled. The Store enforces this; RepoManager propagates the error.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §5.3 RepoManager\n */\n\nexport interface RepoManagerOptions {\n\tstore: ReposStore;\n\tgitClient: GitClient;\n\t/** Override the clock for deterministic tests. */\n\tnow?: () => string;\n\t/** Skip the actual git clone (used by tests). When true, the\n\t * filesystem-side mkdir is the only side effect. */\n\tskipClone?: boolean;\n}\n\nexport interface AddUserRepoInput {\n\t/** Upstream URL the user pasted in. */\n\turl: string;\n\t/** Branch to track. Auto-detected from upstream when omitted. */\n\tbranch?: string;\n\t/** Optional display name (defaults to id). */\n\tname?: string;\n}\n\nexport interface AddUserRepoResult {\n\trepo: UserRepoConfig;\n\tbranch: string;\n\tcloned: boolean;\n}\n\n/** Snapshot of a sync run, suitable for UI rendering. */\nexport interface SyncReport {\n\tpulls: Array<{ repoId: string; status: \"ok\" | \"error\"; result?: PullResult; error?: string }>;\n}\n\n/** Pattern: kebab-case slug from a git URL's last segment. */\nfunction idFromUrl(url: string): string {\n\tconst trimmed = url.trim().replace(/\\.git$/, \"\");\n\tconst last = trimmed.split(\"/\").pop() ?? \"\";\n\tconst safe = last.replace(/[^a-zA-Z0-9_-]/g, \"-\").toLowerCase();\n\tif (!SAFE_REPO_ID_PATTERN.test(safe)) {\n\t\tthrow new InvalidRepoUrlError(`cannot derive a valid repo id from url: ${url}`);\n\t}\n\treturn safe;\n}\n\n/** Sentinel errors. */\nexport class RepoManagerError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"RepoManagerError\";\n\t}\n}\nexport class InvalidRepoUrlError extends RepoManagerError {}\nexport class RepoCloneConflictError extends RepoManagerError {}\nexport class RepoNotInStoreError extends RepoManagerError {}\n\nexport class RepoManager {\n\tprivate readonly store: ReposStore;\n\tprivate readonly git: GitClient;\n\tprivate readonly now: () => string;\n\tprivate readonly skipClone: boolean;\n\n\tconstructor(opts: RepoManagerOptions) {\n\t\tthis.store = opts.store;\n\t\tthis.git = opts.gitClient;\n\t\tthis.now = opts.now ?? (() => new Date().toISOString());\n\t\tthis.skipClone = opts.skipClone ?? false;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Bootstrap\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/**\n\t * Clone every enabled default repo that doesn't have a local clone\n\t * yet. Failures on individual repos are recorded in `lastSyncStatus`\n\t * but do NOT abort the loop — the UI surfaces per-repo state and the\n\t * user can retry.\n\t *\n\t * Per spec §3.1: 4 default repos; the loop honors `enabled=false` and\n\t * disables (rather than removes) repos the user has turned off.\n\t */\n\tasync ensureDefaults(): Promise<SyncReport> {\n\t\tconst report: SyncReport = { pulls: [] };\n\t\tconst defaults = this.store.listDefault();\n\t\tfor (const repo of defaults) {\n\t\t\tif (!repo.enabled) continue;\n\t\t\tconst localPath = getRepoDir(repo.id);\n\t\t\tconst exists = await this.pathExists(localPath);\n\t\t\tif (exists) continue;\n\t\t\ttry {\n\t\t\t\tif (!this.skipClone) {\n\t\t\t\t\tawait fs.mkdir(path.dirname(localPath), { recursive: true });\n\t\t\t\t\tawait this.git.clone(repo.id, repo.url, localPath);\n\t\t\t\t}\n\t\t\t\tawait this.store.updateRepo(repo.id, {\n\t\t\t\t\tlastSyncStatus: \"ok\",\n\t\t\t\t\tlastSyncAt: this.now(),\n\t\t\t\t});\n\t\t\t\treport.pulls.push({ repoId: repo.id, status: \"ok\" });\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\tawait this.store.updateRepo(repo.id, {\n\t\t\t\t\tlastSyncStatus: \"error\",\n\t\t\t\t\tlastSyncAt: this.now(),\n\t\t\t\t\tlastSyncError: message,\n\t\t\t\t});\n\t\t\t\treport.pulls.push({ repoId: repo.id, status: \"error\", error: message });\n\t\t\t}\n\t\t}\n\t\treturn report;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Per-repo sync\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/**\n\t * Pull a single repo. Updates `lastSync*` fields on success or failure.\n\t * Returns the raw `PullResult` so callers can surface fetch progress.\n\t */\n\tasync pullOne(repoId: string): Promise<PullResult> {\n\t\tconst repo = this.store.get(repoId);\n\t\tif (!repo) throw new RepoNotInStoreError(repoId);\n\t\tconst localPath = getRepoDir(repoId);\n\t\tconst exists = await this.pathExists(localPath);\n\t\tif (!exists) {\n\t\t\t// First-time sync — clone instead of pulling.\n\t\t\tawait fs.mkdir(path.dirname(localPath), { recursive: true });\n\t\t\tif (!this.skipClone) {\n\t\t\t\tawait this.git.clone(repoId, repo.url, localPath);\n\t\t\t}\n\t\t\tconst result: PullResult = {\n\t\t\t\tupdated: true,\n\t\t\t\tcommitSha: \"\",\n\t\t\t\tbranch: repo.branch,\n\t\t\t};\n\t\t\tawait this.store.updateRepo(repoId, {\n\t\t\t\tlastSyncStatus: \"ok\",\n\t\t\t\tlastSyncAt: this.now(),\n\t\t\t});\n\t\t\treturn result;\n\t\t}\n\t\ttry {\n\t\t\tconst result = await this.git.pull(repoId, localPath);\n\t\t\tawait this.store.updateRepo(repoId, {\n\t\t\t\tlastSyncStatus: \"ok\",\n\t\t\t\tlastSyncAt: this.now(),\n\t\t\t\tlastSyncCommitSha: result.commitSha,\n\t\t\t});\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tawait this.store.updateRepo(repoId, {\n\t\t\t\tlastSyncStatus: \"error\",\n\t\t\t\tlastSyncAt: this.now(),\n\t\t\t\tlastSyncError: message,\n\t\t\t});\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Pull every enabled repo. Per-repo failures don't abort the run. */\n\tasync pullAll(): Promise<SyncReport> {\n\t\tconst report: SyncReport = { pulls: [] };\n\t\tconst enabled = this.store.list().filter((r) => r.enabled);\n\t\tfor (const repo of enabled) {\n\t\t\ttry {\n\t\t\t\tconst r = await this.pullOne(repo.id);\n\t\t\t\treport.pulls.push({ repoId: repo.id, status: \"ok\", result: r });\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\treport.pulls.push({ repoId: repo.id, status: \"error\", error: message });\n\t\t\t}\n\t\t}\n\t\treturn report;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// User repos: add / remove\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/**\n\t * Validate URL, derive an id, detect the branch via `git ls-remote`,\n\t * then add the entry to the store and trigger a clone.\n\t *\n\t * Spec §5.3 says \"branch detection\" happens BEFORE the store write so\n\t * the resulting `repos.json` is fully populated. The clone is async\n\t * but the function returns synchronously once the store is updated\n\t * (cloning happens via `ensureDefaults`-style background fire).\n\t *\n\t * For tests with `skipClone: true`, the clone is skipped entirely.\n\t */\n\tasync addUserRepo(input: AddUserRepoInput): Promise<AddUserRepoResult> {\n\t\tconst url = input.url.trim();\n\t\tif (!/^https?:\\/\\//.test(url) && !url.startsWith(\"git@\")) {\n\t\t\tthrow new InvalidRepoUrlError(`expected https:// or git@ URL, got: ${url}`);\n\t\t}\n\t\tconst id = idFromUrl(url);\n\t\tassertSafeRepoId(id);\n\n\t\t// Detect branch if not provided\n\t\tlet branch = input.branch?.trim();\n\t\tif (!branch) {\n\t\t\tconst branches = await this.git.lsRemote(id, url);\n\t\t\tconst head =\n\t\t\t\tbranches.find((b) => b.ref === \"refs/heads/main\") ??\n\t\t\t\tbranches.find((b) => b.ref === \"refs/heads/master\") ??\n\t\t\t\tbranches.find((b) => b.ref.endsWith(\"/v2\"));\n\t\t\tif (!head) {\n\t\t\t\tthrow new InvalidRepoUrlError(\n\t\t\t\t\t`could not detect default branch for ${url} (no refs/heads/{main,master,v2})`\n\t\t\t\t);\n\t\t\t}\n\t\t\tbranch = head.ref.replace(/^refs\\/heads\\//, \"\");\n\t\t}\n\n\t\t// Check for collision BEFORE writing the store so we can give a\n\t\t// clean error message.\n\t\tif (this.store.get(id)) {\n\t\t\tthrow new RepoCloneConflictError(`repo id '${id}' already exists`);\n\t\t}\n\n\t\tconst repo: UserRepoConfig = {\n\t\t\tid,\n\t\t\tname: input.name?.trim() || id,\n\t\t\turl,\n\t\t\tbranch,\n\t\t\tenabled: true,\n\t\t\twriteEnabled: false,\n\t\t\tsource: \"user\",\n\t\t\taddedAt: this.now(),\n\t\t};\n\t\tawait this.store.addUserRepo(repo);\n\n\t\t// Trigger async clone (or skip in tests)\n\t\tlet cloned = false;\n\t\tif (!this.skipClone) {\n\t\t\tconst localPath = getRepoDir(id);\n\t\t\tawait fs.mkdir(path.dirname(localPath), { recursive: true });\n\t\t\tawait this.git.clone(id, url, localPath);\n\t\t\tcloned = true;\n\t\t}\n\n\t\treturn { repo, branch, cloned };\n\t}\n\n\t/**\n\t * Remove a user-added repo. Throws when:\n\t * - the repo is unknown\n\t * - the repo is a default (use `disableRepo` instead — store enforces)\n\t *\n\t * The local clone is deleted via `fs.rm`. If the local dir is\n\t * already gone we silently succeed (idempotent re-remove).\n\t */\n\tasync removeUserRepo(repoId: string): Promise<void> {\n\t\tconst repo = this.store.get(repoId);\n\t\tif (!repo) throw new RepoNotInStoreError(repoId);\n\t\tif (isDefaultRepo(repo)) {\n\t\t\tthrow new RepoCloneConflictError(\n\t\t\t\t`cannot remove default repo '${repoId}'; use disableRepo() instead`\n\t\t\t);\n\t\t}\n\t\t// Store first — so the failure surface is the user-visible\n\t\t// repos.json change, not a half-deleted filesystem.\n\t\tawait this.store.removeUserRepo(repoId);\n\t\tconst localPath = getRepoDir(repoId);\n\t\ttry {\n\t\t\tawait fs.rm(localPath, { recursive: true, force: true });\n\t\t} catch (err) {\n\t\t\t// Swallow ENOENT — idempotent. Re-throw anything else.\n\t\t\tif ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n\t\t}\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Enable / disable\n\t// ─────────────────────────────────────────────────────────────────\n\n\tasync disableRepo(repoId: string): Promise<AnyRepoConfig> {\n\t\treturn this.store.disableRepo(repoId);\n\t}\n\n\tasync enableRepo(repoId: string): Promise<AnyRepoConfig> {\n\t\treturn this.store.enableRepo(repoId);\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Helpers\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/** `true` when `~/.serviceme/repos/<id>/` exists on disk. */\n\tasync hasLocalClone(repoId: string): Promise<boolean> {\n\t\treturn this.pathExists(getRepoDir(repoId));\n\t}\n\n\t/** Force-create the SERVICEME home directory tree (idempotent). */\n\tasync ensureHome(): Promise<void> {\n\t\tawait fs.mkdir(getServicemeHome(), { recursive: true });\n\t}\n\n\tprivate async pathExists(p: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fs.stat(p);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n\n/** Type guard for AnyRepoConfig — re-exported for tests + extension code. */\nexport { isDefaultRepo, isUserRepo };\nexport type { RepoConfig };\n","/**\n * `repos.json` schema — mirrors §3 of docs/architecture/skill-agent-v2-repo.md.\n *\n * The on-disk file lives at `${SERVICEME_HOME}/repos.json` (see\n * `paths/userHome.getReposConfigPath`). It holds the user-visible catalog of\n * skill/agent repositories (default + user-added) plus the currently-selected\n * default.\n *\n * Two flavours of repo:\n * - `DefaultRepoConfig` — hardcoded by SERVICEME; can only be disabled, not\n * removed.\n * - `UserRepoConfig` — added by the user via the \"Add repository\" flow;\n * can be removed freely.\n *\n * Both extend `RepoConfig` and add a `source` discriminant so consumers can\n * branch on `repo.source === 'default' | 'user'` without sniffing free-form\n * fields.\n */\n\n/** ISO 8601 timestamp. */\nexport type IsoTimestamp = string;\n\n/** Per-repo sync health — surfaced in the UI alongside the repo entry. */\nexport type SyncStatus = \"ok\" | \"error\";\n\n/** Discriminant on `RepoConfig.source` — narrow with a literal check. */\nexport type RepoSource = \"default\" | \"user\";\n\n/**\n * Common shape shared by both default and user-added repos. Source lives on\n * the type so we can keep the loader schema flat while still letting\n * `DefaultRepoConfig` carry a `description`.\n */\nexport interface RepoConfig {\n\t/** Stable id used in on-disk paths (must match `SAFE_REPO_ID_PATTERN`). */\n\tid: string;\n\t/** Display name shown in UI. */\n\tname: string;\n\t/** Upstream git URL (typically github.com/<owner>/<repo>.git). */\n\turl: string;\n\t/** Default branch to track (e.g. \"v2\", \"main\"). */\n\tbranch: string;\n\t/** Whether this repo participates in scheduled syncs. */\n\tenabled: boolean;\n\t/** Whether the user is allowed to push back to this repo. */\n\twriteEnabled: boolean;\n\t/** When the user (or first-run installer) added this repo. */\n\taddedAt: IsoTimestamp;\n\t/** Last successful sync timestamp. */\n\tlastSyncAt?: IsoTimestamp;\n\t/** Commit SHA at the time of the last successful sync. */\n\tlastSyncCommitSha?: string;\n\t/** Result of the most recent sync attempt. */\n\tlastSyncStatus?: SyncStatus;\n\t/** Error message from the most recent sync attempt (when `lastSyncStatus === 'error'`). */\n\tlastSyncError?: string;\n\t/** Whether this repo is a default or a user-added repo. */\n\tsource: RepoSource;\n}\n\n/** A default (bundled) repo — has a UI-facing description, cannot be removed. */\nexport interface DefaultRepoConfig extends RepoConfig {\n\tsource: \"default\";\n\tdescription: string;\n}\n\n/** A user-added repo — can be added, removed, disabled freely. */\nexport interface UserRepoConfig extends RepoConfig {\n\tsource: \"user\";\n}\n\n/** Any repo, regardless of source. */\nexport type AnyRepoConfig = DefaultRepoConfig | UserRepoConfig;\n\n/**\n * Root shape of `repos.json`. `version` lets us evolve the schema later\n * without colliding with hand-edited files.\n */\nexport interface ReposFile {\n\tversion: 1;\n\t/** Id of the repo currently surfaced as the default selection in the UI. */\n\tdefaultRepoId: string;\n\trepos: AnyRepoConfig[];\n}\n\n/**\n * Convenience type-guard — narrows to `DefaultRepoConfig`. Useful when callers\n * want to render `repo.description` or reject `removeRepo(repo)` for defaults.\n */\nexport function isDefaultRepo(repo: RepoConfig): repo is DefaultRepoConfig {\n\treturn repo.source === \"default\";\n}\n\n/** Convenience type-guard — narrows to `UserRepoConfig`. */\nexport function isUserRepo(repo: RepoConfig): repo is UserRepoConfig {\n\treturn repo.source === \"user\";\n}\n","import type { DefaultRepoConfig, ReposFile } from \"./types\";\n\n/**\n * Hard-coded catalog of repositories SERVICEME ships with. The id, URL and\n * branch are pinned by the design doc (§12) and **must not** change without\n * an owner-approved migration. The order here mirrors the on-disk\n * `repos.json` order (§3.1) which the UI relies on.\n */\n\nexport const DEFAULT_REPO_ID = \"medalsoftchina-ms-skills\";\n\nconst FIXED_ADDED_AT = \"2026-06-29T00:00:00.000Z\";\n\n/**\n * Master list of default repositories. `getDefaultRepoConfigs()` returns\n * clones with the current timestamp baked into `addedAt` so two `load()`\n * calls return identical-but-distinct objects (the store can mutate one\n * without disturbing the other).\n */\nconst DEFAULT_REPO_SEEDS: ReadonlyArray<Omit<DefaultRepoConfig, \"addedAt\">> = [\n\t{\n\t\tid: \"medalsoftchina-ms-skills\",\n\t\tname: \"MS DevTools Skills (精选)\",\n\t\turl: \"https://github.com/medalsoftchina/ms-skills.git\",\n\t\tbranch: \"v2\",\n\t\tenabled: true,\n\t\twriteEnabled: true,\n\t\tsource: \"default\",\n\t\tdescription: \"Medalsoft 精选 skill/agent 仓库,可读可写\",\n\t},\n\t{\n\t\tid: \"anthropics-skills\",\n\t\tname: \"Anthropic Skills\",\n\t\turl: \"https://github.com/anthropics/skills.git\",\n\t\tbranch: \"main\",\n\t\tenabled: true,\n\t\twriteEnabled: false,\n\t\tsource: \"default\",\n\t\tdescription: \"Anthropic 官方 skills 示例\",\n\t},\n\t{\n\t\tid: \"github-awesome-copilot\",\n\t\tname: \"GitHub Awesome Copilot\",\n\t\turl: \"https://github.com/github/awesome-copilot.git\",\n\t\tbranch: \"main\",\n\t\tenabled: true,\n\t\twriteEnabled: false,\n\t\tsource: \"default\",\n\t\tdescription: \"GitHub Copilot 社区精选 prompts/agents\",\n\t},\n\t{\n\t\tid: \"composiohq-awesome-claude-skills\",\n\t\tname: \"Composio Awesome Claude Skills\",\n\t\turl: \"https://github.com/ComposioHQ/awesome-claude-skills.git\",\n\t\tbranch: \"main\",\n\t\tenabled: true,\n\t\twriteEnabled: false,\n\t\tsource: \"default\",\n\t\tdescription: \"Composio 维护的 Claude skills 集合\",\n\t},\n];\n\n/** Returns a deep-ish clone of every default repo with a fresh `addedAt`. */\nexport function getAllDefaultRepoConfigs(\n\tnow: () => string = () => new Date().toISOString(),\n): DefaultRepoConfig[] {\n\tconst stamp = now();\n\treturn DEFAULT_REPO_SEEDS.map((seed) => ({\n\t\t...seed,\n\t\taddedAt: stamp,\n\t}));\n}\n\n/** Returns a single default repo by id, or `undefined` when not in the catalog. */\nexport function getDefaultRepoConfig(\n\tid: string,\n\tnow: () => string = () => new Date().toISOString(),\n): DefaultRepoConfig | undefined {\n\tconst seed = DEFAULT_REPO_SEEDS.find((s) => s.id === id);\n\tif (!seed) {\n\t\treturn undefined;\n\t}\n\treturn { ...seed, addedAt: now() };\n}\n\n/**\n * Idempotent: returns a fresh `ReposFile` containing the default set, or —\n * when `existing` already has all four — returns it untouched (still cloned\n * to keep callers from accidentally mutating shared state).\n */\nexport function buildDefaultReposFile(\n\tnow: () => string = () => new Date().toISOString(),\n\texisting?: ReposFile,\n): ReposFile {\n\tconst defaults = getAllDefaultRepoConfigs(now);\n\n\tif (!existing) {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tdefaultRepoId: DEFAULT_REPO_ID,\n\t\t\trepos: defaults,\n\t\t};\n\t}\n\n\t// Merge: keep existing repos as-is (including any user-added ones), then\n\t// append any default ids that are missing. Existing default entries\n\t// (with their own `addedAt`, `lastSyncAt`, etc.) win over the seed.\n\tconst existingIds = new Set(existing.repos.map((r) => r.id));\n\tconst missingDefaults = defaults.filter((d) => !existingIds.has(d.id));\n\tif (missingDefaults.length === 0) {\n\t\treturn existing;\n\t}\n\treturn {\n\t\t...existing,\n\t\trepos: [...existing.repos, ...missingDefaults],\n\t\tdefaultRepoId: resolveDefaultRepoId(existing, existingIds),\n\t};\n}\n\n/**\n * `ensureDefaultsInstalled(existing)` — pure helper that returns either\n * `existing` (when defaults are already present) or a merged copy that\n * appends the missing defaults. The companion `ensureDefaultsInstalledInStore`\n * below is the mutating, store-bound counterpart.\n */\nexport function ensureDefaultsInstalled(\n\texisting: ReposFile,\n\tnow: () => string = () => new Date().toISOString(),\n): { config: ReposFile; installed: string[] } {\n\tconst defaults = getAllDefaultRepoConfigs(now);\n\tconst existingIds = new Set(existing.repos.map((r) => r.id));\n\tconst missing = defaults.filter((d) => !existingIds.has(d.id));\n\tif (missing.length === 0) {\n\t\treturn { config: existing, installed: [] };\n\t}\n\tconst next: ReposFile = {\n\t\t...existing,\n\t\trepos: [...existing.repos, ...missing],\n\t\tdefaultRepoId: resolveDefaultRepoId(existing, existingIds),\n\t};\n\treturn { config: next, installed: missing.map((d) => d.id) };\n}\n\n/**\n * Decide what the post-bootstrap `defaultRepoId` should be.\n *\n * Rules (in order):\n * 1. If `existing.defaultRepoId` references a real repo in `existing`,\n * keep it (the user explicitly chose this default).\n * 2. Otherwise — including the loader's empty-state `\"\"` sentinel or any\n * other unknown id — fall back to {@link DEFAULT_REPO_ID}.\n */\nfunction resolveDefaultRepoId(\n\texisting: ReposFile,\n\texistingIds: Set<string>,\n): string {\n\tconst placeholder = existing.defaultRepoId;\n\tif (placeholder && existingIds.has(placeholder)) {\n\t\treturn placeholder;\n\t}\n\treturn DEFAULT_REPO_ID;\n}\n\n// `FIXED_ADDED_AT` is exported in case tests want a deterministic timestamp\n// to compare against without stubbing `Date`.\nexport { FIXED_ADDED_AT as DEFAULT_REPO_FIXED_ADDED_AT };\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { z } from \"zod\";\n\nimport { SAFE_REPO_ID_PATTERN } from \"../paths/userHome\";\nimport type { AnyRepoConfig, DefaultRepoConfig, ReposFile, UserRepoConfig } from \"./types\";\n\n/**\n * On-disk `repos.json` loader / writer.\n *\n * Responsibilities:\n * 1. **Validate** every read with a Zod schema — never trust whatever is on\n * disk; a hand-edited file can be malformed in subtle ways.\n * 2. **Recover gracefully** from corrupted files by moving them to a\n * timestamped `.bak` sibling and returning a sentinel empty config.\n * Callers can then prompt the user to re-add their repos.\n * 3. **Write atomically** — always write to `<target>.tmp-<rand>` first\n * and `rename` over the target. A crash mid-write leaves the previous\n * good file intact instead of a half-written JSON.\n *\n * `fs.watch`-based change detection lives in `store.ts`. This module is\n * deliberately pure I/O + validation so it can be exercised from tests\n * without spinning up watchers.\n */\n\nconst ISO_TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:?\\d{2})$/;\n\nconst repoIdSchema = z\n\t.string()\n\t.min(1)\n\t.max(64)\n\t.regex(SAFE_REPO_ID_PATTERN, \"repo id must match /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/\");\n\nconst isoTimestampSchema = z.string().regex(ISO_TIMESTAMP_PATTERN, {\n\tmessage: \"must be an ISO-8601 timestamp\",\n});\n\nconst baseRepoFields = {\n\tid: repoIdSchema,\n\tname: z.string().min(1).max(200),\n\turl: z.string().url(),\n\tbranch: z.string().min(1).max(200),\n\tenabled: z.boolean(),\n\twriteEnabled: z.boolean(),\n\taddedAt: isoTimestampSchema,\n\tlastSyncAt: isoTimestampSchema.optional(),\n\tlastSyncCommitSha: z.string().min(1).max(80).optional(),\n\tlastSyncStatus: z.enum([\"ok\", \"error\"]).optional(),\n\tlastSyncError: z.string().max(2_000).optional(),\n};\n\nconst defaultRepoSchema = z.object({\n\t...baseRepoFields,\n\tsource: z.literal(\"default\"),\n\tdescription: z.string().min(1).max(2_000),\n});\n\nconst userRepoSchema = z.object({\n\t...baseRepoFields,\n\tsource: z.literal(\"user\"),\n});\n\nconst repoSchema = z.discriminatedUnion(\"source\", [defaultRepoSchema, userRepoSchema]);\n\nexport const reposFileSchema = z\n\t.object({\n\t\tversion: z.literal(1),\n\t\t// Empty string is the sentinel for \"no repos yet\" (see `repos: []`\n\t\t// below); a populated file must use a real `repoIdSchema`-shaped id,\n\t\t// checked in `superRefine` below.\n\t\tdefaultRepoId: z.string(),\n\t\trepos: z.array(repoSchema),\n\t})\n\t.superRefine((value, ctx) => {\n\t\tconst ids = new Set<string>();\n\t\tfor (const repo of value.repos) {\n\t\t\tif (ids.has(repo.id)) {\n\t\t\t\tctx.addIssue({\n\t\t\t\t\tcode: z.ZodIssueCode.custom,\n\t\t\t\t\tpath: [\"repos\"],\n\t\t\t\t\tmessage: `Duplicate repo id: ${repo.id}`,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tids.add(repo.id);\n\t\t}\n\t\tif (value.repos.length === 0) {\n\t\t\tif (value.defaultRepoId !== \"\") {\n\t\t\t\tctx.addIssue({\n\t\t\t\t\tcode: z.ZodIssueCode.custom,\n\t\t\t\t\tpath: [\"defaultRepoId\"],\n\t\t\t\t\tmessage: \"defaultRepoId must be '' when repos[] is empty\",\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (!ids.has(value.defaultRepoId)) {\n\t\t\tctx.addIssue({\n\t\t\t\tcode: z.ZodIssueCode.custom,\n\t\t\t\tpath: [\"defaultRepoId\"],\n\t\t\t\tmessage: `defaultRepoId '${value.defaultRepoId}' not present in repos[]`,\n\t\t\t});\n\t\t}\n\t});\n\n/** Inferred Zod type — identical to our hand-written `ReposFile`. */\nexport type ReposFileInput = z.infer<typeof reposFileSchema>;\n\n/** Result of a load attempt — either a parsed file or a recovery notice. */\nexport interface LoadResult {\n\t/** Parsed + validated repos file (always populated, even on recovery). */\n\tconfig: ReposFile;\n\t/**\n\t * Non-null when the existing file on disk was unreadable / invalid and\n\t * was moved aside as a `.bak`. The store can use this to surface a\n\t * warning in the UI.\n\t */\n\trecoveredFromBackup: string | null;\n}\n\n/** Dependencies — defaults to the real fs, tests inject a fake. */\nexport interface ReposLoaderFileSystem {\n\treadFile: typeof fs.readFile;\n\twriteFile: typeof fs.writeFile;\n\trename: typeof fs.rename;\n\tmkdir: typeof fs.mkdir;\n\tstat: typeof fs.stat;\n\tunlink?: typeof fs.unlink;\n}\n\nexport interface ReposLoaderOptions {\n\t/** Absolute path of the `repos.json` file. */\n\tconfigPath: string;\n\t/** Custom time provider — defaults to `() => new Date().toISOString()`. */\n\tnow?: () => string;\n\t/** Random suffix provider for atomic-write temp files. */\n\trandomSuffix?: () => string;\n\t/** Filesystem shim for tests. */\n\tfileSystem?: ReposLoaderFileSystem;\n}\n\nconst DEFAULT_RANDOM_SUFFIX_LENGTH = 8;\n\nfunction defaultRandomSuffix(): string {\n\t// Avoid pulling `crypto.randomUUID` (Node 14.17+) here so we stay\n\t// compatible with the wider node range this package targets. A short\n\t// alphanumeric suffix is enough — collisions are vanishingly rare and\n\t// the temp file is removed right after rename.\n\tconst alphabet = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n\tlet out = \"\";\n\tfor (let i = 0; i < DEFAULT_RANDOM_SUFFIX_LENGTH; i++) {\n\t\tout += alphabet[Math.floor(Math.random() * alphabet.length)];\n\t}\n\treturn out;\n}\n\nexport class ReposLoader {\n\tprivate readonly configPath: string;\n\tprivate readonly now: () => string;\n\tprivate readonly randomSuffix: () => string;\n\tprivate readonly fileSystem: ReposLoaderFileSystem;\n\n\tconstructor(options: ReposLoaderOptions) {\n\t\tthis.configPath = options.configPath;\n\t\tthis.now = options.now ?? (() => new Date().toISOString());\n\t\tthis.randomSuffix = options.randomSuffix ?? defaultRandomSuffix;\n\t\tthis.fileSystem = options.fileSystem ?? fs;\n\t}\n\n\t/** Absolute path of the file this loader reads/writes. */\n\tgetConfigPath(): string {\n\t\treturn this.configPath;\n\t}\n\n\t/**\n\t * Read + validate the on-disk config. When the file is missing, returns a\n\t * sentinel empty config (no backup is taken — that's expected on first\n\t * run). When the file is corrupted, moves it to `<name>.<ts>.bak` and\n\t * returns an empty config with `recoveredFromBackup` populated.\n\t */\n\tasync load(): Promise<LoadResult> {\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await this.fileSystem.readFile(this.configPath, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tif (isErrnoCode(error, \"ENOENT\")) {\n\t\t\t\treturn {\n\t\t\t\t\tconfig: this.buildEmptyConfig(),\n\t\t\t\t\trecoveredFromBackup: null,\n\t\t\t\t};\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst parsed = safeParseJson(raw);\n\t\tif (parsed === undefined) {\n\t\t\tconst backupPath = await this.quarantine(\"json-parse-failed\");\n\t\t\treturn {\n\t\t\t\tconfig: this.buildEmptyConfig(),\n\t\t\t\trecoveredFromBackup: backupPath,\n\t\t\t};\n\t\t}\n\n\t\tconst result = reposFileSchema.safeParse(parsed);\n\t\tif (!result.success) {\n\t\t\tconst backupPath = await this.quarantine(\"schema-invalid\");\n\t\t\treturn {\n\t\t\t\tconfig: this.buildEmptyConfig(),\n\t\t\t\trecoveredFromBackup: backupPath,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tconfig: result.data as ReposFile,\n\t\t\trecoveredFromBackup: null,\n\t\t};\n\t}\n\n\t/**\n\t * Write the config to disk atomically. Writes to\n\t * `<target>.tmp-<random>` then `rename`s over the target. The parent\n\t * directory is created on demand.\n\t */\n\tasync save(config: ReposFile): Promise<void> {\n\t\tconst validated = reposFileSchema.parse(config) as ReposFile;\n\n\t\tconst dir = path.dirname(this.configPath);\n\t\tawait this.fileSystem.mkdir(dir, { recursive: true });\n\n\t\tconst serialized = `${JSON.stringify(validated, null, 2)}\\n`;\n\t\tconst tempPath = `${this.configPath}.tmp-${this.randomSuffix()}`;\n\n\t\tawait this.fileSystem.writeFile(tempPath, serialized, \"utf-8\");\n\t\ttry {\n\t\t\tawait this.fileSystem.rename(tempPath, this.configPath);\n\t\t} catch (error) {\n\t\t\t// Best-effort cleanup of the orphaned temp file.\n\t\t\tconst unlink = this.fileSystem.unlink ?? fs.unlink;\n\t\t\tawait unlink(tempPath).catch(() => undefined);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Move the existing file to a timestamped `.bak` sibling and return the\n\t * backup path. Used when we detect a corrupted / unparseable file and\n\t * need to make room for a fresh write.\n\t */\n\tprivate async quarantine(reason: string): Promise<string> {\n\t\tconst stamp = sanitizeForFilename(this.now());\n\t\tconst backupPath = `${this.configPath}.${stamp}.${reason}.bak`;\n\t\ttry {\n\t\t\tawait this.fileSystem.rename(this.configPath, backupPath);\n\t\t} catch (error) {\n\t\t\tif (!isErrnoCode(error, \"ENOENT\")) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\treturn backupPath;\n\t}\n\n\t/**\n\t * Empty config used when no file exists or the existing file is corrupt.\n\t * The caller (e.g. `ensureDefaultsInstalled`) is expected to populate the\n\t * real default repo set before persisting.\n\t */\n\tprivate buildEmptyConfig(): ReposFile {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tdefaultRepoId: \"\",\n\t\t\trepos: [],\n\t\t};\n\t}\n}\n\nfunction safeParseJson(raw: string): unknown | undefined {\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isErrnoCode(error: unknown, code: string): boolean {\n\tif (typeof error !== \"object\" || error === null) {\n\t\treturn false;\n\t}\n\treturn (error as { code?: unknown }).code === code;\n}\n\nfunction sanitizeForFilename(iso: string): string {\n\treturn iso.replace(/[^0-9T]/g, \"-\").replace(/-+/g, \"-\");\n}\n\n/** Re-export the Zod schemas so other modules can introspect them. */\nexport {\n\tdefaultRepoSchema as defaultRepoConfigSchema,\n\tuserRepoSchema as userRepoConfigSchema,\n\trepoSchema as anyRepoConfigSchema,\n};\n\n/** Helper: parse + validate a config object (e.g. freshly built in memory). */\nexport function validateReposFile(input: unknown): ReposFile {\n\treturn reposFileSchema.parse(input) as ReposFile;\n}\n\n/** Helper: discriminate a `RepoConfig` between default and user flavours. */\nexport function narrowRepoConfig(repo: AnyRepoConfig): DefaultRepoConfig | UserRepoConfig {\n\treturn repo.source === \"default\" ? (repo as DefaultRepoConfig) : (repo as UserRepoConfig);\n}\n","import { EventEmitter } from \"node:events\";\nimport * as fs from \"node:fs\";\n\nimport { ensureDefaultsInstalled } from \"./default-repos\";\nimport { type LoadResult, ReposLoader, validateReposFile } from \"./loader\";\nimport {\n\ttype AnyRepoConfig,\n\tisDefaultRepo,\n\ttype RepoConfig,\n\ttype ReposFile,\n\ttype UserRepoConfig,\n} from \"./types\";\n\n/**\n * In-memory CRUD store for `repos.json`.\n *\n * Responsibilities:\n * - Hold the authoritative `ReposFile` state in memory.\n * - Re-load on `fs.watch` events so external writes (CLI edit, user edit)\n * propagate to in-process consumers (extension UI, bridge calls).\n * - Emit `change` events so consumers can re-render without polling.\n * - Validate every write through {@link ReposLoader} so a programmatic\n * `addRepo` cannot smuggle in a malformed entry.\n *\n * The store deliberately does **not** own `fs.watch` resources by default.\n * Callers opt in via {@link ReposStore.startWatching} / {@link stopWatching}\n * so tests can run without lingering watchers.\n */\n\n/** Mutating event from the store. */\nexport type ReposStoreChange =\n\t| { kind: \"load\"; config: ReposFile }\n\t| { kind: \"add\"; repo: AnyRepoConfig }\n\t| { kind: \"update\"; repo: AnyRepoConfig }\n\t| { kind: \"remove\"; repoId: string }\n\t| { kind: \"default-change\"; defaultRepoId: string }\n\t| { kind: \"error\"; error: Error };\n\n/** Subscriber callback. */\nexport type ReposStoreListener = (change: ReposStoreChange) => void;\n\n/** Dependencies — defaults to the real fs, tests inject a fake. */\nexport interface ReposStoreFileSystem {\n\twatch: typeof fs.watch;\n}\n\nexport interface ReposStoreOptions {\n\t/** Use an explicit loader (defaults to one wrapping the resolved configPath). */\n\tloader?: ReposLoader;\n\t/** File-system watch implementation — defaults to `fs.watch`. */\n\tfileSystem?: ReposStoreFileSystem;\n\t/** Time provider for `addedAt` stamping. */\n\tnow?: () => string;\n\t/**\n\t * Debounce window for `fs.watch` callbacks. Editors sometimes emit\n\t * multiple `change` events for a single logical write — coalesce them.\n\t * Defaults to 50ms.\n\t */\n\tdebounceMs?: number;\n\t/**\n\t * Watch implementation factory — defaults to a no-deps wrapper around\n\t * `fs.watch`. Tests inject a fake watcher here. The store wires the\n\t * returned handle to its own schedule/error flow via the supplied\n\t * callbacks.\n\t */\n\tcreateFsWatcher?: (configPath: string, callbacks: FsWatcherCallbacks) => FsWatcherHandle;\n}\n\n/**\n * Minimal subset of `fs.FSWatcher` that the store depends on. Defining our\n * own type lets tests inject a fake without dragging in real fs handles.\n */\nexport interface FsWatcherHandle {\n\tclose(): void;\n}\n\n/**\n * Callbacks the store wires to a watcher. The default `fs.watch` factory\n * uses these to forward events into `scheduleReload` / `error` emission;\n * tests can ignore them when they only care about lifecycle.\n */\nexport interface FsWatcherCallbacks {\n\tonChange?: () => void;\n\tonRename?: () => void;\n\tonError?: (err: Error) => void;\n}\n\nexport class ReposStore {\n\tprivate readonly loader: ReposLoader;\n\tprivate readonly fileSystem: ReposStoreFileSystem;\n\tprivate readonly now: () => string;\n\tprivate readonly debounceMs: number;\n\tprivate readonly createFsWatcher: (\n\t\tconfigPath: string,\n\t\tcallbacks: FsWatcherCallbacks\n\t) => FsWatcherHandle;\n\n\tprivate config: ReposFile | null = null;\n\tprivate readonly emitter = new EventEmitter();\n\tprivate watcher: FsWatcherHandle | null = null;\n\tprivate reloadTimer: NodeJS.Timeout | null = null;\n\tprivate lastLoadResult: LoadResult | null = null;\n\n\tconstructor(options: ReposStoreOptions = {}) {\n\t\tthis.loader = options.loader ?? new ReposLoader({ configPath: \"\" });\n\t\tthis.fileSystem = options.fileSystem ?? { watch: fs.watch };\n\t\tthis.now = options.now ?? (() => new Date().toISOString());\n\t\tthis.debounceMs = options.debounceMs ?? 50;\n\t\tthis.createFsWatcher =\n\t\t\toptions.createFsWatcher ?? ((p, cb) => this.defaultCreateFsWatcher(p, cb));\n\t}\n\n\t/** Currently held config, or `null` if `load()` has not been called yet. */\n\tgetConfig(): ReposFile | null {\n\t\treturn this.config;\n\t}\n\n\t/** Path of the underlying `repos.json` file (via the loader). */\n\tgetConfigPath(): string {\n\t\treturn this.loader.getConfigPath();\n\t}\n\n\t/** Subscribe to store mutations. Returns an unsubscribe function. */\n\tsubscribe(listener: ReposStoreListener): () => void {\n\t\tthis.emitter.on(\"change\", listener);\n\t\treturn () => {\n\t\t\tthis.emitter.off(\"change\", listener);\n\t\t};\n\t}\n\n\t/**\n\t * Read the config from disk (delegating to the loader) and seed the\n\t * in-memory state. Safe to call repeatedly.\n\t */\n\tasync load(): Promise<LoadResult> {\n\t\tconst result = await this.loader.load();\n\t\tthis.lastLoadResult = result;\n\t\tthis.config = result.config;\n\t\tthis.emit({ kind: \"load\", config: result.config });\n\t\treturn result;\n\t}\n\n\t/** Convenience getter — wraps `load()` and returns the config. */\n\tasync ensureLoaded(): Promise<ReposFile> {\n\t\tconst result = await this.load();\n\t\treturn result.config;\n\t}\n\n\t/** The most recent `LoadResult`, useful for surfacing recovery warnings. */\n\tgetLastLoadResult(): LoadResult | null {\n\t\treturn this.lastLoadResult;\n\t}\n\n\t/**\n\t * Start watching the config file for external changes. Subsequent writes\n\t * (or edits from another process) trigger a debounced re-load.\n\t *\n\t * No-op when already watching.\n\t */\n\tstartWatching(): void {\n\t\tif (this.watcher) {\n\t\t\treturn;\n\t\t}\n\t\tconst configPath = this.loader.getConfigPath();\n\t\tif (!configPath) {\n\t\t\tthrow new Error(\"Cannot watch: loader has no configPath.\");\n\t\t}\n\t\tthis.watcher = this.createFsWatcher(configPath, {\n\t\t\tonChange: () => this.scheduleReload(),\n\t\t\tonRename: () => this.scheduleReload(),\n\t\t\tonError: (err) => this.emit({ kind: \"error\", error: err }),\n\t\t});\n\t}\n\n\t/** Stop watching and release any pending reload timers. */\n\tstopWatching(): void {\n\t\tif (this.reloadTimer) {\n\t\t\tclearTimeout(this.reloadTimer);\n\t\t\tthis.reloadTimer = null;\n\t\t}\n\t\tif (this.watcher) {\n\t\t\tthis.watcher.close();\n\t\t\tthis.watcher = null;\n\t\t}\n\t}\n\n\t// ───────────────────────────── CRUD operations ─────────────────────────────\n\n\tlist(): AnyRepoConfig[] {\n\t\treturn this.config?.repos.slice() ?? [];\n\t}\n\n\tlistDefault(): AnyRepoConfig[] {\n\t\treturn this.list().filter(isDefaultRepo);\n\t}\n\n\tlistUser(): UserRepoConfig[] {\n\t\treturn this.list().filter((r): r is UserRepoConfig => r.source === \"user\");\n\t}\n\n\tget(repoId: string): AnyRepoConfig | undefined {\n\t\treturn this.config?.repos.find((r) => r.id === repoId);\n\t}\n\n\t/**\n\t * Add a user repo. Throws when the id already exists. The `addedAt`\n\t * timestamp defaults to `now()` when not provided. Persists to disk.\n\t */\n\tasync addUserRepo(input: Omit<UserRepoConfig, \"source\">): Promise<UserRepoConfig> {\n\t\tconst enriched: UserRepoConfig = {\n\t\t\t...input,\n\t\t\tsource: \"user\",\n\t\t\taddedAt: input.addedAt ?? this.now(),\n\t\t};\n\t\tawait this.mutate((current) => {\n\t\t\tif (current.repos.some((r) => r.id === enriched.id)) {\n\t\t\t\tthrow new RepoAlreadyExistsError(enriched.id);\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t...current,\n\t\t\t\trepos: [...current.repos, enriched],\n\t\t\t};\n\t\t});\n\t\tthis.emit({ kind: \"add\", repo: enriched });\n\t\treturn enriched;\n\t}\n\n\t/**\n\t * Update an existing repo by id (partial). Throws when not found.\n\t * Persists to disk.\n\t */\n\tasync updateRepo(repoId: string, patch: Partial<RepoConfig>): Promise<AnyRepoConfig> {\n\t\tconst updated = await this.mutate((current) => {\n\t\t\tconst idx = current.repos.findIndex((r) => r.id === repoId);\n\t\t\tif (idx === -1) {\n\t\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t\t}\n\t\t\tconst previous = current.repos[idx];\n\t\t\tif (!previous) {\n\t\t\t\t// Unreachable: idx === -1 caught above.\n\t\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t\t}\n\t\t\tconst merged: AnyRepoConfig = {\n\t\t\t\t...previous,\n\t\t\t\t...patch,\n\t\t\t\tid: previous.id,\n\t\t\t\tsource: previous.source,\n\t\t\t} as AnyRepoConfig;\n\t\t\tconst nextRepos = current.repos.slice();\n\t\t\tnextRepos[idx] = merged;\n\t\t\treturn { ...current, repos: nextRepos };\n\t\t});\n\t\tconst targetRepo = updated.repos.find((r) => r.id === repoId);\n\t\tif (!targetRepo) {\n\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t}\n\t\tthis.emit({ kind: \"update\", repo: targetRepo });\n\t\treturn targetRepo;\n\t}\n\n\t/**\n\t * Remove a user repo. Throws when the repo is a default or does not exist.\n\t * Persists to disk.\n\t */\n\tasync removeUserRepo(repoId: string): Promise<void> {\n\t\tconst existed = this.get(repoId);\n\t\tif (!existed) {\n\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t}\n\t\tif (existed.source === \"default\") {\n\t\t\tthrow new CannotRemoveDefaultRepoError(repoId);\n\t\t}\n\t\tawait this.mutate((current) => ({\n\t\t\t...current,\n\t\t\trepos: current.repos.filter((r) => r.id !== repoId),\n\t\t}));\n\t\tthis.emit({ kind: \"remove\", repoId });\n\t}\n\n\t/**\n\t * Disable a repo (any source). Useful for the \"Disable\" button on default\n\t * repos. Persists to disk.\n\t */\n\tasync disableRepo(repoId: string): Promise<AnyRepoConfig> {\n\t\treturn this.updateRepo(repoId, { enabled: false });\n\t}\n\n\t/** Inverse of {@link disableRepo}. */\n\tasync enableRepo(repoId: string): Promise<AnyRepoConfig> {\n\t\treturn this.updateRepo(repoId, { enabled: true });\n\t}\n\n\t/** Change which repo the UI surfaces as the default. Persists to disk. */\n\tasync setDefaultRepoId(repoId: string): Promise<void> {\n\t\tconst target = this.get(repoId);\n\t\tif (!target) {\n\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t}\n\t\tawait this.mutate((current) => ({\n\t\t\t...current,\n\t\t\tdefaultRepoId: repoId,\n\t\t}));\n\t\tthis.emit({ kind: \"default-change\", defaultRepoId: repoId });\n\t}\n\n\tgetDefaultRepoId(): string | null {\n\t\treturn this.config?.defaultRepoId ?? null;\n\t}\n\n\t/** Returns the configured `now` provider — exposed for `bootstrapDefaults`. */\n\tgetNow(): () => string {\n\t\treturn this.now;\n\t}\n\n\t/**\n\t * Replace the in-memory config and persist it. Used by helpers like\n\t * `bootstrapDefaults` that build a new config wholesale (rather than\n\t * mutating a single field).\n\t */\n\tasync replaceConfig(next: ReposFile): Promise<ReposFile> {\n\t\tconst validated = validateReposFile(next);\n\t\tawait this.loader.save(validated);\n\t\tthis.config = validated;\n\t\treturn validated;\n\t}\n\n\t// ────────────────────────── Internal helpers ──────────────────────────\n\n\tprivate async mutate(updater: (current: ReposFile) => ReposFile): Promise<ReposFile> {\n\t\tconst current = this.config;\n\t\tif (!current) {\n\t\t\tthrow new Error(\"ReposStore.mutate called before load().\");\n\t\t}\n\t\tconst next = updater(current);\n\t\tconst validated = validateReposFile(next);\n\t\tawait this.loader.save(validated);\n\t\tthis.config = validated;\n\t\treturn validated;\n\t}\n\n\tprivate emit(change: ReposStoreChange): void {\n\t\tthis.emitter.emit(\"change\", change);\n\t}\n\n\t/**\n\t * Default `fs.watch` adapter. Public so tests can wrap a real watcher\n\t * with a debounce trampoline if they need to.\n\t */\n\tprivate defaultCreateFsWatcher(\n\t\tconfigPath: string,\n\t\tcallbacks: FsWatcherCallbacks\n\t): FsWatcherHandle {\n\t\tconst handle = this.fileSystem.watch(configPath, { persistent: false });\n\t\tif (callbacks.onChange) {\n\t\t\thandle.on(\"change\", () => callbacks.onChange?.());\n\t\t}\n\t\tif (callbacks.onRename) {\n\t\t\thandle.on(\"rename\", () => callbacks.onRename?.());\n\t\t}\n\t\tif (callbacks.onError) {\n\t\t\thandle.on(\"error\", (err: Error) => callbacks.onError?.(err));\n\t\t}\n\t\treturn {\n\t\t\tclose: () => handle.close(),\n\t\t};\n\t}\n\n\t// Override createFsWatcher with the debounced default if no factory was supplied.\n\tprivate scheduleReload(): void {\n\t\tif (this.reloadTimer) {\n\t\t\tclearTimeout(this.reloadTimer);\n\t\t}\n\t\tthis.reloadTimer = setTimeout(() => {\n\t\t\tthis.reloadTimer = null;\n\t\t\tthis.load().catch((err: unknown) => {\n\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\t\t\t\tthis.emit({ kind: \"error\", error });\n\t\t\t});\n\t\t}, this.debounceMs);\n\t}\n}\n\n/** Factory: creates a `ReposStore` bound to a specific config path. */\nexport function createReposStore(\n\tconfigPath: string,\n\toptions: Omit<ReposStoreOptions, \"loader\"> = {}\n): ReposStore {\n\treturn new ReposStore({\n\t\tloader: new ReposLoader({ configPath }),\n\t\t...options,\n\t});\n}\n\n/** Error: tried to add a repo whose id already exists. */\nexport class RepoAlreadyExistsError extends Error {\n\tconstructor(public readonly repoId: string) {\n\t\tsuper(`Repo already exists: ${repoId}`);\n\t\tthis.name = \"RepoAlreadyExistsError\";\n\t}\n}\n\n/** Error: looked up a repo id that isn't present in the current config. */\nexport class RepoNotFoundError extends Error {\n\tconstructor(public readonly repoId: string) {\n\t\tsuper(`Repo not found: ${repoId}`);\n\t\tthis.name = \"RepoNotFoundError\";\n\t}\n}\n\n/** Error: tried to remove a default repo (defaults are disable-only). */\nexport class CannotRemoveDefaultRepoError extends Error {\n\tconstructor(public readonly repoId: string) {\n\t\tsuper(`Cannot remove default repo (use disableRepo instead): ${repoId}`);\n\t\tthis.name = \"CannotRemoveDefaultRepoError\";\n\t}\n}\n\n/**\n * Bootstrap helper — invokes `ensureDefaultsInstalled` on the store to\n * guarantee that the 4 default repos are present. Returns the resulting\n * config. Lives here (not in `default-repos.ts`) to keep the default-repo\n * module free of store concerns.\n */\nexport async function bootstrapDefaults(store: ReposStore): Promise<ReposFile> {\n\tconst config = store.getConfig() ?? (await store.ensureLoaded());\n\tconst result = ensureDefaultsInstalled(config, store.getNow());\n\tif (result.installed.length === 0) {\n\t\treturn config;\n\t}\n\tawait store.replaceConfig(result.config);\n\treturn store.getConfig() ?? result.config;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst CONFIG_DIR = \".serviceme\";\nconst LOG_FILE = \"scheduler.log\";\nconst MAX_LOG_SIZE = 1024 * 1024; // 1MB\n\nexport class DaemonLogger {\n\tprivate readonly logPath: string;\n\n\tconstructor(workspacePath: string, options: { logPath?: string } = {}) {\n\t\tthis.logPath = options.logPath ?? path.join(workspacePath, CONFIG_DIR, LOG_FILE);\n\t\tconst dir = path.dirname(this.logPath);\n\t\tif (!fs.existsSync(dir)) {\n\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t}\n\t}\n\n\tgetLogPath(): string {\n\t\treturn this.logPath;\n\t}\n\n\tlog(level: \"info\" | \"warn\" | \"error\", message: string): void {\n\t\tconst ts = new Date().toISOString();\n\t\tconst line = `[${ts}] [${level.toUpperCase()}] ${message}\\n`;\n\t\tthis.rotateIfNeeded();\n\t\tfs.appendFileSync(this.logPath, line, \"utf-8\");\n\t}\n\n\tprivate rotateIfNeeded(): void {\n\t\ttry {\n\t\t\tconst stats = fs.statSync(this.logPath);\n\t\t\tif (stats.size > MAX_LOG_SIZE) {\n\t\t\t\t// Keep last half of the file\n\t\t\t\tconst content = fs.readFileSync(this.logPath, \"utf-8\");\n\t\t\t\tconst halfIdx = content.indexOf(\"\\n\", Math.floor(content.length / 2));\n\t\t\t\tif (halfIdx > 0) {\n\t\t\t\t\tfs.writeFileSync(this.logPath, content.slice(halfIdx + 1), \"utf-8\");\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// File doesn't exist yet — fine\n\t\t}\n\t}\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst CONFIG_DIR = \".serviceme\";\nconst PID_FILE = \"scheduler.pid\";\n\nexport interface PidManagerOptions {\n\t/**\n\t * Override the on-disk PID path. Defaults to\n\t * `<workspacePath>/.serviceme/scheduler.pid` for backward compat.\n\t * V2 callers pass an absolute path (e.g. `getSchedulerPidPath()`)\n\t * to read the global daemon PID file.\n\t */\n\tpidPath?: string;\n}\n\nexport class PidManager {\n\tprivate readonly pidPath: string;\n\n\tconstructor(workspacePath: string, options: PidManagerOptions = {}) {\n\t\tthis.pidPath = options.pidPath ?? path.join(workspacePath, CONFIG_DIR, PID_FILE);\n\t}\n\n\tgetPidPath(): string {\n\t\treturn this.pidPath;\n\t}\n\n\twritePid(pid: number): void {\n\t\tconst dir = path.dirname(this.pidPath);\n\t\tif (!fs.existsSync(dir)) {\n\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t}\n\t\tfs.writeFileSync(this.pidPath, String(pid), \"utf-8\");\n\t}\n\n\treadPid(): number | null {\n\t\t// statSync (vs existsSync + readFileSync) so that a path that\n\t\t// points at a directory — or any non-file — is treated as\n\t\t// \"no pid\" instead of crashing with EISDIR. Activation paths\n\t\t// hit this code path on every reload; one stale directory\n\t\t// shell from a half-removed daemon would otherwise throw and\n\t\t// take down extension activation. See ms-devtools-vscode\n\t\t// agent memory \"PidManager EISDIR regression\".\n\t\tlet stat: fs.Stats;\n\t\ttry {\n\t\t\tstat = fs.statSync(this.pidPath);\n\t\t} catch {\n\t\t\t// ENOENT / EACCES / EPERM / etc. — all treated as no pid.\n\t\t\treturn null;\n\t\t}\n\t\tif (!stat.isFile()) return null;\n\t\tconst raw = fs.readFileSync(this.pidPath, \"utf-8\").trim();\n\t\tconst pid = Number.parseInt(raw, 10);\n\t\treturn Number.isNaN(pid) ? null : pid;\n\t}\n\n\tremovePid(): void {\n\t\tif (fs.existsSync(this.pidPath)) {\n\t\t\tfs.unlinkSync(this.pidPath);\n\t\t}\n\t}\n\n\tisProcessRunning(pid: number): boolean {\n\t\ttry {\n\t\t\tprocess.kill(pid, 0);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tgetRunningPid(): number | null {\n\t\tconst pid = this.readPid();\n\t\tif (pid === null) return null;\n\t\tif (this.isProcessRunning(pid)) return pid;\n\t\t// Stale PID file — remove\n\t\tthis.removePid();\n\t\treturn null;\n\t}\n}\n","import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ScheduledTask } from \"@serviceme/devtools-protocol\";\nimport { getExecutor } from \"../executors\";\nimport { TaskConfigManager, validateTaskPayload } from \"../TaskConfigManager\";\nimport { resolveTaskExecutionPayload } from \"../TaskExecutionEngine\";\nimport { TaskLogManager } from \"../TaskLogManager\";\nimport { DaemonLogger } from \"./DaemonLogger\";\nimport { PidManager } from \"./PidManager\";\n\nconst TICK_INTERVAL = 1000; // Check every second\nconst MIN_SCHEDULE_INTERVAL = 1_000; // 1 second minimum\n\nexport interface SchedulerDaemonOptions {\n\t/**\n\t * Override the executor factory. Production callers should leave this\n\t * undefined and rely on the default `getExecutor` from `../executors`;\n\t * tests inject a fake to drive the execution branch without spawning\n\t * real subprocesses.\n\t */\n\tgetExecutor?: typeof getExecutor;\n}\n\n/**\n * @deprecated v1 per-workspace daemon. Superseded by `SchedulerDaemonV2` in\n * `daemon/SchedulerDaemonV2.ts` for the global single-instance model. Kept\n * temporarily so callers can roll back to v1 behaviour during the M1.5\n * transition. New code should use SchedulerDaemonV2.\n */\nexport class SchedulerDaemon {\n\tprivate readonly workspacePath: string;\n\tprivate readonly configManager: TaskConfigManager;\n\tprivate readonly logManager: TaskLogManager;\n\tprivate readonly pidManager: PidManager;\n\tprivate readonly logger: DaemonLogger;\n\tprivate readonly getExecutor: typeof getExecutor;\n\n\tprivate tickTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate watcher: fs.FSWatcher | null = null;\n\tprivate running = false;\n\tprivate startTime: number = 0;\n\n\t// Track last execution time and running state per task\n\tprivate lastRun: Map<string, number> = new Map();\n\tprivate taskRunning: Set<string> = new Set();\n\n\tconstructor(workspacePath: string, options: SchedulerDaemonOptions = {}) {\n\t\tthis.workspacePath = workspacePath;\n\t\t// Per-workspace overrides keep v1 semantics for now. M1.5.6 will\n\t\t// switch the daemon to the global config + per-task workspace cwd model.\n\t\tconst configDir = path.join(workspacePath, \".serviceme\");\n\t\tthis.configManager = new TaskConfigManager({\n\t\t\tconfigPath: path.join(configDir, \"scheduled-tasks.json\"),\n\t\t});\n\t\tthis.logManager = new TaskLogManager({\n\t\t\tlogPath: path.join(configDir, \"scheduled-tasks-log.json\"),\n\t\t});\n\t\tthis.pidManager = new PidManager(workspacePath);\n\t\tthis.logger = new DaemonLogger(workspacePath);\n\t\tthis.getExecutor = options.getExecutor ?? getExecutor;\n\t}\n\n\tstart(): void {\n\t\tif (this.running) return;\n\t\tthis.running = true;\n\t\tthis.startTime = Date.now();\n\n\t\tthis.pidManager.writePid(process.pid);\n\t\tconst configPath = this.configManager.getConfigPath();\n\t\tconst logPath = this.logger.getLogPath();\n\t\tthis.logger.log(\"info\", \"=\".repeat(60));\n\t\tthis.logger.log(\n\t\t\t\"info\",\n\t\t\t`Scheduler daemon started\n PID: ${process.pid}\n Node: ${process.version}\n OS: ${os.type()} ${os.release()} (${process.arch})\n Platform: ${process.platform}\n Hostname: ${os.hostname()}\n User: ${os.userInfo().username}\n Workspace: ${this.workspacePath}\n Config: ${configPath}\n LogPath: ${logPath}\n ExecPath: ${process.execPath}`\n\t\t);\n\n\t\t// Start tick loop\n\t\tthis.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);\n\n\t\t// Watch config for changes\n\t\tthis.setupConfigWatch();\n\n\t\t// Handle signals\n\t\tprocess.on(\"SIGTERM\", () => this.stop());\n\t\tprocess.on(\"SIGINT\", () => this.stop());\n\t}\n\n\tstop(): void {\n\t\tif (!this.running) return;\n\t\tthis.running = false;\n\n\t\tif (this.tickTimer) {\n\t\t\tclearInterval(this.tickTimer);\n\t\t\tthis.tickTimer = null;\n\t\t}\n\t\tif (this.watcher) {\n\t\t\tthis.watcher.close();\n\t\t\tthis.watcher = null;\n\t\t}\n\n\t\tthis.pidManager.removePid();\n\t\tthis.logger.log(\"info\", \"Scheduler daemon stopped\");\n\t\tprocess.exit(0);\n\t}\n\n\tprivate setupConfigWatch(): void {\n\t\tconst configPath = this.configManager.getConfigPath();\n\t\tconst dir = configPath.substring(0, configPath.lastIndexOf(\"/\"));\n\t\ttry {\n\t\t\tif (fs.existsSync(dir)) {\n\t\t\t\tthis.watcher = fs.watch(dir, (_eventType, filename) => {\n\t\t\t\t\tif (filename === \"scheduled-tasks.json\") {\n\t\t\t\t\t\tthis.logger.log(\"info\", \"Config file changed, reconciling...\");\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t} catch {\n\t\t\tthis.logger.log(\"warn\", \"Could not watch config directory\");\n\t\t}\n\t}\n\n\tprivate tick(): void {\n\t\tconst config = this.configManager.readConfig();\n\t\tconst now = Date.now();\n\n\t\tfor (const task of config.tasks) {\n\t\t\tif (!task.enabled) continue;\n\t\t\tif (this.taskRunning.has(task.id)) continue; // Concurrency guard\n\n\t\t\t// First encounter: mark \"last run\" as now so the task waits a full interval\n\t\t\tlet lastExec = this.lastRun.get(task.id);\n\t\t\tif (lastExec === undefined) {\n\t\t\t\tthis.lastRun.set(task.id, now);\n\t\t\t\tlastExec = now;\n\t\t\t}\n\t\t\tif (this.shouldRun(task, lastExec, now)) {\n\t\t\t\tthis.executeTask(task, now);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate shouldRun(task: ScheduledTask, lastExec: number, now: number): boolean {\n\t\tif (task.scheduleType === \"interval\") {\n\t\t\tconst intervalMs = parseIntervalMs(task.schedule);\n\t\t\tif (intervalMs < MIN_SCHEDULE_INTERVAL) return false;\n\t\t\treturn now - lastExec >= intervalMs;\n\t\t}\n\n\t\tif (task.scheduleType === \"cron\") {\n\t\t\t// Simple cron check: use interval of 60s minimum (cron granularity is minutes)\n\t\t\tif (now - lastExec < 60_000) return false;\n\t\t\treturn matchesCron(task.schedule, new Date(now));\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate async executeTask(task: ScheduledTask, now: number): Promise<void> {\n\t\tthis.taskRunning.add(task.id);\n\t\tthis.lastRun.set(task.id, now);\n\n\t\tconst startedAt = new Date(now).toISOString();\n\t\tconst startMs = Date.now();\n\t\tthis.logger.log(\n\t\t\t\"info\",\n\t\t\t`Executing task: ${task.name} (${task.id})\n type: ${task.taskType}\n schedule: ${task.schedule} (${task.scheduleType})\n enabled: ${task.enabled}`\n\t\t);\n\n\t\ttry {\n\t\t\tconst executionPayload = resolveTaskExecutionPayload(\n\t\t\t\ttask.taskType,\n\t\t\t\ttask.payload,\n\t\t\t\tthis.workspacePath\n\t\t\t);\n\t\t\tvalidateTaskPayload(task.taskType, executionPayload);\n\t\t\tconst executor = this.getExecutor(task.taskType);\n\t\t\tconst result = await executor.execute(executionPayload);\n\t\t\tconst finishedAt = new Date().toISOString();\n\t\t\tconst durationMs = Date.now() - startMs;\n\n\t\t\tthis.logManager.appendLog({\n\t\t\t\ttaskId: task.id,\n\t\t\t\ttaskName: task.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt,\n\t\t\t\tstatus: result.status === \"running\" ? \"failure\" : result.status,\n\t\t\t\toutput: result.output,\n\t\t\t\terror: result.error,\n\t\t\t});\n\n\t\t\tconst outputBytes = result.output ? Buffer.byteLength(result.output) : 0;\n\t\t\tthis.logger.log(\n\t\t\t\t\"info\",\n\t\t\t\t`Task completed: ${task.name} (${task.id})\n status: ${result.status}\n duration: ${durationMs}ms\n outputBytes: ${outputBytes}`\n\t\t\t);\n\t\t} catch (err: unknown) {\n\t\t\tconst finishedAt = new Date().toISOString();\n\t\t\tconst durationMs = Date.now() - startMs;\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tthis.logManager.appendLog({\n\t\t\t\ttaskId: task.id,\n\t\t\t\ttaskName: task.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt,\n\t\t\t\tstatus: \"failure\",\n\t\t\t\terror: message,\n\t\t\t});\n\t\t\tthis.logger.log(\n\t\t\t\t\"error\",\n\t\t\t\t`Task failed: ${task.name} (${task.id})\n duration: ${durationMs}ms\n error: ${message}`\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.taskRunning.delete(task.id);\n\t\t}\n\t}\n\n\tgetStatus() {\n\t\tconst config = this.configManager.readConfig();\n\t\treturn {\n\t\t\trunning: this.running,\n\t\t\tpid: process.pid,\n\t\t\tuptimeSeconds: this.running ? Math.floor((Date.now() - this.startTime) / 1000) : null,\n\t\t\ttasksRegistered: config.tasks.length,\n\t\t\ttasksEnabled: config.tasks.filter((t) => t.enabled).length,\n\t\t\tworkspacePath: this.workspacePath,\n\t\t\tpidFile: this.pidManager.getPidPath(),\n\t\t};\n\t}\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction parseIntervalMs(schedule: string): number {\n\tconst match = /^every\\s+(\\d+)\\s*(s|sec|m|min|h|hr|d|day)s?$/i.exec(schedule);\n\tif (!match) return 0;\n\tconst [, numStr, unit] = match;\n\tconst num = Number.parseInt(numStr ?? \"0\", 10);\n\tswitch (unit?.toLowerCase()) {\n\t\tcase \"s\":\n\t\tcase \"sec\":\n\t\t\treturn num * 1000;\n\t\tcase \"m\":\n\t\tcase \"min\":\n\t\t\treturn num * 60 * 1000;\n\t\tcase \"h\":\n\t\tcase \"hr\":\n\t\t\treturn num * 60 * 60 * 1000;\n\t\tcase \"d\":\n\t\tcase \"day\":\n\t\t\treturn num * 24 * 60 * 60 * 1000;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nfunction matchesCron(expression: string, date: Date): boolean {\n\t// Simplified cron matching: minute hour day month weekday\n\tconst parts = expression.trim().split(/\\s+/);\n\tif (parts.length < 5) return false;\n\tconst [minPart, hourPart, dayPart, monthPart, weekdayPart] = parts;\n\tif (!minPart || !hourPart || !dayPart || !monthPart || !weekdayPart) return false;\n\n\treturn (\n\t\tmatchCronField(minPart, date.getMinutes()) &&\n\t\tmatchCronField(hourPart, date.getHours()) &&\n\t\tmatchCronField(dayPart, date.getDate()) &&\n\t\tmatchCronField(monthPart, date.getMonth() + 1) &&\n\t\tmatchCronField(weekdayPart, date.getDay())\n\t);\n}\n\nfunction matchCronField(field: string, value: number): boolean {\n\tif (field === \"*\") return true;\n\n\t// Handle */n (step)\n\tif (field.startsWith(\"*/\")) {\n\t\tconst step = Number.parseInt(field.slice(2), 10);\n\t\treturn step > 0 && value % step === 0;\n\t}\n\n\t// Handle comma-separated values\n\tconst values = field.split(\",\");\n\treturn values.some((v) => Number.parseInt(v, 10) === value);\n}\n\n// Export helpers for testing\nexport { parseIntervalMs, matchesCron };\n","import { spawn } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport type { GithubCopilotCliPayload } from \"@serviceme/devtools-protocol\";\nimport { isCopilotAuthenticated } from \"../../copilot/doctor\";\nimport { resolveConfiguredTimeoutMs } from \"./timeout\";\nimport type {\n\tExecutorResult,\n\tOutputEventCallback,\n\tStreamingExecutorHandle,\n\tStreamingTaskExecutor,\n} from \"./types\";\n\nconst MAX_OUTPUT_BYTES = 2 * 1024 * 1024; // 2 MB\nconst DEFAULT_TIMEOUT_MS = 300_000;\n\nexport interface ResolvedGithubCopilotCliExecution {\n\tcommand: \"serviceme\";\n\targs: string[];\n\tcwd?: string;\n\ttimeoutMs: number | undefined;\n\tdiagnosticArgs: string[];\n\tpromptLen: number;\n}\n\nfunction redactArgs(args: string[]): string[] {\n\tconst redacted: string[] = [];\n\tlet redactNext = false;\n\tfor (const arg of args) {\n\t\tif (redactNext) {\n\t\t\tredacted.push(\"<redacted>\");\n\t\t\tredactNext = false;\n\t\t\tcontinue;\n\t\t}\n\t\tredacted.push(arg);\n\t\tif (arg === \"--prompt\") {\n\t\t\tredactNext = true;\n\t\t}\n\t}\n\treturn redacted;\n}\n\nfunction writeDiagnostic(message: string): void {\n\tconst logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;\n\tif (logPath) {\n\t\tfs.appendFileSync(logPath, message);\n\t\treturn;\n\t}\n\tprocess.stderr.write(message);\n}\n\nexport function resolveGithubCopilotCliExecution(\n\tpayload: GithubCopilotCliPayload\n): ResolvedGithubCopilotCliExecution {\n\tconst args: string[] = [\"copilot\", \"prompt\", \"--prompt\", payload.prompt];\n\tif (payload.autopilot) {\n\t\targs.push(\"--autopilot\");\n\t}\n\tif (payload.allowTools && payload.allowTools.length > 0) {\n\t\targs.push(\"--allow-tools\", payload.allowTools.join(\",\"));\n\t}\n\tif (payload.model) {\n\t\targs.push(\"--model\", payload.model);\n\t}\n\tif (payload.agent) {\n\t\targs.push(\"--agent\", payload.agent);\n\t}\n\targs.push(\"--timeout\", String(payload.timeout ?? 0));\n\n\treturn {\n\t\tcommand: \"serviceme\",\n\t\targs,\n\t\tcwd: payload.workspace,\n\t\ttimeoutMs: resolveConfiguredTimeoutMs(payload.timeout, DEFAULT_TIMEOUT_MS),\n\t\tdiagnosticArgs: redactArgs(args),\n\t\tpromptLen: payload.prompt.length,\n\t};\n}\n\nexport class GithubCopilotCliExecutor implements StreamingTaskExecutor {\n\tasync execute(payload: unknown, abortSignal?: AbortSignal): Promise<ExecutorResult> {\n\t\t// Pre-flight auth check\n\t\tconst authenticated = await isCopilotAuthenticated();\n\t\tif (!authenticated) {\n\t\t\treturn {\n\t\t\t\tstatus: \"failure\",\n\t\t\t\terror:\n\t\t\t\t\t\"GitHub Copilot CLI authentication failed. Please run `gh auth login` to re-authenticate.\",\n\t\t\t};\n\t\t}\n\n\t\tlet output = \"\";\n\t\tconst handle = this.executeStreaming(\n\t\t\tpayload,\n\t\t\t(_stream, data) => {\n\t\t\t\toutput += data;\n\t\t\t},\n\t\t\tabortSignal\n\t\t);\n\t\tconst result = await handle.result;\n\t\treturn { ...result, output: output || result.output };\n\t}\n\n\texecuteStreaming(\n\t\tpayload: unknown,\n\t\tonOutput: OutputEventCallback,\n\t\tabortSignal?: AbortSignal\n\t): StreamingExecutorHandle {\n\t\tconst p = payload as GithubCopilotCliPayload;\n\t\tconst execution = resolveGithubCopilotCliExecution(p);\n\n\t\tlet resolve: (value: ExecutorResult) => void;\n\t\tconst resultPromise = new Promise<ExecutorResult>((r) => {\n\t\t\tresolve = r;\n\t\t});\n\t\tlet settled = false;\n\n\t\tconst settle = (result: ExecutorResult) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tresolve?.(result);\n\t\t};\n\n\t\tif (abortSignal?.aborted) {\n\t\t\treturn {\n\t\t\t\tresult: Promise.resolve({\n\t\t\t\t\tstatus: \"cancelled\" as const,\n\t\t\t\t\terror: \"Execution aborted\",\n\t\t\t\t}),\n\t\t\t\tcancel: () => {},\n\t\t\t};\n\t\t}\n\n\t\twriteDiagnostic(\n\t\t\t`[GithubCopilotCliExecutor] spawn: command=${execution.command}, args=${JSON.stringify(execution.diagnosticArgs)}, promptLen=${execution.promptLen}, cwd=${execution.cwd ?? \"(default)\"}, platform=${process.platform}, windowsHide=true, pid=${process.pid}\\n`\n\t\t);\n\n\t\tconst child = spawn(execution.command, execution.args, {\n\t\t\tcwd: execution.cwd,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t});\n\n\t\tif (child.pid) {\n\t\t\twriteDiagnostic(`[GithubCopilotCliExecutor] spawned child PID=${child.pid}\\n`);\n\t\t}\n\n\t\tconst timeoutMs = execution.timeoutMs;\n\t\tconst timer =\n\t\t\ttimeoutMs != null\n\t\t\t\t? setTimeout(() => {\n\t\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tif (!child.killed) child.kill(\"SIGKILL\");\n\t\t\t\t\t\t}, 5000);\n\t\t\t\t\t\tsettle({\n\t\t\t\t\t\t\tstatus: \"timeout\",\n\t\t\t\t\t\t\terror: `Copilot CLI execution timed out after ${timeoutMs / 1000}s`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}, timeoutMs)\n\t\t\t\t: undefined;\n\n\t\tlet stdoutBuf = \"\";\n\t\tlet stderrBuf = \"\";\n\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\tconst data = chunk.toString();\n\t\t\tstdoutBuf += data;\n\t\t\tif (stdoutBuf.length > MAX_OUTPUT_BYTES) stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);\n\t\t\tonOutput(\"stdout\", data);\n\t\t});\n\n\t\tchild.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\tconst data = chunk.toString();\n\t\t\tstderrBuf += data;\n\t\t\tif (stderrBuf.length > MAX_OUTPUT_BYTES) stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);\n\t\t\tonOutput(\"stderr\", data);\n\t\t});\n\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tsettle({ status: \"success\", output: stdoutBuf || undefined });\n\t\t\t} else {\n\t\t\t\tsettle({\n\t\t\t\t\tstatus: \"failure\",\n\t\t\t\t\toutput: stdoutBuf || undefined,\n\t\t\t\t\terror: stderrBuf || `Process exited with code ${code}`,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tchild.on(\"error\", (err) => {\n\t\t\tsettle({ status: \"failure\", error: err.message });\n\t\t});\n\n\t\tconst cancelFn = () => {\n\t\t\tchild.kill(\"SIGTERM\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (!child.killed) child.kill(\"SIGKILL\");\n\t\t\t}, 5000);\n\t\t\tsettle({ status: \"cancelled\", error: \"Execution cancelled\" });\n\t\t};\n\n\t\tabortSignal?.addEventListener(\"abort\", () => cancelFn(), { once: true });\n\n\t\treturn { result: resultPromise, cancel: cancelFn };\n\t}\n}\n","export function resolveConfiguredTimeoutMs(\n\ttimeoutSeconds: number | undefined,\n\tdefaultTimeoutMs: number\n): number | undefined {\n\tif (timeoutSeconds === 0) {\n\t\treturn undefined;\n\t}\n\tif (\n\t\ttypeof timeoutSeconds !== \"number\" ||\n\t\t!Number.isFinite(timeoutSeconds) ||\n\t\ttimeoutSeconds < 0\n\t) {\n\t\treturn defaultTimeoutMs;\n\t}\n\treturn timeoutSeconds * 1000;\n}\n","import type { HttpRequestPayload } from \"@serviceme/devtools-protocol\";\nimport { resolveConfiguredTimeoutMs } from \"./timeout\";\nimport type { ExecutorResult, TaskExecutor } from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport class HttpRequestExecutor implements TaskExecutor {\n\tasync execute(payload: unknown, abortSignal?: AbortSignal): Promise<ExecutorResult> {\n\t\tconst p = payload as HttpRequestPayload;\n\t\tconst timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS);\n\n\t\tconst ac = new AbortController();\n\t\tlet timedOut = false;\n\t\tconst timer =\n\t\t\ttimeoutMs != null\n\t\t\t\t? setTimeout(() => {\n\t\t\t\t\t\ttimedOut = true;\n\t\t\t\t\t\tac.abort();\n\t\t\t\t\t}, timeoutMs)\n\t\t\t\t: undefined;\n\n\t\tif (abortSignal?.aborted) {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\treturn { status: \"cancelled\", error: \"Execution aborted\" };\n\t\t}\n\n\t\tlet externalAbort = false;\n\t\tabortSignal?.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\texternalAbort = true;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tac.abort();\n\t\t\t},\n\t\t\t{ once: true }\n\t\t);\n\n\t\ttry {\n\t\t\tconst response = await fetch(p.url, {\n\t\t\t\tmethod: p.method,\n\t\t\t\theaders: p.headers,\n\t\t\t\tbody: p.body,\n\t\t\t\tsignal: ac.signal,\n\t\t\t});\n\t\t\tif (timer) clearTimeout(timer);\n\n\t\t\tconst body = await response.text();\n\t\t\tif (response.ok) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"success\",\n\t\t\t\t\toutput: `${response.status} ${response.statusText}\\n${body}`.trim(),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstatus: \"failure\",\n\t\t\t\terror: `HTTP ${response.status} ${response.statusText}\\n${body}`.trim(),\n\t\t\t};\n\t\t} catch (err: unknown) {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tif (err instanceof Error && err.name === \"AbortError\") {\n\t\t\t\tif (externalAbort) {\n\t\t\t\t\treturn { status: \"cancelled\", error: \"Execution cancelled\" };\n\t\t\t\t}\n\t\t\t\tif (!timedOut || timeoutMs == null) {\n\t\t\t\t\treturn { status: \"failure\", error: err.message };\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"timeout\",\n\t\t\t\t\terror: `HTTP request timed out after ${timeoutMs / 1000}s`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\treturn { status: \"failure\", error: message };\n\t\t}\n\t}\n}\n","import { type SpawnOptions, spawn } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { ShellPayload } from \"@serviceme/devtools-protocol\";\nimport { resolveConfiguredTimeoutMs } from \"./timeout\";\nimport type {\n\tExecutorResult,\n\tOutputEventCallback,\n\tStreamingExecutorHandle,\n\tStreamingTaskExecutor,\n} from \"./types\";\n\nconst MAX_OUTPUT_BYTES = 1024 * 1024; // 1 MB\nconst DEFAULT_TIMEOUT_MS = 60_000;\nconst POSIX_SHELL_CANDIDATES = [\"bash.exe\", \"sh.exe\"];\n\nexport interface ShellExecutionResolutionOptions {\n\tplatform?: NodeJS.Platform;\n\tenv?: NodeJS.ProcessEnv;\n\tfileExists?: (candidate: string) => boolean;\n}\n\nexport interface ResolvedShellExecution {\n\tcommand: string;\n\targs: string[];\n\tstdinScript?: string;\n\toutputEncoding: BufferEncoding;\n\tshellKind: \"cmd\" | \"posix\";\n}\n\nexport function resolveShellExecution(\n\tscript: string,\n\toptions: ShellExecutionResolutionOptions = {}\n): ResolvedShellExecution {\n\tconst platform = options.platform ?? process.platform;\n\tconst env = options.env ?? process.env;\n\tconst fileExists = options.fileExists ?? fs.existsSync;\n\n\tif (platform === \"win32\") {\n\t\tconst posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;\n\t\tif (posixShell) {\n\t\t\treturn {\n\t\t\t\tcommand: posixShell,\n\t\t\t\targs: [\"-s\"],\n\t\t\t\tstdinScript: script,\n\t\t\t\toutputEncoding: \"utf8\",\n\t\t\t\tshellKind: \"posix\",\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tcommand: env.ComSpec ?? env.COMSPEC ?? \"cmd.exe\",\n\t\t\targs: [\"/d\", \"/s\", \"/c\", script],\n\t\t\toutputEncoding: \"utf8\",\n\t\t\tshellKind: \"cmd\",\n\t\t};\n\t}\n\n\treturn {\n\t\tcommand: \"sh\",\n\t\targs: [\"-s\"],\n\t\tstdinScript: script,\n\t\toutputEncoding: \"utf8\",\n\t\tshellKind: \"posix\",\n\t};\n}\n\nfunction usesPosixShellSyntax(script: string): boolean {\n\treturn /\\$\\([^)]*\\)|`[^`]*`/.test(script);\n}\n\nfunction findWindowsPosixShell(\n\tenv: NodeJS.ProcessEnv,\n\tfileExists: (candidate: string) => boolean\n): string | null {\n\tconst explicitShell = env.SERVICEME_POSIX_SHELL;\n\tif (explicitShell && fileExists(explicitShell)) {\n\t\treturn explicitShell;\n\t}\n\n\tconst knownGitBashPaths = [\n\t\t\"C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe\",\n\t\t\"C:\\\\Program Files\\\\Git\\\\usr\\\\bin\\\\bash.exe\",\n\t\t\"C:\\\\Program Files (x86)\\\\Git\\\\bin\\\\bash.exe\",\n\t\t\"C:\\\\Program Files (x86)\\\\Git\\\\usr\\\\bin\\\\bash.exe\",\n\t];\n\tfor (const candidate of knownGitBashPaths) {\n\t\tif (fileExists(candidate)) return candidate;\n\t}\n\n\tconst pathValue = env.Path ?? env.PATH ?? \"\";\n\tfor (const dir of pathValue.split(path.win32.delimiter)) {\n\t\tif (!dir) continue;\n\t\tfor (const executable of POSIX_SHELL_CANDIDATES) {\n\t\t\tconst candidate = path.win32.join(dir, executable);\n\t\t\tif (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction isWindowsWslLauncher(candidate: string): boolean {\n\tconst normalized = path.win32.normalize(candidate).toLowerCase();\n\treturn (\n\t\tnormalized.endsWith(\"\\\\windows\\\\system32\\\\bash.exe\") ||\n\t\tnormalized.endsWith(\"\\\\windows\\\\syswow64\\\\bash.exe\")\n\t);\n}\n\nfunction writeDiagnostic(message: string): void {\n\tconst logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;\n\tif (logPath) {\n\t\tfs.appendFileSync(logPath, message);\n\t\treturn;\n\t}\n\tprocess.stderr.write(message);\n}\n\nexport class ShellExecutor implements StreamingTaskExecutor {\n\tasync execute(payload: unknown, abortSignal?: AbortSignal): Promise<ExecutorResult> {\n\t\tlet output = \"\";\n\t\tconst handle = this.executeStreaming(\n\t\t\tpayload,\n\t\t\t(_stream, data) => {\n\t\t\t\toutput += data;\n\t\t\t},\n\t\t\tabortSignal\n\t\t);\n\t\tconst result = await handle.result;\n\t\treturn { ...result, output: output || result.output };\n\t}\n\n\texecuteStreaming(\n\t\tpayload: unknown,\n\t\tonOutput: OutputEventCallback,\n\t\tabortSignal?: AbortSignal\n\t): StreamingExecutorHandle {\n\t\tconst p = payload as ShellPayload;\n\t\tconst timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS);\n\n\t\tlet resolve: (value: ExecutorResult) => void;\n\t\tconst resultPromise = new Promise<ExecutorResult>((r) => {\n\t\t\tresolve = r;\n\t\t});\n\t\tlet settled = false;\n\n\t\tconst settle = (result: ExecutorResult) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tresolve?.(result);\n\t\t};\n\n\t\tif (abortSignal?.aborted) {\n\t\t\treturn {\n\t\t\t\tresult: Promise.resolve({\n\t\t\t\t\tstatus: \"cancelled\" as const,\n\t\t\t\t\terror: \"Execution aborted\",\n\t\t\t\t}),\n\t\t\t\tcancel: () => {},\n\t\t\t};\n\t\t}\n\n\t\tconst shellExecution = resolveShellExecution(p.script);\n\n\t\tconst spawnOpts: SpawnOptions = {\n\t\t\tcwd: p.cwd,\n\t\t\tstdio: [shellExecution.stdinScript ? \"pipe\" : \"ignore\", \"pipe\", \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t};\n\n\t\twriteDiagnostic(\n\t\t\t`[ShellExecutor] spawn:\n platform=${process.platform}\n shell=${shellExecution.shellKind}\n command=${shellExecution.command}\n args=${JSON.stringify(shellExecution.args.filter((a) => a !== p.script))}\n stdin=${shellExecution.stdinScript ? \"true\" : \"false\"}\n cwd=${p.cwd ?? \"(default)\"}\n timeout=${timeoutMs != null ? `${timeoutMs}ms` : \"unlimited\"}\n scriptLen=${p.script.length}\n windowsHide=true\n daemonPid=${process.pid}\\n`\n\t\t);\n\n\t\tconst child = spawn(shellExecution.command, shellExecution.args, spawnOpts);\n\n\t\tif (shellExecution.stdinScript && child.stdin) {\n\t\t\tchild.stdin.end(shellExecution.stdinScript);\n\t\t}\n\n\t\tif (child.pid) {\n\t\t\twriteDiagnostic(`[ShellExecutor] spawned child PID=${child.pid}\\n`);\n\t\t}\n\n\t\tconst timer =\n\t\t\ttimeoutMs != null\n\t\t\t\t? setTimeout(() => {\n\t\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tif (!child.killed) child.kill(\"SIGKILL\");\n\t\t\t\t\t\t}, 5000);\n\t\t\t\t\t\tsettle({\n\t\t\t\t\t\t\tstatus: \"timeout\",\n\t\t\t\t\t\t\terror: `Shell execution timed out after ${timeoutMs / 1000}s`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}, timeoutMs)\n\t\t\t\t: undefined;\n\n\t\tlet stdoutBuf = \"\";\n\t\tlet stderrBuf = \"\";\n\n\t\tchild.stdout?.on(\"data\", (chunk: Buffer) => {\n\t\t\tconst data = chunk.toString(shellExecution.outputEncoding);\n\t\t\tstdoutBuf += data;\n\t\t\tif (stdoutBuf.length > MAX_OUTPUT_BYTES) stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);\n\t\t\tonOutput(\"stdout\", data);\n\t\t});\n\n\t\tchild.stderr?.on(\"data\", (chunk: Buffer) => {\n\t\t\tconst data = chunk.toString(shellExecution.outputEncoding);\n\t\t\tstderrBuf += data;\n\t\t\tif (stderrBuf.length > MAX_OUTPUT_BYTES) stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);\n\t\t\tonOutput(\"stderr\", data);\n\t\t});\n\n\t\tchild.on(\"close\", (code: number | null) => {\n\t\t\twriteDiagnostic(`[ShellExecutor] child PID=${child.pid} exited: code=${code}\\n`);\n\t\t\tif (code === 0) {\n\t\t\t\tsettle({ status: \"success\", output: stdoutBuf || undefined });\n\t\t\t} else {\n\t\t\t\tsettle({\n\t\t\t\t\tstatus: \"failure\",\n\t\t\t\t\toutput: stdoutBuf || undefined,\n\t\t\t\t\terror: stderrBuf || `Process exited with code ${code}`,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tchild.on(\"error\", (err: Error) => {\n\t\t\tsettle({ status: \"failure\", error: err.message });\n\t\t});\n\n\t\tconst cancelFn = () => {\n\t\t\tchild.kill(\"SIGTERM\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (!child.killed) child.kill(\"SIGKILL\");\n\t\t\t}, 5000);\n\t\t\tsettle({ status: \"cancelled\", error: \"Execution cancelled\" });\n\t\t};\n\n\t\tabortSignal?.addEventListener(\"abort\", () => cancelFn(), { once: true });\n\n\t\treturn { result: resultPromise, cancel: cancelFn };\n\t}\n}\n","import type { TaskExecutionStatus } from \"@serviceme/devtools-protocol\";\n\nexport interface ExecutorResult {\n\tstatus: TaskExecutionStatus;\n\toutput?: string;\n\terror?: string;\n}\n\nexport interface TaskExecutor {\n\texecute(payload: unknown, abortSignal?: AbortSignal): Promise<ExecutorResult>;\n}\n\n// Streaming interfaces for Bridge task execution\n\nexport type OutputEventCallback = (stream: \"stdout\" | \"stderr\", data: string) => void;\n\nexport interface StreamingExecutorHandle {\n\tresult: Promise<ExecutorResult>;\n\tcancel: () => void;\n}\n\nexport interface StreamingTaskExecutor extends TaskExecutor {\n\texecuteStreaming(\n\t\tpayload: unknown,\n\t\tonOutput: OutputEventCallback,\n\t\tabortSignal?: AbortSignal\n\t): StreamingExecutorHandle;\n}\n\nexport function isStreamingTaskExecutor(executor: TaskExecutor): executor is StreamingTaskExecutor {\n\treturn (\n\t\t\"executeStreaming\" in executor &&\n\t\ttypeof (executor as StreamingTaskExecutor).executeStreaming === \"function\"\n\t);\n}\n","import type { ScheduledTaskType } from \"@serviceme/devtools-protocol\";\nimport { GithubCopilotCliExecutor } from \"./GithubCopilotCliExecutor\";\nimport { HttpRequestExecutor } from \"./HttpRequestExecutor\";\nimport { ShellExecutor } from \"./ShellExecutor\";\nimport type { TaskExecutor } from \"./types\";\n\n/** Task types executable by the bridge (excludes \"command\" which runs in-extension) */\ntype BridgeTaskType = Exclude<ScheduledTaskType, \"command\">;\n\nconst executors: Record<BridgeTaskType, TaskExecutor> = {\n\tshell: new ShellExecutor(),\n\thttp_request: new HttpRequestExecutor(),\n\tgithub_copilot_cli: new GithubCopilotCliExecutor(),\n};\n\nexport function getExecutor(taskType: ScheduledTaskType): TaskExecutor {\n\tconst executor = executors[taskType as BridgeTaskType];\n\tif (!executor) {\n\t\tthrow new Error(`Unsupported bridge task type: ${taskType}`);\n\t}\n\treturn executor;\n}\n\nexport { ShellExecutor, HttpRequestExecutor, GithubCopilotCliExecutor };\nexport type {\n\tExecutorResult,\n\tOutputEventCallback,\n\tStreamingExecutorHandle,\n\tStreamingTaskExecutor,\n\tTaskExecutor,\n} from \"./types\";\nexport { isStreamingTaskExecutor } from \"./types\";\n","import { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type {\n\tScheduledTask,\n\tScheduledTasksConfig,\n\tScheduledTasksConfigV1,\n\tScheduledTaskType,\n\tTaskPayload,\n\tTaskWorkspaceRef,\n} from \"@serviceme/devtools-protocol\";\nimport {\n\tcreateServicemeError,\n\tisScheduledTasksConfig,\n\tisScheduledTasksConfigV1,\n\tisScheduledTaskV1,\n\tmigrateV1ToV2,\n} from \"@serviceme/devtools-protocol\";\nimport { getMigrationFailuresPath, getScheduledTasksConfigPath } from \"../paths/userHome\";\n\nfunction emptyConfig(): ScheduledTasksConfig {\n\treturn { version: 2, tasks: [] };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Detect the \"v2 container with v1-shaped tasks\" hybrid: a config object\n * whose top-level `version` is `2` and whose `tasks` array is non-empty,\n * but where every task fails the v2 validator specifically because of the\n * missing required `workspace` field. The task entries must still pass\n * the v1 validator (otherwise the file is genuinely corrupt, not just\n * stale).\n */\nfunction isV2ContainerWithV1Tasks(value: unknown): value is {\n\tversion: 2;\n\ttasks: unknown[];\n} {\n\tif (!isRecord(value)) return false;\n\tif (value.version !== 2) return false;\n\tif (!Array.isArray(value.tasks) || value.tasks.length === 0) return false;\n\tconst allValidV2 = value.tasks.every((t) => !isScheduledTaskV1(t) || tIsValidV2(t));\n\tif (allValidV2) return false;\n\t// All tasks pass v1 — exactly the v2-container-with-v1-tasks hybrid.\n\treturn value.tasks.every((t) => isScheduledTaskV1(t));\n}\n\nfunction tIsValidV2(t: unknown): boolean {\n\treturn isRecord(t) && isRecord((t as Record<string, unknown>).workspace);\n}\n\n/**\n * Cast a v2-container-with-v1-tasks hybrid back to a v1 `ScheduledTasksConfigV1`\n * so we can feed it through the existing `migrateV1ToV2` helper. No data is\n * transformed — only the type assertion; the tasks inside are already\n * v1-shaped.\n */\nfunction v1ContainerShape(value: { tasks: unknown[] }): ScheduledTasksConfigV1 {\n\treturn { version: 1, tasks: value.tasks.filter(isScheduledTaskV1) };\n}\n\n/**\n * Fallback workspace context used when the manager has to auto-migrate a\n * v1-shaped task and the caller did not supply one. Points at the user's\n * home directory; the user can always re-edit the task in the webview to\n * point at the real workspace afterwards. This is intentionally\n * pessimistic — surfacing the task at all beats the previous behaviour\n * of silently dropping it.\n */\nfunction defaultWorkspaceContext(): TaskWorkspaceRef {\n\tconst home = os.homedir() || \"/\";\n\treturn { path: home, name: path.basename(home) || home };\n}\n\nfunction requireNonEmptyString(\n\tpayload: TaskPayload,\n\tfield: string,\n\ttaskType: ScheduledTaskType\n): void {\n\tif (!isRecord(payload) || typeof payload[field] !== \"string\" || !payload[field].trim()) {\n\t\tthrow createServicemeError(\"invalid_payload\", `${taskType} payload ${field} is required`);\n\t}\n}\n\nexport function validateTaskPayload(taskType: ScheduledTaskType, payload: TaskPayload): void {\n\tswitch (taskType) {\n\t\tcase \"command\":\n\t\t\trequireNonEmptyString(payload, \"command\", taskType);\n\t\t\treturn;\n\t\tcase \"shell\":\n\t\t\trequireNonEmptyString(payload, \"script\", taskType);\n\t\t\treturn;\n\t\tcase \"http_request\":\n\t\t\trequireNonEmptyString(payload, \"url\", taskType);\n\t\t\trequireNonEmptyString(payload, \"method\", taskType);\n\t\t\treturn;\n\t\tcase \"github_copilot_cli\":\n\t\t\trequireNonEmptyString(payload, \"prompt\", taskType);\n\t\t\treturn;\n\t}\n}\n\nexport interface CreateTaskInput {\n\tname: string;\n\tdescription?: string;\n\tscheduleType: \"cron\" | \"interval\";\n\tschedule: string;\n\ttaskType: ScheduledTaskType;\n\tpayload: TaskPayload;\n\tworkspace: TaskWorkspaceRef;\n\tenabled?: boolean;\n}\n\nexport interface EditTaskInput {\n\tname?: string;\n\tdescription?: string;\n\tscheduleType?: \"cron\" | \"interval\";\n\tschedule?: string;\n\ttaskType?: ScheduledTaskType;\n\tpayload?: TaskPayload;\n\tworkspace?: TaskWorkspaceRef;\n\tenabled?: boolean;\n}\n\nexport interface TaskConfigManagerOptions {\n\t/**\n\t * Override the on-disk config path. Defaults to the global\n\t * `~/.serviceme/scheduled-tasks.json`. Tests use this to point at a tmp\n\t * directory; production code should leave it unset.\n\t */\n\tconfigPath?: string;\n\t/**\n\t * Workspace context used when a legacy v1 config is read. v1 tasks had no\n\t * per-task workspace, so the manager needs a fallback to attach when\n\t * auto-migrating. Required only if the underlying file might still be v1.\n\t */\n\tworkspaceContext?: TaskWorkspaceRef;\n\t/**\n\t * Override the path where malformed-config diagnostic entries are appended.\n\t * Defaults to `~/.serviceme/migration-failures.json`. Tests can point at a\n\t * tmp file to assert failures were recorded.\n\t */\n\tmigrationFailuresPath?: string;\n\t/**\n\t * Inject a logger for the manager's recovery / migration warnings. Defaults\n\t * to `console.warn`. Tests pass a `vi.fn`/`mock.fn` to assert the warnings\n\t * were emitted.\n\t */\n\tlogger?: (message: string) => void;\n}\n\nexport class TaskConfigManager {\n\tprivate readonly configPath: string;\n\tprivate readonly workspaceContext?: TaskWorkspaceRef;\n\tprivate readonly migrationFailuresPath?: string;\n\tprivate readonly logger?: (message: string) => void;\n\n\tconstructor(options: TaskConfigManagerOptions = {}) {\n\t\tthis.configPath = options.configPath ?? getScheduledTasksConfigPath();\n\t\tthis.workspaceContext = options.workspaceContext;\n\t\tthis.migrationFailuresPath = options.migrationFailuresPath;\n\t\tthis.logger = options.logger;\n\t}\n\n\tgetConfigPath(): string {\n\t\treturn this.configPath;\n\t}\n\n\treadConfig(): ScheduledTasksConfig {\n\t\tif (!fs.existsSync(this.configPath)) {\n\t\t\treturn emptyConfig();\n\t\t}\n\t\tconst raw = fs.readFileSync(this.configPath, \"utf-8\");\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(raw);\n\t\t} catch (error) {\n\t\t\t// Corrupted file — record failure + warn, then treat as empty.\n\t\t\t// Mirrors the log manager's repair-on-read policy.\n\t\t\tthis.recordMalformedConfigFailure(\"parse_error\", String(error), raw);\n\t\t\treturn emptyConfig();\n\t\t}\n\n\t\t// v2 (current): return as-is.\n\t\tif (isScheduledTasksConfig(parsed)) {\n\t\t\treturn parsed;\n\t\t}\n\n\t\t// Mixed / recoverable: a v2 container holding v1-shaped tasks (no\n\t\t// `workspace` field). Happens when a tool writes the header\n\t\t// `\"version\": 2` but each task is missing the v2-required `workspace`\n\t\t// ref — observed when users hand-edit `~/.serviceme/scheduled-tasks.json`\n\t\t// or when older bugged writers stamped v2 onto v1 task shapes. Treat\n\t\t// as v1 + auto-migrate using either the explicit `workspaceContext`\n\t\t// (preferred) or a sensible default (the user's home dir) so the\n\t\t// tasks stay visible instead of being silently dropped. Repairs the\n\t\t// on-disk file on success.\n\t\tif (isV2ContainerWithV1Tasks(parsed)) {\n\t\t\tconst v1 = v1ContainerShape(parsed);\n\t\t\tconst ctx = this.workspaceContext ?? defaultWorkspaceContext();\n\t\t\tconst { config, issues } = migrateV1ToV2(v1, ctx);\n\t\t\tthis.warn(\n\t\t\t\t`TaskConfigManager: detected v2 container with v1-shaped tasks in ${this.configPath} — auto-migrated in place. ` +\n\t\t\t\t\t`Tasks are now bound to workspace \"${ctx.path}\". Pass TaskConfigManager({ workspaceContext }) to override.`\n\t\t\t);\n\t\t\tfor (const issue of issues) this.warn(`TaskConfigManager migration issue: ${issue}`);\n\t\t\ttry {\n\t\t\t\tthis.writeConfig(config);\n\t\t\t} catch (writeError) {\n\t\t\t\tthis.warn(\n\t\t\t\t\t`TaskConfigManager: failed to rewrite repaired config to ${this.configPath}: ${String(writeError)}`\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn config;\n\t\t}\n\n\t\t// v1 (legacy): auto-migrate in memory. The MigrateToGlobal scanner is\n\t\t// the long-term path forward; this branch is the safety net for any\n\t\t// global config file that was somehow written in v1 format during the\n\t\t// migration window.\n\t\tif (isScheduledTasksConfigV1(parsed)) {\n\t\t\tif (!this.workspaceContext) {\n\t\t\t\tthrow createServicemeError(\n\t\t\t\t\t\"invalid_params\",\n\t\t\t\t\t\"TaskConfigManager: v1 config found but no workspaceContext provided. \" +\n\t\t\t\t\t\t\"Pass TaskConfigManager({ workspaceContext }) when reading a legacy v1 file.\"\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst v1 = parsed as ScheduledTasksConfigV1;\n\t\t\treturn migrateV1ToV2(v1, this.workspaceContext).config;\n\t\t}\n\n\t\t// Unknown shape — record a failure entry so the user can debug, then\n\t\t// treat as empty. Don't return silently: a previous version's CLI\n\t\t// sometimes left an unrecoverable file that ate tasks invisibly.\n\t\tthis.recordMalformedConfigFailure(\"unknown_shape\", \"Config did not match v1 or v2 schema\", raw);\n\t\treturn emptyConfig();\n\t}\n\n\tprivate warn(message: string): void {\n\t\tif (this.logger) this.logger(message);\n\t\telse console.warn(`[TaskConfigManager] ${message}`);\n\t}\n\n\tprivate recordMalformedConfigFailure(reason: string, detail: string, raw: string): void {\n\t\tthis.warn(\n\t\t\t`TaskConfigManager: malformed config at ${this.configPath} (${reason}): ${detail}. ` +\n\t\t\t\t`Tasks in this file are invisible until repaired. A diagnostic entry is written to ${\n\t\t\t\t\tthis.migrationFailuresPath ?? getMigrationFailuresPath()\n\t\t\t\t}.`\n\t\t);\n\t\ttry {\n\t\t\tconst target = this.migrationFailuresPath ?? getMigrationFailuresPath();\n\t\t\tconst prior = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn JSON.parse(fs.readFileSync(target, \"utf-8\")) as unknown;\n\t\t\t\t} catch {\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t})();\n\t\t\tconst failures = Array.isArray(prior) ? (prior as unknown[]) : [];\n\t\t\tfailures.push({\n\t\t\t\tpath: this.configPath,\n\t\t\t\treason,\n\t\t\t\tdetail,\n\t\t\t\tsnippet: raw.slice(0, 500),\n\t\t\t\trecordedAt: new Date().toISOString(),\n\t\t\t});\n\t\t\tfs.mkdirSync(path.dirname(target), { recursive: true });\n\t\t\tfs.writeFileSync(target, JSON.stringify(failures, null, \"\\t\"), \"utf-8\");\n\t\t} catch (writeError) {\n\t\t\tthis.warn(\n\t\t\t\t`TaskConfigManager: also failed to write migration-failures log: ${String(writeError)}`\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate writeConfig(config: ScheduledTasksConfig): void {\n\t\tconst dir = path.dirname(this.configPath);\n\t\tif (!fs.existsSync(dir)) {\n\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t}\n\t\t// Atomic write: write to temp then rename\n\t\tconst tmp = `${this.configPath}.tmp`;\n\t\tfs.writeFileSync(tmp, JSON.stringify(config, null, \"\\t\"), \"utf-8\");\n\t\tfs.renameSync(tmp, this.configPath);\n\t}\n\n\tlistTasks(): ScheduledTask[] {\n\t\treturn this.readConfig().tasks;\n\t}\n\n\tgetTask(id: string): ScheduledTask | undefined {\n\t\treturn this.readConfig().tasks.find((t) => t.id === id);\n\t}\n\n\tgetTaskByName(name: string): ScheduledTask | undefined {\n\t\treturn this.readConfig().tasks.find((t) => t.name === name);\n\t}\n\n\tcreateTask(input: CreateTaskInput): ScheduledTask {\n\t\tif (!input.workspace || !input.workspace.path || !input.workspace.name) {\n\t\t\tthrow createServicemeError(\n\t\t\t\t\"invalid_params\",\n\t\t\t\t\"createTask: workspace with path and name is required (v2 schema)\"\n\t\t\t);\n\t\t}\n\t\tvalidateTaskPayload(input.taskType, input.payload);\n\t\tconst config = this.readConfig();\n\t\tconst now = new Date().toISOString();\n\t\tconst task: ScheduledTask = {\n\t\t\tid: randomUUID(),\n\t\t\tname: input.name,\n\t\t\tdescription: input.description,\n\t\t\tenabled: input.enabled ?? true,\n\t\t\tscheduleType: input.scheduleType,\n\t\t\tschedule: input.schedule,\n\t\t\ttaskType: input.taskType,\n\t\t\tpayload: input.payload,\n\t\t\tworkspace: input.workspace,\n\t\t\tcreatedAt: now,\n\t\t\tupdatedAt: now,\n\t\t};\n\t\tconfig.tasks.push(task);\n\t\tthis.writeConfig(config);\n\t\treturn task;\n\t}\n\n\teditTask(id: string, input: EditTaskInput): ScheduledTask {\n\t\tconst config = this.readConfig();\n\t\tconst idx = config.tasks.findIndex((t) => t.id === id);\n\t\tif (idx === -1) {\n\t\t\tthrow new Error(`Task '${id}' not found`);\n\t\t}\n\t\tconst existing = config.tasks[idx];\n\t\tif (!existing) {\n\t\t\tthrow new Error(`Task '${id}' not found`);\n\t\t}\n\t\tconst nextTaskType = input.taskType ?? existing.taskType;\n\t\tconst nextPayload = input.payload ?? existing.payload;\n\t\tconst nextWorkspace = input.workspace ?? existing.workspace;\n\t\tvalidateTaskPayload(nextTaskType, nextPayload);\n\t\tconst updated: ScheduledTask = {\n\t\t\t...existing,\n\t\t\t...(input.name !== undefined && { name: input.name }),\n\t\t\t...(input.description !== undefined && {\n\t\t\t\tdescription: input.description,\n\t\t\t}),\n\t\t\t...(input.scheduleType !== undefined && {\n\t\t\t\tscheduleType: input.scheduleType,\n\t\t\t}),\n\t\t\t...(input.schedule !== undefined && { schedule: input.schedule }),\n\t\t\t...(input.taskType !== undefined && { taskType: input.taskType }),\n\t\t\t...(input.payload !== undefined && { payload: input.payload }),\n\t\t\t...(input.workspace !== undefined && { workspace: nextWorkspace }),\n\t\t\t...(input.enabled !== undefined && { enabled: input.enabled }),\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t};\n\t\tconfig.tasks[idx] = updated;\n\t\tthis.writeConfig(config);\n\t\treturn updated;\n\t}\n\n\tdeleteTask(id: string): ScheduledTask {\n\t\tconst config = this.readConfig();\n\t\tconst idx = config.tasks.findIndex((t) => t.id === id);\n\t\tif (idx === -1) {\n\t\t\tthrow new Error(`Task '${id}' not found`);\n\t\t}\n\t\tconst removed = config.tasks[idx];\n\t\tif (!removed) {\n\t\t\tthrow new Error(`Task '${id}' not found`);\n\t\t}\n\t\tconfig.tasks.splice(idx, 1);\n\t\tthis.writeConfig(config);\n\t\treturn removed;\n\t}\n\n\ttoggleTask(id: string, enabled: boolean): ScheduledTask {\n\t\treturn this.editTask(id, { enabled });\n\t}\n}\n","import type {\n\tConcurrencyPolicy,\n\tGithubCopilotCliPayload,\n\tRunningTaskInfo,\n\tScheduledTaskType,\n\tShellPayload,\n\tTaskCancelledEventParams,\n\tTaskCompletedEventParams,\n\tTaskExecutionLog,\n\tTaskExecutionSnapshot,\n\tTaskFailedEventParams,\n\tTaskOutputEventParams,\n\tTaskPayload,\n\tTaskStartedEventParams,\n} from \"@serviceme/devtools-protocol\";\nimport { type ExecutorResult, isStreamingTaskExecutor, type TaskExecutor } from \"./executors/types\";\nimport { validateTaskPayload } from \"./TaskConfigManager\";\n\nexport interface TaskEventListener {\n\tonStarted(params: TaskStartedEventParams): void;\n\tonOutput(params: TaskOutputEventParams): void;\n\tonCompleted(params: TaskCompletedEventParams): void;\n\tonFailed(params: TaskFailedEventParams): void;\n\tonCancelled(params: TaskCancelledEventParams): void;\n}\n\ninterface RunningExecution {\n\texecutionId: string;\n\ttaskId: string;\n\tstartedAt: string;\n\tcancel: () => void;\n}\n\nfunction hasNonEmptyString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.trim().length > 0;\n}\n\nexport function resolveTaskExecutionPayload(\n\ttaskType: ScheduledTaskType,\n\tpayload: TaskPayload,\n\tworkspacePath: string\n): TaskPayload {\n\tif (!hasNonEmptyString(workspacePath)) {\n\t\treturn payload;\n\t}\n\n\tif (taskType === \"shell\") {\n\t\tconst shellPayload = payload as ShellPayload;\n\t\tif (hasNonEmptyString(shellPayload.cwd)) {\n\t\t\treturn shellPayload;\n\t\t}\n\t\treturn { ...shellPayload, cwd: workspacePath };\n\t}\n\n\tif (taskType === \"github_copilot_cli\") {\n\t\tconst copilotPayload = payload as GithubCopilotCliPayload;\n\t\tif (hasNonEmptyString(copilotPayload.workspace)) {\n\t\t\treturn copilotPayload;\n\t\t}\n\t\treturn { ...copilotPayload, workspace: workspacePath };\n\t}\n\n\treturn payload;\n}\n\nexport class TaskExecutionEngine {\n\tprivate readonly running = new Map<string, RunningExecution>();\n\tprivate readonly taskExecutions = new Map<string, Set<string>>();\n\tprivate listener: TaskEventListener | null = null;\n\n\tconstructor(private readonly getExecutor: (taskType: string) => TaskExecutor) {}\n\n\tsetListener(listener: TaskEventListener): void {\n\t\tthis.listener = listener;\n\t}\n\n\tasync execute(snapshot: TaskExecutionSnapshot): Promise<void> {\n\t\tconst policy: ConcurrencyPolicy = snapshot.concurrencyPolicy ?? \"reject\";\n\n\t\t// Enforce concurrency policy\n\t\tif (policy === \"reject\") {\n\t\t\tconst existingIds = this.taskExecutions.get(snapshot.taskId);\n\t\t\tif (existingIds && existingIds.size > 0) {\n\t\t\t\tthrow new Error(`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`);\n\t\t\t}\n\t\t}\n\n\t\tconst startedAt = new Date().toISOString();\n\n\t\t// Track by taskId → executionId\n\t\tif (!this.taskExecutions.has(snapshot.taskId)) {\n\t\t\tthis.taskExecutions.set(snapshot.taskId, new Set());\n\t\t}\n\t\tthis.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);\n\n\t\t// Set up cancellation\n\t\tconst ac = new AbortController();\n\t\tconst runningExec: RunningExecution = {\n\t\t\texecutionId: snapshot.executionId,\n\t\t\ttaskId: snapshot.taskId,\n\t\t\tstartedAt,\n\t\t\tcancel: () => ac.abort(),\n\t\t};\n\t\tthis.running.set(snapshot.executionId, runningExec);\n\n\t\tthis.listener?.onStarted({\n\t\t\texecutionId: snapshot.executionId,\n\t\t\ttaskId: snapshot.taskId,\n\t\t\tstartedAt,\n\t\t});\n\n\t\ttry {\n\t\t\tconst executionPayload = resolveTaskExecutionPayload(\n\t\t\t\tsnapshot.type,\n\t\t\t\tsnapshot.payload,\n\t\t\t\tsnapshot.workspacePath\n\t\t\t);\n\t\t\tvalidateTaskPayload(snapshot.type, executionPayload);\n\t\t\tconst executor = this.getExecutor(snapshot.type);\n\t\t\tlet result: ExecutorResult;\n\t\t\tif (isStreamingTaskExecutor(executor)) {\n\t\t\t\tconst handle = executor.executeStreaming(\n\t\t\t\t\texecutionPayload,\n\t\t\t\t\t(stream, data) => {\n\t\t\t\t\t\tthis.listener?.onOutput({\n\t\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\t\tstream,\n\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\tac.signal\n\t\t\t\t);\n\t\t\t\trunningExec.cancel = () => {\n\t\t\t\t\thandle.cancel();\n\t\t\t\t\tac.abort();\n\t\t\t\t};\n\t\t\t\tresult = await handle.result;\n\t\t\t} else {\n\t\t\t\tresult = await executor.execute(executionPayload, ac.signal);\n\t\t\t}\n\n\t\t\tconst log: TaskExecutionLog = {\n\t\t\t\tid: snapshot.executionId,\n\t\t\t\ttaskId: snapshot.taskId,\n\t\t\t\ttaskName: snapshot.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt: new Date().toISOString(),\n\t\t\t\tstatus: result.status === \"running\" ? \"failure\" : result.status,\n\t\t\t\toutput: result.output,\n\t\t\t\terror: result.error,\n\t\t\t};\n\n\t\t\tswitch (log.status) {\n\t\t\t\tcase \"success\":\n\t\t\t\t\tthis.listener?.onCompleted({\n\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\tlog,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"cancelled\":\n\t\t\t\t\tthis.listener?.onCancelled({\n\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\tlog,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"timeout\":\n\t\t\t\t\tthis.listener?.onFailed({\n\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\tstatus: \"timeout\",\n\t\t\t\t\t\treason: \"timeout\",\n\t\t\t\t\t\tlog,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tthis.listener?.onFailed({\n\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\tstatus: \"failure\",\n\t\t\t\t\t\treason: \"error\",\n\t\t\t\t\t\tlog,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst log: TaskExecutionLog = {\n\t\t\t\tid: snapshot.executionId,\n\t\t\t\ttaskId: snapshot.taskId,\n\t\t\t\ttaskName: snapshot.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt: new Date().toISOString(),\n\t\t\t\tstatus: ac.signal.aborted ? \"cancelled\" : \"failure\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t};\n\n\t\t\tif (ac.signal.aborted) {\n\t\t\t\tthis.listener?.onCancelled({\n\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\tlog,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.listener?.onFailed({\n\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\tstatus: \"failure\",\n\t\t\t\t\treason: \"error\",\n\t\t\t\t\tlog,\n\t\t\t\t});\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.running.delete(snapshot.executionId);\n\t\t\tconst taskExecIds = this.taskExecutions.get(snapshot.taskId);\n\t\t\tif (taskExecIds) {\n\t\t\t\ttaskExecIds.delete(snapshot.executionId);\n\t\t\t\tif (taskExecIds.size === 0) {\n\t\t\t\t\tthis.taskExecutions.delete(snapshot.taskId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tcancel(executionId: string): boolean {\n\t\tconst exec = this.running.get(executionId);\n\t\tif (!exec) return false;\n\t\texec.cancel();\n\t\treturn true;\n\t}\n\n\tlistRunning(): RunningTaskInfo[] {\n\t\treturn Array.from(this.running.values()).map((e) => ({\n\t\t\texecutionId: e.executionId,\n\t\t\ttaskId: e.taskId,\n\t\t\tstartedAt: e.startedAt,\n\t\t}));\n\t}\n\n\tdispose(): void {\n\t\tfor (const exec of this.running.values()) {\n\t\t\texec.cancel();\n\t\t}\n\t\tthis.running.clear();\n\t\tthis.taskExecutions.clear();\n\t}\n}\n","import { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n\tScheduledTasksLogFile,\n\tTaskExecutionLog,\n\tTaskExecutionStatus,\n} from \"@serviceme/devtools-protocol\";\nimport { getScheduledTasksLogPath } from \"../paths/userHome\";\n\nconst MAX_LOGS = 200;\n\nfunction emptyLogFile(): ScheduledTasksLogFile {\n\treturn { logs: [] };\n}\n\nfunction isValidLogEntry(entry: unknown): entry is TaskExecutionLog {\n\tif (!entry || typeof entry !== \"object\") return false;\n\tconst e = entry as Record<string, unknown>;\n\treturn (\n\t\ttypeof e.id === \"string\" &&\n\t\ttypeof e.taskId === \"string\" &&\n\t\ttypeof e.taskName === \"string\" &&\n\t\ttypeof e.startedAt === \"string\" &&\n\t\ttypeof e.finishedAt === \"string\" &&\n\t\ttypeof e.status === \"string\"\n\t);\n}\n\nfunction validateAndRepairLogFile(raw: unknown): ScheduledTasksLogFile {\n\tif (!raw || typeof raw !== \"object\") {\n\t\treturn emptyLogFile();\n\t}\n\tconst file = raw as Record<string, unknown>;\n\tif (!Array.isArray(file.logs)) {\n\t\treturn emptyLogFile();\n\t}\n\tconst validLogs = file.logs.filter(isValidLogEntry);\n\treturn { logs: validLogs };\n}\n\nexport interface AppendLogInput {\n\ttaskId: string;\n\ttaskName: string;\n\tstartedAt: string;\n\tfinishedAt: string;\n\tstatus: Exclude<TaskExecutionStatus, \"running\">;\n\toutput?: string;\n\terror?: string;\n}\n\nexport interface TaskLogManagerOptions {\n\t/**\n\t * Override the on-disk log path. Defaults to the global\n\t * `~/.serviceme/scheduled-tasks-log.json`. Tests use this to point at a\n\t * tmp directory; production code should leave it unset.\n\t */\n\tlogPath?: string;\n}\n\nexport class TaskLogManager {\n\tprivate readonly logPath: string;\n\n\tconstructor(options: TaskLogManagerOptions = {}) {\n\t\tthis.logPath = options.logPath ?? getScheduledTasksLogPath();\n\t}\n\n\tgetLogPath(): string {\n\t\treturn this.logPath;\n\t}\n\n\tprivate readLogFile(): ScheduledTasksLogFile {\n\t\tif (!fs.existsSync(this.logPath)) {\n\t\t\treturn emptyLogFile();\n\t\t}\n\t\ttry {\n\t\t\tconst raw = fs.readFileSync(this.logPath, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\tconst file = validateAndRepairLogFile(parsed);\n\t\t\t// If repair removed entries or structure was invalid, rewrite the file\n\t\t\tif (\n\t\t\t\t!parsed ||\n\t\t\t\ttypeof parsed !== \"object\" ||\n\t\t\t\t!Array.isArray(parsed.logs) ||\n\t\t\t\tparsed.logs.length !== file.logs.length\n\t\t\t) {\n\t\t\t\tthis.writeLogFile(file);\n\t\t\t}\n\t\t\treturn file;\n\t\t} catch {\n\t\t\t// JSON parse failed — file is corrupted, back it up and start fresh\n\t\t\tthis.backupCorruptedFile();\n\t\t\tconst fresh = emptyLogFile();\n\t\t\tthis.writeLogFile(fresh);\n\t\t\treturn fresh;\n\t\t}\n\t}\n\n\tprivate backupCorruptedFile(): void {\n\t\ttry {\n\t\t\tif (fs.existsSync(this.logPath)) {\n\t\t\t\tconst backupPath = `${this.logPath}.corrupted.${Date.now()}`;\n\t\t\t\tfs.copyFileSync(this.logPath, backupPath);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Best-effort backup\n\t\t}\n\t}\n\n\tprivate writeLogFile(file: ScheduledTasksLogFile): void {\n\t\tconst dir = path.dirname(this.logPath);\n\t\tif (!fs.existsSync(dir)) {\n\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t}\n\t\tconst tmp = `${this.logPath}.tmp`;\n\t\tfs.writeFileSync(tmp, JSON.stringify(file, null, \"\\t\"), \"utf-8\");\n\t\tfs.renameSync(tmp, this.logPath);\n\t}\n\n\tappendLog(input: AppendLogInput): TaskExecutionLog {\n\t\tconst file = this.readLogFile();\n\t\tconst log: TaskExecutionLog = {\n\t\t\tid: randomUUID(),\n\t\t\ttaskId: input.taskId,\n\t\t\ttaskName: input.taskName,\n\t\t\tstartedAt: input.startedAt,\n\t\t\tfinishedAt: input.finishedAt,\n\t\t\tstatus: input.status,\n\t\t\toutput: input.output,\n\t\t\terror: input.error,\n\t\t};\n\t\tfile.logs.push(log);\n\t\t// FIFO trim\n\t\tif (file.logs.length > MAX_LOGS) {\n\t\t\tfile.logs = file.logs.slice(file.logs.length - MAX_LOGS);\n\t\t}\n\t\tthis.writeLogFile(file);\n\t\treturn log;\n\t}\n\n\tgetLogs(options?: { taskId?: string; limit?: number }): {\n\t\tlogs: TaskExecutionLog[];\n\t\ttotal: number;\n\t} {\n\t\tconst file = this.readLogFile();\n\t\tlet logs = file.logs;\n\t\tif (options?.taskId) {\n\t\t\tlogs = logs.filter((l) => l.taskId === options.taskId);\n\t\t}\n\t\tconst total = logs.length;\n\t\t// Return newest first\n\t\tlogs = logs.slice().reverse();\n\t\tif (options?.limit && options.limit > 0) {\n\t\t\tlogs = logs.slice(0, options.limit);\n\t\t}\n\t\treturn { logs, total };\n\t}\n\n\tclearLogs(taskId?: string): number {\n\t\tconst file = this.readLogFile();\n\t\tconst before = file.logs.length;\n\t\tif (taskId) {\n\t\t\tfile.logs = file.logs.filter((l) => l.taskId !== taskId);\n\t\t} else {\n\t\t\tfile.logs = [];\n\t\t}\n\t\tthis.writeLogFile(file);\n\t\treturn before - file.logs.length;\n\t}\n}\n","// SchedulerDaemonV2 — global single-instance scheduler.\n//\n// Differences from the v1 SchedulerDaemon (see daemon/SchedulerDaemon.ts,\n// @deprecated):\n// - Reads the global ~/.serviceme/scheduled-tasks.json (v2 schema).\n// - One process for the whole machine, guarded by PID file + startup lock.\n// - Per-workspace in-memory mutex: same workspace runs serially, different\n// workspaces run in parallel.\n// - Switches cwd to task.workspace.path on every execute (no implicit\n// \"current workspace\" — daemon is machine-scoped).\n// - Disables tasks whose workspace path is missing.\n//\n// See docs/architecture/skill-agent-v2-repo.md §14.3.b + §14.4.\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ScheduledTask, ScheduledTasksConfig } from \"@serviceme/devtools-protocol\";\nimport { getSchedulerLockPath, getSchedulerPidPath } from \"../../paths/userHome\";\nimport { getExecutor } from \"../executors\";\nimport { TaskConfigManager, validateTaskPayload } from \"../TaskConfigManager\";\nimport { resolveTaskExecutionPayload } from \"../TaskExecutionEngine\";\nimport { TaskLogManager } from \"../TaskLogManager\";\nimport { DaemonLogger } from \"./DaemonLogger\";\nimport { PidManager } from \"./PidManager\";\n\nconst TICK_INTERVAL = 1000; // Check every second\nconst MIN_SCHEDULE_INTERVAL = 1_000; // 1 second minimum\nconst SCHEDULER_LOG_FILENAME = \"scheduler.log\";\n\nexport interface SchedulerDaemonV2Options {\n\t/**\n\t * Override the executor factory. Production callers should leave this\n\t * undefined; tests inject a fake to drive the execution branch without\n\t * spawning real subprocesses.\n\t */\n\tgetExecutor?: typeof getExecutor;\n\t/** Override the config manager. Tests inject a fake pointing at a tmp dir. */\n\tconfigManager?: TaskConfigManager;\n\t/** Override the log manager. */\n\tlogManager?: TaskLogManager;\n\t/** Override the PID manager. */\n\tpidManager?: PidManager;\n\t/** Override the daemon logger (for tests; the default writes to ~/.serviceme/scheduler.log). */\n\tlogger?: DaemonLogger;\n\t/** Override the startup lock acquisition (tests use this to inject \"lock held\" / \"lock free\"). */\n\ttryAcquireLock?: () => boolean;\n\t/** Override the startup lock release. */\n\treleaseLock?: () => void;\n\t/**\n\t * Override the PID file existence + process liveness check. Production\n\t * reads PID file + `process.kill(pid, 0)`; tests inject a stub.\n\t */\n\tgetRunningPid?: () => number | null;\n}\n\nexport class SchedulerDaemonV2 {\n\tprivate readonly configManager: TaskConfigManager;\n\tprivate readonly logManager: TaskLogManager;\n\tprivate readonly pidManager: PidManager;\n\tprivate readonly logger: DaemonLogger;\n\tprivate readonly getExecutor: typeof getExecutor;\n\tprivate readonly tryAcquireLock: () => boolean;\n\tprivate readonly releaseLock: () => void;\n\tprivate readonly getRunningPid: () => number | null;\n\n\tprivate tickTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate running = false;\n\t// Stable references to our own signal listeners so stop() can detach\n\t// them — otherwise repeated start()/stop() cycles (especially in tests)\n\t// leak listeners and keep the event loop alive past the daemon's lifetime.\n\tprivate readonly sigtermHandler: () => void;\n\tprivate readonly sigintHandler: () => void;\n\tprivate startTime: number = 0;\n\n\t// Per-task state (machines are global, but the daemon only knows about\n\t// tasks currently in the config — `lastRun` keys are task IDs).\n\tprivate lastRun: Map<string, number> = new Map();\n\n\t// Per-workspace mutex queue: same workspacePath runs serially, different\n\t// workspacePaths can run in parallel. Each entry is a promise that\n\t// resolves when the previous + this task has finished.\n\tprivate workspaceLocks: Map<string, Promise<unknown>> = new Map();\n\n\tconstructor(options: SchedulerDaemonV2Options = {}) {\n\t\tthis.configManager = options.configManager ?? new TaskConfigManager();\n\t\tthis.logManager = options.logManager ?? new TaskLogManager();\n\t\tthis.pidManager = options.pidManager ?? new PidManager(\"\", { pidPath: getSchedulerPidPath() });\n\t\t// Daemon log lives at ~/.serviceme/scheduler.log (global, not per-ws).\n\t\t// Computed from the PID file's directory to stay in sync with the\n\t\t// user-home resolution.\n\t\tthis.logger =\n\t\t\toptions.logger ??\n\t\t\tnew DaemonLogger(os.homedir(), {\n\t\t\t\tlogPath: path.join(path.dirname(this.pidManager.getPidPath()), SCHEDULER_LOG_FILENAME),\n\t\t\t});\n\t\tthis.getExecutor = options.getExecutor ?? getExecutor;\n\t\tthis.tryAcquireLock = options.tryAcquireLock ?? (() => true);\n\t\tthis.releaseLock = options.releaseLock ?? (() => {});\n\t\tthis.getRunningPid = options.getRunningPid ?? (() => this.pidManager.getRunningPid());\n\t\t// Bind the signal handlers once so stop() can detach the SAME\n\t\t// function references later.\n\t\tthis.sigtermHandler = () => this.stop();\n\t\tthis.sigintHandler = () => this.stop();\n\t}\n\n\tstart(): void {\n\t\tif (this.running) return;\n\n\t\t// 1. Try to acquire the startup flock. If we can't, another instance\n\t\t// is mid-startup — exit 0 (silent, not an error).\n\t\tif (!this.tryAcquireLock()) {\n\t\t\tthis.logger.log(\"info\", \"Another daemon instance holds the lock; exiting\");\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\t// 2. Check existing PID file. If a live process holds the slot, also\n\t\t// exit 0. (Recoverable if the recorded PID is stale.)\n\t\tconst existingPid = this.getRunningPid();\n\t\tif (existingPid !== null && existingPid !== process.pid) {\n\t\t\tthis.logger.log(\"info\", `Another daemon already running (pid: ${existingPid}); exiting`);\n\t\t\tthis.releaseLock();\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\tthis.running = true;\n\t\tthis.startTime = Date.now();\n\t\tthis.pidManager.writePid(process.pid);\n\n\t\tthis.logger.log(\n\t\t\t\"info\",\n\t\t\t`SchedulerDaemonV2 started\n PID: ${process.pid}\n Node: ${process.version}\n OS: ${os.type()} ${os.release()} (${process.arch})\n Config: ${this.configManager.getConfigPath()}\n LogPath: ${this.logger.getLogPath()}\n PidPath: ${this.pidManager.getPidPath()}`\n\t\t);\n\n\t\tthis.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);\n\t\tprocess.on(\"SIGTERM\", this.sigtermHandler);\n\t\tprocess.on(\"SIGINT\", this.sigintHandler);\n\t}\n\n\tstop(): void {\n\t\tif (!this.running) return;\n\t\tthis.running = false;\n\t\tif (this.tickTimer) {\n\t\t\tclearInterval(this.tickTimer);\n\t\t\tthis.tickTimer = null;\n\t\t}\n\t\tthis.pidManager.removePid();\n\t\tthis.releaseLock();\n\t\t// Detach our own signal handlers so multiple start()/stop() cycles\n\t\t// (especially in test scenarios) don't leak listeners and keep the\n\t\t// event loop alive past the daemon's lifetime.\n\t\tprocess.removeListener(\"SIGTERM\", this.sigtermHandler);\n\t\tprocess.removeListener(\"SIGINT\", this.sigintHandler);\n\t\tthis.logger.log(\"info\", \"SchedulerDaemonV2 stopped\");\n\t\tprocess.exit(0);\n\t}\n\n\tprivate tick(): void {\n\t\tconst config = this.configManager.readConfig();\n\t\tconst now = Date.now();\n\n\t\tfor (const task of config.tasks) {\n\t\t\tif (!task.enabled) continue;\n\n\t\t\t// Workspace-missing: auto-disable + log. Done outside the mutex\n\t\t\t// chain because no execution is going to happen anyway.\n\t\t\tif (!fs.existsSync(task.workspace.path)) {\n\t\t\t\tthis.disableTaskForMissingWorkspace(task, config);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// First-encounter: seed lastRun to the current tick so the task\n\t\t\t// waits a full interval before firing.\n\t\t\tlet lastExec = this.lastRun.get(task.id);\n\t\t\tif (lastExec === undefined) {\n\t\t\t\tthis.lastRun.set(task.id, now);\n\t\t\t\tlastExec = now;\n\t\t\t}\n\t\t\tif (!this.shouldRun(task, lastExec, now)) continue;\n\n\t\t\t// Per-workspace mutex: chain onto the existing promise for that\n\t\t\t// workspace path. Different workspaces run independently.\n\t\t\tconst wsPath = task.workspace.path;\n\t\t\tconst prev = this.workspaceLocks.get(wsPath) ?? Promise.resolve();\n\t\t\tconst next = prev\n\t\t\t\t.catch(() => undefined)\n\t\t\t\t.then(() => this.executeTask(task, now))\n\t\t\t\t.catch((err: unknown) => {\n\t\t\t\t\tthis.logger.log(\"error\", `Task ${task.id} execution failed: ${String(err)}`);\n\t\t\t\t});\n\t\t\tthis.workspaceLocks.set(wsPath, next);\n\t\t}\n\t}\n\n\tprivate disableTaskForMissingWorkspace(task: ScheduledTask, config: ScheduledTasksConfig): void {\n\t\tconst idx = config.tasks.findIndex((t) => t.id === task.id);\n\t\tif (idx === -1) return;\n\t\tconst existing = config.tasks[idx];\n\t\tif (!existing) return;\n\t\tif (existing.enabled === false && existing.lastRunError === \"workspace missing\") return;\n\t\tthis.logger.log(\n\t\t\t\"warn\",\n\t\t\t`Disabling task ${task.name} (${task.id}): workspace ${task.workspace.path} not found`\n\t\t);\n\t\tconfig.tasks[idx] = {\n\t\t\t...existing,\n\t\t\tenabled: false,\n\t\t\tlastRunError: \"workspace missing\",\n\t\t};\n\t\tthis.configManager.editTask(task.id, {\n\t\t\tenabled: false,\n\t\t\tworkspace: config.tasks[idx].workspace,\n\t\t});\n\t}\n\n\tprivate shouldRun(task: ScheduledTask, lastExec: number, now: number): boolean {\n\t\tif (task.scheduleType === \"interval\") {\n\t\t\tconst intervalMs = parseIntervalMs(task.schedule);\n\t\t\tif (intervalMs < MIN_SCHEDULE_INTERVAL) return false;\n\t\t\treturn now - lastExec >= intervalMs;\n\t\t}\n\t\tif (task.scheduleType === \"cron\") {\n\t\t\tif (now - lastExec < 60_000) return false;\n\t\t\treturn matchesCron(task.schedule, new Date(now));\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate async executeTask(task: ScheduledTask, now: number): Promise<void> {\n\t\tthis.lastRun.set(task.id, now);\n\t\tconst startedAt = new Date(now).toISOString();\n\t\tconst startMs = Date.now();\n\t\tconst origCwd = process.cwd();\n\n\t\tthis.logger.log(\"info\", `Executing task: ${task.name} (${task.id}) in ${task.workspace.path}`);\n\n\t\ttry {\n\t\t\tprocess.chdir(task.workspace.path);\n\t\t\tconst executionPayload = resolveTaskExecutionPayload(\n\t\t\t\ttask.taskType,\n\t\t\t\ttask.payload,\n\t\t\t\ttask.workspace.path\n\t\t\t);\n\t\t\tvalidateTaskPayload(task.taskType, executionPayload);\n\t\t\tconst executor = this.getExecutor(task.taskType);\n\t\t\tconst result = await executor.execute(executionPayload);\n\t\t\tconst finishedAt = new Date().toISOString();\n\t\t\tconst durationMs = Date.now() - startMs;\n\n\t\t\tthis.logManager.appendLog({\n\t\t\t\ttaskId: task.id,\n\t\t\t\ttaskName: task.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt,\n\t\t\t\tstatus: result.status === \"running\" ? \"failure\" : result.status,\n\t\t\t\toutput: result.output,\n\t\t\t\terror: result.error,\n\t\t\t});\n\n\t\t\t// Update runtime metadata on the task itself.\n\t\t\tthis.configManager.editTask(task.id, {\n\t\t\t\tenabled: task.enabled,\n\t\t\t\tworkspace: task.workspace,\n\t\t\t});\n\n\t\t\tthis.logger.log(\n\t\t\t\t\"info\",\n\t\t\t\t`Task completed: ${task.name} status=${result.status} duration=${durationMs}ms`\n\t\t\t);\n\t\t} catch (err: unknown) {\n\t\t\tconst finishedAt = new Date().toISOString();\n\t\t\tconst durationMs = Date.now() - startMs;\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tthis.logManager.appendLog({\n\t\t\t\ttaskId: task.id,\n\t\t\t\ttaskName: task.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt,\n\t\t\t\tstatus: \"failure\",\n\t\t\t\terror: message,\n\t\t\t});\n\t\t\tthis.logger.log(\n\t\t\t\t\"error\",\n\t\t\t\t`Task failed: ${task.name} (${task.id}) duration=${durationMs}ms error=${message}`\n\t\t\t);\n\t\t} finally {\n\t\t\tprocess.chdir(origCwd);\n\t\t}\n\t}\n\n\tgetStatus() {\n\t\tconst config = this.configManager.readConfig();\n\t\treturn {\n\t\t\trunning: this.running,\n\t\t\tpid: process.pid,\n\t\t\tuptimeSeconds: this.running ? Math.floor((Date.now() - this.startTime) / 1000) : null,\n\t\t\ttasksRegistered: config.tasks.length,\n\t\t\ttasksEnabled: config.tasks.filter((t) => t.enabled).length,\n\t\t\tpidFile: this.pidManager.getPidPath(),\n\t\t};\n\t}\n}\n\n// ─── Helpers (duplicated from v1 to keep v1 untouched) ─────────────────────\n\nfunction parseIntervalMs(schedule: string): number {\n\tconst match = /^every\\s+(\\d+)\\s*(s|sec|m|min|h|hr|d|day)s?$/i.exec(schedule);\n\tif (!match) return 0;\n\tconst [, numStr, unit] = match;\n\tconst num = Number.parseInt(numStr ?? \"0\", 10);\n\tswitch (unit?.toLowerCase()) {\n\t\tcase \"s\":\n\t\tcase \"sec\":\n\t\t\treturn num * 1000;\n\t\tcase \"m\":\n\t\tcase \"min\":\n\t\t\treturn num * 60 * 1000;\n\t\tcase \"h\":\n\t\tcase \"hr\":\n\t\t\treturn num * 60 * 60 * 1000;\n\t\tcase \"d\":\n\t\tcase \"day\":\n\t\t\treturn num * 24 * 60 * 60 * 1000;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nfunction matchesCron(expression: string, date: Date): boolean {\n\tconst parts = expression.trim().split(/\\s+/);\n\tif (parts.length < 5) return false;\n\tconst [minPart, hourPart, dayPart, monthPart, weekdayPart] = parts;\n\tif (!minPart || !hourPart || !dayPart || !monthPart || !weekdayPart) return false;\n\treturn (\n\t\tmatchCronField(minPart, date.getMinutes()) &&\n\t\tmatchCronField(hourPart, date.getHours()) &&\n\t\tmatchCronField(dayPart, date.getDate()) &&\n\t\tmatchCronField(monthPart, date.getMonth() + 1) &&\n\t\tmatchCronField(weekdayPart, date.getDay())\n\t);\n}\n\nfunction matchCronField(field: string, value: number): boolean {\n\tif (field === \"*\") return true;\n\tif (field.startsWith(\"*/\")) {\n\t\tconst step = Number.parseInt(field.slice(2), 10);\n\t\treturn step > 0 && value % step === 0;\n\t}\n\tconst values = field.split(\",\");\n\treturn values.some((v) => Number.parseInt(v, 10) === value);\n}\n\n// Export helpers for testing.\nexport { matchesCron, parseIntervalMs };\n","// MigrateToGlobal — scan known workspaces for legacy per-workspace\n// <workspace>/.serviceme/scheduled-tasks.json, migrate v1 → v2, append\n// to the global ~/.serviceme/scheduled-tasks.json, and DELETE the\n// original file. Failures (parse errors, schema issues) are recorded\n// in ~/.serviceme/migration-failures.json and the original is also\n// deleted — no half-migration state is left on disk.\n// See docs/architecture/skill-agent-v2-repo.md §14.3.a + §14.4 + §14.5.\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n\tScheduledTask,\n\tScheduledTasksConfig,\n\tScheduledTasksConfigV1,\n\tTaskWorkspaceRef,\n} from \"@serviceme/devtools-protocol\";\nimport { isScheduledTasksConfigV1, migrateV1ToV2 } from \"@serviceme/devtools-protocol\";\nimport { getMigrationFailuresPath, getScheduledTasksConfigPath } from \"../../paths/userHome\";\n\nexport interface MigrateToGlobalOptions {\n\t/**\n\t * Workspace paths to scan for legacy v1 files. May include the current\n\t * workspace + any persisted in `~/.serviceme/known-workspaces.json`.\n\t */\n\tworkspacePaths: string[];\n\t/**\n\t * Workspace context to apply to each scanned file. The scanner calls the\n\t * probe for each candidate path to populate path/name/git metadata.\n\t * Tests can inject a stub that returns synthetic data.\n\t */\n\tprobe?: (workspacePath: string) => Promise<TaskWorkspaceRef> | TaskWorkspaceRef;\n\t/**\n\t * Override the global config path. Tests use this; production should\n\t * leave it unset to get the standard ~/.serviceme/scheduled-tasks.json.\n\t */\n\tglobalConfigPath?: string;\n\t/**\n\t * Override the migration-failures path. Same convention.\n\t */\n\tmigrationFailuresPath?: string;\n}\n\nexport interface MigrationResult {\n\t/** Number of v1 files successfully migrated into the global config. */\n\tmigrated: number;\n\t/** Number of v1 files that failed to parse / migrate. */\n\tfailed: number;\n\t/** Task names that had to be disambiguated because they appeared in multiple workspaces. */\n\tconflicts: string[];\n\t/** Soft issues surfaced by migrateV1ToV2 (e.g. unknown taskType). */\n\tissues: string[];\n}\n\ninterface FailureEntry {\n\tworkspacePath: string;\n\tv1Path: string;\n\terror: string;\n\tat: string; // ISO timestamp\n}\n\nconst WORKSPACE_DIR = \".serviceme\";\nconst V1_FILENAME = \"scheduled-tasks.json\";\n\n/**\n * Default workspace probe: returns a minimal TaskWorkspaceRef derived from\n * the path (name = basename). The extension injects the real WorkspaceProbe\n * at startup; tests inject a stub.\n */\nfunction defaultProbe(workspacePath: string): TaskWorkspaceRef {\n\treturn {\n\t\tpath: workspacePath,\n\t\tname: path.basename(workspacePath) || workspacePath,\n\t};\n}\n\nfunction readV1Config(\n\tv1Path: string\n): { ok: true; config: ScheduledTasksConfigV1 } | { ok: false; error: string } {\n\tlet raw: string;\n\ttry {\n\t\traw = fs.readFileSync(v1Path, \"utf-8\");\n\t} catch (err) {\n\t\treturn { ok: false, error: `read failed: ${err instanceof Error ? err.message : String(err)}` };\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `JSON parse failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t};\n\t}\n\tif (!isScheduledTasksConfigV1(parsed)) {\n\t\treturn { ok: false, error: \"not a valid v1 scheduledTasksConfig\" };\n\t}\n\treturn { ok: true, config: parsed };\n}\n\nfunction safeDelete(filePath: string): void {\n\ttry {\n\t\tfs.unlinkSync(filePath);\n\t} catch {\n\t\t// Best-effort: file may have been already removed or never existed.\n\t}\n}\n\nfunction ensureDir(filePath: string): void {\n\tconst dir = path.dirname(filePath);\n\tif (!fs.existsSync(dir)) {\n\t\tfs.mkdirSync(dir, { recursive: true });\n\t}\n}\n\nfunction readJsonFile<T>(filePath: string): T | null {\n\tif (!fs.existsSync(filePath)) return null;\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(filePath, \"utf-8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction writeJsonFile(filePath: string, data: unknown): void {\n\tensureDir(filePath);\n\tfs.writeFileSync(filePath, JSON.stringify(data, null, \"\\t\"), \"utf-8\");\n}\n\n/**\n * Disambiguate task names that collide across workspaces by suffixing the\n * workspace name in brackets. Mutates a copy of `task` and returns it.\n */\nfunction disambiguateName(\n\ttask: ScheduledTask,\n\texistingNames: Set<string>,\n\tworkspaceName: string\n): ScheduledTask {\n\tif (!existingNames.has(task.name)) {\n\t\texistingNames.add(task.name);\n\t\treturn task;\n\t}\n\tconst base = `${task.name} [${workspaceName}]`;\n\tlet candidate = base;\n\tlet counter = 2;\n\twhile (existingNames.has(candidate)) {\n\t\tcandidate = `${base} (${counter})`;\n\t\tcounter += 1;\n\t}\n\texistingNames.add(candidate);\n\treturn { ...task, name: candidate };\n}\n\nexport class MigrateToGlobal {\n\t/**\n\t * Run the migration. Idempotent: each v1 file is deleted after processing,\n\t * so re-running this method on a partially-migrated state is safe (the\n\t * already-migrated files no longer exist; only the remainder is processed).\n\t */\n\tstatic async run(options: MigrateToGlobalOptions): Promise<MigrationResult> {\n\t\tconst globalConfigPath = options.globalConfigPath ?? getScheduledTasksConfigPath();\n\t\tconst migrationFailuresPath = options.migrationFailuresPath ?? getMigrationFailuresPath();\n\t\tconst probe = options.probe ?? defaultProbe;\n\n\t\t// Load or seed the global config.\n\t\tconst existing = readJsonFile<ScheduledTasksConfig>(globalConfigPath);\n\t\tconst baseConfig: ScheduledTasksConfig =\n\t\t\texisting && existing.version === 2 ? existing : { version: 2, tasks: [] };\n\t\tconst existingNames = new Set(baseConfig.tasks.map((t) => t.name));\n\n\t\t// Read prior failures (so we accumulate, not overwrite).\n\t\tconst priorFailures = readJsonFile<FailureEntry[]>(migrationFailuresPath) ?? [];\n\t\tconst failures: FailureEntry[] = [...priorFailures];\n\n\t\tlet migrated = 0;\n\t\tconst conflicts: string[] = [];\n\t\tconst issues: string[] = [];\n\n\t\tfor (const workspacePath of options.workspacePaths) {\n\t\t\tconst v1Path = path.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);\n\t\t\tif (!fs.existsSync(v1Path)) continue;\n\n\t\t\tconst v1 = readV1Config(v1Path);\n\t\t\tif (!v1.ok) {\n\t\t\t\tfailures.push({\n\t\t\t\t\tworkspacePath,\n\t\t\t\t\tv1Path,\n\t\t\t\t\terror: v1.error,\n\t\t\t\t\tat: new Date().toISOString(),\n\t\t\t\t});\n\t\t\t\t// DELETE the original — no half-migration state.\n\t\t\t\tsafeDelete(v1Path);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet workspaceRef: TaskWorkspaceRef;\n\t\t\ttry {\n\t\t\t\tworkspaceRef = await probe(workspacePath);\n\t\t\t} catch (err) {\n\t\t\t\tfailures.push({\n\t\t\t\t\tworkspacePath,\n\t\t\t\t\tv1Path,\n\t\t\t\t\terror: `probe failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\tat: new Date().toISOString(),\n\t\t\t\t});\n\t\t\t\tsafeDelete(v1Path);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst { config, issues: taskIssues } = migrateV1ToV2(v1.config, workspaceRef);\n\t\t\tissues.push(...taskIssues);\n\n\t\t\t// Disambiguate name conflicts.\n\t\t\tconst newTasks: ScheduledTask[] = config.tasks.map((t) => {\n\t\t\t\tif (existingNames.has(t.name)) {\n\t\t\t\t\tconflicts.push(t.name);\n\t\t\t\t\treturn disambiguateName(t, existingNames, workspaceRef.name);\n\t\t\t\t}\n\t\t\t\texistingNames.add(t.name);\n\t\t\t\treturn t;\n\t\t\t});\n\n\t\t\tbaseConfig.tasks.push(...newTasks);\n\t\t\tmigrated += 1;\n\n\t\t\t// DELETE the original — no half-migration state.\n\t\t\tsafeDelete(v1Path);\n\t\t}\n\n\t\t// Persist the new global config + failures.\n\t\tif (migrated > 0) {\n\t\t\t// Use TaskConfigManager's atomic write semantics via the public\n\t\t\t// listTasks + createTask path would lose ordering and re-validate\n\t\t\t// everything, which is wasteful. Write directly using the same\n\t\t\t// tmp → rename pattern the manager uses.\n\t\t\tensureDir(globalConfigPath);\n\t\t\tconst tmp = `${globalConfigPath}.tmp`;\n\t\t\tfs.writeFileSync(tmp, JSON.stringify(baseConfig, null, \"\\t\"), \"utf-8\");\n\t\t\tfs.renameSync(tmp, globalConfigPath);\n\t\t}\n\t\tif (failures.length > priorFailures.length) {\n\t\t\twriteJsonFile(migrationFailuresPath, failures);\n\t\t} else if (failures.length === 0 && priorFailures.length > 0) {\n\t\t\t// All prior failures cleared; remove the file.\n\t\t\tsafeDelete(migrationFailuresPath);\n\t\t}\n\n\t\treturn { migrated, failed: failures.length - priorFailures.length, conflicts, issues };\n\t}\n}\n","// WorkspaceProbe — auto-detects git metadata for a workspace path.\n// Used by extension startup and task creation to populate TaskWorkspaceRef.\n// See docs/architecture/skill-agent-v2-repo.md §14.3.c (workspace auto-probe).\n\nimport { spawn } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { TaskWorkspaceRef } from \"@serviceme/devtools-protocol\";\n\n/** Result of probing a single workspace. */\nexport interface ProbeResult {\n\t/** Always populated; empty fields are left undefined. */\n\tworkspace: TaskWorkspaceRef;\n\t/** null on success; one of the {@link ProbeError} tags on failure. */\n\terror: ProbeError | null;\n}\n\nexport type ProbeError = \"path-not-found\" | \"not-a-git-repo\" | \"git-timeout\" | \"git-error\";\n\nexport interface ProbeOptions {\n\t/** Timeout for each git invocation in ms. Default: 2000. */\n\ttimeoutMs?: number;\n\t/** Override the git binary (default: 'git'). Tests inject a stub. */\n\tgitBinary?: string;\n\t/**\n\t * Override the underlying runner. Receives the args + cwd and returns\n\t * { stdout, stderr, code } on success, or throws on timeout. The default\n\t * implementation spawns `gitBinary` with the given args.\n\t */\n\trunGit?: (\n\t\targs: string[],\n\t\tcwd: string\n\t) => Promise<{\n\t\tstdout: string;\n\t\tstderr: string;\n\t\tcode: number;\n\t}>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 2000;\n\nclass GitTimeoutError extends Error {\n\tconstructor() {\n\t\tsuper(\"git-timeout\");\n\t\tthis.name = \"GitTimeoutError\";\n\t}\n}\n\nfunction isTimeout(err: unknown): boolean {\n\tif (err instanceof GitTimeoutError) return true;\n\tif (err instanceof Error) {\n\t\treturn err.name === \"GitTimeoutError\" || err.message === \"git-timeout\";\n\t}\n\treturn false;\n}\n\nfunction defaultRunGit(\n\tgitBinary: string,\n\targs: string[],\n\tcwd: string,\n\ttimeoutMs: number\n): Promise<{ stdout: string; stderr: string; code: number }> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst child = spawn(gitBinary, args, {\n\t\t\tcwd,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tchild.kill(\"SIGTERM\");\n\t\t\treject(new GitTimeoutError());\n\t\t}, timeoutMs);\n\n\t\tchild.stdout.on(\"data\", (chunk) => {\n\t\t\tstdout += chunk.toString(\"utf-8\");\n\t\t});\n\t\tchild.stderr.on(\"data\", (chunk) => {\n\t\t\tstderr += chunk.toString(\"utf-8\");\n\t\t});\n\t\tchild.on(\"error\", (err) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\treject(err);\n\t\t});\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tresolve({ stdout, stderr, code: code ?? 0 });\n\t\t});\n\t});\n}\n\n/**\n * Probes a workspace for git metadata. Returns a {@link ProbeResult} with\n * `error: null` on success and an error tag on failure — the workspace\n * descriptor is always populated so callers can still surface a partial\n * record.\n */\nexport class WorkspaceProbe {\n\tprivate readonly timeoutMs: number;\n\tprivate readonly gitBinary: string;\n\tprivate readonly runGitFn: (\n\t\targs: string[],\n\t\tcwd: string\n\t) => Promise<{ stdout: string; stderr: string; code: number }>;\n\n\tconstructor(options: ProbeOptions = {}) {\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\tthis.gitBinary = options.gitBinary ?? \"git\";\n\t\tif (options.runGit) {\n\t\t\tthis.runGitFn = options.runGit;\n\t\t} else {\n\t\t\tconst binary = this.gitBinary;\n\t\t\tconst timeoutMs = this.timeoutMs;\n\t\t\tthis.runGitFn = (args, cwd) => defaultRunGit(binary, args, cwd, timeoutMs);\n\t\t}\n\t}\n\n\tasync probe(workspacePath: string): Promise<ProbeResult> {\n\t\tconst name = path.basename(workspacePath) || workspacePath;\n\n\t\tif (!workspacePath || !fs.existsSync(workspacePath)) {\n\t\t\treturn {\n\t\t\t\tworkspace: { path: workspacePath, name },\n\t\t\t\terror: \"path-not-found\",\n\t\t\t};\n\t\t}\n\n\t\tlet gitRemote: string | undefined;\n\t\ttry {\n\t\t\tconst remote = await this.runGitFn([\"remote\", \"get-url\", \"origin\"], workspacePath);\n\t\t\tif (remote.code === 0 && remote.stdout.trim()) {\n\t\t\t\tgitRemote = remote.stdout.trim();\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (isTimeout(err)) {\n\t\t\t\treturn {\n\t\t\t\t\tworkspace: { path: workspacePath, name, gitRemote },\n\t\t\t\t\terror: \"git-timeout\",\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tworkspace: { path: workspacePath, name },\n\t\t\t\terror: \"not-a-git-repo\",\n\t\t\t};\n\t\t}\n\n\t\tlet gitBranch: string | undefined;\n\t\ttry {\n\t\t\tconst branch = await this.runGitFn([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], workspacePath);\n\t\t\tif (branch.code === 0 && branch.stdout.trim()) {\n\t\t\t\tgitBranch = branch.stdout.trim();\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (isTimeout(err)) {\n\t\t\t\treturn {\n\t\t\t\t\tworkspace: { path: workspacePath, name, gitRemote, gitBranch },\n\t\t\t\t\terror: \"git-timeout\",\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tworkspace: { path: workspacePath, name, gitRemote },\n\t\t\t\terror: \"not-a-git-repo\",\n\t\t\t};\n\t\t}\n\n\t\t// If neither command produced output, treat as not-a-git-repo.\n\t\tif (!gitRemote && !gitBranch) {\n\t\t\treturn {\n\t\t\t\tworkspace: { path: workspacePath, name },\n\t\t\t\terror: \"not-a-git-repo\",\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tworkspace: {\n\t\t\t\tpath: workspacePath,\n\t\t\t\tname,\n\t\t\t\tgitRemote,\n\t\t\t\tgitBranch,\n\t\t\t\tlastSeenAt: new Date().toISOString(),\n\t\t\t},\n\t\t\terror: null,\n\t\t};\n\t}\n}\n","import type { SkillMarketplaceEntry } from \"@serviceme/devtools-protocol\";\nimport { createServicemeError } from \"@serviceme/devtools-protocol\";\nimport type { SkillDownloadFile } from \"./types\";\n\nexport interface SkillCatalog {\n\tskills: SkillMarketplaceEntry[];\n\tfetchedAt: string;\n}\n\nexport interface SkillCatalogClientOptions {\n\tfetchImpl?: typeof fetch;\n\tbaseUrl?: string;\n}\n\nexport class SkillCatalogClient {\n\tprivate readonly fetchImpl: typeof fetch;\n\tprivate readonly baseUrl?: string;\n\n\tconstructor(options: SkillCatalogClientOptions = {}) {\n\t\tthis.fetchImpl = options.fetchImpl ?? fetch;\n\t\tthis.baseUrl = options.baseUrl;\n\t}\n\n\tasync getCatalog(): Promise<SkillCatalog> {\n\t\tif (!this.baseUrl) {\n\t\t\tthrow createServicemeError(\"workspace_not_found\", \"Skill catalog baseUrl is not configured.\");\n\t\t}\n\n\t\tconst response = await this.fetchImpl(`${this.baseUrl}/api/v1/marketplace/skills`);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to fetch skills catalog: ${response.status}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as {\n\t\t\tskills?: SkillMarketplaceEntry[];\n\t\t};\n\t\treturn {\n\t\t\tskills: data.skills ?? [],\n\t\t\tfetchedAt: new Date().toISOString(),\n\t\t};\n\t}\n\n\tasync downloadSkill(remoteId: string): Promise<SkillDownloadFile[]> {\n\t\tif (!this.baseUrl) {\n\t\t\tthrow createServicemeError(\"workspace_not_found\", \"Skill catalog baseUrl is not configured.\");\n\t\t}\n\n\t\tconst response = await this.fetchImpl(\n\t\t\t`${this.baseUrl}/api/v1/marketplace/skills/download/${remoteId}`\n\t\t);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 404) {\n\t\t\t\tthrow createServicemeError(\"not_found\", `Skill '${remoteId}' not found`);\n\t\t\t}\n\t\t\tthrow new Error(`Failed to download skill ${remoteId}: ${response.status}`);\n\t\t}\n\n\t\tconst payload = (await response.json()) as {\n\t\t\tdata?: { files?: SkillDownloadFile[] };\n\t\t};\n\t\treturn payload.data?.files ?? [];\n\t}\n}\n","import type { SkillMutationRequest } from \"@serviceme/devtools-protocol\";\n\ninterface CatalogSkillLike {\n\tid: string;\n\thasScripts?: boolean;\n\thasHooks?: boolean;\n}\n\ninterface CatalogLike {\n\tskills: CatalogSkillLike[];\n\tfetchedAt: string;\n}\n\ninterface SkillStoreLike {\n\tnormalizeRemoteSkillId(remoteId: string): string;\n\tlistWorkspaceSkillIds(): Promise<string[]>;\n\tlistUserSkillIds(): Promise<string[]>;\n}\n\ninterface SkillCatalogClientLike {\n\tgetCatalog(): Promise<CatalogLike>;\n}\n\nexport interface SkillReconcilerDependencies {\n\tskillStore: SkillStoreLike;\n\tcatalogClient: SkillCatalogClientLike;\n}\n\nexport interface SkillMutateResult {\n\tstatus: \"success\" | \"blocked\" | \"requires_confirmation\";\n\tchanged: boolean;\n\tmessage?: string;\n\thasScripts?: boolean;\n\thasHooks?: boolean;\n}\n\nexport class SkillReconciler {\n\tconstructor(private readonly deps: SkillReconcilerDependencies) {}\n\n\tasync mutate(request: SkillMutationRequest): Promise<SkillMutateResult> {\n\t\tif (request.targetScope !== \"workspace\" && request.targetScope !== \"user\") {\n\t\t\tthrow new Error(`Invalid target scope: ${String(request.targetScope)}`);\n\t\t}\n\n\t\tif (\n\t\t\trequest.action === \"uninstall\" ||\n\t\t\trequest.action === \"move\" ||\n\t\t\trequest.action === \"removeExternal\"\n\t\t) {\n\t\t\treturn {\n\t\t\t\tstatus: \"success\",\n\t\t\t\tchanged: true,\n\t\t\t\tmessage: `Skill ${request.action} completed.`,\n\t\t\t};\n\t\t}\n\n\t\tif (request.action !== \"install\") {\n\t\t\treturn {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: `Skill action is not supported by bridge reconciler: ${request.action}`,\n\t\t\t};\n\t\t}\n\n\t\tconst catalog = await this.deps.catalogClient.getCatalog();\n\t\tconst remoteSkill = catalog.skills.find(\n\t\t\t(skill) => this.deps.skillStore.normalizeRemoteSkillId(skill.id) === request.skillId\n\t\t);\n\n\t\tif (!remoteSkill) {\n\t\t\treturn {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: \"Skill not found in catalog.\",\n\t\t\t};\n\t\t}\n\n\t\tif (!request.confirmed && (remoteSkill.hasScripts || remoteSkill.hasHooks)) {\n\t\t\treturn {\n\t\t\t\tstatus: \"requires_confirmation\",\n\t\t\t\tchanged: false,\n\t\t\t\thasScripts: Boolean(remoteSkill.hasScripts),\n\t\t\t\thasHooks: Boolean(remoteSkill.hasHooks),\n\t\t\t\tmessage: \"This skill contains executable scripts that require confirmation.\",\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: \"success\",\n\t\t\tchanged: true,\n\t\t\tmessage: \"Skill installed.\",\n\t\t};\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { SkillDownloadFile, SkillStoreFileSystem, SkillStoreOptions } from \"./types\";\n\nconst USER_SKILL_MARKER_FILE = \".serviceme-skill.json\";\nconst LEGACY_USER_SKILL_MARKER_FILE = \".ms-devtools-skill.json\";\nconst WORKSPACE_SKILLS_ROOT_RELATIVE = \".github/skills\";\nconst WORKSPACE_SKILLS_MARKER_RELATIVE = \".github/.serviceme-skills.yml\";\nconst SAFE_LOCAL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\n\nfunction assertSafeLocalSkillId(skillId: string): string {\n\tif (\n\t\ttypeof skillId !== \"string\" ||\n\t\tskillId.length === 0 ||\n\t\tskillId === \".\" ||\n\t\tskillId === \"..\" ||\n\t\tskillId.includes(\"/\") ||\n\t\tskillId.includes(\"\\\\\") ||\n\t\t!SAFE_LOCAL_ID_PATTERN.test(skillId)\n\t) {\n\t\tthrow new Error(`Invalid skill id: ${skillId}`);\n\t}\n\n\treturn skillId;\n}\n\nexport class SkillStore {\n\tprivate readonly workspacePath: string;\n\tprivate readonly userSkillsRoot: string;\n\tprivate readonly fileSystem: SkillStoreFileSystem;\n\n\tconstructor(options: SkillStoreOptions) {\n\t\tthis.workspacePath = options.workspacePath;\n\t\tthis.userSkillsRoot = options.userSkillsRoot;\n\t\tthis.fileSystem = options.fileSystem ?? fs;\n\t}\n\n\tnormalizeRemoteSkillId(remoteId: string): string {\n\t\tif (remoteId.startsWith(\"official/\")) {\n\t\t\treturn assertSafeLocalSkillId(remoteId.slice(\"official/\".length));\n\t\t}\n\t\tif (remoteId.startsWith(\"community/\")) {\n\t\t\tconst lastSlash = remoteId.lastIndexOf(\"/\");\n\t\t\treturn assertSafeLocalSkillId(remoteId.slice(lastSlash + 1));\n\t\t}\n\t\treturn assertSafeLocalSkillId(remoteId);\n\t}\n\n\tgetWorkspaceSkillPath(skillId: string): string {\n\t\treturn `${WORKSPACE_SKILLS_ROOT_RELATIVE}/${skillId}`;\n\t}\n\n\tgetWorkspaceMarkerPath(): string {\n\t\treturn WORKSPACE_SKILLS_MARKER_RELATIVE;\n\t}\n\n\tgetUserSkillPath(skillId: string): string {\n\t\treturn path.join(this.userSkillsRoot, skillId);\n\t}\n\n\tasync listWorkspaceSkillIds(): Promise<string[]> {\n\t\tconst skillsRootPath = path.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE);\n\t\ttry {\n\t\t\tconst entries = await this.fileSystem.readdir(skillsRootPath, {\n\t\t\t\twithFileTypes: true,\n\t\t\t});\n\t\t\treturn entries\n\t\t\t\t.filter((entry) => entry.isDirectory() && !entry.name.startsWith(\".\"))\n\t\t\t\t.map((entry) => entry.name)\n\t\t\t\t.sort();\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tasync listUserSkillIds(): Promise<string[]> {\n\t\ttry {\n\t\t\tconst entries = await this.fileSystem.readdir(this.userSkillsRoot, {\n\t\t\t\twithFileTypes: true,\n\t\t\t});\n\t\t\treturn entries\n\t\t\t\t.filter((entry) => entry.isDirectory() && !entry.name.startsWith(\".\"))\n\t\t\t\t.map((entry) => entry.name)\n\t\t\t\t.sort();\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tasync writeManagedUserSkillMarker(skillId: string): Promise<void> {\n\t\tconst targetDir = this.getUserSkillPath(skillId);\n\t\tawait this.fileSystem.mkdir(targetDir, { recursive: true });\n\t\tawait this.fileSystem.writeFile(\n\t\t\tpath.join(targetDir, USER_SKILL_MARKER_FILE),\n\t\t\tJSON.stringify({ skillId, installedBy: \"serviceme\" }, null, 2),\n\t\t\t\"utf-8\"\n\t\t);\n\t}\n\n\tasync isManagedUserSkill(skillId: string): Promise<boolean> {\n\t\tawait this.migrateLegacyUserSkillMarker(skillId);\n\t\ttry {\n\t\t\tconst marker = await this.fileSystem.readFile(\n\t\t\t\tpath.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),\n\t\t\t\t\"utf-8\"\n\t\t\t);\n\t\t\tconst parsed = JSON.parse(marker) as { skillId?: string };\n\t\t\treturn parsed.skillId === skillId;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * One-time migration: the user-scope skill marker used to be named\n\t * `.ms-devtools-skill.json`. If the new `.serviceme-skill.json` doesn't\n\t * exist yet but the legacy marker does, copy it forward so an existing\n\t * skill doesn't lose its \"managed\" status.\n\t */\n\tprivate async migrateLegacyUserSkillMarker(skillId: string): Promise<void> {\n\t\tconst targetDir = this.getUserSkillPath(skillId);\n\t\tconst newPath = path.join(targetDir, USER_SKILL_MARKER_FILE);\n\t\tconst legacyPath = path.join(targetDir, LEGACY_USER_SKILL_MARKER_FILE);\n\t\ttry {\n\t\t\tawait this.fileSystem.readFile(newPath, \"utf-8\");\n\t\t\treturn;\n\t\t} catch {\n\t\t\t// new marker missing — check the legacy marker below\n\t\t}\n\t\ttry {\n\t\t\tconst legacyContent = await this.fileSystem.readFile(legacyPath, \"utf-8\");\n\t\t\tawait this.fileSystem.writeFile(newPath, legacyContent, \"utf-8\");\n\t\t} catch {\n\t\t\t// legacy marker doesn't exist either — nothing to migrate\n\t\t}\n\t}\n\n\tasync writeSkillFiles(\n\t\tskillId: string,\n\t\tscope: \"workspace\" | \"user\",\n\t\tfiles: SkillDownloadFile[]\n\t): Promise<void> {\n\t\tconst root =\n\t\t\tscope === \"workspace\"\n\t\t\t\t? path.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE)\n\t\t\t\t: this.userSkillsRoot;\n\t\tconst targetDir = path.join(root, skillId);\n\t\tawait this.fileSystem.mkdir(targetDir, { recursive: true });\n\n\t\tfor (const file of files) {\n\t\t\tconst filePath = path.join(targetDir, file.path);\n\t\t\tawait this.fileSystem.mkdir(path.dirname(filePath), { recursive: true });\n\t\t\tawait this.fileSystem.writeFile(filePath, file.content, \"utf-8\");\n\t\t\tif (file.executable) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.fileSystem.chmod(filePath, 0o755);\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore chmod failures on unsupported environments\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { GitClient } from \"../git-client\";\nimport { getRepoDir } from \"../paths/userHome\";\nimport type { SkillFile } from \"../skill-store/types\";\nimport type { SubmitValidationRequest, SubmitValidationResponse } from \"./types\";\nimport { SubmitError } from \"./types\";\n\n// Re-export so consumers (bridge handlers, CLI commands) can detect\n// SubmitError via `instanceof` without reaching into the internal\n// `./types` module.\nexport { SubmitError } from \"./types\";\n\n/**\n * Skill & Agent v2 — SubmitClient (M4)\n *\n * SubmitClient orchestrates the \"validate then push\" flow described\n * in docs/architecture/skill-agent-v2-repo.md §5.7:\n *\n * 1. **validate** — POST the candidate files to the server's\n * `/api/v1/skills/validate` endpoint (5 deny reasons: repo_not_\n * writable, file_too_large, path_traversal, invalid_frontmatter,\n * name_conflict).\n * 2. **write** — materialize the files into the local repo clone at\n * `~/.serviceme/repos/<repoId>/skills/<name>/` (or `agents/`).\n * 3. **commit** — `git add . && git commit -m \"feat(skills): add <name>\"`.\n * 4. **push** — `git push origin <branch>` via the server proxy.\n *\n * The client is intentionally thin: it does NOT do its own validation\n * (the server is the gate), and it does NOT cache anything across\n * calls. The single source of truth for \"is this submission allowed?\"\n * is the server's validate endpoint.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §5.7 SubmitClient\n */\n\nexport interface SubmitOptions {\n\t/** Override the server's validate URL. Defaults to `http://localhost:3000/api/v1`. */\n\tserverBaseUrl?: string;\n\t/** Override the branch to push. Defaults to the repo's `branch` field. */\n\tbranch?: string;\n\t/** Skip the actual push (for tests + dry-runs). When true, step 4 returns a synthetic PushResult. */\n\tskipPush?: boolean;\n}\n\nexport interface SubmitResult {\n\trepoId: string;\n\tskillName: string;\n\tcommitSha: string;\n\tpushedRef?: string;\n\tpushedSha?: string;\n}\n\nexport interface SubmitClientOptions {\n\tgitClient: GitClient;\n\t/** Lookup the default branch for a repo (config-driven). */\n\tgetRepoBranch?: (repoId: string) => string | undefined;\n\t/** HTTP fetch impl (defaults to the global `fetch`). */\n\tfetcher?: typeof fetch;\n\t/** Default server base URL when no override is supplied. */\n\tdefaultServerBaseUrl?: string;\n}\n\nexport class SubmitClient {\n\tprivate readonly git: GitClient;\n\tprivate readonly getRepoBranch: (repoId: string) => string | undefined;\n\tprivate readonly fetcher: typeof fetch;\n\tprivate readonly defaultServerBaseUrl: string;\n\n\tconstructor(opts: SubmitClientOptions) {\n\t\tthis.git = opts.gitClient;\n\t\tthis.getRepoBranch = opts.getRepoBranch ?? (() => undefined);\n\t\tthis.fetcher = opts.fetcher ?? (globalThis.fetch as typeof fetch);\n\t\tthis.defaultServerBaseUrl = opts.defaultServerBaseUrl ?? \"http://localhost:3000\";\n\t}\n\n\t/**\n\t * Validate-only path. Useful for the UI's \"Save Draft\" flow which\n\t * wants to surface validation errors without committing or pushing.\n\t */\n\tasync validate(req: SubmitValidationRequest): Promise<SubmitValidationResponse> {\n\t\tconst url = `${this.defaultServerBaseUrl}/api/v1/skills/validate`;\n\t\tconst res = await this.fetcher(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(req),\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tthrow new SubmitError(\"network_error\", `validate request failed: HTTP ${res.status}`);\n\t\t}\n\t\treturn (await res.json()) as SubmitValidationResponse;\n\t}\n\n\t/**\n\t * Full submit pipeline. Throws `SubmitError` on:\n\t * - validate deny (reason echoed)\n\t * - network failure (network_error)\n\t * - local write failure\n\t * - commit/push failure\n\t */\n\tasync submit(\n\t\trepoId: string,\n\t\tskillName: string,\n\t\tfiles: SkillFile[],\n\t\topts: SubmitOptions = {}\n\t): Promise<SubmitResult> {\n\t\t// (1) Validate\n\t\tconst v = await this.validate({ repoId, skillName, files });\n\t\tif (!v.allow) {\n\t\t\tthrow new SubmitError(v.reason ?? \"unknown\", v.detail ?? \"validation denied\");\n\t\t}\n\n\t\t// (2) Write files into the local repo clone\n\t\tconst localRepoPath = getRepoDir(repoId);\n\t\tconst targetDir = path.join(localRepoPath, \"skills\", skillName);\n\t\tawait fs.mkdir(targetDir, { recursive: true });\n\t\tfor (const f of files) {\n\t\t\tconst full = path.join(targetDir, f.path);\n\t\t\tawait fs.mkdir(path.dirname(full), { recursive: true });\n\t\t\tconst tmp = `${full}.${process.pid}.${Date.now()}.tmp`;\n\t\t\tawait fs.writeFile(tmp, f.content, \"utf8\");\n\t\t\tawait fs.rename(tmp, full);\n\t\t}\n\n\t\t// (3) Commit (convention: `feat(skills): add <name>`)\n\t\tconst commitMessage = `feat(skills): add ${skillName}`;\n\t\tconst { commitSha } = await this.git.commit(localRepoPath, commitMessage);\n\n\t\t// (4) Push\n\t\tlet pushedRef: string | undefined;\n\t\tlet pushedSha: string | undefined;\n\t\tif (!opts.skipPush) {\n\t\t\tconst branch = opts.branch ?? this.getRepoBranch(repoId) ?? \"main\";\n\t\t\tconst pushResult = await this.git.push(repoId, localRepoPath, branch);\n\t\t\tpushedRef = pushResult.ref;\n\t\t\tpushedSha = pushResult.commitSha;\n\t\t}\n\n\t\treturn {\n\t\t\trepoId,\n\t\t\tskillName,\n\t\t\tcommitSha,\n\t\t\tpushedRef,\n\t\t\tpushedSha,\n\t\t};\n\t}\n}\n","/**\n * Skill & Agent v2 — SubmitClient Types (M4)\n *\n * Mirrors the server's POST /api/v1/skills/validate contract (M2\n * SubmitApi). Defined here as a separate types file so the test\n * fixtures + the client can both import without circular deps.\n */\n\n/** Reasons the server may deny a submission. */\nexport type DenyReason =\n\t| \"repo_not_writable\"\n\t| \"file_too_large\"\n\t| \"path_traversal\"\n\t| \"invalid_frontmatter\"\n\t| \"name_conflict\"\n\t/** Local-client-side errors that don't come from the server. */\n\t| \"network_error\"\n\t| \"write_error\"\n\t| \"commit_error\"\n\t| \"push_error\"\n\t| \"unknown\";\n\n/** Request payload. Identical to the server's SubmitValidationRequest. */\nexport interface SubmitValidationRequest {\n\trepoId: string;\n\tskillName: string;\n\tfiles: Array<{ path: string; content: string }>;\n}\n\n/** Response payload. Identical to the server's SubmitValidationResponse. */\nexport interface SubmitValidationResponse {\n\tallow: boolean;\n\treason?: DenyReason;\n\tdetail?: string;\n}\n\n/** Sentinel error thrown by SubmitClient.submit() / validate(). */\nexport class SubmitError extends Error {\n\treadonly reason: DenyReason;\n\treadonly detail: string;\n\treadonly status?: number;\n\n\tconstructor(reason: DenyReason, detail: string, options?: { status?: number }) {\n\t\tsuper(`submit failed (${reason}): ${detail}`);\n\t\tthis.name = \"SubmitError\";\n\t\tthis.reason = reason;\n\t\tthis.detail = detail;\n\t\tthis.status = options?.status;\n\t}\n}\n","/**\n * toolbox sort + dedup — pure helpers, no I/O.\n *\n * The Extension's `ToolBoxService` keeps a stable `order` index and\n * surfaces \"recently used\" via the `lastUsedAt` ISO timestamp. These\n * helpers codify that algorithm so the CLI and Extension agree on the\n * \"最近使用置顶\" UX.\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `sort.ts 排序与去重纯函数`\n */\n\nimport type { ExternalTool } from \"@serviceme/devtools-protocol\";\n\n/**\n * Parsed timestamp from `lastUsedAt`. Returns 0 when the field is\n * missing or unparseable (so the entry falls below entries with a\n * real timestamp).\n */\nfunction lastUsedTimestamp(tool: ExternalTool): number {\n\tif (!tool.lastUsedAt) return 0;\n\tconst ms = Date.parse(tool.lastUsedAt);\n\treturn Number.isFinite(ms) ? ms : 0;\n}\n\n/**\n * Sort by \"recently used\" — entries with newer `lastUsedAt` float to\n * the top, entries with no `lastUsedAt` sort to the bottom (preserving\n * their relative `order` index when both are absent).\n *\n * Ties (same `lastUsedAt` or both missing) are broken by `order`, then\n * by `id` for determinism.\n */\nexport function sortByRecentFirst(tools: readonly ExternalTool[]): ExternalTool[] {\n\treturn [...tools].sort((a, b) => {\n\t\tconst tsA = lastUsedTimestamp(a);\n\t\tconst tsB = lastUsedTimestamp(b);\n\t\tif (tsA !== tsB) return tsB - tsA;\n\t\tconst orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n\t\tconst orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n\t\tif (orderA !== orderB) return orderA - orderB;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n}\n\n/**\n * Sort by user-defined `order` field. Entries without `order` are\n * appended in their input order (stable sort via `id` tiebreaker).\n */\nexport function sortByUserOrder(tools: readonly ExternalTool[]): ExternalTool[] {\n\treturn [...tools].sort((a, b) => {\n\t\tconst orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n\t\tconst orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n\t\tif (orderA !== orderB) return orderA - orderB;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n}\n\n/**\n * Deduplicate by `id`. The first occurrence wins; later ones are\n * discarded. Used when merging built-in + user seeds so duplicates\n * from the on-disk file don't shadow the built-in defaults.\n */\nexport function dedupeById(tools: readonly ExternalTool[]): ExternalTool[] {\n\tconst seen = new Set<string>();\n\tconst out: ExternalTool[] = [];\n\tfor (const tool of tools) {\n\t\tif (seen.has(tool.id)) continue;\n\t\tseen.add(tool.id);\n\t\tout.push(tool);\n\t}\n\treturn out;\n}\n\n/**\n * Merge built-in defaults with user-stored tools. Defaults keep\n * `isDefault: true` and their `order`; user tools are appended with\n * their stored metadata. Output is sorted by user order.\n */\nexport function mergeWithDefaults(\n\tdefaults: readonly ExternalTool[],\n\tuserTools: readonly ExternalTool[]\n): ExternalTool[] {\n\tconst defaultIds = new Set(defaults.map((d) => d.id));\n\tconst uniqueUserTools = userTools.filter((t) => !defaultIds.has(t.id));\n\treturn sortByUserOrder([...defaults, ...uniqueUserTools]);\n}\n\n/**\n * Re-number `order` for a list of tool ids. Used by\n * `toolbox.update` when the user drags-and-drops entries in the UI.\n */\nexport function reindexOrder(\n\ttools: ExternalTool[],\n\tnewOrderIds: readonly string[]\n): ExternalTool[] {\n\tconst orderMap = new Map<string, number>();\n\tfor (const [index, id] of newOrderIds.entries()) {\n\t\torderMap.set(id, index);\n\t}\n\tfor (const tool of tools) {\n\t\tconst next = orderMap.get(tool.id);\n\t\tif (next !== undefined) tool.order = next;\n\t}\n\treturn tools;\n}\n\n/**\n * Stamp `lastUsedAt` on the targeted tool (clones the array). Returns\n * a new array; the input is left untouched.\n */\nexport function touchLastUsedAt(\n\ttools: readonly ExternalTool[],\n\tid: string,\n\twhen: Date = new Date()\n): ExternalTool[] {\n\treturn tools.map((tool) => (tool.id === id ? { ...tool, lastUsedAt: when.toISOString() } : tool));\n}\n","/**\n * ToolboxStore — JSON persistence for the toolbox entries.\n *\n * Two scopes:\n * - `user` → `~/.serviceme/toolbox.json`\n * - `workspace` → `<cwd>/.github/.serviceme-toolbox.json`\n *\n * Both files share the `PersistedToolbox` shape and the same atomic\n * write pattern (tmp + rename + fsync) as `IdentityStore`. Concurrent\n * writers are serialized via a mkdir-based file lock (Phase 6+ may\n * upgrade to `proper-lockfile`).\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `ToolboxStore.ts JSON 持久化(user + workspace scope)`\n * - `3.功能拆分.md` §3 — toolbox wire shape\n */\n\nimport * as fsp from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nimport type { ExternalTool, ToolboxScope } from \"@serviceme/devtools-protocol\";\n\nimport { getToolboxJsonPath } from \"../paths/userHome\";\n\nimport { mergeWithDefaults, sortByUserOrder } from \"./sort\";\nimport {\n\tBUILTIN_DEFAULT_TOOLS,\n\ttype DefaultToolSeed,\n\ttype PersistedToolbox,\n\ttype ResolvedToolbox,\n\tTOOLBOX_JSON_SCHEMA_VERSION,\n} from \"./types\";\n\nconst FILE_MODE = 0o600;\nconst LOCK_DIR_MODE = 0o700;\nconst DEFAULT_LOCK_TIMEOUT_MS = 5000;\nconst DEFAULT_LOCK_RETRY_MS = 25;\nconst TMP_SUFFIX = \".tmp\";\nexport const WORKSPACE_TOOLBOX_RELATIVE_PATH = path.join(\".github\", \".serviceme-toolbox.json\");\nconst LEGACY_WORKSPACE_TOOLBOX_FILENAME = \".ms-devtools-toolbox.json\";\n\n/**\n * One-time migration: the workspace-scope toolbox file used to be named\n * `.ms-devtools-toolbox.json`. If the new `.serviceme-toolbox.json` doesn't\n * exist yet but the legacy file does (in the same directory), rename it\n * forward so existing toolbox entries aren't silently lost.\n */\nasync function migrateLegacyWorkspaceToolboxFile(filePath: string | null): Promise<void> {\n\tif (!filePath) return;\n\tconst legacyPath = path.join(path.dirname(filePath), LEGACY_WORKSPACE_TOOLBOX_FILENAME);\n\tif (legacyPath === filePath) return;\n\ttry {\n\t\tawait fsp.access(filePath);\n\t\treturn;\n\t} catch {\n\t\t// new file missing — check the legacy path below\n\t}\n\ttry {\n\t\tawait fsp.rename(legacyPath, filePath);\n\t} catch {\n\t\t// legacy file doesn't exist either — nothing to migrate\n\t}\n}\n\nexport interface ToolboxFileBackend {\n\tread(filePath: string): Promise<PersistedToolbox | null>;\n\twrite(filePath: string, payload: PersistedToolbox): Promise<void>;\n\texists(filePath: string): Promise<boolean>;\n}\n\nexport interface ToolboxStoreOptions {\n\t/** Override the user-scope file path (default: `getToolboxJsonPath()`). */\n\tuserFilePath?: string;\n\t/** Override the workspace-scope file path resolver (default: cwd-relative). */\n\tresolveWorkspacePath?: () => string | null;\n\t/** Injectable built-in tool seeds (default: `BUILTIN_DEFAULT_TOOLS`). */\n\tdefaultTools?: readonly DefaultToolSeed[];\n\thooks?: ToolboxStoreHooks;\n\tbackend?: ToolboxFileBackend;\n\tlockTimeoutMs?: number;\n\tlockRetryMs?: number;\n}\n\nexport interface ToolboxStoreHooks {\n\tbeforeWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;\n\tafterWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;\n}\n\nexport class FsToolboxFileBackend implements ToolboxFileBackend {\n\tasync exists(filePath: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fsp.access(filePath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync read(filePath: string): Promise<PersistedToolbox | null> {\n\t\tlet buf: string;\n\t\ttry {\n\t\t\tbuf = await fsp.readFile(filePath, \"utf8\");\n\t\t} catch (err) {\n\t\t\tif (isNodeError(err) && err.code === \"ENOENT\") return null;\n\t\t\tthrow err;\n\t\t}\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(buf) as unknown;\n\t\t\treturn coercePersistedToolbox(parsed);\n\t\t} catch {\n\t\t\t// File exists but is unparseable JSON or fails schema validation\n\t\t\t// (e.g. hand-edited, written by an older/incompatible schema, or\n\t\t\t// truncated by a crash). Quarantine it as a timestamped `.bak`\n\t\t\t// sibling and fall back to an empty toolbox (defaults still\n\t\t\t// merge in via `ToolboxStore.read()`) rather than crashing the\n\t\t\t// whole \"get external tools\" request.\n\t\t\tawait this.backupCorruptedFile(filePath);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate async backupCorruptedFile(filePath: string): Promise<void> {\n\t\ttry {\n\t\t\tconst backupPath = `${filePath}.corrupted.${Date.now()}.bak`;\n\t\t\tawait fsp.copyFile(filePath, backupPath);\n\t\t} catch {\n\t\t\t// Best-effort backup — never let backup failure mask the real recovery.\n\t\t}\n\t}\n\n\tasync write(filePath: string, payload: PersistedToolbox): Promise<void> {\n\t\tawait fsp.mkdir(path.dirname(filePath), { recursive: true });\n\t\tconst tmpPath = `${filePath}${TMP_SUFFIX}`;\n\t\tconst bytes = Buffer.from(JSON.stringify(payload, null, \"\\t\"), \"utf8\");\n\t\tawait fsp.rm(tmpPath, { force: true });\n\t\tconst handle = await fsp.open(tmpPath, \"w\", FILE_MODE);\n\t\ttry {\n\t\t\tawait handle.writeFile(bytes);\n\t\t\tawait handle.sync();\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait fsp.rename(tmpPath, filePath);\n\t\tawait fsp.chmod(filePath, FILE_MODE).catch(() => undefined);\n\t}\n}\n\nfunction coercePersistedToolbox(parsed: unknown): PersistedToolbox {\n\tif (typeof parsed !== \"object\" || parsed === null) {\n\t\tthrow new Error(\"toolbox.json: top-level must be an object\");\n\t}\n\tconst obj = parsed as Record<string, unknown>;\n\tconst version = obj.version;\n\tif (version !== TOOLBOX_JSON_SCHEMA_VERSION) {\n\t\tthrow new Error(`toolbox.json: unsupported schema version ${String(version)}`);\n\t}\n\tif (!Array.isArray(obj.tools)) {\n\t\tthrow new Error(\"toolbox.json: 'tools' must be an array\");\n\t}\n\treturn { version, tools: obj.tools as ExternalTool[] };\n}\n\nfunction isNodeError(value: unknown): value is NodeJS.ErrnoException {\n\treturn value instanceof Error && typeof (value as { code?: unknown }).code === \"string\";\n}\n\nclass ToolboxFileLock {\n\tprivate readonly dirPath: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly retryMs: number;\n\tprivate acquired = false;\n\n\tconstructor(filePath: string, timeoutMs: number, retryMs: number) {\n\t\tthis.dirPath = `${filePath}.lock`;\n\t\tthis.timeoutMs = timeoutMs;\n\t\tthis.retryMs = retryMs;\n\t}\n\n\tasync acquire(): Promise<void> {\n\t\tconst start = Date.now();\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tawait fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });\n\t\t\t\tthis.acquired = true;\n\t\t\t\treturn;\n\t\t\t} catch (err) {\n\t\t\t\tif (!isNodeError(err) || err.code !== \"EEXIST\") throw err;\n\t\t\t\tif (Date.now() - start >= this.timeoutMs) {\n\t\t\t\t\tthrow new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);\n\t\t\t\t}\n\t\t\t\tawait delay(this.retryMs);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync release(): Promise<void> {\n\t\tif (!this.acquired) return;\n\t\tthis.acquired = false;\n\t\tawait fsp.rm(this.dirPath, { recursive: true, force: true });\n\t}\n}\n\nfunction defaultWorkspacePath(): string | null {\n\t// The store caller (ToolboxCore) supplies a cwd, but tests want a\n\t// stable default. Return null when the env var says \"no workspace\".\n\tif (process.env.SERVICEME_NO_WORKSPACE_TOOLBOX === \"1\") return null;\n\treturn path.join(process.cwd(), WORKSPACE_TOOLBOX_RELATIVE_PATH);\n}\n\nexport class ToolboxStore {\n\tprivate readonly userFilePath: string;\n\tprivate readonly resolveWorkspacePath: () => string | null;\n\tprivate readonly defaults: readonly DefaultToolSeed[];\n\tprivate readonly hooks: ToolboxStoreHooks;\n\tprivate readonly backend: ToolboxFileBackend;\n\tprivate readonly lockTimeoutMs: number;\n\tprivate readonly lockRetryMs: number;\n\n\tconstructor(opts: ToolboxStoreOptions = {}) {\n\t\tthis.userFilePath = opts.userFilePath ?? getToolboxJsonPath();\n\t\tthis.resolveWorkspacePath = opts.resolveWorkspacePath ?? defaultWorkspacePath;\n\t\tthis.defaults = opts.defaultTools ?? BUILTIN_DEFAULT_TOOLS;\n\t\tthis.hooks = opts.hooks ?? {};\n\t\tthis.backend = opts.backend ?? new FsToolboxFileBackend();\n\t\tthis.lockTimeoutMs = opts.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;\n\t\tthis.lockRetryMs = opts.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;\n\t}\n\n\t/** Read a scope; returns the resolved toolbox (with defaults merged when empty). */\n\tasync read(scope: ToolboxScope): Promise<ResolvedToolbox> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (scope === \"workspace\") {\n\t\t\tawait migrateLegacyWorkspaceToolboxFile(filePath);\n\t\t}\n\t\tconst stored = filePath ? await this.backend.read(filePath) : null;\n\t\tconst storedTools = stored?.tools ?? [];\n\t\tconst defaults = this.defaults.map((seed, index) => toDefaultTool(seed, index));\n\t\tconst merged = mergeWithDefaults(defaults, storedTools);\n\t\tconst userOnly = storedTools.filter((t) => !this.defaults.some((d) => d.id === t.id));\n\t\tvoid userOnly;\n\t\treturn { scope, tools: merged };\n\t}\n\n\t/**\n\t * Atomic write under a file lock. Replaces the entire `tools` array\n\t * with the provided snapshot.\n\t */\n\tasync write(scope: ToolboxScope, tools: readonly ExternalTool[]): Promise<void> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (!filePath) {\n\t\t\tthrow new Error(`Cannot write toolbox scope '${scope}': file path is unavailable`);\n\t\t}\n\t\tif (scope === \"workspace\") {\n\t\t\tawait migrateLegacyWorkspaceToolboxFile(filePath);\n\t\t}\n\t\tconst payload: PersistedToolbox = {\n\t\t\tversion: TOOLBOX_JSON_SCHEMA_VERSION,\n\t\t\ttools: sortByUserOrder([...tools]),\n\t\t};\n\t\tawait this.hooks.beforeWrite?.(scope, payload);\n\t\tconst lock = new ToolboxFileLock(filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tawait this.backend.write(filePath, payload);\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t\tawait this.hooks.afterWrite?.(scope, payload);\n\t}\n\n\t/**\n\t * Read-modify-write under the file lock. The mutator receives the\n\t * current user-only list (defaults not included) and returns the\n\t * replacement list. Throwing inside the mutator aborts the write.\n\t */\n\tasync mutate(\n\t\tscope: ToolboxScope,\n\t\tmutator: (current: ExternalTool[]) => Promise<ExternalTool[]>\n\t): Promise<ExternalTool[]> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (!filePath) {\n\t\t\tthrow new Error(`Cannot mutate toolbox scope '${scope}': file path is unavailable`);\n\t\t}\n\t\tif (scope === \"workspace\") {\n\t\t\tawait migrateLegacyWorkspaceToolboxFile(filePath);\n\t\t}\n\t\tconst lock = new ToolboxFileLock(filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tconst stored = await this.backend.read(filePath);\n\t\t\tconst storedTools = stored?.tools ?? [];\n\t\t\tconst defaultIds = new Set(this.defaults.map((d) => d.id));\n\t\t\tconst userTools = storedTools.filter((t) => !defaultIds.has(t.id));\n\t\t\tconst next = await mutator(userTools);\n\t\t\tconst payload: PersistedToolbox = {\n\t\t\t\tversion: TOOLBOX_JSON_SCHEMA_VERSION,\n\t\t\t\ttools: sortByUserOrder([...next]),\n\t\t\t};\n\t\t\tawait this.hooks.beforeWrite?.(scope, payload);\n\t\t\tawait this.backend.write(filePath, payload);\n\t\t\tawait this.hooks.afterWrite?.(scope, payload);\n\t\t\treturn next;\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t}\n\n\t/** Wipe a scope entirely (used by `toolbox.remove --all` extensions). */\n\tasync clear(scope: ToolboxScope): Promise<void> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (!filePath) return;\n\t\tawait fsp.rm(filePath, { force: true });\n\t}\n\n\t/** Test seam — resolve the user-scope file path. */\n\tgetUserFilePath(): string {\n\t\treturn this.userFilePath;\n\t}\n\n\t/** Test seam — resolve the workspace-scope file path (or null when disabled). */\n\tgetWorkspaceFilePath(): string | null {\n\t\treturn this.resolveWorkspacePath();\n\t}\n\n\tprivate filePathFor(scope: ToolboxScope): string | null {\n\t\treturn scope === \"user\" ? this.userFilePath : this.resolveWorkspacePath();\n\t}\n}\n\nfunction toDefaultTool(seed: DefaultToolSeed, order: number): ExternalTool {\n\treturn {\n\t\tid: seed.id,\n\t\tname: seed.name,\n\t\tdescription: seed.description,\n\t\ticon: seed.icon,\n\t\turl: seed.url,\n\t\tisDefault: true,\n\t\torder,\n\t\tscope: \"user\",\n\t};\n}\n","/**\n * toolbox types — internal-only data shapes.\n *\n * The public data model lives in `@serviceme/devtools-protocol/toolbox`\n * (`ExternalTool`, `ToolboxScope`, `ToolboxList`). The types below are\n * the persisted on-disk shape and the patch helpers, scoped to the\n * toolbox domain only.\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `toolbox/types.ts`\n */\n\nimport type { ExternalTool, ToolboxScope } from \"@serviceme/devtools-protocol\";\n\n/** Schema version of the on-disk toolbox JSON files. Bumped on breaking changes. */\nexport const TOOLBOX_JSON_SCHEMA_VERSION = 1;\n\nexport interface PersistedToolbox {\n\tversion: number;\n\ttools: ExternalTool[];\n}\n\n/** Patch payload accepted by `ToolboxCore.update` (mirrors `ExternalToolPatch`). */\nexport type ToolboxPatch = Partial<Omit<ExternalTool, \"id\" | \"isDefault\">>;\n\n/**\n * Built-in (default) tool seeds. Mirrors the Extension's\n * `apps/extension/src/config/external-tools.ts`. The Core stores these\n * verbatim and never mutates them — the Extension's `ToolBoxService`\n * runs the same merge on top of the Core's view.\n */\nexport interface DefaultToolSeed {\n\tid: string;\n\tname: string;\n\tdescription: string;\n\ticon: string;\n\turl: string;\n}\n\n/** Built-in tools rendered when the JSON file is missing or empty. */\nexport const BUILTIN_DEFAULT_TOOLS: readonly DefaultToolSeed[] = [\n\t{\n\t\tid: \"builtin-docs\",\n\t\tname: \"SERVICEME Docs\",\n\t\tdescription: \"Official documentation portal\",\n\t\ticon: \"book\",\n\t\turl: \"https://docs.medalsoft.com/serviceme\",\n\t},\n\t{\n\t\tid: \"builtin-issues\",\n\t\tname: \"Issue Tracker\",\n\t\tdescription: \"Report bugs and feature requests\",\n\t\ticon: \"bug\",\n\t\turl: \"https://github.com/medalsoftchina/ms-devtools-vscode/issues\",\n\t},\n\t{\n\t\tid: \"builtin-changelog\",\n\t\tname: \"Changelog\",\n\t\tdescription: \"Release notes for every published version\",\n\t\ticon: \"history\",\n\t\turl: \"https://github.com/medalsoftchina/ms-devtools-vscode/releases\",\n\t},\n];\n\n/** Resolved toolbox shape returned by `ToolboxStore.read()`. */\nexport interface ResolvedToolbox {\n\tscope: ToolboxScope;\n\ttools: ExternalTool[];\n}\n","/**\n * ToolboxCore — Main entry for the toolbox domain.\n *\n * Orchestrates the user + workspace scopes via `ToolboxStore`, applies\n * the \"最近使用置顶\" sort, and exposes a small surface that mirrors\n * the Phase 5.1 bridge methods:\n *\n * - `list(scope?)` → `toolbox.list`\n * - `add(tool, scope)` → `toolbox.add`\n * - `remove(id, scope?)` → `toolbox.remove`\n * - `update(id, patch, scope?)` → `toolbox.update`\n *\n * Default tools (built-in seeds) are never user-deletable. `remove()`\n * is a no-op for default tools and surfaces a structured error so\n * callers can map it to `TOOLBOX_DEFAULT_IMMUTABLE` (Phase 5.3 error\n * code).\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `ToolboxCore.ts 主入口`\n * - `3.功能拆分.md` §3 — wire shape\n */\n\nimport type { ExternalTool, ToolboxList, ToolboxScope } from \"@serviceme/devtools-protocol\";\n\nimport { sortByRecentFirst, sortByUserOrder, touchLastUsedAt } from \"./sort\";\nimport { ToolboxStore } from \"./ToolboxStore\";\nimport { BUILTIN_DEFAULT_TOOLS, type DefaultToolSeed, type ToolboxPatch } from \"./types\";\n\n/** Sentinel — caller tried to remove a built-in (immutable) tool. */\nexport class DefaultToolImmutableError extends Error {\n\tconstructor(public readonly toolId: string) {\n\t\tsuper(`Cannot remove default toolbox entry: ${toolId}`);\n\t\tthis.name = \"DefaultToolImmutableError\";\n\t}\n}\n\nexport interface ToolboxCoreOptions {\n\tstore?: ToolboxStore;\n\tdefaultTools?: readonly DefaultToolSeed[];\n\t/** Sort applied to the merged list returned by `list()`. Default: `sortByUserOrder`. */\n\tlistSort?: (tools: readonly ExternalTool[]) => ExternalTool[];\n}\n\nexport class ToolboxCore {\n\tprivate readonly store: ToolboxStore;\n\tprivate readonly defaults: readonly DefaultToolSeed[];\n\tprivate readonly listSort: (tools: readonly ExternalTool[]) => ExternalTool[];\n\n\tconstructor(opts: ToolboxCoreOptions = {}) {\n\t\tthis.store = opts.store ?? new ToolboxStore();\n\t\tthis.defaults = opts.defaultTools ?? BUILTIN_DEFAULT_TOOLS;\n\t\tthis.listSort = opts.listSort ?? sortByUserOrder;\n\t}\n\n\t/** List all tools in a scope. Built-in defaults are merged in. */\n\tasync list(scope: ToolboxScope = \"user\"): Promise<ToolboxList> {\n\t\tconst resolved = await this.store.read(scope);\n\t\treturn {\n\t\t\tscope,\n\t\t\ttools: this.listSort(resolved.tools),\n\t\t};\n\t}\n\n\t/**\n\t * Append a new tool to the requested scope. Default tools are\n\t * rejected (they are seeds, not user entries).\n\t */\n\tasync add(tool: ExternalTool, scope: ToolboxScope = \"user\"): Promise<ToolboxList> {\n\t\tif (this.isDefaultId(tool.id)) {\n\t\t\tthrow new DefaultToolImmutableError(tool.id);\n\t\t}\n\t\tconst next = await this.store.mutate(scope, async (current) => {\n\t\t\tconst filtered = current.filter((t) => t.id !== tool.id);\n\t\t\treturn [\n\t\t\t\t...filtered,\n\t\t\t\t{\n\t\t\t\t\t...tool,\n\t\t\t\t\tscope,\n\t\t\t\t\tisDefault: false,\n\t\t\t\t\torder: tool.order ?? filtered.length,\n\t\t\t\t},\n\t\t\t];\n\t\t});\n\t\treturn { scope, tools: next };\n\t}\n\n\t/**\n\t * Remove a tool by id. Returns `success: false` when the id is a\n\t * built-in default (idempotent, never throws on missing entries).\n\t */\n\tasync remove(\n\t\tid: string,\n\t\tscope: ToolboxScope = \"user\"\n\t): Promise<{ scope: ToolboxScope; toolId: string; success: boolean }> {\n\t\tif (this.isDefaultId(id)) {\n\t\t\tthrow new DefaultToolImmutableError(id);\n\t\t}\n\t\tconst removed = await this.store.mutate(scope, async (current) =>\n\t\t\tcurrent.filter((t) => t.id !== id)\n\t\t);\n\t\treturn { scope, toolId: id, success: !removed.some((t) => t.id === id) };\n\t}\n\n\t/**\n\t * Patch a tool by id. Default tools can only have their `order`\n\t * updated; other patches are silently ignored for default entries\n\t * (callers can compare before/after to detect the ignore).\n\t */\n\tasync update(\n\t\tid: string,\n\t\tpatch: ToolboxPatch,\n\t\tscope: ToolboxScope = \"user\"\n\t): Promise<ToolboxList> {\n\t\tconst isDefault = this.isDefaultId(id);\n\t\tconst next = await this.store.mutate(scope, async (current) => {\n\t\t\tif (isDefault) {\n\t\t\t\t// Default tools are not stored on disk — silently ignore\n\t\t\t\t// non-order patches. The order field is also not persisted\n\t\t\t\t// (default order is set in memory at merge time), so the\n\t\t\t\t// caller can compare before/after to detect the ignore.\n\t\t\t\treturn current;\n\t\t\t}\n\t\t\treturn current.map((tool) =>\n\t\t\t\ttool.id === id ? { ...tool, ...patch, scope, isDefault: false } : tool\n\t\t\t);\n\t\t});\n\t\treturn { scope, tools: next };\n\t}\n\n\t/**\n\t * Stamp `lastUsedAt` on the targeted tool. This is the \"recently\n\t * used\" hook the Extension's webview uses when a user clicks a\n\t * toolbox entry.\n\t */\n\tasync recordUsage(\n\t\tid: string,\n\t\tscope: ToolboxScope = \"user\",\n\t\twhen: Date = new Date()\n\t): Promise<ExternalTool | null> {\n\t\tif (this.isDefaultId(id)) {\n\t\t\t// Defaults live in memory only — return a synthetic record so\n\t\t\t// callers can render the click without persisting anything.\n\t\t\tconst seed = this.defaults.find((d) => d.id === id);\n\t\t\tif (!seed) return null;\n\t\t\treturn { ...seed, scope, isDefault: true, order: 0, lastUsedAt: when.toISOString() };\n\t\t}\n\t\tlet updated: ExternalTool | null = null;\n\t\tawait this.store.mutate(scope, async (current) => {\n\t\t\tconst next = touchLastUsedAt(current, id, when);\n\t\t\tupdated = next.find((t) => t.id === id) ?? null;\n\t\t\treturn next;\n\t\t});\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Combined view: user + workspace scopes merged, sorted by recent\n\t * usage. Workspace tools overlay user tools (workspace entries win\n\t * on `id` collision).\n\t */\n\tasync listMerged(): Promise<ToolboxList> {\n\t\tconst [user, workspace] = await Promise.all([\n\t\t\tthis.store.read(\"user\"),\n\t\t\tthis.store.read(\"workspace\"),\n\t\t]);\n\t\tconst seen = new Set<string>();\n\t\tconst merged: ExternalTool[] = [];\n\t\tfor (const tool of workspace.tools) {\n\t\t\tseen.add(tool.id);\n\t\t\tmerged.push({ ...tool, scope: \"workspace\" });\n\t\t}\n\t\tfor (const tool of user.tools) {\n\t\t\tif (seen.has(tool.id)) continue;\n\t\t\tmerged.push({ ...tool, scope: \"user\" });\n\t\t}\n\t\treturn { scope: \"user\", tools: sortByRecentFirst(merged) };\n\t}\n\n\t/** Expose the underlying store (CLI / Bridge use it for path-level access). */\n\tgetStore(): ToolboxStore {\n\t\treturn this.store;\n\t}\n\n\tprivate isDefaultId(id: string): boolean {\n\t\treturn this.defaults.some((d) => d.id === id);\n\t}\n}\n"],"mappings":"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","names":["resolve","createServicemeError","resolve","createServicemeError","COPILOT_ERROR_CODES","createServicemeError","COPILOT_COMMAND","createServicemeError","COPILOT_ERROR_CODES","createHash","platform","os","path","os","path","platform","fs","path","fs","path","fs","path","stat","createServicemeError","createServicemeError","isTimeout","spawn","path","resolve","spawn","fs","path","createServicemeError","createServicemeError","createServicemeError","randomUUID","fs","path","randomUUID","fs","path","createServicemeError","access","mkdir","readdir","rename","rm","dirname","join","resolve","createServicemeError","fs","path","fs","path","fs","unlink","EventEmitter","fs","EventEmitter","fs","path","fs","path","CONFIG_DIR","stat","fs","os","path","spawn","fs","DEFAULT_TIMEOUT_MS","resolve","spawn","DEFAULT_TIMEOUT_MS","spawn","fs","path","MAX_OUTPUT_BYTES","DEFAULT_TIMEOUT_MS","platform","writeDiagnostic","resolve","spawn","randomUUID","fs","os","path","createServicemeError","isRecord","createServicemeError","randomUUID","getExecutor","randomUUID","fs","path","randomUUID","fs","os","path","TICK_INTERVAL","MIN_SCHEDULE_INTERVAL","SCHEDULER_LOG_FILENAME","parseIntervalMs","matchesCron","matchCronField","fs","path","isScheduledTasksConfigV1","migrateV1ToV2","isScheduledTasksConfigV1","migrateV1ToV2","spawn","fs","path","DEFAULT_TIMEOUT_MS","resolve","createServicemeError","fs","path","SAFE_LOCAL_ID_PATTERN","fs","path","fsp","path","delay","FILE_MODE","LOCK_DIR_MODE","DEFAULT_LOCK_TIMEOUT_MS","DEFAULT_LOCK_RETRY_MS","TMP_SUFFIX","isNodeError","delay"]}
1
+ {"version":3,"sources":["../src/agents/AgentCatalogClient.ts","../src/permissions/agent-permissions.ts","../src/agents/AgentReconciler.ts","../src/agents/AgentStore.ts","../src/auth/AccessControl.ts","../src/auth/AuthStateManager.ts","../src/auth/ProviderRegistry.ts","../src/auth/AuthCore.ts","../src/auth/KeychainAuthTokenStore.ts","../src/auth/utils/githubUserEmail.ts","../src/auth/providers/GitHubAuthProvider.ts","../src/auth/providers/MicrosoftAuthProvider.ts","../src/copilot/doctor.ts","../src/process/runCommand.ts","../src/copilot/prompt.ts","../src/device/deviceAuth.ts","../src/device/Enroller.ts","../src/device/InstallationId.ts","../src/device/IdentityStore.ts","../src/paths/userHome.ts","../src/device/types.ts","../src/device/DeviceCore.ts","../src/drafts/index.ts","../src/skill-store/index.ts","../src/repo-layout/index.ts","../src/env/environmentInspector.ts","../src/git-client/index.ts","../src/git-client/types.ts","../src/image/imageTools.ts","../src/json/jsonTools.ts","../src/logger.ts","../src/phase5/bootstrap.ts","../src/project/projectTools.ts","../src/utils/fileUtils.ts","../src/repo-manager/index.ts","../src/repos/types.ts","../src/repos/default-repos.ts","../src/repos/loader.ts","../src/repos/store.ts","../src/scheduled-tasks/daemon/DaemonLogger.ts","../src/scheduled-tasks/daemon/PidManager.ts","../src/scheduled-tasks/daemon/SchedulerDaemon.ts","../src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts","../src/scheduled-tasks/executors/timeout.ts","../src/scheduled-tasks/executors/HttpRequestExecutor.ts","../src/scheduled-tasks/executors/ShellExecutor.ts","../src/scheduled-tasks/executors/types.ts","../src/scheduled-tasks/executors/index.ts","../src/scheduled-tasks/TaskConfigManager.ts","../src/scheduled-tasks/TaskExecutionEngine.ts","../src/scheduled-tasks/TaskLogManager.ts","../src/scheduled-tasks/daemon/SchedulerDaemonV2.ts","../src/scheduled-tasks/migration/MigrateToGlobal.ts","../src/scheduled-tasks/workspace-probe/WorkspaceProbe.ts","../src/skills/SkillCatalogClient.ts","../src/skills/SkillReconciler.ts","../src/skills/SkillStore.ts","../src/submit/index.ts","../src/submit/types.ts","../src/toolbox/sort.ts","../src/toolbox/ToolboxStore.ts","../src/toolbox/types.ts","../src/toolbox/ToolboxCore.ts"],"sourcesContent":["import type { AgentMarketplaceEntry } from \"@serviceme/devtools-protocol\";\nimport { createServicemeError } from \"@serviceme/devtools-protocol\";\nimport type { AgentDownloadFile } from \"./types\";\n\nexport interface AgentCatalog {\n\tagents: AgentMarketplaceEntry[];\n\tfetchedAt: string;\n}\n\nexport interface AgentCatalogClientOptions {\n\tfetchImpl?: typeof fetch;\n\tbaseUrl?: string;\n}\n\nexport class AgentCatalogClient {\n\tprivate readonly fetchImpl: typeof fetch;\n\tprivate readonly baseUrl?: string;\n\n\tconstructor(options: AgentCatalogClientOptions = {}) {\n\t\tthis.fetchImpl = options.fetchImpl ?? fetch;\n\t\tthis.baseUrl = options.baseUrl;\n\t}\n\n\tasync getCatalog(): Promise<AgentCatalog> {\n\t\tif (!this.baseUrl) {\n\t\t\tthrow createServicemeError(\"workspace_not_found\", \"Agent catalog baseUrl is not configured.\");\n\t\t}\n\n\t\tconst response = await this.fetchImpl(`${this.baseUrl}/api/v1/marketplace/agents`);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to fetch agents catalog: ${response.status}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as {\n\t\t\tagents?: AgentMarketplaceEntry[];\n\t\t};\n\t\treturn {\n\t\t\tagents: data.agents ?? [],\n\t\t\tfetchedAt: new Date().toISOString(),\n\t\t};\n\t}\n\n\tasync downloadAgent(remoteId: string): Promise<AgentDownloadFile[]> {\n\t\tif (!this.baseUrl) {\n\t\t\tthrow createServicemeError(\"workspace_not_found\", \"Agent catalog baseUrl is not configured.\");\n\t\t}\n\n\t\tconst response = await this.fetchImpl(\n\t\t\t`${this.baseUrl}/api/v1/marketplace/agents/download/${remoteId}`\n\t\t);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 404) {\n\t\t\t\tthrow createServicemeError(\"not_found\", `Agent '${remoteId}' not found`);\n\t\t\t}\n\t\t\tthrow new Error(`Failed to download agent ${remoteId}: ${response.status}`);\n\t\t}\n\n\t\tconst payload = (await response.json()) as {\n\t\t\tdata?: { files?: AgentDownloadFile[] };\n\t\t};\n\t\treturn payload.data?.files ?? [];\n\t}\n}\n","import type { AgentToolPermission, AgentToolRiskLevel } from \"@serviceme/devtools-protocol\";\n\nexport const TOOL_RISK_MAP: Record<string, AgentToolRiskLevel> = {\n\tshell: \"high\",\n\tterminal: \"high\",\n\trun_in_terminal: \"high\",\n\texecution_subagent: \"high\",\n\tfilesystem: \"medium\",\n\tfetch: \"medium\",\n\tfetch_webpage: \"medium\",\n\tcreate_file: \"medium\",\n\treplace_string_in_file: \"medium\",\n\tmulti_replace_string_in_file: \"medium\",\n\tread_file: \"low\",\n\tsearch: \"low\",\n\tgrep_search: \"low\",\n\tfile_search: \"low\",\n\tsemantic_search: \"low\",\n\tlist_dir: \"low\",\n};\n\nconst FRONTMATTER_REGEX = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/;\nconst TOOLS_LINE_REGEX = /^tools:\\s*$/m;\nconst TOOLS_INLINE_REGEX = /^tools:\\s*\\[([^\\]]*)\\]/m;\nconst LIST_ITEM_REGEX = /^\\s*-\\s+(.+)$/;\n\nexport function parseAgentToolPermissions(content: string): AgentToolPermission[] {\n\tconst fmMatch = content.match(FRONTMATTER_REGEX);\n\tif (!fmMatch?.[1]) return [];\n\n\tconst frontmatter = fmMatch[1];\n\n\tconst inlineMatch = frontmatter.match(TOOLS_INLINE_REGEX);\n\tif (inlineMatch?.[1] != null) {\n\t\tconst raw = inlineMatch[1];\n\t\treturn raw\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean)\n\t\t\t.map((tool) => ({\n\t\t\t\ttool,\n\t\t\t\triskLevel: TOOL_RISK_MAP[tool] ?? \"medium\",\n\t\t\t}));\n\t}\n\n\tconst blockMatch = frontmatter.match(TOOLS_LINE_REGEX);\n\tif (!blockMatch?.[0]) return [];\n\n\tconst toolsStartIndex = frontmatter.indexOf(blockMatch[0]) + blockMatch[0].length;\n\tconst remaining = frontmatter.slice(toolsStartIndex);\n\tconst lines = remaining.split(/\\r?\\n/);\n\tconst tools: AgentToolPermission[] = [];\n\n\tfor (const line of lines) {\n\t\tconst itemMatch = line.match(LIST_ITEM_REGEX);\n\t\tif (itemMatch?.[1]) {\n\t\t\tconst tool = itemMatch[1].trim();\n\t\t\ttools.push({ tool, riskLevel: TOOL_RISK_MAP[tool] ?? \"medium\" });\n\t\t} else if (line.trim() !== \"\" && !line.startsWith(\" \") && !line.startsWith(\"\\t\")) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn tools;\n}\n","import type {\n\tAgentMarketplaceEntry,\n\tAgentMutationRequest,\n\tAgentPermissionSummary,\n} from \"@serviceme/devtools-protocol\";\nimport { parseAgentToolPermissions, TOOL_RISK_MAP } from \"../permissions\";\n\ninterface AgentStoreLike {\n\tnormalizeRemoteAgentId(remoteId: string): string;\n\tlistWorkspaceAgentIds(): Promise<string[]>;\n\tlistUserAgentIds(): Promise<string[]>;\n}\n\ninterface AgentCatalogLike {\n\tagents: AgentMarketplaceEntry[];\n\tfetchedAt: string;\n}\n\ninterface AgentCatalogClientLike {\n\tgetCatalog(): Promise<AgentCatalogLike>;\n}\n\nexport interface AgentReconcilerDependencies {\n\tagentStore: AgentStoreLike;\n\tcatalogClient: AgentCatalogClientLike;\n}\n\nexport interface AgentMutateResult {\n\tstatus: \"success\" | \"blocked\" | \"requires_confirmation\";\n\tchanged: boolean;\n\tmessage?: string;\n\ttools?: string[];\n}\n\nexport class AgentReconciler {\n\tconstructor(private readonly deps: AgentReconcilerDependencies) {}\n\n\tasync mutate(request: AgentMutationRequest): Promise<AgentMutateResult> {\n\t\tif (request.targetScope !== \"workspace\" && request.targetScope !== \"user\") {\n\t\t\tthrow new Error(`Invalid target scope: ${String(request.targetScope)}`);\n\t\t}\n\n\t\tif (\n\t\t\trequest.action === \"uninstall\" ||\n\t\t\trequest.action === \"move\" ||\n\t\t\trequest.action === \"removeExternal\"\n\t\t) {\n\t\t\treturn {\n\t\t\t\tstatus: \"success\",\n\t\t\t\tchanged: true,\n\t\t\t\tmessage: `Agent ${request.action} completed.`,\n\t\t\t};\n\t\t}\n\n\t\tif (request.action !== \"install\") {\n\t\t\treturn {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: `Agent action is not supported by bridge reconciler: ${request.action}`,\n\t\t\t};\n\t\t}\n\n\t\tconst catalog = await this.deps.catalogClient.getCatalog();\n\t\tconst remoteAgent = catalog.agents.find(\n\t\t\t(agent) => this.deps.agentStore.normalizeRemoteAgentId(agent.id) === request.agentId\n\t\t);\n\n\t\tif (!remoteAgent) {\n\t\t\treturn {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: \"Agent not found in catalog.\",\n\t\t\t};\n\t\t}\n\n\t\tif (!request.confirmed && this.hasHighRiskTool(remoteAgent.tools)) {\n\t\t\treturn {\n\t\t\t\tstatus: \"requires_confirmation\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: \"This agent uses high-risk tools that require confirmation.\",\n\t\t\t\ttools: remoteAgent.tools,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: \"success\",\n\t\t\tchanged: true,\n\t\t\tmessage: \"Agent installed.\",\n\t\t};\n\t}\n\n\tgetPermissionSummary(\n\t\tagentId: string,\n\t\tagentName: string,\n\t\tcontent: string\n\t): AgentPermissionSummary {\n\t\tconst tools = parseAgentToolPermissions(content);\n\t\treturn {\n\t\t\tagentId,\n\t\t\tagentName,\n\t\t\ttools,\n\t\t\thighRiskCount: tools.filter((tool) => tool.riskLevel === \"high\").length,\n\t\t\tmediumRiskCount: tools.filter((tool) => tool.riskLevel === \"medium\").length,\n\t\t\tlowRiskCount: tools.filter((tool) => tool.riskLevel === \"low\").length,\n\t\t};\n\t}\n\n\tprivate hasHighRiskTool(tools: string[]): boolean {\n\t\treturn tools.some((tool) => TOOL_RISK_MAP[tool] === \"high\");\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type {\n\tAgentDownloadFile,\n\tAgentStoreFileSystem,\n\tAgentStoreOptions,\n\tAgentsStateFile,\n\tInstalledAgent,\n} from \"./types\";\n\nconst WORKSPACE_AGENTS_ROOT_RELATIVE = \".github/agents\";\nconst WORKSPACE_AGENTS_STATE_RELATIVE = \".github/.serviceme-agents.yml\";\nconst LEGACY_WORKSPACE_AGENTS_STATE_RELATIVE = \".github/.ms-devtools-agents.yml\";\nconst SAFE_LOCAL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\n\nfunction assertSafeLocalAgentId(agentId: string): string {\n\tif (\n\t\ttypeof agentId !== \"string\" ||\n\t\tagentId.length === 0 ||\n\t\tagentId === \".\" ||\n\t\tagentId === \"..\" ||\n\t\tagentId.includes(\"/\") ||\n\t\tagentId.includes(\"\\\\\") ||\n\t\t!SAFE_LOCAL_ID_PATTERN.test(agentId)\n\t) {\n\t\tthrow new Error(`Invalid agent id: ${agentId}`);\n\t}\n\n\treturn agentId;\n}\n\nexport class AgentStore {\n\tprivate readonly workspacePath: string;\n\tprivate readonly userAgentsRoot: string;\n\tprivate readonly fileSystem: AgentStoreFileSystem;\n\tprivate readonly schemaVersion: number;\n\n\tconstructor(options: AgentStoreOptions) {\n\t\tthis.workspacePath = options.workspacePath;\n\t\tthis.userAgentsRoot = options.userAgentsRoot;\n\t\tthis.fileSystem = options.fileSystem ?? fs;\n\t\tthis.schemaVersion = options.schemaVersion ?? 1;\n\t}\n\n\tnormalizeRemoteAgentId(remoteId: string): string {\n\t\tif (remoteId.startsWith(\"official/\")) {\n\t\t\treturn assertSafeLocalAgentId(remoteId.slice(\"official/\".length));\n\t\t}\n\t\tif (remoteId.startsWith(\"community/\")) {\n\t\t\tconst lastSlash = remoteId.lastIndexOf(\"/\");\n\t\t\treturn assertSafeLocalAgentId(remoteId.slice(lastSlash + 1));\n\t\t}\n\t\treturn assertSafeLocalAgentId(remoteId);\n\t}\n\n\tgetWorkspaceAgentsRootPath(): string {\n\t\treturn WORKSPACE_AGENTS_ROOT_RELATIVE;\n\t}\n\n\tgetWorkspaceStateFilePath(): string {\n\t\treturn WORKSPACE_AGENTS_STATE_RELATIVE;\n\t}\n\n\tgetUserAgentsRootPath(): string {\n\t\treturn this.userAgentsRoot;\n\t}\n\n\tasync listWorkspaceAgentIds(): Promise<string[]> {\n\t\treturn this.listAgentIds(path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE));\n\t}\n\n\tasync listUserAgentIds(): Promise<string[]> {\n\t\treturn this.listAgentIds(this.userAgentsRoot);\n\t}\n\n\tasync readState(): Promise<AgentsStateFile | null> {\n\t\tawait this.migrateLegacyState();\n\t\ttry {\n\t\t\tconst raw = await this.fileSystem.readFile(\n\t\t\t\tpath.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE),\n\t\t\t\t\"utf-8\"\n\t\t\t);\n\t\t\tconst parsed = JSON.parse(raw) as AgentsStateFile;\n\t\t\tif (typeof parsed.schemaVersion !== \"number\" || !Array.isArray(parsed.installedAgents)) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn parsed;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * One-time migration: the workspace agents state file used to be named\n\t * `.github/.ms-devtools-agents.yml`. If the new `.serviceme-agents.yml`\n\t * doesn't exist yet but the legacy file does, copy its content forward\n\t * so existing installed-agent state isn't silently lost.\n\t */\n\tprivate async migrateLegacyState(): Promise<void> {\n\t\tconst newPath = path.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE);\n\t\tconst legacyPath = path.join(this.workspacePath, LEGACY_WORKSPACE_AGENTS_STATE_RELATIVE);\n\t\ttry {\n\t\t\tawait this.fileSystem.readFile(newPath, \"utf-8\");\n\t\t\treturn;\n\t\t} catch {\n\t\t\t// new file missing — check the legacy path below\n\t\t}\n\t\ttry {\n\t\t\tconst legacyContent = await this.fileSystem.readFile(legacyPath, \"utf-8\");\n\t\t\tawait this.fileSystem.mkdir(path.dirname(newPath), { recursive: true });\n\t\t\tawait this.fileSystem.writeFile(newPath, legacyContent, \"utf-8\");\n\t\t} catch {\n\t\t\t// legacy file doesn't exist either — nothing to migrate\n\t\t}\n\t}\n\n\tasync writeState(state: AgentsStateFile): Promise<void> {\n\t\tconst statePath = path.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE);\n\t\tawait this.fileSystem.mkdir(path.dirname(statePath), { recursive: true });\n\t\tawait this.fileSystem.writeFile(statePath, JSON.stringify(state, null, 2), \"utf-8\");\n\t}\n\n\tasync addInstalledAgent(entry: InstalledAgent): Promise<void> {\n\t\tconst state =\n\t\t\t(await this.readState()) ??\n\t\t\t({\n\t\t\t\tschemaVersion: this.schemaVersion,\n\t\t\t\tinstalledAgents: [],\n\t\t\t} as AgentsStateFile);\n\t\tstate.installedAgents = state.installedAgents.filter((agent) => agent.id !== entry.id);\n\t\tstate.installedAgents.push(entry);\n\t\tawait this.writeState(state);\n\t}\n\n\tasync removeInstalledAgent(agentId: string): Promise<void> {\n\t\tconst state = await this.readState();\n\t\tif (!state) {\n\t\t\treturn;\n\t\t}\n\t\tstate.installedAgents = state.installedAgents.filter((agent) => agent.id !== agentId);\n\t\tawait this.writeState(state);\n\t}\n\n\tasync writeAgentFiles(\n\t\tagentId: string,\n\t\tscope: \"workspace\" | \"user\",\n\t\tfiles: AgentDownloadFile[]\n\t): Promise<void> {\n\t\tconst root =\n\t\t\tscope === \"workspace\"\n\t\t\t\t? path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE)\n\t\t\t\t: this.userAgentsRoot;\n\t\tconst firstFile = files[0];\n\t\tconst isSingleFlatFile =\n\t\t\tfiles.length === 1 &&\n\t\t\tfirstFile !== undefined &&\n\t\t\tfirstFile.path === `${agentId}.agent.md` &&\n\t\t\t!firstFile.path.includes(\"/\");\n\t\tconst targetDir = isSingleFlatFile ? root : path.join(root, agentId);\n\t\tawait this.fileSystem.mkdir(targetDir, { recursive: true });\n\n\t\tfor (const file of files) {\n\t\t\tconst filePath = path.join(targetDir, file.path);\n\t\t\tawait this.fileSystem.mkdir(path.dirname(filePath), { recursive: true });\n\t\t\tawait this.fileSystem.writeFile(filePath, file.content, \"utf-8\");\n\t\t\tif (file.executable) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.fileSystem.chmod(filePath, 0o755);\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore chmod failures on unsupported environments\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async listAgentIds(dir: string): Promise<string[]> {\n\t\ttry {\n\t\t\tconst entries = await this.fileSystem.readdir(dir, {\n\t\t\t\twithFileTypes: true,\n\t\t\t});\n\t\t\tconst ids: string[] = [];\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tids.push(entry.name);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (entry.isFile() && entry.name.endsWith(\".agent.md\")) {\n\t\t\t\t\tids.push(entry.name.replace(/\\.agent\\.md$/, \"\"));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ids.sort();\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n}\n","/**\n * AccessControl — Pure logic for org-membership gating.\n *\n * Ported from `apps/extension/src/services/auth/AccessControlService.ts`\n * but stripped of all VSCode dependencies:\n * - No `vscode.window.showWarningMessage` — callers render the notice.\n * - No `vscode.context.globalState` — callers inject a `KeyValueStore`.\n * - No `vscode.Event` — uses a plain `onDidChange` callback.\n *\n * The class is generic over the membership-fetcher function so it can\n * run identically with the CLI's `@serviceme/shared` `getGitHubOrgMembership`\n * or with the Extension's bundled copy. ADL-003 forbids Core from\n * importing `@serviceme/shared` directly, so the org membership\n * result type is defined locally and the runtime adapter wraps the\n * shared helper at the boundary (Phase 5.3+).\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AccessControl.ts 从 AccessControlService 改写`\n * - ADL-003 — `@serviceme/shared` boundary (Core MUST NOT import shared)\n */\n\nimport type { AuthAccountMeta, AuthProvider } from \"@serviceme/devtools-protocol\";\n\n/** Core-local mirror of `GitHubOrgMembershipCheckResult` (defined in `@serviceme/shared`). */\nexport interface CoreOrgMembershipResult {\n\tstatus: \"active\" | \"pending\" | \"not_member\" | \"unknown\";\n\thttpStatus?: number;\n\trole?: string;\n\tdirectMembership?: boolean;\n}\n\n/**\n * Upstream org-membership fetcher. CLI / Extension adapters wrap\n * `getGitHubOrgMembership` (or the local extension copy) to match\n * this signature.\n */\nexport type OrgMembershipFetcher = (token: string, org: string) => Promise<CoreOrgMembershipResult>;\n\n/** Pluggable key/value store. Extension injects `globalState`-backed impl; CLI injects a file-backed impl. */\nexport interface KeyValueStore {\n\tget<T>(key: string): T | undefined;\n\tupdate<T>(key: string, value: T): Promise<void>;\n}\n\nexport interface AccessCheckResult {\n\tallowed: boolean;\n\treason?: \"not_authenticated\" | \"not_member\";\n\tusername?: string;\n}\n\nexport interface AccessControlOptions {\n\torg: string;\n\t/** Domains that auto-qualify Microsoft users without a GitHub link. */\n\tmicrosoftEmailDomains?: readonly string[];\n\t/** TTL for in-memory membership cache (ms). */\n\tcacheTtlMs?: number;\n\t/** TTL for persisted membership cache (ms). */\n\tpersistentCacheTtlMs?: number;\n\t/** Tokens whose bearer can be supplied to `OrgMembershipFetcher`. */\n\ttokenFetcher: (provider: AuthProvider) => Promise<string | null>;\n\t/** Upstream org-membership fetcher (typically `getGitHubOrgMembership` from `@serviceme/shared`). */\n\torgFetcher: OrgMembershipFetcher;\n\t/** Clock seam for tests. */\n\tnow?: () => number;\n}\n\nconst DEFAULT_CACHE_TTL_MS = 10 * 60 * 1000;\nconst DEFAULT_PERSISTENT_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\nconst KEY_GRANTED_USERS = \"msDevTools.accessControl.grantedUsers\";\nconst KEY_MEMBERSHIP_CACHE = \"msDevTools.accessControl.membershipCache\";\nconst KEY_SERVER_IN_ORG = \"msDevTools.accessControl.serverInOrg\";\n\ninterface CacheEntry {\n\tisMember: boolean;\n\tat: number;\n}\n\ninterface PersistentCache {\n\t[username: string]: CacheEntry;\n}\n\nexport class AccessControl {\n\tprivate readonly memCache = new Map<string, CacheEntry>();\n\tprivate readonly serverInOrgMem = new Map<string, boolean>();\n\tprivate readonly listeners = new Set<() => void>();\n\tprivate readonly opts: Required<\n\t\tOmit<AccessControlOptions, \"now\" | \"tokenFetcher\" | \"microsoftEmailDomains\" | \"orgFetcher\">\n\t> & {\n\t\ttokenFetcher: AccessControlOptions[\"tokenFetcher\"];\n\t\torgFetcher: OrgMembershipFetcher;\n\t\tmicrosoftEmailDomains: readonly string[];\n\t\tnow: () => number;\n\t};\n\n\tconstructor(\n\t\tprivate readonly kv: KeyValueStore,\n\t\toptions: AccessControlOptions\n\t) {\n\t\tthis.opts = {\n\t\t\torg: options.org,\n\t\t\ttokenFetcher: options.tokenFetcher,\n\t\t\torgFetcher: options.orgFetcher,\n\t\t\tmicrosoftEmailDomains: options.microsoftEmailDomains ?? [],\n\t\t\tcacheTtlMs: options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,\n\t\t\tpersistentCacheTtlMs: options.persistentCacheTtlMs ?? DEFAULT_PERSISTENT_CACHE_TTL_MS,\n\t\t\tnow: options.now ?? (() => Date.now()),\n\t\t};\n\t\t// Hydrate `serverInOrg` from persistent storage so the gate can run instantly on restart.\n\t\tconst persistedServerInOrg = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG);\n\t\tif (persistedServerInOrg) {\n\t\t\tfor (const [key, value] of Object.entries(persistedServerInOrg)) {\n\t\t\t\tthis.serverInOrgMem.set(key, value);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Subscribe to inOrg changes (used by extension to refresh access-gated UI). */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\t/** Full check: requires authentication + org membership / admin grant. */\n\tasync checkAccess(user: AuthAccountMeta | null): Promise<AccessCheckResult> {\n\t\tif (!user) {\n\t\t\treturn { allowed: false, reason: \"not_authenticated\" };\n\t\t}\n\n\t\t// Microsoft users: server-confirmed inOrg takes priority over GitHub link.\n\t\tif (user.provider === \"microsoft\" && user.login) {\n\t\t\tconst inOrg = this.serverInOrgMem.get(user.login.toLowerCase());\n\t\t\tif (inOrg === true) return { allowed: true };\n\t\t\tif (inOrg === false) {\n\t\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t\t}\n\t\t}\n\n\t\t// Find a GitHub account if available (linked for Microsoft users).\n\t\tconst githubAccount = user.provider === \"github\" ? user : null;\n\t\tif (!githubAccount && user.provider === \"microsoft\" && user.login) {\n\t\t\tconst [, domain = \"\"] = user.login.split(\"@\");\n\t\t\tconst isDomainMember =\n\t\t\t\tdomain.length > 0 && this.opts.microsoftEmailDomains.includes(domain.toLowerCase());\n\t\t\tif (isDomainMember) return { allowed: true };\n\t\t\t// No GitHub link + unknown domain — deny until the next server-confirmed claim.\n\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t}\n\t\tif (!githubAccount) {\n\t\t\t// No identity at all — treat as authenticated for now (caller may have other gating).\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst accessUsername = githubAccount.login ?? user.login ?? \"\";\n\t\tif (accessUsername.length === 0) {\n\t\t\treturn { allowed: false, reason: \"not_member\", username: \"\" };\n\t\t}\n\n\t\t// Admin-granted users bypass the org check.\n\t\tconst granted = this.getGrantedUsers();\n\t\tif (granted.includes(accessUsername.toLowerCase())) {\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst isMember = await this.checkOrgMembership(accessUsername);\n\t\treturn isMember\n\t\t\t? { allowed: true }\n\t\t\t: { allowed: false, reason: \"not_member\", username: accessUsername };\n\t}\n\n\t/** Admin: grant access to a GitHub username (bypasses org check). */\n\tasync grantAccess(username: string): Promise<void> {\n\t\tconst list = this.getGrantedUsers();\n\t\tconst key = username.toLowerCase();\n\t\tif (!list.includes(key)) {\n\t\t\tlist.push(key);\n\t\t\tawait this.kv.update(KEY_GRANTED_USERS, list);\n\t\t}\n\t}\n\n\t/** Admin: revoke a previously granted access. */\n\tasync revokeAccess(username: string): Promise<void> {\n\t\tconst updated = this.getGrantedUsers().filter((u) => u !== username.toLowerCase());\n\t\tawait this.kv.update(KEY_GRANTED_USERS, updated);\n\t}\n\n\t/** Snapshot of all admin-granted usernames (lowercased). */\n\tgetGrantedUsers(): string[] {\n\t\treturn this.kv.get<string[]>(KEY_GRANTED_USERS) ?? [];\n\t}\n\n\t/** Cached membership lookup with both in-memory + persistent layers. */\n\tasync checkOrgMembership(username: string): Promise<boolean> {\n\t\tconst now = this.opts.now();\n\n\t\t// 1. In-memory cache.\n\t\tconst inMem = this.memCache.get(username);\n\t\tif (inMem && now - inMem.at < this.opts.cacheTtlMs) {\n\t\t\treturn inMem.isMember;\n\t\t}\n\n\t\t// 2. Persistent cache.\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tconst persistentEntry = persistent[username];\n\t\tif (persistentEntry && now - persistentEntry.at < this.opts.persistentCacheTtlMs) {\n\t\t\tthis.memCache.set(username, persistentEntry);\n\t\t\treturn persistentEntry.isMember;\n\t\t}\n\n\t\t// 3. Hit upstream GitHub API via the injected fetcher.\n\t\tconst token = await this.opts.tokenFetcher(\"github\");\n\t\tif (!token) return false;\n\n\t\ttry {\n\t\t\tconst result = await this.opts.orgFetcher(token, this.opts.org);\n\t\t\tconst decision = this.resolveDecision(username, result);\n\t\t\tif (typeof decision === \"boolean\") {\n\t\t\t\tthis.updateCache(username, decision);\n\t\t\t\treturn decision;\n\t\t\t}\n\t\t\t// Indeterminate — fail-open.\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// Fail-open on transient network errors.\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/** Server-confirmed inOrg status (set by device claim flow). */\n\tsetServerInOrg(username: string, inOrg: boolean): void {\n\t\tconst key = username.toLowerCase();\n\t\tthis.serverInOrgMem.set(key, inOrg);\n\t\tconst persisted = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG) ?? {};\n\t\tpersisted[key] = inOrg;\n\t\tvoid this.kv.update(KEY_SERVER_IN_ORG, persisted);\n\t\tthis.notifyListeners();\n\t}\n\n\t/** Inspect server-confirmed inOrg for a username; returns undefined when unknown. */\n\tgetServerInOrg(username: string): boolean | undefined {\n\t\treturn this.serverInOrgMem.get(username.toLowerCase());\n\t}\n\n\tprivate resolveDecision(\n\t\t_username: string,\n\t\tmembership: CoreOrgMembershipResult\n\t): boolean | undefined {\n\t\tif (membership.status === \"active\" || membership.status === \"pending\") return true;\n\t\tif (membership.status === \"not_member\") return false;\n\t\treturn undefined;\n\t}\n\n\tprivate updateCache(username: string, isMember: boolean): void {\n\t\tconst at = this.opts.now();\n\t\tthis.memCache.set(username, { isMember, at });\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tpersistent[username] = { isMember, at };\n\t\tvoid this.kv.update(KEY_MEMBERSHIP_CACHE, persistent);\n\t}\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch {\n\t\t\t\t// listener errors must not propagate; AccessControl is best-effort.\n\t\t\t}\n\t\t}\n\t}\n}\n","/**\n * AuthStateManager — In-memory mirror of the persisted auth state.\n *\n * Holds the multi-account map (provider + accountId keyed) plus the\n * \"active\" provider/account pair. Emits change events via Node's\n * built-in `events.EventEmitter` so listeners stay decoupled.\n *\n * Important: this class does NOT touch `vscode.SecretStorage` or any\n * file — it only keeps the metadata (`AuthAccountMeta`) and the\n * `activeProvider` selection. Token bytes are looked up via the\n * `KeychainAuthTokenStore` on demand. This separation is what lets\n * `AuthCore` run identically in CLI + Extension.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthStateManager.ts events.EventEmitter, NO VSCode dep`\n * - ADL-004 — token bytes never enter Core state\n */\n\nimport { EventEmitter } from \"node:events\";\n\nimport type { AuthAccountMeta, AuthProvider, AuthStatus } from \"@serviceme/devtools-protocol\";\n\nexport interface AuthStateManagerEvents {\n\tchange: () => void;\n}\n\nexport interface AuthStateManagerOptions {\n\t/** EventEmitter listener cap; defaults to 32 (Node default) but raised for hot test paths. */\n\tmaxListeners?: number;\n}\n\nexport class AuthStateManager {\n\tprivate readonly emitter = new EventEmitter();\n\tprivate accounts: AuthAccountMeta[] = [];\n\tprivate activeProvider: AuthProvider | null = null;\n\tprivate activeAccountId: string | null = null;\n\tprivate lastError: string | null = null;\n\n\tconstructor(opts: AuthStateManagerOptions = {}) {\n\t\tif (opts.maxListeners !== undefined) {\n\t\t\tthis.emitter.setMaxListeners(opts.maxListeners);\n\t\t}\n\t}\n\n\t/** Subscribe to state-change events. Returns a disposer. */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.emitter.on(\"change\", listener);\n\t\treturn () => this.emitter.off(\"change\", listener);\n\t}\n\n\t/** Snapshot of all known accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn [...this.accounts];\n\t}\n\n\t/** Find an account by `(provider, accountId)` tuple; returns `null` when absent. */\n\tfindAccount(provider: AuthProvider, accountId: string): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider && a.id === accountId) ?? null;\n\t}\n\n\t/** First account for the requested provider — used for \"switch to GitHub\" UX. */\n\tfindFirstForProvider(provider: AuthProvider): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider) ?? null;\n\t}\n\n\t/** Insert or update an account entry. New accounts land at the head of the list. */\n\tupsertAccount(meta: AuthAccountMeta): void {\n\t\tconst idx = this.accounts.findIndex((a) => a.provider === meta.provider && a.id === meta.id);\n\t\tif (idx >= 0) {\n\t\t\tthis.accounts[idx] = meta;\n\t\t} else {\n\t\t\tthis.accounts.unshift(meta);\n\t\t}\n\t\tthis.fire();\n\t}\n\n\t/** Remove an account entry. Returns `true` when an entry was removed. */\n\tremoveAccount(provider: AuthProvider, accountId: string): boolean {\n\t\tconst before = this.accounts.length;\n\t\tthis.accounts = this.accounts.filter((a) => !(a.provider === provider && a.id === accountId));\n\t\tconst removed = this.accounts.length !== before;\n\t\t// If we removed the active provider's account, clear the active marker.\n\t\tif (removed && this.activeProvider === provider && this.activeAccountId === accountId) {\n\t\t\tconst stillHasSameProvider = this.accounts.some((a) => a.provider === provider);\n\t\t\tif (!stillHasSameProvider) {\n\t\t\t\tthis.activeProvider = null;\n\t\t\t\tthis.activeAccountId = null;\n\t\t\t} else {\n\t\t\t\tconst next = this.accounts.find((a) => a.provider === provider);\n\t\t\t\tif (next) {\n\t\t\t\t\tthis.activeAccountId = next.id;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (removed) this.fire();\n\t\treturn removed;\n\t}\n\n\t/** Set the active provider. When `accountId` is omitted, picks the first account for that provider. */\n\tsetActive(provider: AuthProvider, accountId?: string): boolean {\n\t\tconst match =\n\t\t\taccountId !== undefined\n\t\t\t\t? this.accounts.find((a) => a.provider === provider && a.id === accountId)\n\t\t\t\t: this.accounts.find((a) => a.provider === provider);\n\t\tif (!match) return false;\n\t\tthis.activeProvider = provider;\n\t\tthis.activeAccountId = match.id;\n\t\tthis.fire();\n\t\treturn true;\n\t}\n\n\t/** Currently active provider — `null` when no session is active. */\n\tgetActiveProvider(): AuthProvider | null {\n\t\treturn this.activeProvider;\n\t}\n\n\t/** Currently active account metadata — `null` when none. */\n\tgetActiveAccount(): AuthAccountMeta | null {\n\t\tif (!this.activeProvider || !this.activeAccountId) return null;\n\t\treturn (\n\t\t\tthis.accounts.find(\n\t\t\t\t(a) => a.provider === this.activeProvider && a.id === this.activeAccountId\n\t\t\t) ?? null\n\t\t);\n\t}\n\n\t/** True when at least one provider has an account entry. */\n\thasAnySession(): boolean {\n\t\treturn this.accounts.length > 0 && this.activeProvider !== null;\n\t}\n\n\t/** Snapshot of the state for `auth.status` and bridge serialization. */\n\tgetStatus(): AuthStatus {\n\t\tconst grouped: Partial<Record<AuthProvider, AuthAccountMeta>> = {};\n\t\tfor (const account of this.accounts) {\n\t\t\tgrouped[account.provider] = account;\n\t\t}\n\t\treturn {\n\t\t\tactiveProvider: this.activeProvider,\n\t\t\taccounts: grouped,\n\t\t\thasAnySession: this.hasAnySession(),\n\t\t\tlastError: this.lastError ?? undefined,\n\t\t};\n\t}\n\n\t/** Record a non-fatal error from the last login/refresh attempt. */\n\trecordError(message: string): void {\n\t\tthis.lastError = message;\n\t\tthis.fire();\n\t}\n\n\t/** Clear the recorded error (e.g. after a successful login). */\n\tclearError(): void {\n\t\tif (this.lastError === null) return;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\t/** Drop every account — used by `auth.logout` with no provider. */\n\tclearAll(): void {\n\t\tthis.accounts = [];\n\t\tthis.activeProvider = null;\n\t\tthis.activeAccountId = null;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\tprivate fire(): void {\n\t\tthis.emitter.emit(\"change\");\n\t}\n}\n","/**\n * ProviderRegistry — Maps `AuthProvider` ids to `IAuthProvider` instances.\n *\n * `AuthCore` looks up providers by `AuthProvider` enum (string union\n * per the protocol). The registry is mutable so that callers can\n * replace a provider (e.g. swap a real keyring-backed implementation\n * in tests), but providers must be registered before `AuthCore.login()`\n * is called.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `ProviderRegistry.ts`\n */\n\nimport type { AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider } from \"./providers/IAuthProvider\";\n\nexport class ProviderRegistry {\n\tprivate readonly providers = new Map<AuthProvider, IAuthProvider>();\n\n\t/** Register or replace a provider implementation. */\n\tregister(provider: IAuthProvider): void {\n\t\tthis.providers.set(provider.providerId, provider);\n\t}\n\n\t/** Look up a registered provider by id; throws when missing. */\n\tget(providerId: AuthProvider): IAuthProvider {\n\t\tconst p = this.providers.get(providerId);\n\t\tif (!p) {\n\t\t\tthrow new Error(`Auth provider not registered: ${providerId}`);\n\t\t}\n\t\treturn p;\n\t}\n\n\t/** Non-throwing lookup; returns `undefined` when the provider is unknown. */\n\ttryGet(providerId: AuthProvider): IAuthProvider | undefined {\n\t\treturn this.providers.get(providerId);\n\t}\n\n\t/** Return all registered provider ids — used by `auth.status` and bridge capability hints. */\n\tlist(): AuthProvider[] {\n\t\treturn [...this.providers.keys()];\n\t}\n\n\t/** True when a provider has been registered for this id. */\n\thas(providerId: AuthProvider): boolean {\n\t\treturn this.providers.has(providerId);\n\t}\n}\n","/**\n * AuthCore — Main entry for the auth domain.\n *\n * Owns the `ProviderRegistry` + `AuthStateManager` + `KeychainAuthTokenStore`.\n * Provides a small, side-effectful surface that CLI / Extension / Bridge\n * handlers can call:\n *\n * - `login(provider)` → drives Device Flow, persists token via store.\n * - `logout(provider?)` → clears the active session (and token bytes).\n * - `status()` → snapshot for `auth.status`.\n * - `whoami()` → minimal identity for `auth.whoami`.\n * - `switchProvider(p)` → flip the active provider (multi-account).\n *\n * Core NEVER holds token bytes past the `KeychainAuthTokenStore.set()`\n * boundary — callers pass the token to the store immediately, then\n * drop the local reference. Per ADL-004 the token bytes only live in\n * (a) SecretStorage / (b) keyring / (c) HTTP Authorization header.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthCore.ts 主入口`\n * - ADL-002 — Device Flow OAuth\n * - ADL-004 — Auth token storage\n */\n\nimport type {\n\tAuthAccountMeta,\n\tAuthLoginResult,\n\tAuthProvider,\n\tAuthStatus,\n\tAuthSwitchResult,\n\tAuthWhoamiResult,\n} from \"@serviceme/devtools-protocol\";\n\nimport type { AccessCheckResult, AccessControl } from \"./AccessControl\";\nimport { AuthStateManager } from \"./AuthStateManager\";\nimport type { KeychainAuthTokenStore } from \"./KeychainAuthTokenStore\";\nimport { ProviderRegistry } from \"./ProviderRegistry\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./providers/IAuthProvider\";\n\nexport interface AuthCoreOptions {\n\tproviders: IAuthProvider[];\n\ttokenStore: KeychainAuthTokenStore;\n\taccessControl?: AccessControl;\n\tstateManager?: AuthStateManager;\n}\n\n/**\n * Callback invoked once the user finishes the device-flow handshake but\n * BEFORE the token is written to the keychain. Lets the caller display\n * the `userCode` + `verificationUrl` to the user (Phase 5.3 CLI prints to\n * stdout; Phase 5.5 Extension pops a webview notification).\n */\nexport type DeviceFlowUiCallback = (result: AuthLoginResult) => void | Promise<void>;\n\n/**\n * Optional cancellation handle — when `shouldContinue()` returns false,\n * the polling loop exits with a clear error so callers can render\n * \"Login cancelled\" UX.\n */\nexport type CancellationCheck = () => boolean;\n\nexport class AuthCore {\n\tprivate readonly registry: ProviderRegistry;\n\tprivate readonly state: AuthStateManager;\n\tprivate readonly tokenStore: KeychainAuthTokenStore;\n\tprivate readonly accessControl?: AccessControl;\n\n\tconstructor(opts: AuthCoreOptions) {\n\t\tthis.registry = new ProviderRegistry();\n\t\tfor (const provider of opts.providers) {\n\t\t\tthis.registry.register(provider);\n\t\t}\n\t\tthis.state = opts.stateManager ?? new AuthStateManager();\n\t\tthis.tokenStore = opts.tokenStore;\n\t\tthis.accessControl = opts.accessControl;\n\t}\n\n\t/** Snapshot of every account, the active provider, and the last error. */\n\tstatus(): AuthStatus {\n\t\treturn this.state.getStatus();\n\t}\n\n\t/** List accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn this.state.listAccounts();\n\t}\n\n\t/**\n\t * Drive the device-flow login for `provider`.\n\t *\n\t * Sequence:\n\t * 1. Ask the provider for the user code + verification URL.\n\t * 2. Surface the code to the user (via `ui`).\n\t * 3. Poll until the user authorizes (or `shouldContinue` aborts).\n\t * 4. Persist the token via `KeychainAuthTokenStore.set()`.\n\t * 5. Insert the resulting `AuthAccountMeta` into state and mark active.\n\t */\n\tasync login(\n\t\tprovider: AuthProvider,\n\t\tui: DeviceFlowUiCallback,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\ttry {\n\t\t\tconst initial = await providerImpl.requestDeviceFlow();\n\t\t\tawait ui(initial);\n\t\t\tconst session = await providerImpl.completeDeviceFlow(\n\t\t\t\tinitial.userCode ? ((initial as unknown as { deviceCode?: string }).deviceCode ?? \"\") : \"\",\n\t\t\t\tshouldContinue\n\t\t\t);\n\t\t\treturn await this.persistSession(providerImpl, session);\n\t\t} catch (err) {\n\t\t\tthis.state.recordError(err instanceof Error ? err.message : String(err));\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/**\n\t * Complete the device-flow handshake given a pre-fetched user code.\n\t * Useful when the caller (Phase 5.3 CLI) wants to fetch the code,\n\t * print the URL, and then poll on a subsequent invocation.\n\t */\n\tasync completeLogin(\n\t\tprovider: AuthProvider,\n\t\tdeviceCode: string,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\ttry {\n\t\t\tconst session = await providerImpl.completeDeviceFlow(deviceCode, shouldContinue);\n\t\t\treturn await this.persistSession(providerImpl, session);\n\t\t} catch (err) {\n\t\t\tthis.state.recordError(err instanceof Error ? err.message : String(err));\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Resolve the provider + token for the active session; returns null when no session is active. */\n\tasync resolveActiveToken(): Promise<{\n\t\tprovider: AuthProvider;\n\t\taccount: AuthAccountMeta;\n\t\ttoken: string;\n\t} | null> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tif (!provider) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!account) return null;\n\t\tconst envelope = await this.tokenStore.get({ provider, accountId: account.id });\n\t\tif (!envelope) return null;\n\t\treturn { provider, account, token: envelope.token };\n\t}\n\n\t/**\n\t * Logout: removes the token bytes from the keychain + drops the\n\t * account entry from state. When `provider` is omitted, clears\n\t * every account.\n\t */\n\tasync logout(\n\t\tprovider?: AuthProvider\n\t): Promise<{ provider: AuthProvider | null; success: boolean }> {\n\t\tif (!provider) {\n\t\t\t// Clear every account's token bytes + metadata.\n\t\t\tfor (const account of this.state.listAccounts()) {\n\t\t\t\tawait this.tokenStore.delete({ provider: account.provider, accountId: account.id });\n\t\t\t\tthis.state.removeAccount(account.provider, account.id);\n\t\t\t}\n\t\t\tthis.state.clearAll();\n\t\t\treturn { provider: null, success: true };\n\t\t}\n\n\t\tconst accountsForProvider = this.state.listAccounts().filter((a) => a.provider === provider);\n\t\tlet removed = false;\n\t\tfor (const account of accountsForProvider) {\n\t\t\tawait this.tokenStore.delete({ provider, accountId: account.id });\n\t\t\tconst didRemove = this.state.removeAccount(provider, account.id);\n\t\t\tremoved = removed || didRemove;\n\t\t}\n\t\tif (this.state.getActiveProvider() === provider) {\n\t\t\t// Reset active if we just removed the active provider's account.\n\t\t\tconst firstRemaining = this.state.listAccounts()[0];\n\t\t\tif (firstRemaining) {\n\t\t\t\tthis.state.setActive(firstRemaining.provider);\n\t\t\t}\n\t\t}\n\t\treturn { provider, success: removed };\n\t}\n\n\t/** Minimal identity for `auth.whoami`. */\n\tasync whoami(): Promise<AuthWhoamiResult> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!provider || !account) {\n\t\t\treturn { provider: null };\n\t\t}\n\t\treturn {\n\t\t\tprovider,\n\t\t\tlogin: account.login,\n\t\t\tname: account.displayName,\n\t\t\tavatarUrl: account.avatarUrl,\n\t\t\temail: account.email,\n\t\t};\n\t}\n\n\t/** Switch the active provider. Returns the resulting active account. */\n\tswitchProvider(provider: AuthProvider, accountId?: string): AuthSwitchResult {\n\t\tconst ok = this.state.setActive(provider, accountId);\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn {\n\t\t\tactiveProvider: ok ? provider : this.state.getActiveProvider(),\n\t\t\taccount,\n\t\t};\n\t}\n\n\t/** Run the access-control check against the active account (optional, requires AccessControl). */\n\tasync checkAccess(): Promise<AccessCheckResult | null> {\n\t\tif (!this.accessControl) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn this.accessControl.checkAccess(account);\n\t}\n\n\t/** Expose the state manager for test inspection (not for mutation). */\n\tgetStateManager(): AuthStateManager {\n\t\treturn this.state;\n\t}\n\n\t/** Expose the provider registry for test inspection. */\n\tgetProviderRegistry(): ProviderRegistry {\n\t\treturn this.registry;\n\t}\n\n\t/** Expose the access control (or undefined when not configured). */\n\tgetAccessControl(): AccessControl | undefined {\n\t\treturn this.accessControl;\n\t}\n\n\tprivate async persistSession(\n\t\tproviderImpl: IAuthProvider,\n\t\tsession: IAuthProviderSession\n\t): Promise<AuthLoginResult> {\n\t\tconst expiresAt = session.expiresIn ? Date.now() + session.expiresIn * 1000 : null;\n\t\tconst meta = await providerImpl.fetchAccountMeta(session.token);\n\t\tconst accountMeta: AuthAccountMeta = {\n\t\t\tid: meta.id,\n\t\t\tprovider: meta.provider,\n\t\t\tdisplayName: meta.displayName,\n\t\t\tlogin: meta.login,\n\t\t\temail: meta.email,\n\t\t\tavatarUrl: meta.avatarUrl,\n\t\t\texpiresAt: meta.expiresAt ?? expiresAt,\n\t\t};\n\n\t\tawait this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {\n\t\t\texpiresAt,\n\t\t});\n\t\tthis.state.upsertAccount(accountMeta);\n\t\tthis.state.setActive(meta.provider, meta.id);\n\t\tthis.state.clearError();\n\t\treturn {\n\t\t\tprovider: meta.provider,\n\t\t\tmessage: `Logged in as ${meta.login ?? meta.id}`,\n\t\t};\n\t}\n}\n","/**\n * KeychainAuthTokenStore — abstract adapter for token byte persistence.\n *\n * Per ADL-004 the OAuth token bytes live in one of three places:\n * (a) VSCode `SecretStorage` (Extension process)\n * (b) `@napi-rs/keyring` Entry (CLI process)\n * (c) HTTP request `Authorization` header\n *\n * `AuthCore` MUST NOT call any of these directly. Instead it receives\n * a `KeychainAuthTokenStore` via DI; CLI provides a `@napi-rs/keyring`\n * adapter, Extension provides a `SecretStorage` adapter. This keeps\n * Core free of native-binding concerns and lets the same business\n * logic power both runtimes.\n *\n * Implementations:\n * - CLI: `apps/serviceme-cli/src/.../KeyringTokenStore.ts` (Phase 5.3)\n * - Extension: `apps/extension/src/.../SecretStorageTokenStore.ts` (Phase 5.5)\n *\n * Failure semantics — `get()` returns `null` when no token is stored\n * (cold-start), throws when the underlying keychain is unavailable\n * (rare on Linux without libsecret). Callers should surface the throw\n * as `AUTH_KEYRING_UNAVAILABLE` rather than silently falling back to\n * in-memory storage (per ADL-004 §不变量).\n *\n * Refs:\n * - ADL-004 — CLI 端 Auth Token 存储选型\n * - 4.功能规划.md §2.1 — \"Core MUST NOT directly depend on `@napi-rs/keyring`\"\n */\n\n/** Opaque account identifier (provider + upstream user id). */\nexport interface KeychainAccountKey {\n\tprovider: string;\n\taccountId: string;\n}\n\n/** Non-secret metadata returned alongside a `get()` so callers can audit. */\nexport interface KeychainTokenMetadata {\n\tprovider: string;\n\taccountId: string;\n\texpiresAt?: number | null;\n\tstoredAt?: number;\n}\n\n/**\n * Result envelope — callers receive the token bytes (for immediate use\n * in an HTTP `Authorization` header) plus optional non-secret metadata.\n *\n * IMPORTANT: the `token` field is plain `string` here, not the\n * `SecretToken` brand. The provider -> store -> HTTP-header pipeline\n * runs inside a single trust boundary; crossing that boundary requires\n * `KeychainTokenEnvelope<SecretToken>` and the `auth.tokenRead`\n * capability gate (see ADL-004 §不变量).\n */\nexport interface KeychainTokenEnvelope {\n\ttoken: string;\n\tmetadata: KeychainTokenMetadata;\n}\n\n/**\n * Abstract token-storage adapter. All methods are async because the\n * keyring / SecretStorage backends are async-by-nature.\n *\n * Thread-safety: implementations MUST be safe for concurrent calls;\n * `AuthCore` will multiplex over multiple providers on the same\n * runtime.\n */\nexport interface KeychainAuthTokenStore {\n\t/** Persist token bytes for the given provider + account pair. */\n\tset(key: KeychainAccountKey, token: string, opts?: { expiresAt?: number | null }): Promise<void>;\n\n\t/** Fetch the token bytes; returns `null` when none is stored. */\n\tget(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null>;\n\n\t/** Remove the token entry. Idempotent — removing a missing entry is not an error. */\n\tdelete(key: KeychainAccountKey): Promise<void>;\n\n\t/**\n\t * List all stored token keys (metadata only — never the token bytes).\n\t * Useful for multi-account enumeration and bridge `auth.status`.\n\t */\n\tlist(): Promise<KeychainAccountKey[]>;\n\n\t/**\n\t * Health probe — returns `false` when the keychain is unreachable\n\t * (e.g. Linux without libsecret). Callers SHOULD probe on first\n\t * use and surface `AUTH_KEYRING_UNAVAILABLE` rather than degrade.\n\t */\n\tisAvailable(): Promise<boolean>;\n}\n\n/**\n * Sentinel error thrown by `KeychainAuthTokenStore.get()` / `.set()`\n * when the underlying keychain is unavailable. Callers map this to\n * `AUTH_KEYRING_UNAVAILABLE` (Phase 5.3 error code).\n */\nexport class KeychainUnavailableError extends Error {\n\tconstructor(message: string, cause?: unknown) {\n\t\t// Use the native ES2022 `Error` two-arg form instead of redeclaring\n\t\t// `cause` as a class field — a field redeclaration shadows the\n\t\t// inherited member and requires an `override` modifier under\n\t\t// `noImplicitOverride` (downstream consumers like the Extension),\n\t\t// which in turn fails THIS package's own build if its `lib` doesn't\n\t\t// also include `cause` on `Error`. Relying on the built-in\n\t\t// constructor option avoids the mismatch entirely (this package's\n\t\t// `tsconfig.json` targets `lib: [\"ES2022\", \"DOM\"]`, so `cause` is\n\t\t// recognized here too).\n\t\tsuper(message, cause !== undefined ? { cause } : undefined);\n\t\tthis.name = \"KeychainUnavailableError\";\n\t}\n}\n\n/**\n * In-memory `KeychainAuthTokenStore` — test/dev fallback. NEVER use\n * in production: tokens live in process memory and disappear on exit.\n * Production wiring is `KeyringTokenStore` (CLI) / `SecretStorageTokenStore`\n * (Extension).\n */\nexport class InMemoryKeychainAuthTokenStore implements KeychainAuthTokenStore {\n\tprivate readonly entries = new Map<\n\t\tstring,\n\t\t{ token: string; expiresAt: number | null; storedAt: number }\n\t>();\n\n\tprivate compositeKey(key: KeychainAccountKey): string {\n\t\treturn `${key.provider}:${key.accountId}`;\n\t}\n\n\tasync set(\n\t\tkey: KeychainAccountKey,\n\t\ttoken: string,\n\t\topts?: { expiresAt?: number | null }\n\t): Promise<void> {\n\t\tthis.entries.set(this.compositeKey(key), {\n\t\t\ttoken,\n\t\t\texpiresAt: opts?.expiresAt ?? null,\n\t\t\tstoredAt: Date.now(),\n\t\t});\n\t}\n\n\tasync get(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null> {\n\t\tconst entry = this.entries.get(this.compositeKey(key));\n\t\tif (!entry) return null;\n\t\treturn {\n\t\t\ttoken: entry.token,\n\t\t\tmetadata: {\n\t\t\t\tprovider: key.provider,\n\t\t\t\taccountId: key.accountId,\n\t\t\t\texpiresAt: entry.expiresAt,\n\t\t\t\tstoredAt: entry.storedAt,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(key: KeychainAccountKey): Promise<void> {\n\t\tthis.entries.delete(this.compositeKey(key));\n\t}\n\n\tasync list(): Promise<KeychainAccountKey[]> {\n\t\tconst out: KeychainAccountKey[] = [];\n\t\tfor (const composite of this.entries.keys()) {\n\t\t\tconst sepIdx = composite.indexOf(\":\");\n\t\t\tif (sepIdx <= 0) continue;\n\t\t\tout.push({\n\t\t\t\tprovider: composite.slice(0, sepIdx),\n\t\t\t\taccountId: composite.slice(sepIdx + 1),\n\t\t\t});\n\t\t}\n\t\treturn out;\n\t}\n\n\tasync isAvailable(): Promise<boolean> {\n\t\treturn true;\n\t}\n}\n","/**\n * AuthCore — Pure local utilities for GitHub local email handling.\n *\n * Copied verbatim from `@serviceme/shared/src/github-user-email.ts` (15 LOC)\n * because ADL-003 forbids Core from depending on `@serviceme/shared`. These\n * helpers are pure functions with zero side effects, so the duplication is\n * trivial to keep in sync.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — \"utils/githubUserEmail.ts 纯函数,直接搬\"\n * - ADL-003 — `@serviceme/shared` boundary decision\n */\n\n/**\n * Returns true when the supplied address is a synthetic GitHub \"local\" email\n * (suffixed with `@github.local`). Real GitHub OAuth clients often return\n * `null` for `email` (user kept it private) and the application substitutes\n * `<login>@github.local` to keep the field non-null.\n */\nexport function isGitHubLocalEmail(email: string | null | undefined): boolean {\n\treturn typeof email === \"string\" && email.trim().toLowerCase().endsWith(\"@github.local\");\n}\n\n/** Build a synthetic `<login>@github.local` address. */\nexport function buildGitHubLocalEmail(login: string): string {\n\treturn `${login}@github.local`;\n}\n\n/**\n * Resolve the user's primary email address — falling back to the synthetic\n * `<login>@github.local` form when the upstream email is missing or blank.\n */\nexport function resolvePrimaryEmail(login: string, email: string | null | undefined): string {\n\tconst normalizedEmail = email?.trim();\n\treturn normalizedEmail || buildGitHubLocalEmail(login);\n}\n","/**\n * GitHubAuthProvider — Device Flow OAuth implementation.\n *\n * Implements the GitHub OAuth Device Flow per the official spec\n * (https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow).\n *\n * Per ADL-002 the device flow is the locked authentication strategy for\n * SERVICEME — no localhost callback server, no PAT. This class is a\n * pure HTTP client (uses native `fetch`, no vscode dependency) that\n * returns the token bytes for the caller's `KeychainAuthTokenStore` to\n * persist immediately.\n *\n * Token bytes NEVER enter logs / errors / debug output. The provider\n * exposes only the user-visible code + verification URL during the\n * device-flow handshake.\n *\n * Refs:\n * - ADL-002 — Device Flow OAuth (locked)\n * - 4.功能规划.md §2.1 — `providers/GitHubAuthProvider.ts`\n */\n\nimport type { AuthAccountMeta, AuthLoginResult, AuthProvider } from \"@serviceme/devtools-protocol\";\nimport { buildGitHubLocalEmail } from \"../utils/githubUserEmail\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\nconst DEFAULT_DEVICE_CODE_URL = \"https://github.com/login/device/code\";\nconst DEFAULT_TOKEN_URL = \"https://github.com/login/oauth/access_token\";\nconst DEFAULT_USER_URL = \"https://api.github.com/user\";\nconst DEFAULT_SCOPE = \"read:user user:email\";\n\n/** Attempts for the one-shot device-code request before surfacing a network error. */\nconst DEVICE_CODE_MAX_ATTEMPTS = 3;\nconst DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS = 500;\n\n/**\n * `fetch` (both browser and Node's undici) rejects with a `TypeError`\n * (e.g. \"fetch failed\") when the underlying connection never completes —\n * DNS failure, TLS handshake reset, ECONNRESET, etc. These are transient\n * network hiccups, distinct from a well-formed non-2xx HTTP response, and\n * should be retried rather than immediately failing the whole login.\n */\nfunction isTransientNetworkError(error: unknown): boolean {\n\treturn error instanceof TypeError;\n}\n\n/** Tunable provider config — exposed for tests + forks. */\nexport interface GitHubAuthProviderConfig {\n\tclientId: string;\n\tdeviceCodeUrl?: string;\n\ttokenUrl?: string;\n\tuserUrl?: string;\n\tscope?: string;\n\t/** Polling interval (ms) floor. The device-flow response's `interval` wins when larger. */\n\tminPollIntervalMs?: number;\n\t/** Maximum poll interval after exponential back-off. */\n\tmaxPollIntervalMs?: number;\n\t/** Fetch override (for tests + DI). */\n\tfetchImpl?: typeof fetch;\n\t/** Maximum wall-clock time to wait for the user before giving up (ms). Defaults to `expires_in * 1000`. */\n\tmaxWaitMs?: number;\n\t/** Base delay (ms) before retrying the device-code request after a transient network error. */\n\tdeviceCodeRetryBaseDelayMs?: number;\n}\n\ninterface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\texpires_in: number;\n\tinterval: number;\n}\n\ninterface TokenPollResponse {\n\taccess_token?: string;\n\trefresh_token?: string;\n\texpires_in?: number;\n\ttoken_type?: string;\n\tscope?: string;\n\terror?: string;\n\terror_description?: string;\n\terror_uri?: string;\n}\n\ninterface GitHubUserResponse {\n\tid: number;\n\tlogin: string;\n\tname?: string | null;\n\temail?: string | null;\n\tavatar_url?: string | null;\n}\n\nexport class GitHubAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"github\";\n\tprivate readonly cfg: Required<Omit<GitHubAuthProviderConfig, \"maxWaitMs\" | \"fetchImpl\">> & {\n\t\tmaxWaitMs?: number;\n\t\tfetchImpl: typeof fetch;\n\t};\n\n\tconstructor(config: GitHubAuthProviderConfig) {\n\t\tif (!config.clientId || config.clientId.length === 0) {\n\t\t\tthrow new Error(\"GitHubAuthProvider requires a non-empty `clientId`\");\n\t\t}\n\t\tthis.cfg = {\n\t\t\tclientId: config.clientId,\n\t\t\tdeviceCodeUrl: config.deviceCodeUrl ?? DEFAULT_DEVICE_CODE_URL,\n\t\t\ttokenUrl: config.tokenUrl ?? DEFAULT_TOKEN_URL,\n\t\t\tuserUrl: config.userUrl ?? DEFAULT_USER_URL,\n\t\t\tscope: config.scope ?? DEFAULT_SCOPE,\n\t\t\tminPollIntervalMs: config.minPollIntervalMs ?? 1000,\n\t\t\tmaxPollIntervalMs: config.maxPollIntervalMs ?? 15000,\n\t\t\tmaxWaitMs: config.maxWaitMs,\n\t\t\tfetchImpl: config.fetchImpl ?? fetch,\n\t\t\tdeviceCodeRetryBaseDelayMs:\n\t\t\t\tconfig.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS,\n\t\t};\n\t}\n\n\tasync requestDeviceFlow(opts?: { scope?: string }): Promise<AuthLoginResult> {\n\t\tconst body = JSON.stringify({\n\t\t\tclient_id: this.cfg.clientId,\n\t\t\tscope: opts?.scope ?? this.cfg.scope,\n\t\t});\n\n\t\tlet lastNetworkError: unknown;\n\t\tfor (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {\n\t\t\tlet resp: Response;\n\t\t\ttry {\n\t\t\t\tresp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t\t},\n\t\t\t\t\tbody,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tlastNetworkError = error;\n\t\t\t\tawait sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!resp.ok) {\n\t\t\t\tthrow new Error(`GitHub device-code request failed: HTTP ${resp.status}`);\n\t\t\t}\n\t\t\tconst data = (await resp.json()) as DeviceCodeResponse;\n\t\t\tif (!data.device_code || !data.user_code || !data.verification_uri) {\n\t\t\t\tthrow new Error(\"GitHub device-code response missing required fields\");\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tprovider: this.providerId,\n\t\t\t\tuserCode: data.user_code,\n\t\t\t\tverificationUrl: data.verification_uri,\n\t\t\t\texpiresAt: Date.now() + data.expires_in * 1000,\n\t\t\t\tmessage: `Open ${data.verification_uri} and enter ${data.user_code}`,\n\t\t\t};\n\t\t}\n\t\t// Unreachable in practice — the loop always returns or throws — but keeps\n\t\t// the type checker happy and surfaces the last transient error if hit.\n\t\tthrow lastNetworkError ?? new Error(\"GitHub device-code request failed\");\n\t}\n\n\tasync completeDeviceFlow(\n\t\tdeviceCode: string,\n\t\tshouldContinue?: () => boolean\n\t): Promise<IAuthProviderSession> {\n\t\tconst start = Date.now();\n\t\tlet pollIntervalMs = this.cfg.minPollIntervalMs;\n\t\tlet consecutiveSlowDown = 0;\n\n\t\twhile (true) {\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\t\t\tconst elapsed = Date.now() - start;\n\t\t\tif (this.cfg.maxWaitMs !== undefined && elapsed > this.cfg.maxWaitMs) {\n\t\t\t\tthrow new Error(\"GitHub device flow exceeded max wait time\");\n\t\t\t}\n\t\t\tawait sleep(pollIntervalMs);\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\n\t\t\tlet resp: Response;\n\t\t\ttry {\n\t\t\t\tresp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\t\t\tdevice_code: deviceCode,\n\t\t\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (!isTransientNetworkError(error)) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\t// Transient network failure (DNS/TLS/reset) — back off and retry\n\t\t\t\t// polling until the user acts, same as a non-2xx HTTP response.\n\t\t\t\tpollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!resp.ok) {\n\t\t\t\t// Transient HTTP failure — back off and retry until the user acts.\n\t\t\t\tpollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\t\tif (data.access_token) {\n\t\t\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\t\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\t\t\treturn {\n\t\t\t\t\ttoken: data.access_token,\n\t\t\t\t\trefreshToken: data.refresh_token,\n\t\t\t\t\texpiresIn: data.expires_in,\n\t\t\t\t\tuser: {\n\t\t\t\t\t\tid: String(rawUser.id),\n\t\t\t\t\t\tlogin: rawUser.login,\n\t\t\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\t\t\temail: email ?? null,\n\t\t\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (data.error === \"authorization_pending\") {\n\t\t\t\t// User hasn't acted yet — keep polling without back-off inflation.\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"slow_down\") {\n\t\t\t\tconsecutiveSlowDown++;\n\t\t\t\tpollIntervalMs = Math.min(\n\t\t\t\t\tMath.round(pollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2000)),\n\t\t\t\t\tthis.cfg.maxPollIntervalMs\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"access_denied\") {\n\t\t\t\tthrow new Error(\"User denied authorization\");\n\t\t\t}\n\t\t\tif (data.error === \"expired_token\") {\n\t\t\t\tthrow new Error(\"GitHub device code expired — restart the flow\");\n\t\t\t}\n\t\t\tthrow new Error(`GitHub device flow error: ${data.error ?? \"unknown\"}`);\n\t\t}\n\t}\n\n\tasync refreshAccessToken(refreshToken: string): Promise<IAuthProviderSession> {\n\t\tif (!refreshToken) {\n\t\t\tthrow new Error(\"refreshAccessToken requires a non-empty refresh token\");\n\t\t}\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t}),\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub refresh failed: HTTP ${resp.status}`);\n\t\t}\n\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\tif (!data.access_token) {\n\t\t\tthrow new Error(`GitHub refresh failed: ${data.error ?? \"no_access_token\"}`);\n\t\t}\n\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\treturn {\n\t\t\ttoken: data.access_token,\n\t\t\trefreshToken: data.refresh_token ?? refreshToken,\n\t\t\texpiresIn: data.expires_in,\n\t\t\tuser: {\n\t\t\t\tid: String(rawUser.id),\n\t\t\t\tlogin: rawUser.login,\n\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\temail: email ?? null,\n\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync validateToken(token: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (resp.status === 401) return false;\n\t\t\tif (!resp.ok) {\n\t\t\t\tthrow new Error(`GitHub /user returned HTTP ${resp.status}`);\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\t// Network / parse errors are surfaced; only 401 means invalid.\n\t\t\tif (err instanceof Error && err.message.includes(\"401\")) return false;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync fetchAccountMeta(token: string): Promise<AuthAccountMeta> {\n\t\tconst user = await this.fetchGitHubUser(token);\n\t\tconst email = await this.resolveEmail(token, user);\n\t\treturn {\n\t\t\tid: String(user.id),\n\t\t\tprovider: this.providerId,\n\t\t\tdisplayName: user.name ?? user.login,\n\t\t\tlogin: user.login,\n\t\t\temail: email ?? buildGitHubLocalEmail(user.login),\n\t\t\tavatarUrl: user.avatar_url ?? undefined,\n\t\t\texpiresAt: null,\n\t\t};\n\t}\n\n\tprivate async fetchGitHubUser(token: string): Promise<GitHubUserResponse> {\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub /user failed: HTTP ${resp.status}`);\n\t\t}\n\t\treturn (await resp.json()) as GitHubUserResponse;\n\t}\n\n\t/**\n\t * GitHub's `/user` endpoint returns `null` when the user kept their\n\t * email private. The `/user/emails` endpoint reveals verified emails;\n\t * we pick the primary one, falling back to the synthetic\n\t * `<login>@github.local` form so the account is never email-less.\n\t */\n\tprivate async resolveEmail(token: string, user: GitHubUserResponse): Promise<string | null> {\n\t\tif (user.email) return user.email;\n\t\tconst emailsResp = await this.cfg.fetchImpl(\"https://api.github.com/user/emails\", {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t});\n\t\tif (!emailsResp.ok) {\n\t\t\treturn null;\n\t\t}\n\t\tconst emails = (await emailsResp.json()) as Array<{\n\t\t\temail: string;\n\t\t\tprimary: boolean;\n\t\t\tvisibility: string | null;\n\t\t\tverified: boolean;\n\t\t}>;\n\t\tconst primary = emails.find((e) => e.primary && e.verified);\n\t\treturn primary?.email ?? null;\n\t}\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * MicrosoftAuthProvider — Microsoft Account (MSA) OAuth Device Flow stub.\n *\n * The Extension's existing `MicrosoftAuthProvider` (`apps/extension/src/services/auth/providers/MicrosoftAuthProvider.ts`)\n * delegates to VSCode's built-in `vscode.authentication.getSession()` API\n * — there's no direct Microsoft device-flow endpoint exposed for\n * first-party apps. To keep Core usable from the CLI without VSCode,\n * we expose a minimal stub that throws \"not implemented in Core\" so\n * callers can detect and route back to the Extension adapter.\n *\n * The reason this lives in Core at all (instead of being purely\n * Extension-side) is the cross-runtime registry: `AuthCore` needs to\n * know which providers it MIGHT support, even if only the Extension\n * process can actually fulfil the Microsoft login.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `providers/MicrosoftAuthProvider.ts 从 extension 整体迁移`\n * - ADL-002 — Device Flow OAuth (locked, GitHub-only)\n */\n\nimport type { AuthAccountMeta, AuthLoginResult, AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\n/**\n * Error thrown when callers invoke Microsoft auth from a non-Extension\n * runtime (CLI / Bridge). The CLI maps this to `AUTH_PROVIDER_REQUIRES_HOST`\n * so the user is prompted to retry inside VSCode.\n */\nexport class MicrosoftProviderNotHostedError extends Error {\n\tconstructor(\n\t\tmessage = \"Microsoft auth requires the VSCode host; CLI does not yet implement MSA device flow\"\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MicrosoftProviderNotHostedError\";\n\t}\n}\n\nexport class MicrosoftAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"microsoft\";\n\n\tasync requestDeviceFlow(): Promise<AuthLoginResult> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync completeDeviceFlow(\n\t\t_deviceCode: string,\n\t\t_shouldContinue?: () => boolean\n\t): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync refreshAccessToken(_refreshToken: string): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync validateToken(_token: string): Promise<boolean> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync fetchAccountMeta(_token: string): Promise<AuthAccountMeta> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n}\n","import {\n\tCOPILOT_ERROR_CODES,\n\ttype CopilotDoctorResult,\n\tcreateServicemeError,\n} from \"@serviceme/devtools-protocol\";\nimport { commandExists, runCommand } from \"../process/runCommand\";\n\nconst COPILOT_COMMAND = \"copilot\";\nconst GH_COMMAND = \"gh\";\n\n/**\n * Quick auth pre-flight check using `gh auth status`.\n * Returns true if gh is authenticated; false otherwise.\n */\nexport async function isCopilotAuthenticated(): Promise<boolean> {\n\ttry {\n\t\tawait runCommand(GH_COMMAND, {\n\t\t\targs: [\"auth\", \"status\"],\n\t\t\ttimeoutMs: 10_000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport async function copilotDoctor(): Promise<CopilotDoctorResult> {\n\tconst exists = await commandExists(COPILOT_COMMAND);\n\tif (!exists) {\n\t\treturn { installed: false, version: null, authenticated: false };\n\t}\n\n\tlet version: string | null = null;\n\ttry {\n\t\tconst versionResult = await runCommand(COPILOT_COMMAND, {\n\t\t\targs: [\"--version\"],\n\t\t\ttimeoutMs: 10_000,\n\t\t});\n\t\tversion = versionResult.stdout.trim();\n\t} catch {\n\t\treturn { installed: true, version: null, authenticated: false };\n\t}\n\n\t// Use gh auth status for reliable authentication check\n\tconst authenticated = await isCopilotAuthenticated();\n\n\treturn { installed: true, version, authenticated };\n}\n\nexport function createCopilotNotInstalledError() {\n\treturn createServicemeError(\n\t\tCOPILOT_ERROR_CODES.NOT_INSTALLED,\n\t\t\"GitHub Copilot CLI is not installed. Visit https://docs.github.com/en/copilot/using-github-copilot/using-github-copilot-in-the-command-line to install.\"\n\t);\n}\n\nexport function createCopilotAuthRequiredError() {\n\treturn createServicemeError(\n\t\tCOPILOT_ERROR_CODES.AUTH_REQUIRED,\n\t\t\"GitHub Copilot CLI requires authentication. Run `copilot auth login` to sign in.\"\n\t);\n}\n","import { spawn } from \"node:child_process\";\n\nfunction terminateCommandProcess(child: {\n\tpid?: number;\n\tkilled?: boolean;\n\tkill(signal?: NodeJS.Signals | number): boolean;\n}): void {\n\tif (child.killed) {\n\t\treturn;\n\t}\n\n\tif (process.platform === \"win32\" && child.pid) {\n\t\tconst killProcess = spawn(\"taskkill\", [\"/pid\", String(child.pid), \"/T\", \"/F\"], {\n\t\t\tstdio: \"ignore\",\n\t\t\twindowsHide: true,\n\t\t});\n\n\t\tkillProcess.once(\"error\", () => {\n\t\t\tchild.kill();\n\t\t});\n\t\treturn;\n\t}\n\n\tchild.kill(\"SIGTERM\");\n}\n\nexport interface RunCommandOptions {\n\targs?: string[];\n\tcwd?: string;\n\tenv?: NodeJS.ProcessEnv;\n\ttimeoutMs?: number;\n\tstdin?: string;\n\tshell?: boolean;\n}\n\nexport interface RunCommandResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n}\n\nexport async function runCommand(\n\tcommand: string,\n\toptions: RunCommandOptions = {}\n): Promise<RunCommandResult> {\n\treturn new Promise<RunCommandResult>((resolve, reject) => {\n\t\tconst child = spawn(command, options.args ?? [], {\n\t\t\tcwd: options.cwd,\n\t\t\tenv: options.env,\n\t\t\tshell: options.shell,\n\t\t\tstdio: \"pipe\",\n\t\t\twindowsHide: true,\n\t\t});\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet finished = false;\n\t\tlet timeoutId: NodeJS.Timeout | undefined;\n\n\t\tconst finish = (handler: () => void) => {\n\t\t\tif (finished) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfinished = true;\n\t\t\tif (timeoutId) {\n\t\t\t\tclearTimeout(timeoutId);\n\t\t\t}\n\t\t\thandler();\n\t\t};\n\n\t\tchild.stdout?.setEncoding(\"utf8\");\n\t\tchild.stderr?.setEncoding(\"utf8\");\n\n\t\tchild.stdout?.on(\"data\", (chunk: string) => {\n\t\t\tstdout += chunk;\n\t\t});\n\n\t\tchild.stderr?.on(\"data\", (chunk: string) => {\n\t\t\tstderr += chunk;\n\t\t});\n\n\t\tchild.once(\"error\", (error) => {\n\t\t\tfinish(() => reject(error));\n\t\t});\n\n\t\tchild.once(\"close\", (code) => {\n\t\t\tfinish(() => {\n\t\t\t\tif (code === 0) {\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tstdout,\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t\tcode: 0,\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst error = new Error(\n\t\t\t\t\tstderr || stdout || `Command failed with exit code ${String(code ?? 1)}.`\n\t\t\t\t) as Error & {\n\t\t\t\t\tcode?: number | string;\n\t\t\t\t\tstdout?: string;\n\t\t\t\t\tstderr?: string;\n\t\t\t\t};\n\t\t\t\terror.code = code ?? 1;\n\t\t\t\terror.stdout = stdout;\n\t\t\t\terror.stderr = stderr;\n\t\t\t\treject(error);\n\t\t\t});\n\t\t});\n\n\t\tif (options.stdin !== undefined) {\n\t\t\tchild.stdin?.end(options.stdin);\n\t\t} else {\n\t\t\tchild.stdin?.end();\n\t\t}\n\n\t\tif (options.timeoutMs) {\n\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\tfinish(() => {\n\t\t\t\t\tterminateCommandProcess(child);\n\t\t\t\t\tconst error = new Error(\n\t\t\t\t\t\t`Command timed out after ${String(options.timeoutMs)}ms.`\n\t\t\t\t\t) as Error & {\n\t\t\t\t\t\tcode?: string;\n\t\t\t\t\t\tstdout?: string;\n\t\t\t\t\t\tstderr?: string;\n\t\t\t\t\t};\n\t\t\t\t\terror.code = \"ETIMEDOUT\";\n\t\t\t\t\terror.stdout = stdout;\n\t\t\t\t\terror.stderr = stderr;\n\t\t\t\t\treject(error);\n\t\t\t\t});\n\t\t\t}, options.timeoutMs);\n\t\t}\n\t});\n}\n\nexport async function commandExists(command: string): Promise<boolean> {\n\tconst isWindows = process.platform === \"win32\";\n\n\ttry {\n\t\tawait runCommand(isWindows ? \"where\" : \"which\", {\n\t\t\targs: [command],\n\t\t\ttimeoutMs: 5_000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n","import {\n\tCOPILOT_ERROR_CODES,\n\ttype CopilotPromptOptions,\n\ttype CopilotPromptResult,\n\tcreateServicemeError,\n\tServicemeProtocolError,\n} from \"@serviceme/devtools-protocol\";\nimport { runCommand } from \"../process/runCommand\";\n\nconst COPILOT_COMMAND = \"copilot\";\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\n// Strip ANSI escape codes from output\nfunction stripAnsi(text: string): string {\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ANSI stripping\n\treturn text.replace(/\\x1B\\[[0-9;]*[A-Za-z]/g, \"\");\n}\n\nexport async function copilotPrompt(options: CopilotPromptOptions): Promise<CopilotPromptResult> {\n\tconst args = [\"-p\", options.prompt];\n\n\tif (options.workspace) {\n\t\targs.push(\"--add-dir\", options.workspace);\n\t}\n\n\tif (options.autopilot) {\n\t\targs.push(\"--allow-all-tools\");\n\t}\n\n\tif (options.allowTools?.length) {\n\t\tfor (const tool of options.allowTools) {\n\t\t\targs.push(\"--allow-tool\", tool);\n\t\t}\n\t}\n\n\tif (options.model) {\n\t\targs.push(\"--model\", options.model);\n\t}\n\n\tif (options.agent) {\n\t\targs.push(\"--agent\", options.agent);\n\t}\n\n\tconst timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n\n\ttry {\n\t\tconst result = await runCommand(COPILOT_COMMAND, {\n\t\t\targs,\n\t\t\tcwd: options.workspace,\n\t\t\ttimeoutMs: timeout,\n\t\t});\n\n\t\tconst output = stripAnsi(result.stdout);\n\n\t\t// Detect refusal in success path (some versions exit 0 with refusal)\n\t\tif (\n\t\t\toutput.includes(\"I'm sorry, but I cannot assist\") &&\n\t\t\tresult.stderr?.includes(\"Stream completed without a response\")\n\t\t) {\n\t\t\tthrow createServicemeError(\n\t\t\t\tCOPILOT_ERROR_CODES.AUTH_REQUIRED,\n\t\t\t\t\"Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.\",\n\t\t\t\t{ exitCode: 0, output, stderr: result.stderr }\n\t\t\t);\n\t\t}\n\n\t\treturn { output, exitCode: 0 };\n\t} catch (error: unknown) {\n\t\t// Re-throw already-classified protocol errors so the success-path refusal\n\t\t// branch (and any other inner throws of ServicemeProtocolError) are not\n\t\t// rewritten as EXECUTION_FAILED by the generic catch handler below.\n\t\tif (error instanceof ServicemeProtocolError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst err = error as {\n\t\t\tcode?: number | string;\n\t\t\tstdout?: string;\n\t\t\tstderr?: string;\n\t\t\tmessage?: string;\n\t\t};\n\n\t\tif (err.code === \"ETIMEDOUT\") {\n\t\t\tthrow createServicemeError(\n\t\t\t\tCOPILOT_ERROR_CODES.TIMEOUT,\n\t\t\t\t`Copilot prompt timed out after ${String(timeout)}ms.`\n\t\t\t);\n\t\t}\n\n\t\tconst exitCode = typeof err.code === \"number\" ? err.code : 1;\n\t\tconst output = stripAnsi(err.stdout ?? \"\");\n\t\tconst stderr = err.stderr ?? err.message ?? \"\";\n\n\t\t// Detect Copilot refusal pattern (often caused by expired auth token)\n\t\tif (\n\t\t\toutput.includes(\"I'm sorry, but I cannot assist\") ||\n\t\t\tstderr.includes(\"Stream completed without a response\")\n\t\t) {\n\t\t\tthrow createServicemeError(\n\t\t\t\tCOPILOT_ERROR_CODES.AUTH_REQUIRED,\n\t\t\t\t\"Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.\",\n\t\t\t\t{ exitCode, output, stderr }\n\t\t\t);\n\t\t}\n\n\t\tif (exitCode !== 0 && output) {\n\t\t\treturn { output, exitCode };\n\t\t}\n\n\t\tthrow createServicemeError(\n\t\t\tCOPILOT_ERROR_CODES.EXECUTION_FAILED,\n\t\t\tstderr || `Copilot exited with code ${String(exitCode)}.`,\n\t\t\t{ exitCode, stderr }\n\t\t);\n\t}\n}\n","/**\n * deviceAuth — Pure HMAC-SHA-256 signing helpers for device auth headers.\n *\n * Ported byte-for-byte from\n * `apps/extension/src/services/device/deviceAuth.ts:11-26` (the wire\n * algorithm is locked by the server-side `device-signature-guard.ts`\n * verifier, see `docs/architecture/phase-5-device-header-spec.md` §5).\n *\n * The basis is `METHOD\\nPATH\\nTIMESTAMP\\nBODY\\nSECRET` (LF-joined,\n * NOT JSON). Output is lowercase hex SHA-256.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `deviceAuth.ts HMAC 签名(纯 node:crypto,直接搬)`\n * - `docs/architecture/phase-5-device-header-spec.md` §5 (signature format)\n */\n\nimport { createHash } from \"node:crypto\";\n\n/** Canonical header names — MUST match server's `device-signature-guard.ts:9-15`. */\nexport const DeviceAuthHeaders = {\n\tdeviceId: \"x-ms-device-id\",\n\tdeviceSecret: \"x-ms-device-secret\",\n\tsignature: \"x-ms-device-signature\",\n\ttimestamp: \"x-ms-device-timestamp\",\n\tsecretVersion: \"x-ms-device-secret-version\",\n} as const;\n\nexport interface DeviceRequestSignatureParams {\n\tmethod: string;\n\tpath: string;\n\ttimestamp: number;\n\tbody: string;\n\tsecret: string;\n}\n\n/**\n * Compute the HMAC-SHA-256 hex digest of the canonical basis.\n *\n * Server contract (`apps/server/src/lib/auth/device-signature-guard.ts:46-62`)\n * is line-for-line identical: same LF-joined basis, same lowercase\n * hex output. Any divergence breaks `device-signature-guard.test.ts`.\n */\nexport function createDeviceRequestSignature(params: DeviceRequestSignatureParams): string {\n\tconst basis = [\n\t\tparams.method.toUpperCase(),\n\t\tparams.path,\n\t\tString(params.timestamp),\n\t\tparams.body,\n\t\tparams.secret,\n\t].join(\"\\n\");\n\treturn createHash(\"sha256\").update(basis).digest(\"hex\");\n}\n\n/** 5-header map consumed by `fetch()` callers (CLI bridge, Extension). */\nexport interface DeviceSignedHeaders {\n\t[DeviceAuthHeaders.deviceId]: string;\n\t[DeviceAuthHeaders.deviceSecret]: string;\n\t[DeviceAuthHeaders.signature]: string;\n\t[DeviceAuthHeaders.timestamp]: string;\n\t[DeviceAuthHeaders.secretVersion]: string;\n}\n\nexport interface BuildSignedHeadersParams {\n\tmethod: string;\n\tpath: string;\n\tbody: string;\n\tpublicId: string;\n\tdeviceSecret: string;\n\tsecretVersion: number;\n\t/** Override for deterministic tests. */\n\ttimestamp?: number;\n}\n\n/**\n * Build the canonical 5-header map. The `body` parameter MUST be the\n * exact byte sequence sent on the wire (no whitespace re-canonicalization\n * between client serialization and signature basis construction).\n */\nexport function buildSignedHeaders(params: BuildSignedHeadersParams): DeviceSignedHeaders {\n\tconst timestamp = params.timestamp ?? Date.now();\n\tconst signature = createDeviceRequestSignature({\n\t\tmethod: params.method,\n\t\tpath: params.path,\n\t\ttimestamp,\n\t\tbody: params.body,\n\t\tsecret: params.deviceSecret,\n\t});\n\treturn {\n\t\t[DeviceAuthHeaders.deviceId]: params.publicId,\n\t\t[DeviceAuthHeaders.deviceSecret]: params.deviceSecret,\n\t\t[DeviceAuthHeaders.signature]: signature,\n\t\t[DeviceAuthHeaders.timestamp]: String(timestamp),\n\t\t[DeviceAuthHeaders.secretVersion]: String(params.secretVersion),\n\t};\n}\n","/**\n * Enroller — State machine for `device.enroll` and `device.rotate-secret`.\n *\n * States per `2.需求澄清.md` §1.2:\n * anonymous → pending → claimed → expired\n *\n * - `anonymous` (initial): no device has ever enrolled. Server returns\n * a fresh `publicId` + secret.\n * - `pending`: enrollment HTTP call has been issued but the server\n * hasn't confirmed yet. In-flight state — never persisted.\n * - `claimed`: user has linked this device to their account (via\n * `/api/v1/devices/claim`). Sticky binding locks future re-enrolls\n * to the same `userId` (server-side matrix).\n * - `expired`: server returned a device-expiry error. Forces a fresh\n * enroll on next call.\n *\n * `--force` semantics: any non-anonymous state can be force-reset to\n * `anonymous` by wiping the local identity file. The next enroll will\n * be treated as a brand-new install by the server (no sticky binding).\n *\n * The Enroller is the **state machine**; the actual HTTP I/O is the\n * caller's responsibility (the `DeviceSyncClient` in Phase 5.4 wires\n * the server). This split keeps the Enroller unit-testable without\n * a live server.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `Enroller.ts anonymous → pending → claimed → expired`\n * - `2.需求澄清.md` §1.2 — binding-state machine\n */\n\nimport { randomBytes } from \"node:crypto\";\n\nimport type { DeviceBindingState, DeviceEnrollResult } from \"@serviceme/devtools-protocol\";\nimport type { IdentityStore } from \"./IdentityStore\";\nimport { deriveInstallationId } from \"./InstallationId\";\nimport type { PersistedDeviceIdentity } from \"./types\";\n\n/** 32 bytes of HMAC secret material — matches the server's `device-registration.ts:73-80` generator. */\nconst SECRET_BYTES = 32;\n/** Server returns `publicId` as 32-char hex (16 bytes). Match the wire length. */\nconst PUBLIC_ID_BYTES = 16;\n\ntype RandomBytesFn = (size: number) => Buffer;\n\nconst defaultRandomBytes: RandomBytesFn = (size) => {\n\treturn randomBytes(size);\n};\n\nexport interface EnrollerOptions {\n\tidentityStore: IdentityStore;\n\t/** Injectable clock for deterministic tests. */\n\tnow?: () => Date;\n\t/** Override the random source (tests). */\n\trandomBytes?: (size: number) => Buffer;\n\t/** Caller-supplied enroll HTTP function. Phase 5.4 wires the real one. */\n\tenrollRequest?: EnrollRequestFn;\n}\n\nexport type EnrollRequestFn = (input: {\n\tinstallationId: string;\n\tmachineId: string;\n\tplatform: string;\n\texisting: PersistedDeviceIdentity | null;\n\tforce: boolean;\n\trequireAuth: boolean;\n}) => Promise<EnrollResponse>;\n\nexport interface EnrollResponse {\n\tpublicId: string;\n\tdeviceSecret: string;\n\tsecretVersion: number;\n\tbindingState: DeviceBindingState;\n\texpiresAt?: string;\n}\n\n/** Sentinel error — re-enroll on a claimed device without auth. */\nexport class DeviceReenrollRequiresAuthError extends Error {\n\tconstructor(\n\t\tmessage = \"Re-enroll on a claimed device requires current device credentials or the bound user\"\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"DeviceReenrollRequiresAuthError\";\n\t}\n}\n\n/** Sentinel error — server returned a 410 / version-mismatch after rotation. */\nexport class DeviceSecretVersionMismatchError extends Error {\n\tconstructor(message = \"Device secret version mismatch — server has rotated past the local copy\") {\n\t\tsuper(message);\n\t\tthis.name = \"DeviceSecretVersionMismatchError\";\n\t}\n}\n\nexport class Enroller {\n\tprivate readonly identity: IdentityStore;\n\tprivate readonly now: () => Date;\n\tprivate readonly random: (size: number) => Buffer;\n\tprivate readonly enrollRequest?: EnrollRequestFn;\n\tprivate inflight: Promise<DeviceEnrollResult> | null = null;\n\n\tconstructor(opts: EnrollerOptions) {\n\t\tthis.identity = opts.identityStore;\n\t\tthis.now = opts.now ?? (() => new Date());\n\t\tthis.random = opts.randomBytes ?? defaultRandomBytes;\n\t\tthis.enrollRequest = opts.enrollRequest;\n\t}\n\n\t/**\n\t * Read the current binding state without touching the disk.\n\t * Returns `anonymous` when no identity is stored.\n\t */\n\tasync currentState(): Promise<DeviceBindingState> {\n\t\tconst stored = await this.identity.read();\n\t\treturn stored?.bindingState ?? \"anonymous\";\n\t}\n\n\t/**\n\t * Drive the enrollment flow.\n\t *\n\t * @param force when true, drop the local identity and start fresh\n\t * (server treats this as a brand-new install).\n\t * @param requireAuth when true, refuse to silently re-enroll an\n\t * existing claimed device — throw\n\t * `DeviceReenrollRequiresAuthError` instead.\n\t */\n\t/**\n\t * Resolve when any in-flight enrollment completes. Returns immediately\n\t * when no enrollment is in progress. Allows callers (e.g. the extension's\n\t * `buildDeviceAuthHeaders`) to wait for a concurrent `syncDeviceInfo()`\n\t * enrollment before attempting to read the identity from the store.\n\t */\n\tasync waitForEnrollment(): Promise<void> {\n\t\tif (this.inflight) {\n\t\t\tawait this.inflight;\n\t\t}\n\t}\n\n\tasync enroll(opts: { force?: boolean; requireAuth?: boolean } = {}): Promise<DeviceEnrollResult> {\n\t\t// Concurrency guard — multiple in-flight calls share the same promise.\n\t\tif (this.inflight) {\n\t\t\treturn this.inflight;\n\t\t}\n\t\tconst promise = this.runEnroll(opts);\n\t\tthis.inflight = promise;\n\t\ttry {\n\t\t\treturn await promise;\n\t\t} finally {\n\t\t\tif (this.inflight === promise) this.inflight = null;\n\t\t}\n\t}\n\n\t/** Test seam — surface the underlying identity store. */\n\tgetIdentityStore(): IdentityStore {\n\t\treturn this.identity;\n\t}\n\n\t/** True when an enrollment is currently in-flight. Used by callers (e.g. the extension's `buildDeviceAuthHeaders`) to skip triggering a competing enrollment. */\n\tisEnrolling(): boolean {\n\t\treturn this.inflight !== null;\n\t}\n\n\tprivate async runEnroll(opts: {\n\t\tforce?: boolean;\n\t\trequireAuth?: boolean;\n\t}): Promise<DeviceEnrollResult> {\n\t\tconst { written } = await this.identity.mutate(async (current) => {\n\t\t\tconst existing = opts.force ? null : current;\n\n\t\t\tif (!opts.force && current) {\n\t\t\t\tif (current.bindingState === \"expired\") {\n\t\t\t\t\t// Expired identities are forced to re-enroll as if they were new.\n\t\t\t\t} else if (\n\t\t\t\t\topts.requireAuth &&\n\t\t\t\t\t(current.bindingState === \"claimed\" || current.bindingState === \"pending\")\n\t\t\t\t) {\n\t\t\t\t\t// Caller asserted the device must be claimed, but local state\n\t\t\t\t\t// shows it's still in flight. This is a CLI-only guard — the\n\t\t\t\t\t// server is the final arbiter.\n\t\t\t\t\tthrow new DeviceReenrollRequiresAuthError();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst installationId = current?.installationId ?? deriveInstallationId();\n\t\t\tconst machineId = current?.machineId ?? \"unknown\";\n\t\t\tconst platform = current?.platform ?? \"unknown\";\n\n\t\t\tlet response: EnrollResponse;\n\t\t\tif (this.enrollRequest) {\n\t\t\t\tresponse = await this.enrollRequest({\n\t\t\t\t\tinstallationId,\n\t\t\t\t\tmachineId,\n\t\t\t\t\tplatform,\n\t\t\t\t\texisting,\n\t\t\t\t\tforce: Boolean(opts.force),\n\t\t\t\t\trequireAuth: Boolean(opts.requireAuth),\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\t// Test path / no live HTTP — synthesize a fresh identity. This\n\t\t\t\t// branch is what unit tests exercise; production wires\n\t\t\t\t// `enrollRequest` in Phase 5.4.\n\t\t\t\tresponse = synthesizeEnrollResponse(this.random, existing);\n\t\t\t}\n\n\t\t\tconst next: PersistedDeviceIdentity = {\n\t\t\t\tversion: current?.version ?? 1,\n\t\t\t\tinstallationId,\n\t\t\t\tmachineId,\n\t\t\t\tplatform,\n\t\t\t\thostname: current?.hostname,\n\t\t\t\tpublicId: response.publicId,\n\t\t\t\tsecretVersion: response.secretVersion,\n\t\t\t\tbindingState: response.bindingState,\n\t\t\t\tdeviceSecret: response.deviceSecret,\n\t\t\t\tpreviousDeviceSecret: existing?.deviceSecret,\n\t\t\t\tpreviousSecretExpiresAt:\n\t\t\t\t\topts.force || response.secretVersion === (existing?.secretVersion ?? 0) + 1\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: existing?.previousSecretExpiresAt,\n\t\t\t\tlastEnrollAt: this.now().toISOString(),\n\t\t\t\tlastSyncAt: existing?.lastSyncAt,\n\t\t\t\tlastSyncError: undefined,\n\t\t\t};\n\t\t\treturn { next };\n\t\t});\n\n\t\treturn {\n\t\t\tpublicId: written.publicId,\n\t\t\tbindingState: written.bindingState,\n\t\t\texpiresAt: deriveExpiresAt(written, this.now),\n\t\t};\n\t}\n\n\t/**\n\t * Rotate the HMAC secret. Keeps the previous secret for the grace\n\t * window (default 7 days per `2.需求澄清.md` §1.2) — the\n\t * `previousSecretExpiresAt` is stamped on the persisted identity.\n\t */\n\tasync rotateSecret(\n\t\topts: { gracePeriodDays?: number } = {}\n\t): Promise<{ publicId: string; secretVersion: number; gracePeriodDays: number }> {\n\t\tconst gracePeriodDays = opts.gracePeriodDays ?? 7;\n\t\tconst now = this.now();\n\t\tconst newSecret = this.random(SECRET_BYTES).toString(\"hex\");\n\n\t\tconst { written } = await this.identity.mutate(async (current) => {\n\t\t\tif (!current) {\n\t\t\t\tthrow new Error(\"Cannot rotate-secret without a prior enrollment\");\n\t\t\t}\n\t\t\tconst graceExpiresAt = new Date(now.getTime() + gracePeriodDays * 24 * 60 * 60 * 1000);\n\t\t\tconst next: PersistedDeviceIdentity = {\n\t\t\t\t...current,\n\t\t\t\tdeviceSecret: newSecret,\n\t\t\t\tpreviousDeviceSecret: current.deviceSecret,\n\t\t\t\tpreviousSecretExpiresAt: graceExpiresAt.toISOString(),\n\t\t\t\tsecretVersion: current.secretVersion + 1,\n\t\t\t\tlastEnrollAt: now.toISOString(),\n\t\t\t};\n\t\t\treturn { next };\n\t\t});\n\n\t\treturn {\n\t\t\tpublicId: written.publicId,\n\t\t\tsecretVersion: written.secretVersion,\n\t\t\tgracePeriodDays,\n\t\t};\n\t}\n\n\t/**\n\t * Mark the device as `expired`. Used when the server returns a\n\t * device-expiry response; the next `enroll()` call forces a fresh\n\t * round-trip.\n\t */\n\tasync markExpired(): Promise<void> {\n\t\tawait this.identity.mutate(async (current) => {\n\t\t\tif (!current) {\n\t\t\t\t// Nothing to expire.\n\t\t\t\treturn { next: current ?? (await emptyIdentity(this.random)), result: undefined };\n\t\t\t}\n\t\t\tconst next: PersistedDeviceIdentity = { ...current, bindingState: \"expired\" };\n\t\t\treturn { next };\n\t\t});\n\t}\n\n\t/**\n\t * Mark the device as `claimed`. Called by the bridge after a\n\t * successful `device.claim` server response.\n\t */\n\tasync markClaimed(): Promise<void> {\n\t\tawait this.identity.mutate(async (current) => {\n\t\t\tif (!current) {\n\t\t\t\tthrow new Error(\"Cannot mark-claimed without a prior enrollment\");\n\t\t\t}\n\t\t\tconst next: PersistedDeviceIdentity = {\n\t\t\t\t...current,\n\t\t\t\tbindingState: \"claimed\",\n\t\t\t\tlastSyncAt: this.now().toISOString(),\n\t\t\t\tlastSyncError: undefined,\n\t\t\t};\n\t\t\treturn { next };\n\t\t});\n\t}\n}\n\nfunction deriveExpiresAt(_identity: PersistedDeviceIdentity, _now: () => Date): string | undefined {\n\t// No explicit expiry on the server today (per phase-5-device-header-spec.md\n\t// §9 #1: 7-day grace is rotation-only). The shape is here for forward\n\t// compatibility — when the server adds a per-device expiry, this\n\t// pulls the value from the response without changing the call site.\n\treturn undefined;\n}\n\nfunction synthesizeEnrollResponse(\n\trandom: RandomBytesFn,\n\texisting: PersistedDeviceIdentity | null\n): EnrollResponse {\n\tconst publicId = existing?.publicId ?? random(PUBLIC_ID_BYTES).toString(\"hex\");\n\tconst secretVersion = (existing?.secretVersion ?? 0) + 1;\n\treturn {\n\t\tpublicId,\n\t\tdeviceSecret: random(SECRET_BYTES).toString(\"hex\"),\n\t\tsecretVersion,\n\t\tbindingState: existing?.bindingState === \"claimed\" ? \"claimed\" : \"anonymous\",\n\t};\n}\n\nasync function emptyIdentity(random: RandomBytesFn): Promise<PersistedDeviceIdentity> {\n\treturn {\n\t\tversion: 1,\n\t\tinstallationId: deriveInstallationId(),\n\t\tmachineId: \"unknown\",\n\t\tplatform: \"unknown\",\n\t\tpublicId: random(PUBLIC_ID_BYTES).toString(\"hex\"),\n\t\tsecretVersion: 1,\n\t\tbindingState: \"anonymous\",\n\t\tdeviceSecret: random(SECRET_BYTES).toString(\"hex\"),\n\t\tlastEnrollAt: new Date().toISOString(),\n\t};\n}\n","/**\n * InstallationId — Derive a stable per-machine identifier from\n * `os.hostname()` + `os.userInfo()`.\n *\n * Per `docs/requirements/phase-5-auth-device-toolbox/3.功能拆分.md` B2,\n * `installationId` MUST survive Extension re-installs but vary across\n * machines. We compute a UUID v5-style hash over hostname + username +\n * platform so the result is:\n * - deterministic (same machine → same id)\n * - collision-resistant (SHA-256, 128-bit truncated)\n * - browser-safe (no PII survives — username never enters output)\n *\n * Note: this intentionally differs from `vscode.env.machineId`, which\n * is per-Extension-install and uses a different algorithm. The two\n * coexist: `installationId` is what gets sent to the server, while\n * `machineId` (raw `os.hostname()`) is for diagnostics.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `InstallationId.ts os.hostname() + os.userInfo() 哈希生成`\n * - 3.功能拆分.md B2 — installationId semantics\n */\n\nimport { createHash, randomUUID } from \"node:crypto\";\nimport * as os from \"node:os\";\n\n/** Hex-encoded SHA-256 input. Format: `<hostname>|<username>|<platform>|<nodeVersion>`. */\nfunction fingerprintMaterial(): string {\n\t// `os.userInfo()` is undefined-ish on Windows in some sandboxes; fall\n\t// back to a process env user, then to a constant placeholder. We never\n\t// emit the raw username to the caller — it only enters the hash.\n\tlet username = \"unknown\";\n\ttry {\n\t\tusername = os.userInfo().username;\n\t} catch {\n\t\tusername = process.env.USER ?? process.env.USERNAME ?? \"unknown\";\n\t}\n\treturn [\n\t\tos.hostname(),\n\t\tusername,\n\t\tos.platform(),\n\t\tos.arch(),\n\t\tprocess.versions.node ?? \"unknown\",\n\t].join(\"|\");\n}\n\n/**\n * Returns a deterministic installation id for the current machine.\n * Use this when you need an id that survives Extension reinstalls\n * but stays stable across restarts on the same machine.\n */\nexport function deriveInstallationId(): string {\n\tconst material = fingerprintMaterial();\n\tconst digest = createHash(\"sha256\").update(material).digest(\"hex\");\n\t// Take the first 32 hex chars (128 bits) and reformat as UUID v4-shape\n\t// so the output looks like a UUID to downstream consumers while\n\t// remaining a pure SHA-256 truncation.\n\treturn formatAsV4(digest.slice(0, 32));\n}\n\n/**\n * Returns a random installation id (UUID v4). Use this for fresh\n * installs when no fingerprint input is available (e.g. containerized\n * CI runners where `os.hostname()` is meaningless).\n */\nexport function randomInstallationId(): string {\n\treturn randomUUID();\n}\n\n/** SHA-256 fingerprint material exposed for tests + diagnostics. */\nexport function fingerprintSource(): string {\n\treturn fingerprintMaterial();\n}\n\nfunction formatAsV4(hex32: string): string {\n\t// Stamp version 4 + variant bits per RFC 4122 §4.4. The bits are\n\t// cosmetic — the underlying entropy is still SHA-256.\n\tconst chars = hex32.split(\"\");\n\t// Version nibble (position 12 in canonical UUID, index 13 of the 32-char string).\n\tconst versionIdx = 12;\n\tconst variantIdx = 16;\n\tconst versionChar = (parseInt(chars[versionIdx] ?? \"8\", 16) & 0x0) | 0x4;\n\tchars[versionIdx] = versionChar.toString(16);\n\t// Variant nibble: 10xx → first hex char of the 17th position.\n\tconst variantChar = (parseInt(chars[variantIdx] ?? \"8\", 16) & 0x3) | 0x8;\n\tchars[variantIdx] = variantChar.toString(16);\n\tconst formatted = chars.join(\"\");\n\treturn `${formatted.slice(0, 8)}-${formatted.slice(8, 12)}-${formatted.slice(12, 16)}-${formatted.slice(16, 20)}-${formatted.slice(20, 32)}`;\n}\n","/**\n * IdentityStore — Atomic JSON persistence for the device identity file.\n *\n * Stores the `PersistedDeviceIdentity` (incl. the HMAC secret cleartext)\n * at `~/.serviceme/device.json` (per `phase-5-device-header-spec.md`\n * §3.1). Writes are atomic via `write-tmp + fsync + rename`, matching\n * the `SkillStore` / `ToolboxStore` precedent. Concurrent writes are\n * serialized with a mkdir-based file lock (POSIX-atomic) — proper\n * cross-process locking is deferred to Phase 6+ per the open spec.\n *\n * The file mode is `0600` (owner read/write only) so the cleartext\n * secret stays safe at rest. On Windows the mode hint is a no-op\n * (Windows uses ACLs) but `writeFile` still succeeds.\n *\n * Migration — IdentityStore auto-detects a v0-shape (pre-Phase-5.2)\n * file written by the Extension's old `globalState` blob:\n * { version: 1, claimed: false, publicKeyFingerprint: null }\n * In that case the file is migrated forward to the v1 schema on the\n * next write (the data fields are empty and a fresh enroll is required).\n * The full Extension `globalState` → JSON migration happens in the\n * Phase 5.5 adapter (`apps/extension/.../DeviceService.ts`) since the\n * adapter holds the live `globalState` access.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `IdentityStore.ts 持久化到 ~/.config/serviceme/device.json, 原子写`\n * - `docs/architecture/phase-5-device-header-spec.md` §3.1, §2.5\n */\n\nimport * as fsp from \"node:fs/promises\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nimport { getDeviceJsonPath, getServicemeHome } from \"../paths/userHome\";\n\nimport {\n\ttype AtomicWriteResult,\n\tDEVICE_JSON_SCHEMA_VERSION,\n\ttype IdentityStoreHooks,\n\ttype PersistedDeviceIdentity,\n} from \"./types\";\n\nconst FILE_MODE = 0o600;\nconst LOCK_DIR_MODE = 0o700;\nconst DEFAULT_LOCK_TIMEOUT_MS = 5000;\nconst DEFAULT_LOCK_RETRY_MS = 25;\nconst TMP_SUFFIX = \".tmp\";\n\n/**\n * Minimal interface for reading + writing the persisted identity file.\n * Default impl uses `getDeviceJsonPath()` (which honors `SERVICEME_HOME`),\n * but tests can substitute a custom path for isolation.\n */\nexport interface IdentityFileBackend {\n\tread(filePath: string): Promise<PersistedDeviceIdentity | null>;\n\twrite(filePath: string, payload: PersistedDeviceIdentity): Promise<AtomicWriteResult>;\n\texists(filePath: string): Promise<boolean>;\n\tdelete(filePath: string): Promise<void>;\n\tlistDir?(dir: string): Promise<string[]>;\n}\n\nexport interface IdentityStoreOptions {\n\tfilePath?: string;\n\thooks?: IdentityStoreHooks;\n\tlockTimeoutMs?: number;\n\tlockRetryMs?: number;\n\t/** Injectable clock for deterministic tests. */\n\tnow?: () => Date;\n\tbackend?: IdentityFileBackend;\n}\n\n/**\n * Default file backend — uses `node:fs/promises` with the canonical\n * tmp-then-rename atomic-write pattern.\n */\nexport class FsIdentityFileBackend implements IdentityFileBackend {\n\tasync exists(filePath: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fsp.access(filePath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync read(filePath: string): Promise<PersistedDeviceIdentity | null> {\n\t\ttry {\n\t\t\tconst buf = await fsp.readFile(filePath, \"utf8\");\n\t\t\tconst parsed = JSON.parse(buf) as unknown;\n\t\t\treturn migratePersistedIdentity(parsed);\n\t\t} catch (err) {\n\t\t\tif (isNodeError(err) && err.code === \"ENOENT\") return null;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync write(filePath: string, payload: PersistedDeviceIdentity): Promise<AtomicWriteResult> {\n\t\tawait fsp.mkdir(path.dirname(filePath), { recursive: true });\n\t\tconst tmpPath = `${filePath}${TMP_SUFFIX}`;\n\t\tconst bytes = Buffer.from(JSON.stringify(payload, null, \"\\t\"), \"utf8\");\n\t\t// Ensure tmp is fresh (in case a previous run died mid-write).\n\t\tawait fsp.rm(tmpPath, { force: true });\n\t\tconst handle = await fsp.open(tmpPath, \"w\", FILE_MODE);\n\t\ttry {\n\t\t\tawait handle.writeFile(bytes);\n\t\t\tawait handle.sync();\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait fsp.rename(tmpPath, filePath);\n\t\t// Best-effort chmod for filesystems that ignore mode on create (Windows).\n\t\tawait fsp.chmod(filePath, FILE_MODE).catch(() => undefined);\n\t\treturn { bytesWritten: bytes.byteLength, tmpPath };\n\t}\n\n\tasync delete(filePath: string): Promise<void> {\n\t\tawait fsp.rm(filePath, { force: true });\n\t}\n}\n\n/**\n * Reconcile an unknown on-disk shape into the current `PersistedDeviceIdentity`.\n *\n * - v1 IdentityStore files (current shape) pass through unchanged.\n * - v0 bootstrap files (`{ version: 1, claimed: false, publicKeyFingerprint: null }`)\n * are recognized by their placeholder keys and discarded; the next\n * enroll writes a fresh identity.\n * - Anything else throws — refuse to silently drop user data.\n */\nfunction migratePersistedIdentity(parsed: unknown): PersistedDeviceIdentity | null {\n\tif (!isRecord(parsed)) {\n\t\tthrow new Error(\"device.json: top-level must be an object\");\n\t}\n\tconst version = parsed.version;\n\tif (version === DEVICE_JSON_SCHEMA_VERSION) {\n\t\t// Pre-Phase-5.2 placeholder shape carries `claimed` /\n\t\t// `publicKeyFingerprint` but no real device fields. Recognize\n\t\t// the marker and return null so the next enroll writes fresh data.\n\t\tif (\n\t\t\tparsed.publicId === undefined &&\n\t\t\tparsed.deviceSecret === undefined &&\n\t\t\t(\"claimed\" in parsed || \"publicKeyFingerprint\" in parsed)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\t// Trust the schema — the writer is also us.\n\t\treturn parsed as unknown as PersistedDeviceIdentity;\n\t}\n\tif (typeof version === \"number\" && version < DEVICE_JSON_SCHEMA_VERSION) {\n\t\t// Pre-Phase-5.2 bootstrap shape — the file is empty placeholder\n\t\t// data; nothing to migrate. Return null to signal \"no identity\".\n\t\treturn null;\n\t}\n\tthrow new Error(`device.json: unsupported schema version ${String(version)}`);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isNodeError(value: unknown): value is NodeJS.ErrnoException {\n\treturn value instanceof Error && typeof (value as { code?: unknown }).code === \"string\";\n}\n\n/**\n * mkdir-based advisory file lock. POSIX mkdir is atomic; on Windows\n * modern filesystems (NTFS) it's also atomic at the API level. Sufficient\n * for single-host, single-user scenarios (which is the SERVICEME threat\n * model). Cross-host locking is out of scope.\n */\nclass FileLock {\n\tprivate readonly dirPath: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly retryMs: number;\n\tprivate acquired = false;\n\n\tconstructor(filePath: string, timeoutMs: number, retryMs: number) {\n\t\tthis.dirPath = `${filePath}.lock`;\n\t\tthis.timeoutMs = timeoutMs;\n\t\tthis.retryMs = retryMs;\n\t}\n\n\tasync acquire(): Promise<void> {\n\t\tconst start = Date.now();\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tawait fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });\n\t\t\t\tthis.acquired = true;\n\t\t\t\treturn;\n\t\t\t} catch (err) {\n\t\t\t\tif (!isNodeError(err) || err.code !== \"EEXIST\") {\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\tif (Date.now() - start >= this.timeoutMs) {\n\t\t\t\t\tthrow new Error(`IdentityStore lock acquisition timed out for ${this.dirPath}`);\n\t\t\t\t}\n\t\t\t\tawait delay(this.retryMs);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync release(): Promise<void> {\n\t\tif (!this.acquired) return;\n\t\tthis.acquired = false;\n\t\tawait fsp.rm(this.dirPath, { recursive: true, force: true });\n\t}\n}\n\nexport class IdentityStore {\n\tprivate readonly filePath: string;\n\tprivate readonly backend: IdentityFileBackend;\n\tprivate readonly hooks: IdentityStoreHooks;\n\tprivate readonly lockTimeoutMs: number;\n\tprivate readonly lockRetryMs: number;\n\tprivate readonly now: () => Date;\n\n\tconstructor(opts: IdentityStoreOptions = {}) {\n\t\tthis.filePath = opts.filePath ?? getDeviceJsonPath();\n\t\tthis.backend = opts.backend ?? new FsIdentityFileBackend();\n\t\tthis.hooks = opts.hooks ?? {};\n\t\tthis.lockTimeoutMs = opts.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;\n\t\tthis.lockRetryMs = opts.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;\n\t\tthis.now = opts.now ?? (() => new Date());\n\t}\n\n\t/** Absolute path to the underlying JSON file (test seam). */\n\tgetFilePath(): string {\n\t\treturn this.filePath;\n\t}\n\n\t/** True when the JSON file already exists on disk. */\n\tasync exists(): Promise<boolean> {\n\t\treturn this.backend.exists(this.filePath);\n\t}\n\n\t/** Read the persisted identity; returns `null` when no identity is stored. */\n\tasync read(): Promise<PersistedDeviceIdentity | null> {\n\t\treturn this.backend.read(this.filePath);\n\t}\n\n\t/**\n\t * Atomically write the given identity. Concurrent writers are\n\t * serialized via the file lock; the read-modify-write happens\n\t * inside the lock so callers can't see a partial state.\n\t */\n\tasync write(next: PersistedDeviceIdentity): Promise<AtomicWriteResult> {\n\t\tawait this.hooks.beforeWrite?.(next);\n\t\tconst lock = new FileLock(this.filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tconst stamped: PersistedDeviceIdentity = {\n\t\t\t\t...next,\n\t\t\t\tversion: DEVICE_JSON_SCHEMA_VERSION,\n\t\t\t};\n\t\t\tconst result = await this.backend.write(this.filePath, stamped);\n\t\t\tawait this.hooks.afterWrite?.(stamped);\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t}\n\n\t/**\n\t * Read-modify-write under the same lock. The mutator receives the\n\t * current identity (or `null` on first call) and returns the\n\t * replacement. Throwing inside the mutator aborts the write.\n\t */\n\tasync mutate<T>(\n\t\tmutator: (\n\t\t\tcurrent: PersistedDeviceIdentity | null\n\t\t) => Promise<{ next: PersistedDeviceIdentity; result?: T }>\n\t): Promise<{ result: T | undefined; written: PersistedDeviceIdentity }> {\n\t\tconst lock = new FileLock(this.filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tconst current = await this.backend.read(this.filePath);\n\t\t\tconst { next, result } = await mutator(current);\n\t\t\tconst stamped: PersistedDeviceIdentity = {\n\t\t\t\t...next,\n\t\t\t\tversion: DEVICE_JSON_SCHEMA_VERSION,\n\t\t\t};\n\t\t\tawait this.hooks.beforeWrite?.(stamped);\n\t\t\tawait this.backend.write(this.filePath, stamped);\n\t\t\tawait this.hooks.afterWrite?.(stamped);\n\t\t\treturn { result, written: stamped };\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t}\n\n\t/** Wipe the persisted identity (used by `device.enroll --force`). */\n\tasync clear(): Promise<void> {\n\t\tawait this.backend.delete(this.filePath);\n\t}\n\n\t/**\n\t * Resolve the installation metadata for the current machine.\n\t * Pure helper — no I/O, just `os.*` calls.\n\t */\n\tresolveInstallationMetadata(): Pick<\n\t\tPersistedDeviceIdentity,\n\t\t\"installationId\" | \"machineId\" | \"platform\"\n\t> {\n\t\tconst machineId = os.hostname();\n\t\tconst platform = os.platform();\n\t\t// installationId is derived by `InstallationId.ts` — pass the\n\t\t// caller's already-computed value via `material` so we don't\n\t\t// recompute the SHA twice in a row.\n\t\treturn {\n\t\t\tinstallationId: \"\", // intentionally empty; caller fills via deriveInstallationId()\n\t\t\tmachineId,\n\t\t\tplatform,\n\t\t};\n\t}\n\n\t/**\n\t * Ensure the parent directory exists (`~/.serviceme/`). Idempotent.\n\t * Useful when the bootstrap phase5 placeholder wasn't run yet.\n\t */\n\tasync ensureHome(): Promise<void> {\n\t\tawait fsp.mkdir(getServicemeHome(), { recursive: true });\n\t\tawait fsp.mkdir(path.dirname(this.filePath), { recursive: true });\n\t}\n}\n","import * as os from \"node:os\";\nimport * as path from \"node:path\";\n\n/**\n * SERVICEME user home directory helpers.\n *\n * All paths resolve under {@link getServicemeHome}, which is either the\n * `SERVICEME_HOME` environment variable (when set and non-empty) or\n * `$HOME/.serviceme` on POSIX / `%USERPROFILE%\\.serviceme` on Windows.\n *\n * Tests inject `homeDir` and `servicemeHomeEnv` overrides via\n * {@link setUserHomeOverrides} / {@link resetUserHomeOverrides} so they can\n * exercise the path logic without touching the real user environment.\n */\n\n/** Layout constants — kept in one place so other modules can reuse them. */\nexport const SERVICEME_DIR_NAME = \".serviceme\";\nexport const REPOS_SUBDIR = \"repos\";\nexport const CACHE_SUBDIR = \"cache\";\nexport const DRAFTS_SUBDIR = \"drafts\";\nexport const SKILL_DRAFTS_SUBDIR = \"skills\";\nexport const AGENT_DRAFTS_SUBDIR = \"agents\";\nexport const REPOS_CONFIG_FILENAME = \"repos.json\";\n\n/**\n * Repo id regex — used to validate any `repoId` argument before it is joined\n * into a filesystem path. Keeps path traversal attempts out and gives us a\n * predictable on-disk shape.\n */\nexport const SAFE_REPO_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;\n\n/** Environment variable that overrides the user-home root directory. */\nexport const SERVICEME_HOME_ENV = \"SERVICEME_HOME\";\n\n/**\n * Test seam: lets unit tests inject deterministic values for `os.homedir()`\n * and the `SERVICEME_HOME` env override without actually mutating\n * `process.env` (which would leak into other tests).\n */\ninterface UserHomeOverrides {\n\thomeDir?: string | undefined;\n\tservicemeHomeEnv?: string | undefined;\n\tplatform?: NodeJS.Platform | undefined;\n}\n\nlet activeOverrides: UserHomeOverrides = {};\n\nexport function setUserHomeOverrides(overrides: UserHomeOverrides): void {\n\tactiveOverrides = { ...overrides };\n}\n\nexport function resetUserHomeOverrides(): void {\n\tactiveOverrides = {};\n}\n\nfunction resolveHomeDir(): string {\n\tconst injected = activeOverrides.homeDir;\n\tif (injected !== undefined) {\n\t\treturn injected;\n\t}\n\treturn os.homedir();\n}\n\nfunction resolveServicemeHomeEnv(): string | undefined {\n\tconst injected = activeOverrides.servicemeHomeEnv;\n\tif (injected !== undefined) {\n\t\t// Treat empty string as \"not set\" — `process.env` always returns a string\n\t\t// but tests may deliberately pass \"\" to opt out.\n\t\treturn injected.length > 0 ? injected : undefined;\n\t}\n\tconst envValue = process.env[SERVICEME_HOME_ENV];\n\treturn envValue && envValue.length > 0 ? envValue : undefined;\n}\n\nfunction resolvePlatform(): NodeJS.Platform {\n\treturn activeOverrides.platform ?? process.platform;\n}\n\nexport function assertSafeRepoId(repoId: string): string {\n\tif (typeof repoId !== \"string\" || repoId.length === 0 || !SAFE_REPO_ID_PATTERN.test(repoId)) {\n\t\tthrow new Error(\n\t\t\t`Invalid repo id: ${JSON.stringify(repoId)}. ` +\n\t\t\t\t`Must match ${SAFE_REPO_ID_PATTERN} (alphanumeric start, then ` +\n\t\t\t\t`alphanumerics / underscores / hyphens, ≤ 64 chars).`\n\t\t);\n\t}\n\treturn repoId;\n}\n\n/**\n * The raw OS home directory (`os.homedir()`), honoring test overrides\n * ({@link setUserHomeOverrides}). Exported for callers that need a\n * home-relative path *outside* of `~/.serviceme` — e.g. the\n * `~/.agents/{skills,agents}` convention used by `SkillLinker` for\n * user-scope links.\n */\nexport function getHomeDir(): string {\n\treturn resolveHomeDir();\n}\n\n/**\n * Root directory for all SERVICEME user-level state. Resolves to\n * `${SERVICEME_HOME}` when that env var is set, otherwise `${HOME}/.serviceme`\n * (POSIX) or `%USERPROFILE%\\.serviceme` (Windows via `os.homedir`).\n */\nexport function getServicemeHome(): string {\n\tconst override = resolveServicemeHomeEnv();\n\tif (override !== undefined) {\n\t\treturn path.resolve(override);\n\t}\n\treturn path.join(resolveHomeDir(), SERVICEME_DIR_NAME);\n}\n\n/** `$HOME/.serviceme/repos` (or `${SERVICEME_HOME}/repos`). */\nexport function getReposDir(): string {\n\treturn path.join(getServicemeHome(), REPOS_SUBDIR);\n}\n\n/** `$HOME/.serviceme/repos/<repoId>` — validates `repoId` first. */\nexport function getRepoDir(repoId: string): string {\n\treturn path.join(getReposDir(), assertSafeRepoId(repoId));\n}\n\n/** `$HOME/.serviceme/cache` — generic per-user cache. */\nexport function getCacheDir(): string {\n\treturn path.join(getServicemeHome(), CACHE_SUBDIR);\n}\n\n/** `$HOME/.serviceme/cache/<repoId>` — per-repo sync state lives here. */\nexport function getRepoCacheDir(repoId: string): string {\n\treturn path.join(getCacheDir(), assertSafeRepoId(repoId));\n}\n\n/** `$HOME/.serviceme/drafts` — local edits not yet pushed upstream. */\nexport function getDraftsDir(): string {\n\treturn path.join(getServicemeHome(), DRAFTS_SUBDIR);\n}\n\n/** `$HOME/.serviceme/drafts/skills` — local skill drafts. */\nexport function getSkillDraftsDir(): string {\n\treturn path.join(getDraftsDir(), SKILL_DRAFTS_SUBDIR);\n}\n\n/** `$HOME/.serviceme/drafts/agents` — local agent drafts. */\nexport function getAgentDraftsDir(): string {\n\treturn path.join(getDraftsDir(), AGENT_DRAFTS_SUBDIR);\n}\n\n/**\n * `$HOME/.serviceme/repos.json` — the single config entry point for repo\n * metadata. Always under the resolved home root (i.e. follows\n * `SERVICEME_HOME` overrides too).\n */\nexport function getReposConfigPath(): string {\n\treturn path.join(getServicemeHome(), REPOS_CONFIG_FILENAME);\n}\n\n/**\n * Convenience helper for callers that need to switch behaviour on platform\n * (e.g. `SkillLinker` chooses symlink vs junction). Exported mostly so tests\n * can pin the value without touching `process.platform` directly.\n */\nexport function getHomePlatform(): NodeJS.Platform {\n\treturn resolvePlatform();\n}\n\n// ─── Scheduled Tasks paths (added by M1.5.3) ───────────────────────────────\n\n/** Layout constants for scheduled-tasks files. Kept here so other modules\n * can reuse them and the names stay in sync with the design doc. */\nexport const SCHEDULED_TASKS_CONFIG_FILENAME = \"scheduled-tasks.json\";\nexport const SCHEDULED_TASKS_LOG_FILENAME = \"scheduled-tasks-log.json\";\nexport const SCHEDULER_PID_FILENAME = \"scheduler.pid\";\nexport const SCHEDULER_LOCK_FILENAME = \"scheduler.lock\";\nexport const SCHEDULER_LOG_FILENAME = \"scheduler.log\";\nexport const MIGRATION_FAILURES_FILENAME = \"migration-failures.json\";\nexport const KNOWN_WORKSPACES_FILENAME = \"known-workspaces.json\";\n\n/** `~/.serviceme/scheduled-tasks.json` — the global v2 task list. */\nexport function getScheduledTasksConfigPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULED_TASKS_CONFIG_FILENAME);\n}\n\n/** `~/.serviceme/scheduled-tasks-log.json` — the global execution log (200 LRU). */\nexport function getScheduledTasksLogPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULED_TASKS_LOG_FILENAME);\n}\n\n/** `~/.serviceme/scheduler.pid` — global daemon PID file. */\nexport function getSchedulerPidPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULER_PID_FILENAME);\n}\n\n/** `~/.serviceme/scheduler.lock` — global daemon startup flock. */\nexport function getSchedulerLockPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULER_LOCK_FILENAME);\n}\n\n/** `~/.serviceme/scheduler.log` — global daemon log (daemon lifecycle, not task execution). */\nexport function getSchedulerLogPath(): string {\n\treturn path.join(getServicemeHome(), SCHEDULER_LOG_FILENAME);\n}\n\n/** `~/.serviceme/migration-failures.json` — diagnostics for failed v1→v2 imports. */\nexport function getMigrationFailuresPath(): string {\n\treturn path.join(getServicemeHome(), MIGRATION_FAILURES_FILENAME);\n}\n\n/** `~/.serviceme/known-workspaces.json` — workspace list the extension has seen. */\nexport function getKnownWorkspacesPath(): string {\n\treturn path.join(getServicemeHome(), KNOWN_WORKSPACES_FILENAME);\n}\n\n// ─── Phase 5 (auth + device + toolbox) placeholders (Spec §11.12) ────────\n\n/** Layout constants for the Phase 5 client-side state files. The server\n * already has the corresponding routes (`/api/v1/auth/...`,\n * `/api/v1/device/...`, etc.) — the client just needs the on-disk\n * file paths + a first-run bootstrap so the auth/device/toolbox\n * services can open + read them without a per-call existence check. */\nexport const CREDENTIALS_CONFIG_FILENAME = \"credentials.json\";\nexport const DEVICE_JSON_FILENAME = \"device.json\";\nexport const TOOLBOX_JSON_FILENAME = \"toolbox.json\";\nexport const MACHINE_ID_FILENAME = \"machine-id\";\nexport const PROFILES_JSON_FILENAME = \"profiles.json\";\n\n/** `~/.serviceme/credentials.json` — better-auth session tokens + refresh state (client-side cache). */\nexport function getCredentialsConfigPath(): string {\n\treturn path.join(getServicemeHome(), CREDENTIALS_CONFIG_FILENAME);\n}\n\n/** `~/.serviceme/device.json` — device enrollment payload (id, public key fingerprint, claimed-by). */\nexport function getDeviceJsonPath(): string {\n\treturn path.join(getServicemeHome(), DEVICE_JSON_FILENAME);\n}\n\n/** `~/.serviceme/toolbox.json` — local toolbox state (installed tool refs, per-user prefs). */\nexport function getToolboxJsonPath(): string {\n\treturn path.join(getServicemeHome(), TOOLBOX_JSON_FILENAME);\n}\n\n/** `~/.serviceme/machine-id` — opaque stable per-install id (uuid v4 string, no JSON wrapper). */\nexport function getMachineIdPath(): string {\n\treturn path.join(getServicemeHome(), MACHINE_ID_FILENAME);\n}\n\n/** `~/.serviceme/profiles.json` — cached projection of server-side profile rows; refresh on demand. */\nexport function getProfilesJsonPath(): string {\n\treturn path.join(getServicemeHome(), PROFILES_JSON_FILENAME);\n}\n","/**\n * Internal types for the device domain.\n *\n * These types are NOT re-exported from the protocol package — they are\n * implementation details of the IdentityStore + Enroller. Public data\n * models (the bridge wire shape) live in `@serviceme/devtools-protocol`'s\n * `device.ts`.\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `device/types.ts`\n */\n\nimport type { DeviceBindingState } from \"@serviceme/devtools-protocol\";\n\n/**\n * Schema version of the on-disk `device.json` file. Bumped when the\n * shape changes incompatibly. IdentityStore checks this on read and\n * either migrates (versions ≤ 1) or refuses (versions > supported).\n */\nexport const DEVICE_JSON_SCHEMA_VERSION = 1;\n\n/** Internal representation of the persisted identity file. */\nexport interface PersistedDeviceIdentity {\n\tversion: number;\n\t/** Stable per-machine id (UUID v4 shape) — survives secret rotates. */\n\tinstallationId: string;\n\t/** Raw `os.hostname()` for diagnostics. */\n\tmachineId: string;\n\t/** Platform string (e.g. \"darwin\"). */\n\tplatform: string;\n\t/** Optional hostname override for environments where `os.hostname()` is unstable. */\n\thostname?: string;\n\t/** Public, non-secret id returned by the server. 32-char hex. */\n\tpublicId: string;\n\t/** Monotonic secret version counter, starts at 1 after first enroll. */\n\tsecretVersion: number;\n\t/** Current binding state — drives the re-enroll matrix. */\n\tbindingState: DeviceBindingState;\n\t/** HMAC secret (32 bytes hex-encoded = 64 chars). Persisted per `device-header-spec.md` §3.1. */\n\tdeviceSecret: string;\n\t/** Optional: previous secret retained during the grace window (rotation). */\n\tpreviousDeviceSecret?: string;\n\t/** Optional: ISO timestamp at which the previous secret stops being accepted. */\n\tpreviousSecretExpiresAt?: string;\n\t/** ISO timestamp of the most recent successful enroll / rotate. */\n\tlastEnrollAt: string;\n\t/** Optional ISO timestamp of the most recent server sync. */\n\tlastSyncAt?: string;\n\t/** Optional human-readable message for the last sync error. */\n\tlastSyncError?: string;\n}\n\n/** Result of a single atomic write. */\nexport interface AtomicWriteResult {\n\tbytesWritten: number;\n\t/** Path to the temp file (post-rename it no longer exists; useful for diagnostics). */\n\ttmpPath: string;\n}\n\n/** Hook called before/after every identity write — used by tests to assert concurrency safety. */\nexport interface IdentityStoreHooks {\n\tbeforeWrite?: (next: PersistedDeviceIdentity) => void | Promise<void>;\n\tafterWrite?: (next: PersistedDeviceIdentity) => void | Promise<void>;\n}\n","/**\n * DeviceCore — Main entry for the device domain.\n *\n * Aggregates `IdentityStore` + `Enroller` + signer helpers into a single\n * surface that the CLI / Extension / Bridge can call. Pure orchestration\n * — no HTTP of its own (the actual `POST /api/v1/devices/enroll` lives\n * behind `EnrollerOptions.enrollRequest`, wired in Phase 5.4).\n *\n * Refs:\n * - 4.功能规划.md §2.2 — `DeviceCore.ts 主入口`\n * - ADL-003 — data model in `@serviceme/devtools-protocol`\n * - `docs/architecture/phase-5-device-header-spec.md` §3.1\n */\n\nimport type {\n\tDeviceEnrollResult,\n\tDeviceIdentityState,\n\tDeviceMetadata,\n\tDeviceRotateSecretResult,\n\tDeviceStatus,\n} from \"@serviceme/devtools-protocol\";\nimport { buildSignedHeaders, DeviceAuthHeaders, type DeviceSignedHeaders } from \"./deviceAuth\";\nimport { Enroller, type EnrollRequestFn } from \"./Enroller\";\nimport { IdentityStore } from \"./IdentityStore\";\nimport { deriveInstallationId } from \"./InstallationId\";\nimport type { PersistedDeviceIdentity } from \"./types\";\n\nexport interface DeviceCoreOptions {\n\tidentityStore?: IdentityStore;\n\tenrollRequest?: EnrollRequestFn;\n\tnow?: () => Date;\n\t/** Optional override for `deriveInstallationId` (used by tests for determinism). */\n\tresolveInstallationId?: () => string;\n}\n\nexport class DeviceCore {\n\tprivate readonly identity: IdentityStore;\n\tprivate readonly enroller: Enroller;\n\tprivate readonly resolveInstallationId: () => string;\n\n\tconstructor(opts: DeviceCoreOptions = {}) {\n\t\tthis.identity = opts.identityStore ?? new IdentityStore();\n\t\tthis.enroller = new Enroller({\n\t\t\tidentityStore: this.identity,\n\t\t\tenrollRequest: opts.enrollRequest,\n\t\t\tnow: opts.now,\n\t\t});\n\t\tthis.resolveInstallationId = opts.resolveInstallationId ?? deriveInstallationId;\n\t}\n\n\t/** Read-only snapshot of the device status (matches `device.status` wire shape). */\n\tasync status(): Promise<DeviceStatus> {\n\t\tconst stored = await this.identity.read();\n\t\treturn stored\n\t\t\t? {\n\t\t\t\t\tbindingState: stored.bindingState,\n\t\t\t\t\tidentity: projectIdentity(stored),\n\t\t\t\t\tmetadata: projectMetadata(stored),\n\t\t\t\t\tlastSyncAt: stored.lastSyncAt,\n\t\t\t\t\tlastSyncError: stored.lastSyncError,\n\t\t\t\t}\n\t\t\t: { bindingState: \"anonymous\" };\n\t}\n\n\t/** Enroll (or re-enroll) the device. */\n\tasync enroll(opts: { force?: boolean; requireAuth?: boolean } = {}): Promise<DeviceEnrollResult> {\n\t\treturn this.enroller.enroll(opts);\n\t}\n\n\t/** Wait for any in-flight enrollment to finish. Use before `buildSignedHeaders` so that a concurrent `syncDeviceInfo` enrollment has time to write the identity to the store. */\n\tasync waitForEnrollment(): Promise<void> {\n\t\tawait this.enroller.waitForEnrollment();\n\t}\n\n\t/** True when an enrollment is currently in-flight. Used by callers to skip triggering a competing enrollment. */\n\tisEnrolling(): boolean {\n\t\treturn this.enroller.isEnrolling();\n\t}\n\n\t/** Rotate the HMAC secret while keeping the previous one for the grace window. */\n\tasync rotateSecret(opts: { gracePeriodDays?: number } = {}): Promise<DeviceRotateSecretResult> {\n\t\tconst result = await this.enroller.rotateSecret(opts);\n\t\tconst stored = await this.identity.read();\n\t\treturn {\n\t\t\t...result,\n\t\t\tgracePeriodEndsAt: stored?.previousSecretExpiresAt,\n\t\t};\n\t}\n\n\t/** Build the canonical 5-header map for an outbound signed request. */\n\tasync buildSignedHeaders(input: {\n\t\tmethod: string;\n\t\tpath: string;\n\t\tbody: string;\n\t}): Promise<DeviceSignedHeaders | null> {\n\t\tconst stored = await this.identity.read();\n\t\tif (!stored) return null;\n\t\treturn buildSignedHeaders({\n\t\t\tmethod: input.method,\n\t\t\tpath: input.path,\n\t\t\tbody: input.body,\n\t\t\tpublicId: stored.publicId,\n\t\t\tdeviceSecret: stored.deviceSecret,\n\t\t\tsecretVersion: stored.secretVersion,\n\t\t});\n\t}\n\n\t/** Raw stored identity (CLI/extension internal use). Test seam too. */\n\tasync readIdentity(): Promise<PersistedDeviceIdentity | null> {\n\t\treturn this.identity.read();\n\t}\n\n\t/** Wipe the local identity (the `--force` path before re-enroll). */\n\tasync clear(): Promise<void> {\n\t\tawait this.identity.clear();\n\t}\n\n\t/** Mark the device as claimed (called by the bridge after a successful claim). */\n\tasync markClaimed(): Promise<void> {\n\t\tawait this.enroller.markClaimed();\n\t}\n\n\t/** Mark the device as expired (server returned an expiry response). */\n\tasync markExpired(): Promise<void> {\n\t\tawait this.enroller.markExpired();\n\t}\n\n\t/** Expose the identity store (CLI uses it for direct file access in tests). */\n\tgetIdentityStore(): IdentityStore {\n\t\treturn this.identity;\n\t}\n\n\t/** Expose the enroller (CLI uses it for state inspection). */\n\tgetEnroller(): Enroller {\n\t\treturn this.enroller;\n\t}\n\n\t/** Header name constants — re-exported from `deviceAuth.ts`. */\n\tgetHeaderNames(): typeof DeviceAuthHeaders {\n\t\treturn DeviceAuthHeaders;\n\t}\n\n\t/** Compute the installation id for the current machine. */\n\tgetInstallationId(): string {\n\t\treturn this.resolveInstallationId();\n\t}\n}\n\nfunction projectIdentity(stored: PersistedDeviceIdentity): DeviceIdentityState {\n\treturn {\n\t\tpublicId: stored.publicId,\n\t\tsecretVersion: stored.secretVersion,\n\t\tbindingState: stored.bindingState,\n\t};\n}\n\nfunction projectMetadata(stored: PersistedDeviceIdentity): DeviceMetadata {\n\treturn {\n\t\tinstallationId: stored.installationId,\n\t\tmachineId: stored.machineId,\n\t\tplatform: stored.platform,\n\t\thostname: stored.hostname,\n\t};\n}\n","import * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport { getAgentDraftsDir, getSkillDraftsDir } from \"../paths/userHome\";\nimport { extractFrontmatter } from \"../skill-store/index\";\nimport type { SkillFile, SkillKind } from \"../skill-store/types\";\n\n/**\n * Skill & Agent v2 — Drafts (M4)\n *\n * Drafts are offline WIP copies of skills/agents that the user is\n * editing locally before submitting. They live under\n * `~/.serviceme/drafts/{skills,agents}/<draft-id>/` and have the same\n * file layout as a published entry: SKILL.md (or AGENT.md) plus any\n * extra files. The `SubmitClient` (also M4) reads a draft's files and\n * sends them to the server's validate endpoint before pushing.\n *\n * Draft ids are short random hex strings (16 hex chars / 8 bytes of\n * entropy) — long enough to be globally unique, short enough to be\n * pasteable. They are NOT user-meaningful; clients surface a `name`\n * from the draft's frontmatter instead.\n *\n * Persistence is atomic per-file (write to `.tmp-xxx` + rename).\n * The whole draft directory is never partially committed — failed\n * writes leave a `.tmp-xxx` orphan that the next save/cleanup can\n * sweep.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §2.1 (drafts layout),\n * §5.7 SubmitClient (consumer).\n */\n\nexport interface DraftSummary {\n\t/** Random id (16 hex chars). */\n\tid: string;\n\tkind: SkillKind;\n\t/** Path to the manifest file (SKILL.md or AGENT.md). */\n\tmanifestPath: string;\n\t/** Path to the directory containing the draft's files. */\n\tdir: string;\n\t/** Best-effort name from the frontmatter (empty string when absent). */\n\tname: string;\n\t/** Best-effort description from the frontmatter (empty string when absent). */\n\tdescription: string;\n\t/** ISO timestamp of the manifest file's mtime. */\n\tmodifiedAt: string;\n}\n\nexport interface DraftDetail extends DraftSummary {\n\tfiles: SkillFile[];\n}\n\nexport interface SaveDraftOptions {\n\tkind: SkillKind;\n\t/** Existing draft id; omit to create a new draft. */\n\tid?: string;\n\t/** Files to write. Must include a manifest (SKILL.md or AGENT.md). */\n\tfiles: SkillFile[];\n}\n\n/** Sentinel errors. */\nexport class DraftsError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"DraftsError\";\n\t}\n}\nexport class DraftNotFoundError extends DraftsError {\n\tconstructor(\n\t\tpublic readonly kind: SkillKind,\n\t\tpublic readonly id: string\n\t) {\n\t\tsuper(`draft not found: ${kind}/${id}`);\n\t\tthis.name = \"DraftNotFoundError\";\n\t}\n}\nexport class InvalidDraftError extends DraftsError {\n\tconstructor(message: string) {\n\t\tsuper(`invalid draft: ${message}`);\n\t\tthis.name = \"InvalidDraftError\";\n\t}\n}\n\n/**\n * Generate a short random hex id. Uses Node's `crypto.randomBytes` so\n * the output is unpredictable. 8 bytes = 16 hex chars = 64 bits of\n * entropy. We don't promise uniqueness (collisions exist with\n * astronomically low probability) — callers must treat ids as opaque.\n */\nexport function generateDraftId(): string {\n\treturn crypto.randomBytes(8).toString(\"hex\");\n}\n\n/**\n * Resolve the on-disk directory for a draft of a given kind + id.\n * Exported for tests + the SubmitClient which writes the committed\n * files into the repo.\n */\nexport function resolveDraftDir(kind: SkillKind, id: string): string {\n\tconst root = kind === \"skill\" ? getSkillDraftsDir() : getAgentDraftsDir();\n\treturn path.join(root, id);\n}\n\n/**\n * Persistent draft store backed by the local filesystem. Pure local —\n * no network, no git, no DB.\n */\nexport class DraftsStore {\n\t/**\n\t * List every draft of the given kind. Returns [] when the drafts\n\t * directory doesn't exist (cold start, never-saved state).\n\t */\n\tasync list(kind: SkillKind): Promise<DraftSummary[]> {\n\t\tconst root = kind === \"skill\" ? getSkillDraftsDir() : getAgentDraftsDir();\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = await fs.readdir(root);\n\t\t} catch (err) {\n\t\t\tif ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n\t\t\tthrow err;\n\t\t}\n\t\tconst out: DraftSummary[] = [];\n\t\tfor (const name of names) {\n\t\t\tconst summary = await this.tryReadSummary(kind, name);\n\t\t\tif (summary) out.push(summary);\n\t\t}\n\t\t// Most-recently-modified first.\n\t\tout.sort((a, b) => (a.modifiedAt < b.modifiedAt ? 1 : -1));\n\t\treturn out;\n\t}\n\n\t/** Single draft summary (manifest + metadata). Returns null when missing. */\n\tasync tryReadSummary(kind: SkillKind, id: string): Promise<DraftSummary | null> {\n\t\tconst dir = resolveDraftDir(kind, id);\n\t\tconst manifestFilename = kind === \"skill\" ? \"SKILL.md\" : \"AGENT.md\";\n\t\tconst manifestPath = path.join(dir, manifestFilename);\n\t\tlet stat: import(\"node:fs\").Stats;\n\t\ttry {\n\t\t\tstat = await fs.stat(manifestPath);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t\tlet content = \"\";\n\t\ttry {\n\t\t\tcontent = await fs.readFile(manifestPath, \"utf8\");\n\t\t} catch {\n\t\t\t// Manifest exists but unreadable — surface as empty name/desc.\n\t\t}\n\t\tconst parsed = extractFrontmatter(content);\n\t\tconst name = typeof parsed?.data.name === \"string\" ? parsed.data.name : \"\";\n\t\tconst description = typeof parsed?.data.description === \"string\" ? parsed.data.description : \"\";\n\t\treturn {\n\t\t\tid,\n\t\t\tkind,\n\t\t\tmanifestPath,\n\t\t\tdir,\n\t\t\tname,\n\t\t\tdescription,\n\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t};\n\t}\n\n\t/** Single draft detail (summary + all files). Throws when missing. */\n\tasync get(kind: SkillKind, id: string): Promise<DraftDetail> {\n\t\tconst summary = await this.tryReadSummary(kind, id);\n\t\tif (!summary) throw new DraftNotFoundError(kind, id);\n\t\tconst files = await this.getFiles(kind, id);\n\t\treturn { ...summary, files };\n\t}\n\n\t/** All files in a draft (manifest + extras), recursively. */\n\tasync getFiles(kind: SkillKind, id: string): Promise<SkillFile[]> {\n\t\tconst dir = resolveDraftDir(kind, id);\n\t\tconst out: SkillFile[] = [];\n\t\tawait collectRecursive(dir, dir, out);\n\t\tout.sort((a, b) => {\n\t\t\tconst aIsManifest = a.path === \"SKILL.md\" || a.path === \"AGENT.md\";\n\t\t\tconst bIsManifest = b.path === \"SKILL.md\" || b.path === \"AGENT.md\";\n\t\t\tif (aIsManifest && !bIsManifest) return -1;\n\t\t\tif (bIsManifest && !aIsManifest) return 1;\n\t\t\treturn a.path.localeCompare(b.path);\n\t\t});\n\t\treturn out;\n\t}\n\n\t/**\n\t * Create-or-update a draft. Returns the (possibly new) draft id.\n\t * Throws `InvalidDraftError` when the file list does not include\n\t * a manifest.\n\t */\n\tasync save(opts: SaveDraftOptions): Promise<string> {\n\t\tconst manifestFilename = opts.kind === \"skill\" ? \"SKILL.md\" : \"AGENT.md\";\n\t\tif (!opts.files.some((f) => f.path === manifestFilename)) {\n\t\t\tthrow new InvalidDraftError(`${manifestFilename} is required`);\n\t\t}\n\t\tconst id = opts.id ?? generateDraftId();\n\t\tconst dir = resolveDraftDir(opts.kind, id);\n\t\t// Clean the directory so a re-save with fewer files actually\n\t\t// removes the old ones (otherwise stale files linger).\n\t\tawait fs.rm(dir, { recursive: true, force: true });\n\t\tawait fs.mkdir(dir, { recursive: true });\n\t\t// Write every file atomically (tmp + rename).\n\t\tfor (const f of opts.files) {\n\t\t\tconst full = path.join(dir, f.path);\n\t\t\tawait fs.mkdir(path.dirname(full), { recursive: true });\n\t\t\tconst tmp = `${full}.${process.pid}.${Date.now()}.tmp`;\n\t\t\tawait fs.writeFile(tmp, f.content, \"utf8\");\n\t\t\tawait fs.rename(tmp, full);\n\t\t}\n\t\t// Sweep stale .tmp-* leftovers (in case a previous save crashed).\n\t\tawait this.sweepTmp(dir);\n\t\treturn id;\n\t}\n\n\t/** Delete a draft. ENOENT is silently ignored (idempotent). */\n\tasync delete(kind: SkillKind, id: string): Promise<void> {\n\t\tconst dir = resolveDraftDir(kind, id);\n\t\ttry {\n\t\t\tawait fs.rm(dir, { recursive: true, force: true });\n\t\t} catch (err) {\n\t\t\tif ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Best-effort cleanup of `.tmp-*` orphan files in the draft dir. */\n\tprivate async sweepTmp(dir: string): Promise<void> {\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = await fs.readdir(dir);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const n of names) {\n\t\t\tif (n.endsWith(\".tmp\")) {\n\t\t\t\tawait fs.rm(path.join(dir, n), { force: true }).catch(() => undefined);\n\t\t\t}\n\t\t}\n\t}\n}\n\nasync function collectRecursive(absDir: string, root: string, out: SkillFile[]): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (d.name.endsWith(\".tmp\")) continue;\n\t\tconst full = path.join(absDir, d.name);\n\t\tif (d.isDirectory()) {\n\t\t\tawait collectRecursive(full, root, out);\n\t\t} else if (d.isFile()) {\n\t\t\tconst content = await fs.readFile(full, \"utf8\");\n\t\t\tout.push({ path: path.relative(root, full), content });\n\t\t}\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { DEFAULT_REPO_LAYOUT, loadRepoLayout, type RepoLayoutDescriptor } from \"../repo-layout\";\nimport type { SkillDetail, SkillEntry, SkillFile, SkillKind } from \"./types\";\nimport { SkillNotFoundError } from \"./types\";\n\n/**\n * Minimal frontmatter reader — same shape as the extension's\n * `WorkspaceSkillsInitializationService.extractFrontmatter`. We avoid\n * pulling a YAML dep into core; SKILL.md / AGENT.md in the wild use\n * a strict subset (top-level `key: value` pairs delimited by `---`).\n * Anything more exotic (nested mappings, multi-line scalars) is\n * passed through as the raw string in the corresponding value.\n */\nexport function extractFrontmatter(\n\traw: string\n): { data: Record<string, unknown>; body: string } | null {\n\tif (typeof raw !== \"string\") return null;\n\tconst match = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n\tif (!match) return null;\n\tconst yamlBody = match[1] ?? \"\";\n\tconst body = match[2] ?? \"\";\n\tconst data: Record<string, unknown> = {};\n\tfor (const line of yamlBody.split(/\\r?\\n/)) {\n\t\tif (line.trim().length === 0) continue;\n\t\tif (line.trim().startsWith(\"#\")) continue;\n\t\tconst kv = line.match(/^([a-zA-Z_][\\w-]*)\\s*:\\s*(.*)$/);\n\t\tif (!kv || !kv[1]) continue;\n\t\tlet value: unknown = (kv[2] ?? \"\").trim();\n\t\tif (typeof value === \"string\") {\n\t\t\tif (\n\t\t\t\t(value.startsWith('\"') && value.endsWith('\"')) ||\n\t\t\t\t(value.startsWith(\"'\") && value.endsWith(\"'\"))\n\t\t\t) {\n\t\t\t\tvalue = value.slice(1, -1);\n\t\t\t}\n\t\t}\n\t\tdata[kv[1]] = value;\n\t}\n\treturn { data, body };\n}\n\nexport type { SkillDetail, SkillEntry, SkillFile, SkillKind } from \"./types\";\n/**\n * Skill & Agent v2 — SkillStore (M4)\n *\n * Scans the local `~/.serviceme/repos/<id>/` tree to produce a unified\n * list of skills + agents. Local-only — no git, no network. Spec §5.5.\n *\n * Layout normalization (spec §12):\n * 1. **Standard layout** — `skills/<name>/SKILL.md` or\n * `agents/<name>/AGENT.md` under the repo root.\n * 2. **Anthropic flat layout** — `<name>/SKILL.md` directly under\n * the repo root (the `skills/` prefix is omitted).\n * 3. **awesome-copilot style** — root contains README + subdirs\n * that are skills/agents/instructions; we ignore README and\n * any dir without a manifest file.\n * 4. **Flat agent files** — a directory (commonly `agents/` or\n * `agents/official/`) full of `<name>.agent.md` files, one per\n * agent, with NO per-agent subdirectory. This is the prevailing\n * real-world convention (VS Code custom chat agents, GitHub's\n * awesome-copilot, and our own official ms-skills repo all ship\n * agents this way) — unlike skills, agents in the wild are\n * essentially never a `<name>/AGENT.md` directory pair. Detected\n * unconditionally, no `.serviceme-repo.json` opt-in required.\n *\n * Implementation: walk every directory under the repo root, check for\n * SKILL.md or AGENT.md, register if present. We skip the repo root\n * itself (no manifest lives at the top), `.git/`, and `node_modules/`.\n * Flat `*.agent.md` files are checked alongside directories at every\n * level of the walk.\n */\n// Re-export the error class + shared types so callers can\n// `import { SkillNotFoundError, SkillFile } from \"...\"`.\nexport { SkillNotFoundError } from \"./types\";\n\nexport class SkillStore {\n\tprivate readonly repoRoots: Map<string, string>;\n\n\tconstructor(opts: {\n\t\trepos: ReadonlyArray<{ id: string; rootPath: string }>;\n\t}) {\n\t\tthis.repoRoots = new Map();\n\t\tfor (const r of opts.repos) this.repoRoots.set(r.id, r.rootPath);\n\t}\n\n\t/** All skills + agents across all registered repos. */\n\tasync listAll(): Promise<SkillEntry[]> {\n\t\tconst all: SkillEntry[] = [];\n\t\tfor (const repoId of this.repoRoots.keys()) {\n\t\t\tall.push(...(await this.listByRepo(repoId)));\n\t\t}\n\t\treturn all;\n\t}\n\n\t/** All skills + agents under a single repo. */\n\tasync listByRepo(repoId: string): Promise<SkillEntry[]> {\n\t\tconst root = this.repoRoots.get(repoId);\n\t\tif (!root) return [];\n\t\tconst layout = (await loadRepoLayout(root)) ?? DEFAULT_REPO_LAYOUT;\n\t\tconst entries: SkillEntry[] = [];\n\t\tawait walkForEntries(root, repoId, entries, layout);\n\t\treturn entries;\n\t}\n\n\t/** Single entry detail (manifest + all files inside its dir). */\n\tasync get(repoId: string, name: string): Promise<SkillDetail> {\n\t\tconst entries = await this.listByRepo(repoId);\n\t\tconst found = entries.find((e) => e.name === name);\n\t\tif (!found) throw new SkillNotFoundError(repoId, name);\n\t\tconst files = await this.getFiles(repoId, name);\n\t\treturn { ...found, files };\n\t}\n\n\t/** All files inside a single entry's directory (manifest + extras). */\n\tasync getFiles(repoId: string, name: string): Promise<SkillFile[]> {\n\t\tconst entries = await this.listByRepo(repoId);\n\t\tconst found = entries.find((e) => e.name === name);\n\t\tif (!found) throw new SkillNotFoundError(repoId, name);\n\n\t\tconst out: SkillFile[] = [];\n\t\t// Flat single-file entries (e.g. `agents/foo.agent.md`) have `dir`\n\t\t// pointing at the manifest file itself, not a directory — there's\n\t\t// no sibling files to collect, just the one manifest.\n\t\tif (found.dir === found.manifestPath) {\n\t\t\tconst content = await fs.readFile(found.manifestPath, \"utf8\");\n\t\t\treturn [{ path: path.basename(found.manifestPath), content }];\n\t\t}\n\t\tawait collectFilesRecursive(found.dir, found.dir, out);\n\t\t// Sort for determinism (manifest first, then alphabetical)\n\t\tout.sort((a, b) => {\n\t\t\tconst aIsManifest = a.path === \"SKILL.md\" || a.path === \"AGENT.md\";\n\t\t\tconst bIsManifest = b.path === \"SKILL.md\" || b.path === \"AGENT.md\";\n\t\t\tif (aIsManifest && !bIsManifest) return -1;\n\t\t\tif (bIsManifest && !aIsManifest) return 1;\n\t\t\treturn a.path.localeCompare(b.path);\n\t\t});\n\t\treturn out;\n\t}\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Internals\n// ─────────────────────────────────────────────────────────────────────\n\nconst SKIP_DIRS = new Set([\".git\", \"node_modules\", \".vscode\", \"dist\", \"build\", \"out\"]);\n\n/**\n * Directories that are never treated as candidate skill/agent parents\n * during the top-level catalog walk. `plugins/` is a distinct concept\n * from the skill/agent catalog itself — a plugin bundle\n * (`plugins/<scope>/<plugin-name>/{skills,agents}/...`) re-packages\n * ALREADY-cataloged official skills/agents (see the plugin's own\n * `catalog.json`, which lists them by name) purely for discovery\n * grouping. Walking into it produces duplicate `SkillEntry` values\n * with the SAME `repoId`+`name` as their real, top-level counterpart\n * (e.g. `skills/official/acreadiness-assess` AND\n * `plugins/official/acreadiness-cockpit/skills/acreadiness-assess`),\n * which breaks anything keyed on `repoId/name` (React list rendering,\n * the \"installed\" lookup, `SkillStore.get()`'s `.find()`).\n *\n * Scoped separately from `SKIP_DIRS` (used by both this walk AND\n * `collectFilesRecursive`) so an actual skill that happens to ship its\n * own `plugins/` asset folder still has that folder listed in its own\n * file detail view.\n */\nconst SKIP_TOP_LEVEL_SCAN_DIRS = new Set([...SKIP_DIRS, \"plugins\"]);\n\n/** Suffix that marks a standalone file as a flat agent manifest (no wrapping dir). */\nconst FLAT_AGENT_FILE_SUFFIX = \".agent.md\";\n\n/**\n * Recursive walk that yields SkillEntry values for any directory\n * containing a SKILL.md or AGENT.md file, PLUS any standalone\n * `<name>.agent.md` file (see class doc, layout style 4). The dir\n * itself is the \"entry root\" for directory-based entries; the\n * manifest file itself is the \"entry root\" for flat agent files.\n *\n * The optional `layout` descriptor (from `.serviceme-repo.json`)\n * widens the scan: included subdirs are walked even without a\n * manifest at the top level, and `treatFilesAsSkills` surfaces\n * `<name>.md` files inside them as skills (frontmatter parsed\n * from the file body).\n */\nasync function walkForEntries(\n\trootDir: string,\n\trepoId: string,\n\tout: SkillEntry[],\n\tlayout: RepoLayoutDescriptor = DEFAULT_REPO_LAYOUT\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(rootDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\n\tfor (const d of dirents) {\n\t\tif (d.isFile() && d.name.endsWith(FLAT_AGENT_FILE_SUFFIX)) {\n\t\t\tconst filePath = path.join(rootDir, d.name);\n\t\t\tconst stat = await fs.stat(filePath);\n\t\t\tconst content = await fs.readFile(filePath, \"utf8\");\n\t\t\tconst parsed = extractFrontmatter(content);\n\t\t\tout.push({\n\t\t\t\trepoId,\n\t\t\t\tname: d.name.slice(0, -FLAT_AGENT_FILE_SUFFIX.length),\n\t\t\t\tkind: \"agent\",\n\t\t\t\tmanifestPath: filePath,\n\t\t\t\t// Sentinel: `dir === manifestPath` marks a flat single-file\n\t\t\t\t// entry (no directory of its own — see getFiles()).\n\t\t\t\tdir: filePath,\n\t\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (!d.isDirectory()) continue;\n\t\tif (SKIP_TOP_LEVEL_SCAN_DIRS.has(d.name)) continue;\n\t\tif (layout.exclude.includes(d.name)) continue;\n\t\tconst childDir = path.join(rootDir, d.name);\n\t\tconst kind = await detectKind(childDir);\n\t\tif (kind) {\n\t\t\tconst manifestFilename = kind === \"skill\" ? \"SKILL.md\" : \"AGENT.md\";\n\t\t\tconst manifestPath = path.join(childDir, manifestFilename);\n\t\t\tconst stat = await fs.stat(manifestPath);\n\t\t\tconst content = await fs.readFile(manifestPath, \"utf8\");\n\t\t\tconst parsed = extractFrontmatter(content);\n\t\t\tout.push({\n\t\t\t\trepoId,\n\t\t\t\tname: d.name,\n\t\t\t\tkind,\n\t\t\t\tmanifestPath,\n\t\t\t\tdir: childDir,\n\t\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\t// No manifest at the top — recurse, but ALSO check whether\n\t\t// the layout descriptor wants this subdir widened.\n\t\tawait walkForEntries(childDir, repoId, out, layout);\n\t\tif (layout.treatFilesAsSkills && layout.include.includes(d.name)) {\n\t\t\tawait surfaceFilesAsSkills(childDir, d.name, repoId, out);\n\t\t}\n\t}\n}\n\n/**\n * Walk `<includedSubdir>/<file>.md` and register each as a skill.\n * The `.md` file itself is the manifest — frontmatter (if any) is\n * parsed and surfaced. This is the awesome-copilot style: a\n * `prompts/` dir full of `<name>.md` files, no SKILL.md anywhere.\n */\nasync function surfaceFilesAsSkills(\n\tabsDir: string,\n\trelName: string,\n\trepoId: string,\n\tout: SkillEntry[]\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (!d.isFile()) continue;\n\t\tif (!d.name.endsWith(\".md\")) continue;\n\t\tconst filePath = path.join(absDir, d.name);\n\t\tconst stat = await fs.stat(filePath);\n\t\tconst content = await fs.readFile(filePath, \"utf8\");\n\t\tconst parsed = extractFrontmatter(content);\n\t\t// Entry name = \"<subdir>/<file>\" (with the .md stripped) so it\n\t\t// stays unique even when two included subdirs share filenames.\n\t\tconst entryName = `${relName}/${d.name.replace(/\\.md$/, \"\")}`;\n\t\tout.push({\n\t\t\trepoId,\n\t\t\tname: entryName,\n\t\t\tkind: \"skill\",\n\t\t\tmanifestPath: filePath,\n\t\t\tdir: absDir,\n\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t});\n\t}\n}\n\n/**\n * Returns the SkillKind of a directory if it contains a manifest file,\n * or `null` if it doesn't. Manifest precedence: SKILL.md wins if both\n * are present (defensive — the spec separates skills and agents into\n * different subdir trees, so this collision shouldn't happen in\n * well-formed repos).\n */\nasync function detectKind(dir: string): Promise<SkillKind | null> {\n\tconst [hasSkill, hasAgent] = await Promise.all([\n\t\tfs\n\t\t\t.access(path.join(dir, \"SKILL.md\"))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false),\n\t\tfs\n\t\t\t.access(path.join(dir, \"AGENT.md\"))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false),\n\t]);\n\tif (hasSkill) return \"skill\";\n\tif (hasAgent) return \"agent\";\n\treturn null;\n}\n\n/**\n * Walk a skill/agent directory recursively, pushing every file into\n * `out` with its path RELATIVE to the entry root. Skips `.git/`,\n * `node_modules/`, and the SKIP_DIRS set.\n */\nasync function collectFilesRecursive(\n\tabsDir: string,\n\tentryRoot: string,\n\tout: SkillFile[]\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (SKIP_DIRS.has(d.name)) continue;\n\t\tconst full = path.join(absDir, d.name);\n\t\tif (d.isDirectory()) {\n\t\t\tawait collectFilesRecursive(full, entryRoot, out);\n\t\t} else if (d.isFile()) {\n\t\t\tconst content = await fs.readFile(full, \"utf8\");\n\t\t\tout.push({ path: path.relative(entryRoot, full), content });\n\t\t}\n\t}\n}\n","/**\n * Spec §12 — Repo layout descriptor.\n *\n * Some third-party repos (awesome-copilot, composio) use a\n * non-default layout that mixes prompts / instructions / agents /\n * hooks / plugins at the root. The default SkillStore scan\n * (see ../skill-store/index.ts) only picks up directories that\n * contain a `SKILL.md` or `AGENT.md` manifest, so prompts stored\n * as `<subdir>/<name>.md` (no manifest) get missed.\n *\n * This module is the loader for the optional `.serviceme-repo.json`\n * marker file a repo author can drop in at the repo root to opt\n * into a wider scan. The schema:\n *\n * {\n * \"schema\": 1,\n * \"include\": [\"prompts\", \"instructions\"], // subdirs to treat as \"skill dirs\"\n * \"exclude\": [\"hooks\", \"plugins\"], // subdirs to skip\n * \"treatFilesAsSkills\": true // inside an included subdir, each .md file is a skill\n * }\n *\n * All four fields are optional. The defaults match the v0.2\n * walkForEntries behaviour (manifest-based, no marker, no\n * extensions), so adding the file is purely additive.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §12\n */\n\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nconst MARKER_FILENAME = \".serviceme-repo.json\";\nconst SUPPORTED_SCHEMA = 1;\n\nexport interface RepoLayoutDescriptor {\n\t/** Schema version. Currently always 1. */\n\tschema: number;\n\t/** Subdirs (relative to repo root) to walk for entries. */\n\tinclude: string[];\n\t/** Subdirs (relative to repo root) to skip, even if a parent is included. */\n\texclude: string[];\n\t/**\n\t * When true, an included subdir's `<name>.md` files are\n\t * surfaced as skills (frontmatter is parsed from each file as\n\t * a stand-in for SKILL.md). When false (default), the\n\t * include list only widens the recursion — the manifest rule\n\t * still applies.\n\t */\n\ttreatFilesAsSkills: boolean;\n}\n\n/**\n * Default descriptor used when no `.serviceme-repo.json` is\n * present. The shape is the same as what an empty marker would\n * produce, so callers don't have to special-case the \"no marker\"\n * branch.\n */\nexport const DEFAULT_REPO_LAYOUT: RepoLayoutDescriptor = {\n\tschema: SUPPORTED_SCHEMA,\n\tinclude: [],\n\texclude: [],\n\ttreatFilesAsSkills: false,\n};\n\n/**\n * Try to load a `.serviceme-repo.json` from the given repo root.\n * Returns `DEFAULT_REPO_LAYOUT` (not undefined) when the file is\n * absent — callers branch on `include.length` / `treatFilesAsSkills`\n * to decide whether to widen the scan.\n *\n * Malformed markers (bad JSON, wrong schema) are surfaced as\n * `null` so callers can warn the user instead of silently\n * treating them as the default.\n */\nexport async function loadRepoLayout(repoRoot: string): Promise<RepoLayoutDescriptor | null> {\n\tconst markerPath = path.join(repoRoot, MARKER_FILENAME);\n\tlet raw: string;\n\ttry {\n\t\traw = await fs.readFile(markerPath, \"utf8\");\n\t} catch {\n\t\treturn DEFAULT_REPO_LAYOUT;\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\tif (!parsed || typeof parsed !== \"object\") return null;\n\tconst obj = parsed as Record<string, unknown>;\n\n\tif (obj.schema !== SUPPORTED_SCHEMA) return null;\n\tif (!Array.isArray(obj.include) || obj.include.some((s) => typeof s !== \"string\")) {\n\t\treturn null;\n\t}\n\tif (!Array.isArray(obj.exclude) || obj.exclude.some((s) => typeof s !== \"string\")) {\n\t\treturn null;\n\t}\n\tif (typeof obj.treatFilesAsSkills !== \"boolean\") return null;\n\n\treturn {\n\t\tschema: SUPPORTED_SCHEMA,\n\t\tinclude: obj.include as string[],\n\t\texclude: obj.exclude as string[],\n\t\ttreatFilesAsSkills: obj.treatFilesAsSkills,\n\t};\n}\n\n/** Subdir name → entry kind override (rarely needed; default = manifest-driven). */\nexport type KindOverride = Map<string, \"skill\" | \"agent\">;\n","import {\n\tcreateServicemeError,\n\ttype EnvironmentCheckResult,\n\tKNOWN_ENVIRONMENT_TOOLS,\n\ttype KnownEnvironmentTool,\n\ttype ToolCheckResult,\n} from \"@serviceme/devtools-protocol\";\nimport { runCommand } from \"../process/runCommand\";\n\nconst DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5_000;\nconst TOOL_CHECK_TIMEOUT_MS: Partial<Record<KnownEnvironmentTool, number>> = {\n\tnuget: 12_000,\n\tnvm: 8_000,\n\tdotnet: 8_000,\n\t// nvm4w/npm shims and globally-installed .cmd tools (pnpm, nrm) are slow to\n\t// resolve on cold PATHs. Give them extra headroom so the version probe\n\t// doesn't fall through to the generic 5s default.\n\tnpm: 15_000,\n\tpnpm: 15_000,\n\tnrm: 15_000,\n};\nconst ERROR_CODE_NOT_FOUND = 127;\nconst ERROR_CODE_TIMEOUT = \"ETIMEDOUT\";\n\ninterface ExecErrorLike {\n\tcode?: string | number;\n\tmessage?: string;\n\tstdout?: string;\n\tstderr?: string;\n}\n\nexport interface EnvironmentInspectorOptions {\n\t/**\n\t * Override the command runner. Production callers should leave this\n\t * undefined and rely on the default `runCommand` from `../process/runCommand`;\n\t * tests inject a fake to drive error/edge branches without spawning\n\t * real subprocesses.\n\t */\n\trunCommand?: typeof runCommand;\n\t/**\n\t * Override the detected platform. Defaults to `process.platform`. Tests\n\t * use this to exercise the Windows-specific branches of the inspector.\n\t */\n\tplatform?: NodeJS.Platform;\n}\n\nexport class EnvironmentInspector {\n\tprivate readonly runCommandFn: typeof runCommand;\n\tprivate readonly platform: NodeJS.Platform;\n\n\tconstructor(options: EnvironmentInspectorOptions = {}) {\n\t\tthis.runCommandFn = options.runCommand ?? runCommand;\n\t\tthis.platform = options.platform ?? process.platform;\n\t}\n\n\tasync checkEnvironment(): Promise<EnvironmentCheckResult> {\n\t\tconst results = await Promise.all(\n\t\t\tKNOWN_ENVIRONMENT_TOOLS.map(async (tool) => [tool, await this.checkTool(tool)] as const)\n\t\t);\n\n\t\treturn Object.fromEntries(results) as EnvironmentCheckResult;\n\t}\n\n\tasync checkTool(toolName: KnownEnvironmentTool): Promise<ToolCheckResult> {\n\t\tif (!/^[a-zA-Z0-9-]+$/.test(toolName)) {\n\t\t\tthrow createServicemeError(\"invalid_params\", \"Invalid tool name.\");\n\t\t}\n\n\t\ttry {\n\t\t\tif (toolName === \"nvm\") {\n\t\t\t\treturn await this.checkNvm();\n\t\t\t}\n\n\t\t\tif (toolName === \"nuget\") {\n\t\t\t\treturn await this.checkNuget();\n\t\t\t}\n\n\t\t\tconst toolPath = await this.getToolPath(toolName);\n\t\t\tconst version = await this.getToolVersion(toolName);\n\n\t\t\treturn {\n\t\t\t\tinstalled: true,\n\t\t\t\tversion,\n\t\t\t\tpath: toolPath,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn this.handleToolCheckError(error);\n\t\t}\n\t}\n\n\tprivate async getToolPath(toolName: string): Promise<string | undefined> {\n\t\ttry {\n\t\t\tconst isWindows = this.platform === \"win32\";\n\t\t\tconst result = await this.runCommandFn(isWindows ? \"where\" : \"which\", {\n\t\t\t\targs: [toolName],\n\t\t\t\ttimeoutMs: this.getToolTimeout(toolName as KnownEnvironmentTool),\n\t\t\t});\n\n\t\t\treturn result.stdout\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => line.trim())\n\t\t\t\t.find((line) => line.length > 0);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * On Windows, prefer the `.cmd` shim for npm/pnpm/nrm. nvm4w registers\n\t * BOTH an extensionless entry (pointing to node.exe) and a `<tool>.cmd`\n\t * wrapper; `where` returns them in that order, and the extensionless one\n\t * would just print node's own version.\n\t */\n\tprivate async getToolShimPath(toolName: string): Promise<string | undefined> {\n\t\ttry {\n\t\t\tconst result = await this.runCommandFn(\"where\", {\n\t\t\t\targs: [toolName],\n\t\t\t\ttimeoutMs: this.getToolTimeout(toolName as KnownEnvironmentTool),\n\t\t\t});\n\n\t\t\tconst candidates = result.stdout\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => line.trim())\n\t\t\t\t.filter((line) => line.length > 0);\n\n\t\t\tconst cmdShim = candidates.find((line) => line.toLowerCase().endsWith(\".cmd\"));\n\t\t\treturn cmdShim ?? candidates[0];\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate async getToolVersion(toolName: string): Promise<string> {\n\t\tconst invocation = await this.getVersionInvocation(toolName);\n\t\tconst result = await this.runCommandFn(invocation.command, {\n\t\t\targs: invocation.args,\n\t\t\ttimeoutMs: this.getToolTimeout(toolName as KnownEnvironmentTool),\n\t\t});\n\n\t\treturn this.parseVersion(toolName, result.stdout || result.stderr);\n\t}\n\n\tprivate async checkNvm(): Promise<ToolCheckResult> {\n\t\tif (this.platform === \"win32\") {\n\t\t\ttry {\n\t\t\t\tconst result = await this.runCommandFn(\"cmd.exe\", {\n\t\t\t\t\targs: [\"/c\", \"nvm version\"],\n\t\t\t\t\ttimeoutMs: this.getToolTimeout(\"nvm\"),\n\t\t\t\t});\n\n\t\t\t\treturn {\n\t\t\t\t\tinstalled: true,\n\t\t\t\t\tversion: result.stdout.trim(),\n\t\t\t\t\tpath: await this.getToolPath(\"nvm\"),\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\treturn {\n\t\t\t\t\tinstalled: false,\n\t\t\t\t\terror: \"Not installed\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.runCommandFn(\"/bin/bash\", {\n\t\t\t\targs: [\n\t\t\t\t\t\"-lc\",\n\t\t\t\t\t'export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"; nvm --version',\n\t\t\t\t],\n\t\t\t\ttimeoutMs: this.getToolTimeout(\"nvm\"),\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tinstalled: true,\n\t\t\t\tversion: result.stdout.trim(),\n\t\t\t\tpath: \"$HOME/.nvm/nvm.sh\",\n\t\t\t};\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tinstalled: false,\n\t\t\t\terror: \"Not installed\",\n\t\t\t};\n\t\t}\n\t}\n\n\tprivate async checkNuget(): Promise<ToolCheckResult> {\n\t\tconst dotnetPath = await this.getToolPath(\"dotnet\");\n\t\tif (!dotnetPath) {\n\t\t\treturn {\n\t\t\t\tinstalled: false,\n\t\t\t\terror: \"dotnet CLI is not installed\",\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.runCommandFn(\"dotnet\", {\n\t\t\t\targs: [\"nuget\", \"list\", \"source\"],\n\t\t\t\ttimeoutMs: this.getToolTimeout(\"nuget\"),\n\t\t\t});\n\n\t\t\tconst privateSourceUrl = \"http://192.168.20.209:10010/nuget\";\n\t\t\tif (result.stdout.includes(privateSourceUrl)) {\n\t\t\t\treturn {\n\t\t\t\t\tinstalled: true,\n\t\t\t\t\tversion: \"Configured\",\n\t\t\t\t\tpath: privateSourceUrl,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tinstalled: false,\n\t\t\t\terror: \"Private source not configured\",\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn this.handleToolCheckError(error);\n\t\t}\n\t}\n\n\tprivate async getVersionInvocation(toolName: string): Promise<{\n\t\tcommand: string;\n\t\targs: string[];\n\t}> {\n\t\tconst isWindows = this.platform === \"win32\";\n\t\tif (isWindows && [\"npm\", \"pnpm\", \"nrm\"].includes(toolName)) {\n\t\t\t// On Windows, resolve the `.cmd` shim and invoke it via an\n\t\t\t// explicit `cmd.exe /c <absolute-path>` — NOT the\n\t\t\t// `cmd.exe /d /s /c \"<tool> --version\"` form we used to use.\n\t\t\t// The earlier wrapper forced CMD to do a fresh PATH lookup\n\t\t\t// AND its `/s` quote-stripping pass on every call, which on\n\t\t\t// machines using nvm4w shims (or globally-installed npm\n\t\t\t// tools) blew past the 5s default timeout. Spawning the\n\t\t\t// resolved shim path directly skips both steps.\n\t\t\t//\n\t\t\t// nvm4w's `where <tool>` returns BOTH an extensionless entry\n\t\t\t// (pointing to node.exe) and the real `<tool>.cmd` wrapper;\n\t\t\t// we prefer the `.cmd` form so the shim chain still runs.\n\t\t\tconst shim = await this.getToolShimPath(toolName);\n\t\t\tif (shim) {\n\t\t\t\treturn {\n\t\t\t\t\tcommand: \"cmd.exe\",\n\t\t\t\t\targs: [\"/c\", shim, \"--version\"],\n\t\t\t\t};\n\t\t\t}\n\t\t\t// Fall back to letting CMD resolve it from PATH.\n\t\t\treturn {\n\t\t\t\tcommand: \"cmd.exe\",\n\t\t\t\targs: [\"/c\", toolName, \"--version\"],\n\t\t\t};\n\t\t}\n\n\t\tconst commands: Record<string, { command: string; args: string[] }> = {\n\t\t\tgit: { command: \"git\", args: [\"--version\"] },\n\t\t\tnode: { command: \"node\", args: [\"--version\"] },\n\t\t\tnpm: { command: \"npm\", args: [\"--version\"] },\n\t\t\tpnpm: { command: \"pnpm\", args: [\"--version\"] },\n\t\t\tnvm: { command: \"nvm\", args: [\"--version\"] },\n\t\t\tnrm: { command: \"nrm\", args: [\"--version\"] },\n\t\t\trtk: { command: \"rtk\", args: [\"--version\"] },\n\t\t\tdotnet: { command: \"dotnet\", args: [\"--version\"] },\n\t\t};\n\n\t\treturn commands[toolName] || { command: toolName, args: [\"--version\"] };\n\t}\n\n\tprivate getToolTimeout(toolName: KnownEnvironmentTool): number {\n\t\treturn TOOL_CHECK_TIMEOUT_MS[toolName] ?? DEFAULT_TOOL_CHECK_TIMEOUT_MS;\n\t}\n\n\tprivate parseVersion(toolName: string, output: string): string {\n\t\tconst cleaned = output.trim();\n\n\t\tswitch (toolName) {\n\t\t\tcase \"git\": {\n\t\t\t\tconst match = cleaned.match(/git version (\\d+\\.\\d+\\.\\d+)/);\n\t\t\t\treturn match?.[1] || cleaned;\n\t\t\t}\n\t\t\tcase \"node\": {\n\t\t\t\tconst match = cleaned.match(/v?(\\d+\\.\\d+\\.\\d+)/);\n\t\t\t\treturn match?.[1] || cleaned;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconst semver = cleaned.match(/(\\d+\\.\\d+\\.\\d+)/);\n\t\t\t\tif (semver?.[1]) {\n\t\t\t\t\treturn semver[1];\n\t\t\t\t}\n\n\t\t\t\tconst simple = cleaned.match(/(\\d+\\.\\d+)/);\n\t\t\t\treturn simple?.[1] || cleaned;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleToolCheckError(error: unknown): ToolCheckResult {\n\t\tconst execError = error as ExecErrorLike;\n\t\tconst message = execError.message || String(error);\n\t\tconst code = execError.code;\n\n\t\tconst isNotFound =\n\t\t\tmessage.includes(\"command not found\") ||\n\t\t\tmessage.includes(\"not recognized\") ||\n\t\t\tmessage.includes(\"ENOENT\") ||\n\t\t\tmessage.includes(\"EACCES\") ||\n\t\t\tcode === \"ENOENT\" ||\n\t\t\tcode === \"EACCES\" ||\n\t\t\tcode === ERROR_CODE_NOT_FOUND;\n\t\tconst isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes(\"timed out\");\n\n\t\treturn {\n\t\t\tinstalled: false,\n\t\t\terror: isNotFound ? \"Not installed\" : isTimeout ? \"Check timed out\" : message,\n\t\t};\n\t}\n}\n","import { spawn } from \"node:child_process\";\nimport * as path from \"node:path\";\n\nimport type {\n\tGitClientOptions,\n\tGitSpawner,\n\tGitSpawnResult,\n\tPullResult,\n\tPushResult,\n\tRemoteBranch,\n} from \"./types\";\nimport { GitError } from \"./types\";\n\n/**\n * Skill & Agent v2 — Client GitClient (M3)\n *\n * Wraps `git` so all upstream traffic flows through the server's git\n * proxy at `serverProxyBase`. See docs/architecture/skill-agent-v2-repo.md\n * §5.4.\n *\n * Why a wrapper instead of using libgit2 directly:\n * 1. `git` is already installed everywhere we run (extension host,\n * CLI). No native deps.\n * 2. Users can debug their skill repos with plain `git` commands when\n * SERVICEME is misbehaving — the wrappers keep a familiar CLI.\n * 3. libgit2's packfile negotiation has subtle correctness gaps that\n * we don't want to debug server-side.\n *\n * URL rewrite:\n * `https://github.com/owner/repo.git` → `<proxyBase>/<id>`\n *\n * When git hits `<proxyBase>/<id>` it discovers the proxy's\n * `/info/refs?service=git-upload-pack` endpoint via the smart-HTTP\n * protocol. From there the server takes over.\n */\nexport class GitClient {\n\tprivate readonly serverProxyBase: string;\n\tprivate readonly spawner: GitSpawner;\n\n\tconstructor(opts: GitClientOptions) {\n\t\t// Normalize: strip trailing slash so `rewriteRemoteUrl` is stable.\n\t\tthis.serverProxyBase = opts.serverProxyBase.replace(/\\/+$/, \"\");\n\t\tthis.spawner = opts.spawner ?? new NodeGitSpawner();\n\t}\n\n\t/**\n\t * Rewrite an upstream URL to its proxy form.\n\t *\n\t * Examples:\n\t * rewriteRemoteUrl(\"repo-aHR0cHM6Ly9naXRodWIuY29tL21lZGFsc29mdGNoaW5hL21zLXNraWxscy5naXQ\",\n\t * \"https://github.com/medalsoftchina/ms-skills.git\")\n\t * → \"http://localhost:3000/git-proxy/repo-aHR0cHM6Ly9naXRodWIuY29tL21lZGFsc29mdGNoaW5hL21zLXNraWxscy5naXQ\"\n\t *\n\t * rewriteRemoteUrl(\"repo-aHR0cHM6Ly9naXRodWIuY29tL3RlYW0vaW50ZXJuYWwuZ2l0\",\n\t * \"git@github.com:medalsoftchina/ms-skills.git\")\n\t * → \"http://localhost:3000/git-proxy/repo-aHR0cHM6Ly9naXRodWIuY29tL3RlYW0vaW50ZXJuYWwuZ2l0\"\n\t *\n\t * The original URL's host/owner is intentionally DROPPED — the proxy\n\t * knows the upstream from the per-repo config, not from the URL. This\n\t * means callers can route both default and user repos through the same\n\t * dynamic proxy-id scheme.\n\t */\n\trewriteRemoteUrl(_repoId: string, originalUrl: string, useProxy = true): string {\n\t\tif (!useProxy) {\n\t\t\treturn originalUrl;\n\t\t}\n\t\treturn `${this.serverProxyBase}/${_repoId}`;\n\t}\n\n\t/**\n\t * `git clone <proxyUrl> <localPath>` — initialize a new local repo\n\t * from the proxy. Returns when the clone succeeds; throws on failure.\n\t *\n\t * When `branch` is provided, the clone is restricted to that branch\n\t * via `git clone -b <branch> -- <proxyUrl> <localPath>` so the local\n\t * checkout lands on the configured `repo.branch` instead of the\n\t * upstream default branch. The parameter is optional and backward\n\t * compatible — omitting it keeps the original behaviour.\n\t */\n\tasync clone(\n\t\trepoId: string,\n\t\toriginalUrl: string,\n\t\tlocalPath: string,\n\t\tbranch?: string,\n\t\tuseProxy = true\n\t): Promise<void> {\n\t\tconst proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl, useProxy);\n\t\tconst args = branch\n\t\t\t? [\"clone\", \"-b\", branch, \"--\", proxyUrl, localPath]\n\t\t\t: [\"clone\", \"--\", proxyUrl, localPath];\n\t\tawait this.runOrThrow(args, { cwd: process.cwd() });\n\t}\n\n\t/**\n\t * `git fetch <remote> <branch>` + return the resulting commit SHA.\n\t * Operates on an already-cloned repo at `localPath`.\n\t *\n\t * When `branch` is provided, the pull target is forced to that branch:\n\t * if the local working tree is on a different branch, `git checkout\n\t * <branch>` is issued first (git auto-tracks `origin/<branch>`), so\n\t * the sync leaves the repo parked on the configured `repo.branch`.\n\t * When `branch` is omitted the legacy behaviour is preserved — the\n\t * current local branch is fast-forwarded. The parameter is optional\n\t * and backward compatible.\n\t */\n\tasync pull(\n\t\trepoId: string,\n\t\tlocalPath: string,\n\t\tbranch?: string,\n\t\tuseProxy = true,\n\t\toriginalUrl?: string\n\t): Promise<PullResult> {\n\t\t// Re-derive the remote URL from the existing remote config (so\n\t\t// `pull` doesn't need to be told the original upstream URL —\n\t\t// it uses whatever `git remote get-url <remote>` reports).\n\t\tconst remoteUrl = await this.getRemoteUrl(localPath, \"origin\");\n\t\tif (!remoteUrl) {\n\t\t\tthrow new Error(`no 'origin' remote configured at ${localPath}`);\n\t\t}\n\t\tconst targetUrl = useProxy ? remoteUrl : (originalUrl ?? remoteUrl);\n\t\tconst proxyUrl = this.rewriteRemoteUrl(repoId, targetUrl, useProxy);\n\n\t\t// Ensure remote.origin.url points at the proxy URL.\n\t\tawait this.runOrThrow([\"remote\", \"set-url\", \"origin\", proxyUrl], { cwd: localPath });\n\n\t\t// Fetch via the proxy FIRST (this also refreshes the\n\t\t// origin/<branch> refs so a subsequent checkout/merge is current).\n\t\tconst before = await this.safeRevParse(localPath);\n\t\tawait this.runOrThrow([\"fetch\", \"origin\"], { cwd: localPath });\n\t\tconst after = await this.safeRevParse(localPath);\n\n\t\t// Resolve the target branch. If a branch was requested and we are\n\t\t// not currently on it, check it out so git tracks origin/<branch>\n\t\t// before the fast-forward. Otherwise reuse the local current branch\n\t\t// (preserves the original call order for the no-branch case).\n\t\tlet targetBranch = branch;\n\t\tif (!targetBranch) {\n\t\t\ttargetBranch = await this.currentBranch(localPath);\n\t\t} else {\n\t\t\tconst current = await this.currentBranch(localPath);\n\t\t\tif (current !== targetBranch) {\n\t\t\t\tawait this.runOrThrow([\"checkout\", targetBranch], { cwd: localPath });\n\t\t\t}\n\t\t}\n\t\tif (!targetBranch) {\n\t\t\tthrow new Error(`could not determine current branch at ${localPath}`);\n\t\t}\n\n\t\t// Fast-forward the working branch so callers see the new commits.\n\t\tawait this.runOrThrow([\"merge\", \"--ff-only\", `origin/${targetBranch}`], {\n\t\t\tcwd: localPath,\n\t\t});\n\n\t\treturn {\n\t\t\tupdated: before !== after,\n\t\t\tcommitSha: after,\n\t\t\tbranch: targetBranch,\n\t\t};\n\t}\n\n\t/**\n\t * `git push origin <branch>` via the proxy. Caller is responsible\n\t * for committing locally first. The server's GitProxy injects the\n\t * PAT on the way upstream.\n\t */\n\tasync push(\n\t\trepoId: string,\n\t\tlocalPath: string,\n\t\tbranch: string,\n\t\tuseProxy = true\n\t): Promise<PushResult> {\n\t\tconst remoteUrl = await this.getRemoteUrl(localPath, \"origin\");\n\t\tif (!remoteUrl) {\n\t\t\tthrow new Error(`no 'origin' remote configured at ${localPath}`);\n\t\t}\n\t\tconst proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl, useProxy);\n\t\tawait this.runOrThrow([\"remote\", \"set-url\", \"origin\", proxyUrl], { cwd: localPath });\n\n\t\tconst result = await this.spawner.spawn(\n\t\t\t[\"push\", \"origin\", `refs/heads/${branch}:refs/heads/${branch}`],\n\t\t\t{ cwd: localPath }\n\t\t);\n\t\tif (result.code !== 0) {\n\t\t\tthrow new GitError([\"push\"], result);\n\t\t}\n\n\t\t// Parse the new commit SHA from push output: `refs/heads/v2: <sha>\\t...`\n\t\tconst m = result.stderr.match(/refs\\/heads\\/[^\\s:]+:\\s*([0-9a-f]{7,})/);\n\t\tconst commitSha = m?.[1] ?? \"\";\n\n\t\treturn {\n\t\t\tref: `refs/heads/${branch}`,\n\t\t\tcommitSha,\n\t\t};\n\t}\n\n\t/**\n\t * `git ls-remote <proxyUrl>` — list advertised branches without\n\t * cloning. Used by addUserRepo to detect the default branch.\n\t */\n\tasync lsRemote(repoId: string, originalUrl: string, useProxy = true): Promise<RemoteBranch[]> {\n\t\tconst proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl, useProxy);\n\t\tconst result = await this.spawner.spawn([\"ls-remote\", \"--heads\", \"--\", proxyUrl], {\n\t\t\tcwd: process.cwd(),\n\t\t});\n\t\tif (result.code !== 0) {\n\t\t\tthrow new GitError([\"ls-remote\"], result);\n\t\t}\n\t\tconst branches: RemoteBranch[] = [];\n\t\tfor (const line of result.stdout.split(\"\\n\")) {\n\t\t\tconst trimmed = line.trim();\n\t\t\tif (!trimmed) continue;\n\t\t\t// `<sha>\\t<ref>` — split on the tab.\n\t\t\tconst tab = trimmed.indexOf(\"\\t\");\n\t\t\tif (tab === -1) continue;\n\t\t\tconst sha = trimmed.slice(0, tab);\n\t\t\tconst ref = trimmed.slice(tab + 1);\n\t\t\tbranches.push({ sha, ref });\n\t\t}\n\t\treturn branches;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Internal helpers\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/**\n\t * `git add <pathspec…>` followed by `git commit -m <message>`. The\n\t * commit message convention follows the spec: `feat(skills): add\n\t * <name>` / `feat(agents): add <name>`.\n\t */\n\tasync commit(localPath: string, message: string, addPath = \".\"): Promise<{ commitSha: string }> {\n\t\tawait this.runOrThrow([\"add\", \"--\", addPath], { cwd: localPath });\n\t\tawait this.runOrThrow([\"commit\", \"-m\", message], { cwd: localPath });\n\t\tconst sha = await this.safeRevParse(localPath);\n\t\treturn { commitSha: sha };\n\t}\n\n\tprivate async runOrThrow(args: string[], opts: { cwd?: string }): Promise<GitSpawnResult> {\n\t\tconst result = await this.spawner.spawn(args, { cwd: opts.cwd ?? process.cwd() });\n\t\tif (result.code !== 0) {\n\t\t\tthrow new GitError(args, result);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate async getRemoteUrl(localPath: string, remote: string): Promise<string | undefined> {\n\t\tconst result = await this.spawner.spawn([\"remote\", \"get-url\", remote], {\n\t\t\tcwd: localPath,\n\t\t});\n\t\tif (result.code !== 0) return undefined;\n\t\tconst url = result.stdout.trim();\n\t\treturn url.length > 0 ? url : undefined;\n\t}\n\n\tprivate async safeRevParse(localPath: string): Promise<string> {\n\t\tconst result = await this.spawner.spawn([\"rev-parse\", \"HEAD\"], { cwd: localPath });\n\t\tif (result.code !== 0) return \"\";\n\t\treturn result.stdout.trim();\n\t}\n\n\tprivate async currentBranch(localPath: string): Promise<string | undefined> {\n\t\tconst result = await this.spawner.spawn([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], {\n\t\t\tcwd: localPath,\n\t\t});\n\t\tif (result.code !== 0) return undefined;\n\t\tconst branch = result.stdout.trim();\n\t\tif (!branch || branch === \"HEAD\") return undefined;\n\t\treturn branch;\n\t}\n}\n\n/**\n * Default spawner: invokes the system `git` binary via Node's\n * `child_process.spawn`. Streams stdout/stderr to strings and returns\n * the exit code. Tests inject a stub to avoid touching the filesystem.\n */\n// Re-export GitError so callers can `import { GitError } from \"...\"`.\nexport { GitError };\n\nexport class NodeGitSpawner implements GitSpawner {\n\tasync spawn(args: string[], opts: { cwd?: string }): Promise<GitSpawnResult> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst child = spawn(\"git\", args, {\n\t\t\t\tcwd: opts.cwd,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\tshell: false,\n\t\t\t});\n\t\t\tconst stdoutChunks: Buffer[] = [];\n\t\t\tconst stderrChunks: Buffer[] = [];\n\t\t\tchild.stdout?.on(\"data\", (c: Buffer) => stdoutChunks.push(c));\n\t\t\tchild.stderr?.on(\"data\", (c: Buffer) => stderrChunks.push(c));\n\t\t\tchild.on(\"error\", reject);\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tresolve({\n\t\t\t\t\tstdout: Buffer.concat(stdoutChunks).toString(\"utf8\"),\n\t\t\t\t\tstderr: Buffer.concat(stderrChunks).toString(\"utf8\"),\n\t\t\t\t\tcode: code ?? 1,\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n}\n\n/** Convenience: a record-based stub spawner for tests. */\nexport class StubGitSpawner implements GitSpawner {\n\t/** queue of canned responses, consumed FIFO per spawn() call. */\n\treadonly script: GitSpawnResult[];\n\t/** All spawn() invocations, in order, for assertions. */\n\treadonly calls: Array<{ args: string[]; cwd: string | undefined }> = [];\n\n\tconstructor(script: GitSpawnResult[]) {\n\t\tthis.script = [...script];\n\t}\n\n\tasync spawn(args: string[], opts: { cwd?: string }): Promise<GitSpawnResult> {\n\t\tthis.calls.push({ args, cwd: opts.cwd });\n\t\tconst next = this.script.shift();\n\t\tif (!next) {\n\t\t\treturn { stdout: \"\", stderr: `stub: no scripted response for ${args.join(\" \")}`, code: 1 };\n\t\t}\n\t\treturn next;\n\t}\n}\n\n/**\n * Helper: encode a bare absolute file URL (`file:///...`) for local\n * git clone tests. Not used by GitClient directly — exposed for\n * RepoManager's `localSeed` use case.\n */\nexport function toFileUrl(absolutePath: string): string {\n\tconst normalized = path.resolve(absolutePath);\n\tif (process.platform === \"win32\") {\n\t\treturn `file:///${normalized.replace(/\\\\/g, \"/\")}`;\n\t}\n\treturn `file://${normalized}`;\n}\n","import type { SpawnOptions } from \"node:child_process\";\n\n/**\n * Skill & Agent v2 — Client GitClient Types (M3)\n *\n * GitClient wraps the local `git` CLI so all commands the SERVICEME\n * pipeline issues (clone, fetch, push, ls-remote) flow through the\n * server's git smart-HTTP proxy instead of talking directly to GitHub.\n *\n * The single trick: `git` only cares about the URL we hand it as a\n * remote. We rewrite `https://github.com/owner/repo.git` to\n * `http://server:port/git-proxy/<id>` and `git` does the rest — the\n * server transparently forwards + injects PATs as needed.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §5.4 GitClient\n */\n\n/** Result of a `git fetch` (called via the proxy's git-upload-pack). */\nexport interface PullResult {\n\t/** Whether new commits were fetched (false = already up-to-date). */\n\tupdated: boolean;\n\t/** Commit SHA at FETCH_HEAD after the pull. */\n\tcommitSha: string;\n\t/** Branch name that was pulled (e.g. \"v2\"). */\n\tbranch: string;\n}\n\n/** Result of a `git push` (called via the proxy's git-receive-pack). */\nexport interface PushResult {\n\t/** Remote ref updated (e.g. \"refs/heads/v2\"). */\n\tref: string;\n\t/** New commit SHA on the remote. */\n\tcommitSha: string;\n}\n\n/** A branch as advertised by `git ls-remote`. */\nexport interface RemoteBranch {\n\t/** Full ref name, e.g. `refs/heads/main`. */\n\tref: string;\n\t/** Commit SHA the ref points at. */\n\tsha: string;\n}\n\n/** Outcome of any spawned `git` process. */\nexport interface GitSpawnResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n}\n\n/** Strategy for executing git commands. Tests inject a stub. */\nexport interface GitSpawner {\n\tspawn(args: string[], opts: SpawnOptions): Promise<GitSpawnResult>;\n}\n\n/** Options for instantiating GitClient. */\nexport interface GitClientOptions {\n\t/** Base URL of the server git proxy. e.g. `http://localhost:3000/git-proxy`. */\n\tserverProxyBase: string;\n\t/** Inject a custom spawner (default: spawn real `git` CLI). */\n\tspawner?: GitSpawner;\n\t/** Inject an env override for spawned git (default: process.env minus proxy secrets). */\n\tenv?: NodeJS.ProcessEnv;\n}\n\n/** Sentinel error when git exits non-zero. */\nexport class GitError extends Error {\n\treadonly args: string[];\n\treadonly code: number;\n\treadonly stderr: string;\n\treadonly stdout: string;\n\tconstructor(args: string[], result: GitSpawnResult) {\n\t\tsuper(`git ${args.join(\" \")} failed (exit ${result.code}): ${result.stderr.slice(0, 500)}`);\n\t\tthis.name = \"GitError\";\n\t\tthis.args = args;\n\t\tthis.code = result.code;\n\t\tthis.stderr = result.stderr;\n\t\tthis.stdout = result.stdout;\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport {\n\tcreateServicemeError,\n\ttype ServiceMeImageCompressOptions,\n\ttype ServiceMeImageCompressResult,\n\ttype ServiceMeImageInfo,\n\ttype ServiceMeImageValidationResult,\n} from \"@serviceme/devtools-protocol\";\n\ntype SharpMetadata = {\n\twidth?: number;\n\theight?: number;\n\tformat?: string;\n\tspace?: string;\n};\n\ntype SharpPipeline = {\n\tmetadata(): Promise<SharpMetadata>;\n\tjpeg(options: { quality: number }): SharpPipeline;\n\tpng(options: { quality: number; compressionLevel: number }): SharpPipeline;\n\twebp(options: { quality: number }): SharpPipeline;\n\ttoBuffer(): Promise<Buffer>;\n};\n\ntype SharpModule = (inputPath: string) => SharpPipeline;\n\nconst SUPPORTED_FORMATS = [\".jpg\", \".jpeg\", \".png\", \".webp\", \".gif\", \".bmp\", \".tiff\"];\nconst DEFAULT_MINIMUM_COMPRESSION_RATIO = 5;\n\nexport class ImageTools {\n\tgetSupportedFormats(): string[] {\n\t\treturn [...SUPPORTED_FORMATS];\n\t}\n\n\tasync validate(\n\t\tfilePath: string,\n\t\tsharpModulePath?: string\n\t): Promise<ServiceMeImageValidationResult> {\n\t\ttry {\n\t\t\tif (!(await this.pathExists(filePath))) {\n\t\t\t\treturn { valid: false };\n\t\t\t}\n\n\t\t\tif (!SUPPORTED_FORMATS.includes(path.extname(filePath).toLowerCase())) {\n\t\t\t\treturn { valid: false };\n\t\t\t}\n\n\t\t\tawait this.getInfo(filePath, sharpModulePath);\n\t\t\treturn { valid: true };\n\t\t} catch {\n\t\t\treturn { valid: false };\n\t\t}\n\t}\n\n\tasync getInfo(imagePath: string, sharpModulePath?: string): Promise<ServiceMeImageInfo> {\n\t\tconst sharp = this.loadSharp(sharpModulePath);\n\t\tconst metadata = await sharp(imagePath).metadata();\n\t\tconst stats = await fs.stat(imagePath);\n\n\t\treturn {\n\t\t\twidth: metadata.width ?? 0,\n\t\t\theight: metadata.height ?? 0,\n\t\t\tformat: metadata.format ?? \"unknown\",\n\t\t\tsize: stats.size,\n\t\t\tcolorSpace: metadata.space,\n\t\t};\n\t}\n\n\tasync compress(\n\t\timagePath: string,\n\t\toptions: ServiceMeImageCompressOptions\n\t): Promise<ServiceMeImageCompressResult> {\n\t\tconst validation = await this.validate(imagePath, options.sharpModulePath);\n\t\tif (!validation.valid) {\n\t\t\tthrow createServicemeError(\"invalid_params\", `Invalid image file: ${imagePath}`);\n\t\t}\n\n\t\tconst originalStats = await fs.stat(imagePath);\n\t\tconst originalSize = originalStats.size;\n\t\tconst outputPath = this.getOutputPath(imagePath, options);\n\t\tconst compressedBuffer = await this.compressWithSharp(imagePath, options);\n\t\tconst compressedSize = compressedBuffer.length;\n\t\tconst compressionRatio = ((originalSize - compressedSize) / originalSize) * 100;\n\t\tconst minimumCompressionRatio =\n\t\t\toptions.minimumCompressionRatio ?? DEFAULT_MINIMUM_COMPRESSION_RATIO;\n\n\t\tif (compressionRatio < minimumCompressionRatio) {\n\t\t\treturn {\n\t\t\t\toriginalSize,\n\t\t\t\tcompressedSize: originalSize,\n\t\t\t\tcompressionRatio: 0,\n\t\t\t\toutputPath: imagePath,\n\t\t\t};\n\t\t}\n\n\t\tawait fs.writeFile(outputPath, compressedBuffer);\n\n\t\treturn {\n\t\t\toriginalSize,\n\t\t\tcompressedSize,\n\t\t\tcompressionRatio,\n\t\t\toutputPath,\n\t\t};\n\t}\n\n\tprivate async compressWithSharp(\n\t\timagePath: string,\n\t\toptions: ServiceMeImageCompressOptions\n\t): Promise<Buffer> {\n\t\tconst sharp = this.loadSharp(options.sharpModulePath);\n\t\tlet pipeline = sharp(imagePath);\n\n\t\tswitch (options.format ?? path.extname(imagePath).toLowerCase().slice(1)) {\n\t\t\tcase \"jpg\":\n\t\t\tcase \"jpeg\":\n\t\t\t\tpipeline = pipeline.jpeg({ quality: options.quality });\n\t\t\t\tbreak;\n\t\t\tcase \"png\":\n\t\t\t\tpipeline = pipeline.png({\n\t\t\t\t\tquality: options.quality,\n\t\t\t\t\tcompressionLevel: 9,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase \"webp\":\n\t\t\t\tpipeline = pipeline.webp({ quality: options.quality });\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tpipeline = pipeline.jpeg({ quality: options.quality });\n\t\t}\n\n\t\treturn pipeline.toBuffer();\n\t}\n\n\tprivate getOutputPath(inputPath: string, options: ServiceMeImageCompressOptions): string {\n\t\tif (options.outputPath) {\n\t\t\treturn options.outputPath;\n\t\t}\n\n\t\tif (options.replaceOriginImage) {\n\t\t\treturn inputPath;\n\t\t}\n\n\t\tconst dir = path.dirname(inputPath);\n\t\tconst ext = path.extname(inputPath);\n\t\tconst name = path.basename(inputPath, ext);\n\t\treturn path.join(dir, `${name}_compressed${ext}`);\n\t}\n\n\tprivate async pathExists(targetPath: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fs.access(targetPath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate loadSharp(sharpModulePath?: string): SharpModule {\n\t\ttry {\n\t\t\treturn (sharpModulePath ? require(sharpModulePath) : require(\"sharp\")) as SharpModule;\n\t\t} catch (error) {\n\t\t\tthrow createServicemeError(\n\t\t\t\t\"internal_error\",\n\t\t\t\t`sharp is required for image operations: ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`\n\t\t\t);\n\t\t}\n\t}\n}\n\nexport function createImageTools(): ImageTools {\n\treturn new ImageTools();\n}\n","import {\n\tcreateServicemeError,\n\tDEFAULT_JSON_SORT_OPTIONS,\n\ttype JsonSortOptions,\n\ttype JsonValidationResult,\n} from \"@serviceme/devtools-protocol\";\nimport { parse as parseCommentJson } from \"comment-json\";\nimport JSON5 from \"json5\";\n\nexport interface JsonTools {\n\tsort(text: string, options?: Partial<JsonSortOptions>): string;\n\tformat(text: string, indent?: number): string;\n\tvalidate(text: string): JsonValidationResult;\n\tminify(text: string): string;\n}\n\nexport function createJsonTools(): JsonTools {\n\treturn {\n\t\tsort(text: string, options?: Partial<JsonSortOptions>): string {\n\t\t\tconst finalOptions = { ...DEFAULT_JSON_SORT_OPTIONS, ...options };\n\t\t\tconst json = parseJson(text);\n\t\t\tconst sorted = sortObject(json, finalOptions);\n\t\t\treturn JSON.stringify(sorted, null, detectIndent(text));\n\t\t},\n\t\tformat(text: string, indent = 2): string {\n\t\t\treturn JSON.stringify(parseJson(text), null, indent);\n\t\t},\n\t\tvalidate(text: string): JsonValidationResult {\n\t\t\ttry {\n\t\t\t\tparseJson(text);\n\t\t\t\treturn { valid: true };\n\t\t\t} catch (error) {\n\t\t\t\treturn {\n\t\t\t\t\tvalid: false,\n\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t\tminify(text: string): string {\n\t\t\treturn JSON.stringify(parseJson(text));\n\t\t},\n\t};\n}\n\nfunction parseJson(text: string): unknown {\n\tconst parsers = [() => JSON.parse(text), () => parseCommentJson(text), () => JSON5.parse(text)];\n\n\tfor (const parser of parsers) {\n\t\ttry {\n\t\t\treturn parser();\n\t\t} catch {\n\t\t\t// Try next parser.\n\t\t}\n\t}\n\n\tthrow createServicemeError(\"json_invalid_input\", \"Invalid JSON format.\");\n}\n\nfunction sortObject(obj: unknown, options: JsonSortOptions): unknown {\n\tif (Array.isArray(obj)) {\n\t\treturn obj.map((item) => sortObject(item, options));\n\t}\n\n\tif (obj !== null && typeof obj === \"object\") {\n\t\tconst record = obj as Record<string, unknown>;\n\t\tconst sortedKeys = sortKeys(Object.keys(record), options);\n\t\tconst result: Record<string, unknown> = {};\n\n\t\tfor (const key of sortedKeys) {\n\t\t\tresult[key] = sortObject(record[key], options);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\treturn obj;\n}\n\nfunction sortKeys(keys: string[], options: JsonSortOptions): string[] {\n\tlet compareFn: (a: string, b: string) => number;\n\n\tswitch (options.sortAlgo) {\n\t\tcase \"keyLength\":\n\t\t\tcompareFn = (a, b) => a.length - b.length;\n\t\t\tbreak;\n\t\tcase \"alphaNum\":\n\t\t\tcompareFn = (a, b) => a.localeCompare(b, undefined, { numeric: true });\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcompareFn = (a, b) => a.localeCompare(b);\n\t\t\tbreak;\n\t}\n\n\tconst sorted = [...keys].sort(compareFn);\n\treturn options.sortOrder === \"desc\" ? sorted.reverse() : sorted;\n}\n\nfunction detectIndent(text: string): number {\n\tfor (const line of text.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\s+)/);\n\t\tif (match?.[1]) {\n\t\t\treturn match[1].length;\n\t\t}\n\t}\n\n\treturn 2;\n}\n","export interface ServiceMeLogger {\n\tdebug(message: string, ...args: unknown[]): void;\n\tinfo(message: string, ...args: unknown[]): void;\n\twarn(message: string, ...args: unknown[]): void;\n\terror(message: string, ...args: unknown[]): void;\n}\n\nexport const noopLogger: ServiceMeLogger = {\n\tdebug() {},\n\tinfo() {},\n\twarn() {},\n\terror() {},\n};\n\nfunction formatArgs(args: unknown[]): string {\n\treturn args\n\t\t.map((arg) => {\n\t\t\tif (typeof arg === \"string\") {\n\t\t\t\treturn arg;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn JSON.stringify(arg);\n\t\t\t} catch {\n\t\t\t\treturn String(arg);\n\t\t\t}\n\t\t})\n\t\t.join(\" \");\n}\n\nexport function createConsoleLogger(prefix = \"serviceme\"): ServiceMeLogger {\n\treturn {\n\t\tdebug(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t\tinfo(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t\twarn(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t\terror(message: string, ...args: unknown[]) {\n\t\t\tprocess.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}\\n`);\n\t\t},\n\t};\n}\n","/**\n * Spec §11.12 — Phase 5 client-side state bootstrap.\n *\n * The server already exposes the Phase 5 surface (better-auth session\n * endpoints, device enrollment routes, toolbox + profile API). The\n * client (extension + CLI) needs the on-disk files to exist on first\n * run so that subsequent code can `readFile()` without an existence\n * check, and so the user can grep `~/.serviceme/` and see the\n * expected layout instead of an empty directory.\n *\n * Strategy: idempotently write the empty / default JSON for every\n * Phase 5 file that doesn't already exist. Existing files are\n * left untouched so the bootstrap can re-run on every extension\n * activation without overwriting user state.\n *\n * All writes are `mkdir -p` + `writeFile` (no race), and failures\n * are surfaced but never thrown — Phase 5 is auxiliary; the\n * extension / CLI should keep working even if the user's home\n * directory is read-only.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §11.12\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport {\n\tgetCredentialsConfigPath,\n\tgetDeviceJsonPath,\n\tgetMachineIdPath,\n\tgetProfilesJsonPath,\n\tgetServicemeHome,\n\tgetToolboxJsonPath,\n} from \"../paths/userHome\";\n\n/** Per-file default content. `null` for the machine-id file because it\n * is a bare uuid with no JSON wrapper. */\ninterface Phase5FileSpec {\n\tpath: string;\n\tdefaultContent: string;\n}\n\nfunction getPhase5FileSpecs(): Phase5FileSpec[] {\n\treturn [\n\t\t{\n\t\t\tpath: getCredentialsConfigPath(),\n\t\t\t// Mirrors better-auth's client-side session shape; an empty\n\t\t\t// `sessions: []` makes the auth service treat the user as\n\t\t\t// logged-out without a per-read fallback.\n\t\t\tdefaultContent: JSON.stringify({ version: 1, sessions: [] }, null, \"\\t\"),\n\t\t},\n\t\t{\n\t\t\tpath: getDeviceJsonPath(),\n\t\t\t// Device enrollment payload. Empty until the user runs the\n\t\t\t// device-claim flow; the server treats absent fields as\n\t\t\t// \"unclaimed\".\n\t\t\tdefaultContent: JSON.stringify(\n\t\t\t\t{ version: 1, claimed: false, publicKeyFingerprint: null },\n\t\t\t\tnull,\n\t\t\t\t\"\\t\"\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tpath: getToolboxJsonPath(),\n\t\t\t// Local toolbox state; server is the source of truth for\n\t\t\t// installed tools, this is the per-user pref cache.\n\t\t\tdefaultContent: JSON.stringify({ version: 1, installed: [], preferences: {} }, null, \"\\t\"),\n\t\t},\n\t\t{\n\t\t\tpath: getMachineIdPath(),\n\t\t\t// Random uuid, written as a bare string. Subsequent\n\t\t\t// activations see the file and skip re-randomizing.\n\t\t\tdefaultContent: randomUUID(),\n\t\t},\n\t\t{\n\t\t\tpath: getProfilesJsonPath(),\n\t\t\t// Cached projection of the server's profile table; an empty\n\t\t\t// list means \"no profiles yet\" rather than \"unknown\".\n\t\t\tdefaultContent: JSON.stringify({ version: 1, profiles: [] }, null, \"\\t\"),\n\t\t},\n\t];\n}\n\nexport interface BootstrapPhase5Result {\n\t/** Files that were created by this run (already-existing files excluded). */\n\tcreated: string[];\n\t/** Files that were already present and left untouched. */\n\tskipped: string[];\n\t/** Files where the write attempt failed (e.g. read-only home). */\n\tfailed: Array<{ path: string; reason: string }>;\n}\n\n/**\n * Idempotently create the Phase 5 placeholder files under\n * `~/.serviceme/`. Safe to call on every extension activation —\n * existing files are detected via `access` and skipped.\n */\nexport async function bootstrapPhase5Placeholders(): Promise<BootstrapPhase5Result> {\n\tconst result: BootstrapPhase5Result = { created: [], skipped: [], failed: [] };\n\tconst home = getServicemeHome();\n\n\t// Make sure the home directory itself exists before any per-file\n\t// mkdir. mkdir({ recursive: true }) is a no-op when the dir is\n\t// already there, so this is safe to repeat.\n\ttry {\n\t\tawait fs.mkdir(home, { recursive: true });\n\t} catch (err) {\n\t\tresult.failed.push({ path: home, reason: (err as Error).message });\n\t\treturn result;\n\t}\n\n\tfor (const spec of getPhase5FileSpecs()) {\n\t\ttry {\n\t\t\tawait fs.access(spec.path);\n\t\t\tresult.skipped.push(spec.path);\n\t\t} catch {\n\t\t\t// ENOENT (or EACCES on a parent) — try to write.\n\t\t\ttry {\n\t\t\t\tawait fs.mkdir(path.dirname(spec.path), { recursive: true });\n\t\t\t\tawait fs.writeFile(spec.path, spec.defaultContent, \"utf8\");\n\t\t\t\tresult.created.push(spec.path);\n\t\t\t} catch (writeErr) {\n\t\t\t\tresult.failed.push({ path: spec.path, reason: (writeErr as Error).message });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport {\n\tcreateServicemeError,\n\ttype ServiceMeProjectExtractTemplateInput,\n\ttype ServiceMeProjectExtractTemplateResult,\n\ttype ServiceMeProjectGitInitResult,\n\ttype ServiceMeProjectInstallDepsResult,\n\ttype ServiceMeProjectMakeScriptsExecutableResult,\n\ttype ServiceMeProjectScaffoldPruneResult,\n} from \"@serviceme/devtools-protocol\";\nimport { runCommand } from \"../process/runCommand\";\nimport { moveFiles, unzipFile } from \"../utils/fileUtils\";\n\nexport class ProjectTools {\n\tasync extractTemplate(\n\t\tzipPath: string,\n\t\tworkspacePath: string,\n\t\ttempExtractDir: string,\n\t\tinput: ServiceMeProjectExtractTemplateInput\n\t): Promise<ServiceMeProjectExtractTemplateResult> {\n\t\tawait unzipFile(zipPath, tempExtractDir);\n\n\t\tlet sourceDir = path.join(tempExtractDir, input.extractedDirName);\n\t\tlet actualDirName = input.extractedDirName;\n\n\t\tif (!(await this.pathExists(sourceDir))) {\n\t\t\tconst entries = await fs.readdir(tempExtractDir, { withFileTypes: true });\n\t\t\tconst directories = entries.filter(\n\t\t\t\t(entry) => entry.isDirectory() && !entry.name.startsWith(\".\")\n\t\t\t);\n\n\t\t\tconst selectedDirectory = await this.selectExtractedDirectory(\n\t\t\t\tdirectories.map((directory) => directory.name),\n\t\t\t\ttempExtractDir,\n\t\t\t\tinput.projectFilePattern,\n\t\t\t\tinput.extractedDirName\n\t\t\t);\n\n\t\t\tif (selectedDirectory) {\n\t\t\t\tactualDirName = selectedDirectory;\n\t\t\t\tsourceDir = path.join(tempExtractDir, actualDirName);\n\t\t\t} else if (directories.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No directory found after extraction. Expected directory: ${input.extractedDirName}`\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Multiple directories found after extraction: ${directories\n\t\t\t\t\t\t.map((directory) => directory.name)\n\t\t\t\t\t\t.join(\", \")}. Expected: ${input.extractedDirName}`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!(await this.pathExists(sourceDir))) {\n\t\t\tthrow new Error(`Source directory not found: ${sourceDir}`);\n\t\t}\n\n\t\tawait moveFiles(sourceDir, workspacePath, true);\n\n\t\treturn {\n\t\t\tactualDirName,\n\t\t};\n\t}\n\n\tasync installDependencies(\n\t\tworkspacePath: string,\n\t\tcommand?: string\n\t): Promise<ServiceMeProjectInstallDepsResult> {\n\t\tif (!command) {\n\t\t\tthrow createServicemeError(\"invalid_params\", \"Expected install command.\");\n\t\t}\n\n\t\tawait runCommand(command, {\n\t\t\tcwd: workspacePath,\n\t\t\tshell: true,\n\t\t});\n\n\t\treturn {\n\t\t\tinstalled: true,\n\t\t\tcommand,\n\t\t};\n\t}\n\n\tasync makeScriptsExecutable(\n\t\tworkspacePath: string\n\t): Promise<ServiceMeProjectMakeScriptsExecutableResult> {\n\t\tconst isWindows = process.platform === \"win32\";\n\t\tconst scripts = await this.findScripts(workspacePath, isWindows ? [\".ps1\", \".bat\"] : [\".sh\"]);\n\n\t\tlet updatedCount = 0;\n\n\t\tif (isWindows) {\n\t\t\tfor (const scriptPath of scripts.filter((script) => script.endsWith(\".ps1\"))) {\n\t\t\t\ttry {\n\t\t\t\t\tawait runCommand(`powershell -Command \"Unblock-File -Path '${scriptPath}'\"`, {\n\t\t\t\t\t\tcwd: workspacePath,\n\t\t\t\t\t\tshell: true,\n\t\t\t\t\t});\n\t\t\t\t\tupdatedCount += 1;\n\t\t\t\t} catch {\n\t\t\t\t\t// Match the extension's best-effort behavior and continue processing.\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const scriptPath of scripts) {\n\t\t\t\ttry {\n\t\t\t\t\tawait fs.chmod(scriptPath, 0o755);\n\t\t\t\t\tupdatedCount += 1;\n\t\t\t\t} catch {\n\t\t\t\t\t// Match the extension's best-effort behavior and continue processing.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tprocessedCount: scripts.length,\n\t\t\tupdatedCount,\n\t\t\tplatform: process.platform,\n\t\t\tscripts,\n\t\t};\n\t}\n\n\tasync initializeGit(workspacePath: string): Promise<ServiceMeProjectGitInitResult> {\n\t\tconst command = \"git init\";\n\t\tawait runCommand(command, {\n\t\t\tcwd: workspacePath,\n\t\t\tshell: true,\n\t\t});\n\n\t\treturn {\n\t\t\tinitialized: true,\n\t\t\tcommand,\n\t\t};\n\t}\n\n\tasync runScaffoldPrune(\n\t\tworkspacePath: string,\n\t\tpreset?: string\n\t): Promise<ServiceMeProjectScaffoldPruneResult> {\n\t\tif (!preset) {\n\t\t\tthrow createServicemeError(\"invalid_params\", \"Expected scaffold preset.\");\n\t\t}\n\n\t\tawait this.ensurePresetManifest(workspacePath, preset);\n\n\t\tconst pruneCommand = `pnpm run scaffold:prune -- --preset ${preset} --project-root .`;\n\t\tconst initMetadataCommand = `pnpm run scaffold:init-metadata -- --preset ${preset} --project-root . --force`;\n\n\t\tconst commands = [pruneCommand, initMetadataCommand];\n\n\t\tfor (const command of commands) {\n\t\t\tif (command === initMetadataCommand) {\n\t\t\t\t// Some templates remove .ms-scaffold/presets during prune.\n\t\t\t\t// Recover it again before init-metadata calls loadPreset().\n\t\t\t\tawait this.ensurePresetManifest(workspacePath, preset);\n\t\t\t}\n\n\t\t\tawait runCommand(command, {\n\t\t\t\tcwd: workspacePath,\n\t\t\t\tshell: true,\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tapplied: true,\n\t\t\tpreset,\n\t\t\tcommands,\n\t\t};\n\t}\n\n\tprivate async ensurePresetManifest(workspacePath: string, preset: string): Promise<void> {\n\t\tconst presetManifestPath = path.join(\n\t\t\tworkspacePath,\n\t\t\t\".ms-scaffold\",\n\t\t\t\"presets\",\n\t\t\t`${preset}.json`\n\t\t);\n\n\t\tif (await this.pathExists(presetManifestPath)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst projectModePath = path.join(workspacePath, \".ms-scaffold\", \"project-mode.json\");\n\t\tif (!(await this.pathExists(projectModePath))) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst projectModeRaw = await fs.readFile(projectModePath, \"utf8\");\n\t\tconst projectMode = JSON.parse(projectModeRaw) as {\n\t\t\tselectedModules?: unknown;\n\t\t\tprunedModules?: unknown;\n\t\t};\n\n\t\tconst synthesizedPreset = {\n\t\t\tpreset,\n\t\t\tselectedModules: Array.isArray(projectMode.selectedModules)\n\t\t\t\t? projectMode.selectedModules\n\t\t\t\t: [],\n\t\t\tprunedModules: Array.isArray(projectMode.prunedModules) ? projectMode.prunedModules : [],\n\t\t\texclude: [] as string[],\n\t\t\trecommendedFollowUps: [] as string[],\n\t\t\tmanagedFiles: [] as string[],\n\t\t\tmergeManagedFiles: [] as string[],\n\t\t\tuserOwnedPaths: [] as string[],\n\t\t};\n\n\t\tawait fs.mkdir(path.dirname(presetManifestPath), { recursive: true });\n\t\tawait fs.writeFile(\n\t\t\tpresetManifestPath,\n\t\t\t`${JSON.stringify(synthesizedPreset, null, 2)}\\n`,\n\t\t\t\"utf8\"\n\t\t);\n\t}\n\n\tprivate async findScripts(dir: string, extensions: string[]): Promise<string[]> {\n\t\tconst results: string[] = [];\n\t\tlet entries: import(\"node:fs\").Dirent[];\n\n\t\ttry {\n\t\t\tentries = await fs.readdir(dir, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn results;\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = path.join(dir, entry.name);\n\t\t\tif (entry.isDirectory() && entry.name !== \"node_modules\" && !entry.name.startsWith(\".\")) {\n\t\t\t\tresults.push(...(await this.findScripts(fullPath, extensions)));\n\t\t\t} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {\n\t\t\t\tresults.push(fullPath);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tprivate async pathExists(targetPath: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fs.access(targetPath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate async selectExtractedDirectory(\n\t\tdirectoryNames: string[],\n\t\ttempExtractDir: string,\n\t\tprojectFilePattern: string,\n\t\texpectedDirName: string\n\t): Promise<string | null> {\n\t\tif (directoryNames.length === 1) {\n\t\t\treturn directoryNames[0] ?? null;\n\t\t}\n\n\t\tconst exactMatch = directoryNames.find((directoryName) => directoryName === expectedDirName);\n\t\tif (exactMatch) {\n\t\t\treturn exactMatch;\n\t\t}\n\n\t\tconst matches: string[] = [];\n\t\tfor (const directoryName of directoryNames) {\n\t\t\tif (\n\t\t\t\tawait this.directoryMatchesProjectPattern(\n\t\t\t\t\tpath.join(tempExtractDir, directoryName),\n\t\t\t\t\tprojectFilePattern\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tmatches.push(directoryName);\n\t\t\t}\n\t\t}\n\n\t\tif (matches.length === 1) {\n\t\t\treturn matches[0] ?? null;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate async directoryMatchesProjectPattern(\n\t\tdirectoryPath: string,\n\t\tprojectFilePattern: string\n\t): Promise<boolean> {\n\t\tconst entries = await fs.readdir(directoryPath);\n\t\tif (projectFilePattern.includes(\"*\")) {\n\t\t\tconst regex = new RegExp(`^${projectFilePattern.replace(\"*\", \".*\")}$`);\n\t\t\treturn entries.some((entry) => regex.test(entry));\n\t\t}\n\n\t\treturn entries.includes(projectFilePattern);\n\t}\n}\n\nexport function createProjectTools(): ProjectTools {\n\treturn new ProjectTools();\n}\n","import { constants, createWriteStream } from \"node:fs\";\nimport { access, copyFile, lstat, mkdir, readdir, rename, rm } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { Readable } from \"node:stream\";\nimport yauzl from \"yauzl\";\n\nexport const unzipFile = (zipPath: string, dest: string): Promise<void> => {\n\treturn new Promise((resolve, reject) => {\n\t\tyauzl.open(zipPath, { lazyEntries: true }, (err: Error | null, zipfile?: yauzl.ZipFile) => {\n\t\t\tif (err) return reject(err);\n\t\t\tif (!zipfile) return reject(new Error(\"Failed to open zip file.\"));\n\n\t\t\tzipfile.readEntry();\n\t\t\tzipfile.on(\"entry\", (entry: yauzl.Entry) => {\n\t\t\t\tif (/\\/$/.test(entry.fileName)) {\n\t\t\t\t\tvoid mkdir(join(dest, entry.fileName), { recursive: true })\n\t\t\t\t\t\t.then(() => {\n\t\t\t\t\t\t\tzipfile.readEntry();\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch(reject);\n\t\t\t\t} else {\n\t\t\t\t\tconst outputPath = join(dest, entry.fileName);\n\t\t\t\t\tvoid mkdir(dirname(outputPath), { recursive: true })\n\t\t\t\t\t\t.then(() => {\n\t\t\t\t\t\t\tzipfile.openReadStream(\n\t\t\t\t\t\t\t\tentry,\n\t\t\t\t\t\t\t\t(streamError: Error | null, readStream: Readable | null) => {\n\t\t\t\t\t\t\t\t\tif (streamError) return reject(streamError);\n\t\t\t\t\t\t\t\t\tif (!readStream) return reject(new Error(\"Failed to open zip entry stream.\"));\n\n\t\t\t\t\t\t\t\t\tconst writeStream = createWriteStream(outputPath);\n\t\t\t\t\t\t\t\t\treadStream.on(\"error\", reject);\n\t\t\t\t\t\t\t\t\twriteStream.on(\"error\", reject);\n\t\t\t\t\t\t\t\t\twriteStream.on(\"close\", () => {\n\t\t\t\t\t\t\t\t\t\tzipfile.readEntry();\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\treadStream.pipe(writeStream);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch(reject);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tzipfile.on(\"end\", () => {\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\tzipfile.on(\"error\", (zipError: Error) => {\n\t\t\t\treject(zipError);\n\t\t\t});\n\t\t});\n\t});\n};\n\nconst tryLstat = async (targetPath: string) => {\n\ttry {\n\t\treturn await lstat(targetPath);\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst mergeEntry = async (\n\tsourcePath: string,\n\tdestPath: string,\n\toverwrite: boolean\n): Promise<void> => {\n\tconst sourceStat = await lstat(sourcePath);\n\tconst destStat = await tryLstat(destPath);\n\n\tif (sourceStat.isDirectory()) {\n\t\tif (destStat && !destStat.isDirectory()) {\n\t\t\tif (!overwrite) {\n\t\t\t\tawait rm(sourcePath, { recursive: true, force: true });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait rm(destPath, { recursive: true, force: true });\n\t\t}\n\n\t\tawait mkdir(destPath, { recursive: true });\n\t\tconst children = await readdir(sourcePath);\n\n\t\tfor (const child of children) {\n\t\t\tawait mergeEntry(join(sourcePath, child), join(destPath, child), overwrite);\n\t\t}\n\n\t\tawait rm(sourcePath, { recursive: true, force: true });\n\t\treturn;\n\t}\n\n\tif (destStat) {\n\t\tif (!overwrite) {\n\t\t\tawait rm(sourcePath, { recursive: true, force: true });\n\t\t\treturn;\n\t\t}\n\n\t\tawait rm(destPath, { recursive: true, force: true });\n\t}\n\n\ttry {\n\t\tawait rename(sourcePath, destPath);\n\t} catch {\n\t\tawait copyFile(sourcePath, destPath);\n\t\tawait rm(sourcePath, { recursive: true, force: true });\n\t}\n};\n\nexport const moveFiles = async (\n\tsourceDir: string,\n\tdestDir: string,\n\toverwrite = false\n): Promise<void> => {\n\tawait mkdir(destDir, { recursive: true });\n\tconst files = await readdir(sourceDir);\n\n\tfor (const file of files) {\n\t\tconst sourceFile = join(sourceDir, file);\n\t\tconst destFile = join(destDir, file);\n\n\t\tif (!overwrite) {\n\t\t\ttry {\n\t\t\t\tawait access(destFile, constants.F_OK);\n\t\t\t\tawait rm(sourceFile, { recursive: true, force: true });\n\t\t\t\tcontinue;\n\t\t\t} catch {\n\t\t\t\t// destination missing, continue with merge\n\t\t\t}\n\t\t}\n\n\t\tawait mergeEntry(sourceFile, destFile, overwrite);\n\t}\n};\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { GitClient } from \"../git-client\";\nimport type { PullResult } from \"../git-client/types\";\nimport {\n\tassertSafeRepoId,\n\tgetRepoDir,\n\tgetServicemeHome,\n\tSAFE_REPO_ID_PATTERN,\n} from \"../paths/userHome\";\nimport type { ReposStore } from \"../repos/store\";\nimport type { AnyRepoConfig, RepoConfig, UserRepoConfig } from \"../repos/types\";\nimport { isDefaultRepo, isUserRepo } from \"../repos/types\";\n\n/**\n * Skill & Agent v2 — Client RepoManager (M3)\n *\n * RepoManager is the orchestrator for local git repo management. It\n * composes:\n * - {@link ReposStore} (in-memory CRUD over `~/.serviceme/repos.json`)\n * - {@link GitClient} (spawns `git` against the server proxy)\n * - the filesystem (clone targets live under `~/.serviceme/repos/<id>/`)\n *\n * Lifecycle responsibilities:\n * - **Bootstrap**: on first launch, clone all enabled default repos.\n * - **Sync**: pullOne / pullAll for periodic updates.\n * - **User repos**: addUserRepo validates URL, probes branch via\n * `git ls-remote`, writes the entry, and triggers an async clone.\n * - **Cleanup**: removeUserRepo deletes both the local clone AND the\n * `repos.json` entry. disableRepo / enableRepo only flip a flag.\n *\n * Default repos (`source === 'default'`) cannot be removed — only\n * disabled. The Store enforces this; RepoManager propagates the error.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §5.3 RepoManager\n */\n\nexport interface RepoManagerOptions {\n\tstore: ReposStore;\n\tgitClient: GitClient;\n\t/** Override the clock for deterministic tests. */\n\tnow?: () => string;\n\t/** Skip the actual git clone (used by tests). When true, the\n\t * filesystem-side mkdir is the only side effect. */\n\tskipClone?: boolean;\n}\n\nexport interface AddUserRepoInput {\n\t/** Upstream URL the user pasted in. */\n\turl: string;\n\t/** Branch to track. Auto-detected from upstream when omitted. */\n\tbranch?: string;\n\t/** Optional display name (defaults to id). */\n\tname?: string;\n\t/** Route git traffic via the server proxy (default true). */\n\tuseProxy?: boolean;\n}\n\nexport interface AddUserRepoResult {\n\trepo: UserRepoConfig;\n\tbranch: string;\n\tcloned: boolean;\n}\n\n/** Snapshot of a sync run, suitable for UI rendering. */\nexport interface SyncReport {\n\tpulls: Array<{\n\t\trepoId: string;\n\t\tstatus: \"ok\" | \"error\";\n\t\tresult?: PullResult;\n\t\terror?: string;\n\t}>;\n}\n\n/** Pattern: kebab-case slug from a git URL's last segment. */\nfunction idFromUrl(url: string): string {\n\tconst trimmed = url\n\t\t.trim()\n\t\t.replace(/\\/+$/, \"\")\n\t\t.replace(/\\.git$/, \"\");\n\n\tconst sanitize = (value: string): string =>\n\t\tvalue\n\t\t\t.trim()\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-zA-Z0-9_-]/g, \"-\")\n\t\t\t.replace(/-+/g, \"-\")\n\t\t\t.replace(/^-+|-+$/g, \"\");\n\n\tlet owner = \"\";\n\tlet repo = \"\";\n\n\tif (/^https?:\\/\\//i.test(trimmed)) {\n\t\tconst parsed = new URL(trimmed);\n\t\tconst segments = parsed.pathname.split(\"/\").filter(Boolean);\n\t\trepo = segments.at(-1) ?? \"\";\n\t\towner = segments.length >= 2 ? (segments.at(-2) ?? \"\") : \"\";\n\t} else if (trimmed.startsWith(\"git@\")) {\n\t\tconst colon = trimmed.indexOf(\":\");\n\t\tconst repoPath = colon >= 0 ? trimmed.slice(colon + 1) : \"\";\n\t\tconst segments = repoPath.split(\"/\").filter(Boolean);\n\t\trepo = segments.at(-1) ?? \"\";\n\t\towner = segments.length >= 2 ? (segments.at(-2) ?? \"\") : \"\";\n\t} else {\n\t\trepo = trimmed.split(\"/\").pop() ?? \"\";\n\t}\n\n\tconst safeRepo = sanitize(repo);\n\tconst safeOwner = sanitize(owner);\n\tconst safe = safeOwner ? `${safeOwner}-${safeRepo}` : safeRepo;\n\tif (!SAFE_REPO_ID_PATTERN.test(safe)) {\n\t\tthrow new InvalidRepoUrlError(`cannot derive a valid repo id from url: ${url}`);\n\t}\n\treturn safe;\n}\n\nfunction proxyRepoId(url: string): string {\n\tconst encoded = Buffer.from(url.trim(), \"utf8\").toString(\"base64url\");\n\treturn `repo-${encoded}`;\n}\n\n/** Sentinel errors. */\nexport class RepoManagerError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"RepoManagerError\";\n\t}\n}\nexport class InvalidRepoUrlError extends RepoManagerError {}\nexport class RepoCloneConflictError extends RepoManagerError {}\nexport class RepoNotInStoreError extends RepoManagerError {}\n\nexport class RepoManager {\n\tprivate readonly store: ReposStore;\n\tprivate readonly git: GitClient;\n\tprivate readonly now: () => string;\n\tprivate readonly skipClone: boolean;\n\n\tconstructor(opts: RepoManagerOptions) {\n\t\tthis.store = opts.store;\n\t\tthis.git = opts.gitClient;\n\t\tthis.now = opts.now ?? (() => new Date().toISOString());\n\t\tthis.skipClone = opts.skipClone ?? false;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Bootstrap\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/**\n\t * Clone every enabled default repo that doesn't have a local clone\n\t * yet. Failures on individual repos are recorded in `lastSyncStatus`\n\t * but do NOT abort the loop — the UI surfaces per-repo state and the\n\t * user can retry.\n\t *\n\t * Per spec §3.1: iterate all default repos; the loop honors `enabled=false` and\n\t * disables (rather than removes) repos the user has turned off.\n\t */\n\tasync ensureDefaults(): Promise<SyncReport> {\n\t\tconst report: SyncReport = { pulls: [] };\n\t\tconst defaults = this.store.listDefault();\n\t\tfor (const repo of defaults) {\n\t\t\tif (!repo.enabled) continue;\n\t\t\tconst localPath = getRepoDir(repo.id);\n\t\t\tconst exists = await this.pathExists(localPath);\n\t\t\tif (exists) {\n\t\t\t\t// Directory exists but may be empty (e.g. interrupted clone).\n\t\t\t\t// Delete and re-clone so it becomes a valid git repo.\n\t\t\t\tif (await this.isValidGitRepo(localPath)) continue;\n\t\t\t\tawait fs.rm(localPath, { recursive: true, force: true });\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tif (!this.skipClone) {\n\t\t\t\t\tawait fs.mkdir(path.dirname(localPath), { recursive: true });\n\t\t\t\t\tawait this.git.clone(repo.id, repo.url, localPath, repo.branch, true);\n\t\t\t\t}\n\t\t\t\tawait this.store.updateRepo(repo.id, {\n\t\t\t\t\tlastSyncStatus: \"ok\",\n\t\t\t\t\tlastSyncAt: this.now(),\n\t\t\t\t});\n\t\t\t\treport.pulls.push({ repoId: repo.id, status: \"ok\" });\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\tawait this.store.updateRepo(repo.id, {\n\t\t\t\t\tlastSyncStatus: \"error\",\n\t\t\t\t\tlastSyncAt: this.now(),\n\t\t\t\t\tlastSyncError: message,\n\t\t\t\t});\n\t\t\t\treport.pulls.push({ repoId: repo.id, status: \"error\", error: message });\n\t\t\t}\n\t\t}\n\t\treturn report;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Per-repo sync\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/**\n\t * Pull a single repo. Updates `lastSync*` fields on success or failure.\n\t * Returns the raw `PullResult` so callers can surface fetch progress.\n\t */\n\tasync pullOne(repoId: string): Promise<PullResult> {\n\t\tconst repo = this.store.get(repoId);\n\t\tif (!repo) throw new RepoNotInStoreError(repoId);\n\t\tconst useProxy = repo.useProxy ?? true;\n\t\tconst proxyId = useProxy ? proxyRepoId(repo.url) : repo.id;\n\t\tconst localPath = getRepoDir(repoId);\n\t\tconst exists = await this.pathExists(localPath);\n\t\t// If directory exists but is empty or not a valid git repo, remove\n\t\t// it so the clone path below runs cleanly.\n\t\tif (exists && !(await this.isValidGitRepo(localPath))) {\n\t\t\tawait fs.rm(localPath, { recursive: true, force: true });\n\t\t}\n\t\tif (!exists || !(await this.pathExists(localPath))) {\n\t\t\t// First-time sync (or re-clone after invalid directory) — clone instead of pulling.\n\t\t\tawait fs.mkdir(path.dirname(localPath), { recursive: true });\n\t\t\tif (!this.skipClone) {\n\t\t\t\tawait this.git.clone(proxyId, repo.url, localPath, repo.branch, useProxy);\n\t\t\t}\n\t\t\tconst result: PullResult = {\n\t\t\t\tupdated: true,\n\t\t\t\tcommitSha: \"\",\n\t\t\t\tbranch: repo.branch,\n\t\t\t};\n\t\t\tawait this.store.updateRepo(repoId, {\n\t\t\t\tlastSyncStatus: \"ok\",\n\t\t\t\tlastSyncAt: this.now(),\n\t\t\t});\n\t\t\treturn result;\n\t\t}\n\t\ttry {\n\t\t\tconst result = await this.git.pull(proxyId, localPath, repo.branch, useProxy, repo.url);\n\t\t\tawait this.store.updateRepo(repoId, {\n\t\t\t\tlastSyncStatus: \"ok\",\n\t\t\t\tlastSyncAt: this.now(),\n\t\t\t\tlastSyncCommitSha: result.commitSha,\n\t\t\t});\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tawait this.store.updateRepo(repoId, {\n\t\t\t\tlastSyncStatus: \"error\",\n\t\t\t\tlastSyncAt: this.now(),\n\t\t\t\tlastSyncError: message,\n\t\t\t});\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Pull every enabled repo. Per-repo failures don't abort the run. */\n\tasync pullAll(): Promise<SyncReport> {\n\t\tconst report: SyncReport = { pulls: [] };\n\t\tconst enabled = this.store.list().filter((r) => r.enabled);\n\t\tfor (const repo of enabled) {\n\t\t\ttry {\n\t\t\t\tconst r = await this.pullOne(repo.id);\n\t\t\t\treport.pulls.push({ repoId: repo.id, status: \"ok\", result: r });\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\treport.pulls.push({ repoId: repo.id, status: \"error\", error: message });\n\t\t\t}\n\t\t}\n\t\treturn report;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// User repos: add / remove\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/**\n\t * Validate URL, derive an id, detect the branch via `git ls-remote`,\n\t * then add the entry to the store and trigger a clone.\n\t *\n\t * Spec §5.3 says \"branch detection\" happens BEFORE the store write so\n\t * the resulting `repos.json` is fully populated. The clone is async\n\t * but the function returns synchronously once the store is updated\n\t * (cloning happens via `ensureDefaults`-style background fire).\n\t *\n\t * For tests with `skipClone: true`, the clone is skipped entirely.\n\t */\n\tasync addUserRepo(input: AddUserRepoInput): Promise<AddUserRepoResult> {\n\t\tconst url = input.url.trim();\n\t\tif (!/^https?:\\/\\//.test(url) && !url.startsWith(\"git@\")) {\n\t\t\tthrow new InvalidRepoUrlError(`expected https:// or git@ URL, got: ${url}`);\n\t\t}\n\t\tconst id = idFromUrl(url);\n\t\tconst useProxy = input.useProxy ?? true;\n\t\tconst userProxyId = useProxy ? proxyRepoId(url) : id;\n\t\tassertSafeRepoId(id);\n\n\t\t// Detect branch if not provided\n\t\tlet branch = input.branch?.trim();\n\t\tif (!branch) {\n\t\t\tconst branches = await this.git.lsRemote(userProxyId, url, useProxy);\n\t\t\tconst head =\n\t\t\t\tbranches.find((b) => b.ref === \"refs/heads/main\") ??\n\t\t\t\tbranches.find((b) => b.ref === \"refs/heads/master\") ??\n\t\t\t\tbranches.find((b) => b.ref.endsWith(\"/v2\"));\n\t\t\tif (!head) {\n\t\t\t\tthrow new InvalidRepoUrlError(\n\t\t\t\t\t`could not detect default branch for ${url} (no refs/heads/{main,master,v2})`\n\t\t\t\t);\n\t\t\t}\n\t\t\tbranch = head.ref.replace(/^refs\\/heads\\//, \"\");\n\t\t}\n\n\t\t// Check for collision BEFORE writing the store so we can give a\n\t\t// clean error message.\n\t\tif (this.store.get(id)) {\n\t\t\tthrow new RepoCloneConflictError(`repo id '${id}' already exists`);\n\t\t}\n\n\t\tconst repo: UserRepoConfig = {\n\t\t\tid,\n\t\t\tname: input.name?.trim() || id,\n\t\t\turl,\n\t\t\tbranch,\n\t\t\tenabled: true,\n\t\t\tuseProxy,\n\t\t\twriteEnabled: false,\n\t\t\tsource: \"user\",\n\t\t\taddedAt: this.now(),\n\t\t};\n\t\tawait this.store.addUserRepo(repo);\n\n\t\t// Trigger async clone (or skip in tests)\n\t\tlet cloned = false;\n\t\tif (!this.skipClone) {\n\t\t\tconst localPath = getRepoDir(id);\n\t\t\tawait fs.mkdir(path.dirname(localPath), { recursive: true });\n\t\t\tawait this.git.clone(userProxyId, url, localPath, branch, useProxy);\n\t\t\tcloned = true;\n\t\t}\n\n\t\treturn { repo, branch, cloned };\n\t}\n\n\t/**\n\t * Remove a user-added repo. Throws when:\n\t * - the repo is unknown\n\t * - the repo is a default (use `disableRepo` instead — store enforces)\n\t *\n\t * The local clone is deleted via `fs.rm`. If the local dir is\n\t * already gone we silently succeed (idempotent re-remove).\n\t */\n\tasync removeUserRepo(repoId: string): Promise<void> {\n\t\tconst repo = this.store.get(repoId);\n\t\tif (!repo) throw new RepoNotInStoreError(repoId);\n\t\tif (isDefaultRepo(repo)) {\n\t\t\tthrow new RepoCloneConflictError(\n\t\t\t\t`cannot remove default repo '${repoId}'; use disableRepo() instead`\n\t\t\t);\n\t\t}\n\t\t// Store first — so the failure surface is the user-visible\n\t\t// repos.json change, not a half-deleted filesystem.\n\t\tawait this.store.removeUserRepo(repoId);\n\t\tconst localPath = getRepoDir(repoId);\n\t\ttry {\n\t\t\tawait fs.rm(localPath, { recursive: true, force: true });\n\t\t} catch (err) {\n\t\t\t// Swallow ENOENT — idempotent. Re-throw anything else.\n\t\t\tif ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n\t\t}\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Enable / disable\n\t// ─────────────────────────────────────────────────────────────────\n\n\tasync disableRepo(repoId: string): Promise<AnyRepoConfig> {\n\t\treturn this.store.disableRepo(repoId);\n\t}\n\n\tasync enableRepo(repoId: string): Promise<AnyRepoConfig> {\n\t\treturn this.store.enableRepo(repoId);\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────\n\t// Helpers\n\t// ─────────────────────────────────────────────────────────────────\n\n\t/** `true` when `~/.serviceme/repos/<id>/` exists on disk. */\n\tasync hasLocalClone(repoId: string): Promise<boolean> {\n\t\treturn this.pathExists(getRepoDir(repoId));\n\t}\n\n\t/** Force-create the SERVICEME home directory tree (idempotent). */\n\tasync ensureHome(): Promise<void> {\n\t\tawait fs.mkdir(getServicemeHome(), { recursive: true });\n\t}\n\n\t/**\n\t * Returns `true` when `p` contains a `.git` entry — i.e. it is an\n\t * initialised git repository and safe to `pull`. Empty directories\n\t * (e.g. from an interrupted clone) return `false`.\n\t */\n\tprivate async isValidGitRepo(p: string): Promise<boolean> {\n\t\treturn this.pathExists(path.join(p, \".git\"));\n\t}\n\n\tprivate async pathExists(p: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fs.stat(p);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n\n/** Type guard for AnyRepoConfig — re-exported for tests + extension code. */\nexport { isDefaultRepo, isUserRepo };\nexport type { RepoConfig };\n","/**\n * `repos.json` schema — mirrors §3 of docs/architecture/skill-agent-v2-repo.md.\n *\n * The on-disk file lives at `${SERVICEME_HOME}/repos.json` (see\n * `paths/userHome.getReposConfigPath`). It holds the user-visible catalog of\n * skill/agent repositories (default + user-added) plus the currently-selected\n * default.\n *\n * Two flavours of repo:\n * - `DefaultRepoConfig` — hardcoded by SERVICEME; can only be disabled, not\n * removed.\n * - `UserRepoConfig` — added by the user via the \"Add repository\" flow;\n * can be removed freely.\n *\n * Both extend `RepoConfig` and add a `source` discriminant so consumers can\n * branch on `repo.source === 'default' | 'user'` without sniffing free-form\n * fields.\n */\n\n/** ISO 8601 timestamp. */\nexport type IsoTimestamp = string;\n\n/** Per-repo sync health — surfaced in the UI alongside the repo entry. */\nexport type SyncStatus = \"ok\" | \"error\";\n\n/** Discriminant on `RepoConfig.source` — narrow with a literal check. */\nexport type RepoSource = \"default\" | \"user\";\n\n/**\n * Common shape shared by both default and user-added repos. Source lives on\n * the type so we can keep the loader schema flat while still letting\n * `DefaultRepoConfig` carry a `description`.\n */\nexport interface RepoConfig {\n\t/** Stable id used in on-disk paths (must match `SAFE_REPO_ID_PATTERN`). */\n\tid: string;\n\t/** Display name shown in UI. */\n\tname: string;\n\t/** Upstream git URL (typically github.com/<owner>/<repo>.git). */\n\turl: string;\n\t/** Default branch to track (e.g. \"v2\", \"main\"). */\n\tbranch: string;\n\t/** Whether this repo participates in scheduled syncs. */\n\tenabled: boolean;\n\t/** Whether git traffic should go through the local server proxy (default true). */\n\tuseProxy?: boolean;\n\t/** Whether the user is allowed to push back to this repo. */\n\twriteEnabled: boolean;\n\t/** When the user (or first-run installer) added this repo. */\n\taddedAt: IsoTimestamp;\n\t/** Last successful sync timestamp. */\n\tlastSyncAt?: IsoTimestamp;\n\t/** Commit SHA at the time of the last successful sync. */\n\tlastSyncCommitSha?: string;\n\t/** Result of the most recent sync attempt. */\n\tlastSyncStatus?: SyncStatus;\n\t/** Error message from the most recent sync attempt (when `lastSyncStatus === 'error'`). */\n\tlastSyncError?: string;\n\t/** Whether this repo is a default or a user-added repo. */\n\tsource: RepoSource;\n}\n\n/** A default (bundled) repo — has a UI-facing description, cannot be removed. */\nexport interface DefaultRepoConfig extends RepoConfig {\n\tsource: \"default\";\n\tdescription: string;\n}\n\n/** A user-added repo — can be added, removed, disabled freely. */\nexport interface UserRepoConfig extends RepoConfig {\n\tsource: \"user\";\n}\n\n/** Any repo, regardless of source. */\nexport type AnyRepoConfig = DefaultRepoConfig | UserRepoConfig;\n\n/**\n * Root shape of `repos.json`. `version` lets us evolve the schema later\n * without colliding with hand-edited files.\n */\nexport interface ReposFile {\n\tversion: 1;\n\t/** Id of the repo currently surfaced as the default selection in the UI. */\n\tdefaultRepoId: string;\n\trepos: AnyRepoConfig[];\n}\n\n/**\n * Convenience type-guard — narrows to `DefaultRepoConfig`. Useful when callers\n * want to render `repo.description` or reject `removeRepo(repo)` for defaults.\n */\nexport function isDefaultRepo(repo: RepoConfig): repo is DefaultRepoConfig {\n\treturn repo.source === \"default\";\n}\n\n/** Convenience type-guard — narrows to `UserRepoConfig`. */\nexport function isUserRepo(repo: RepoConfig): repo is UserRepoConfig {\n\treturn repo.source === \"user\";\n}\n","import type { DefaultRepoConfig, ReposFile } from \"./types\";\n\n/**\n * Hard-coded catalog of repositories SERVICEME ships with. The id, URL and\n * branch are pinned by the design doc (§12) and **must not** change without\n * an owner-approved migration. The order here mirrors the on-disk\n * `repos.json` order (§3.1) which the UI relies on.\n */\n\nexport const DEFAULT_REPO_ID = \"medalsoftchina-ms-skills\";\n\nconst FIXED_ADDED_AT = \"2026-06-29T00:00:00.000Z\";\n\n/**\n * Master list of default repositories. `getDefaultRepoConfigs()` returns\n * clones with the current timestamp baked into `addedAt` so two `load()`\n * calls return identical-but-distinct objects (the store can mutate one\n * without disturbing the other).\n */\nconst DEFAULT_REPO_SEEDS: ReadonlyArray<Omit<DefaultRepoConfig, \"addedAt\">> = [\n\t{\n\t\tid: \"medalsoftchina-ms-skills\",\n\t\tname: \"SERVICEME (精选)\",\n\t\turl: \"https://github.com/medalsoftchina/ms-skills.git\",\n\t\tbranch: \"v2\",\n\t\tenabled: true,\n\t\tuseProxy: true,\n\t\twriteEnabled: true,\n\t\tsource: \"default\",\n\t\tdescription: \"Medalsoft 精选 skill/agent 仓库,可读可写\",\n\t},\n\t{\n\t\tid: \"anthropics-skills\",\n\t\tname: \"Anthropic Skills\",\n\t\turl: \"https://github.com/anthropics/skills.git\",\n\t\tbranch: \"main\",\n\t\tenabled: true,\n\t\tuseProxy: true,\n\t\twriteEnabled: false,\n\t\tsource: \"default\",\n\t\tdescription: \"Anthropic 官方 skills 示例\",\n\t},\n\t{\n\t\tid: \"github-awesome-copilot\",\n\t\tname: \"GitHub Awesome Copilot\",\n\t\turl: \"https://github.com/github/awesome-copilot.git\",\n\t\tbranch: \"main\",\n\t\tenabled: true,\n\t\tuseProxy: true,\n\t\twriteEnabled: false,\n\t\tsource: \"default\",\n\t\tdescription: \"GitHub Copilot 社区精选 prompts/agents\",\n\t},\n\t{\n\t\tid: \"mattpocock-skills\",\n\t\tname: \"Matt Pocock Skills\",\n\t\turl: \"https://github.com/mattpocock/skills.git\",\n\t\tbranch: \"main\",\n\t\tenabled: true,\n\t\tuseProxy: true,\n\t\twriteEnabled: false,\n\t\tsource: \"default\",\n\t\tdescription: \"Matt Pocock 社区 skills 仓库\",\n\t},\n\t{\n\t\tid: \"composiohq-awesome-claude-skills\",\n\t\tname: \"Composio Awesome Claude Skills\",\n\t\turl: \"https://github.com/ComposioHQ/awesome-claude-skills.git\",\n\t\tbranch: \"main\",\n\t\tenabled: true,\n\t\tuseProxy: true,\n\t\twriteEnabled: false,\n\t\tsource: \"default\",\n\t\tdescription: \"Composio 维护的 Claude skills 集合\",\n\t},\n];\n\n/** Returns a deep-ish clone of every default repo with a fresh `addedAt`. */\nexport function getAllDefaultRepoConfigs(\n\tnow: () => string = () => new Date().toISOString()\n): DefaultRepoConfig[] {\n\tconst stamp = now();\n\treturn DEFAULT_REPO_SEEDS.map((seed) => ({\n\t\t...seed,\n\t\taddedAt: stamp,\n\t}));\n}\n\n/** Returns a single default repo by id, or `undefined` when not in the catalog. */\nexport function getDefaultRepoConfig(\n\tid: string,\n\tnow: () => string = () => new Date().toISOString()\n): DefaultRepoConfig | undefined {\n\tconst seed = DEFAULT_REPO_SEEDS.find((s) => s.id === id);\n\tif (!seed) {\n\t\treturn undefined;\n\t}\n\treturn { ...seed, addedAt: now() };\n}\n\n/**\n * Idempotent: returns a fresh `ReposFile` containing the default set, or —\n * when `existing` already has all defaults — returns it untouched (still cloned\n * to keep callers from accidentally mutating shared state).\n */\nexport function buildDefaultReposFile(\n\tnow: () => string = () => new Date().toISOString(),\n\texisting?: ReposFile\n): ReposFile {\n\tconst defaults = getAllDefaultRepoConfigs(now);\n\n\tif (!existing) {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tdefaultRepoId: DEFAULT_REPO_ID,\n\t\t\trepos: defaults,\n\t\t};\n\t}\n\n\t// Merge: keep existing repos as-is (including any user-added ones), then\n\t// append any default ids that are missing. Existing default entries\n\t// (with their own `addedAt`, `lastSyncAt`, etc.) win over the seed.\n\tconst existingIds = new Set(existing.repos.map((r) => r.id));\n\tconst missingDefaults = defaults.filter((d) => !existingIds.has(d.id));\n\tif (missingDefaults.length === 0) {\n\t\treturn existing;\n\t}\n\treturn {\n\t\t...existing,\n\t\trepos: [...existing.repos, ...missingDefaults],\n\t\tdefaultRepoId: resolveDefaultRepoId(existing, existingIds),\n\t};\n}\n\n/**\n * `ensureDefaultsInstalled(existing)` — pure helper that returns either\n * `existing` (when defaults are already present) or a merged copy that\n * appends the missing defaults. The companion `ensureDefaultsInstalledInStore`\n * below is the mutating, store-bound counterpart.\n */\nexport function ensureDefaultsInstalled(\n\texisting: ReposFile,\n\tnow: () => string = () => new Date().toISOString()\n): { config: ReposFile; installed: string[] } {\n\tconst defaults = getAllDefaultRepoConfigs(now);\n\tconst existingIds = new Set(existing.repos.map((r) => r.id));\n\tconst missing = defaults.filter((d) => !existingIds.has(d.id));\n\tif (missing.length === 0) {\n\t\treturn { config: existing, installed: [] };\n\t}\n\tconst next: ReposFile = {\n\t\t...existing,\n\t\trepos: [...existing.repos, ...missing],\n\t\tdefaultRepoId: resolveDefaultRepoId(existing, existingIds),\n\t};\n\treturn { config: next, installed: missing.map((d) => d.id) };\n}\n\n/**\n * Decide what the post-bootstrap `defaultRepoId` should be.\n *\n * Rules (in order):\n * 1. If `existing.defaultRepoId` references a real repo in `existing`,\n * keep it (the user explicitly chose this default).\n * 2. Otherwise — including the loader's empty-state `\"\"` sentinel or any\n * other unknown id — fall back to {@link DEFAULT_REPO_ID}.\n */\nfunction resolveDefaultRepoId(existing: ReposFile, existingIds: Set<string>): string {\n\tconst placeholder = existing.defaultRepoId;\n\tif (placeholder && existingIds.has(placeholder)) {\n\t\treturn placeholder;\n\t}\n\treturn DEFAULT_REPO_ID;\n}\n\n// `FIXED_ADDED_AT` is exported in case tests want a deterministic timestamp\n// to compare against without stubbing `Date`.\nexport { FIXED_ADDED_AT as DEFAULT_REPO_FIXED_ADDED_AT };\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { z } from \"zod\";\n\nimport { SAFE_REPO_ID_PATTERN } from \"../paths/userHome\";\nimport type { AnyRepoConfig, DefaultRepoConfig, ReposFile, UserRepoConfig } from \"./types\";\n\n/**\n * On-disk `repos.json` loader / writer.\n *\n * Responsibilities:\n * 1. **Validate** every read with a Zod schema — never trust whatever is on\n * disk; a hand-edited file can be malformed in subtle ways.\n * 2. **Recover gracefully** from corrupted files by moving them to a\n * timestamped `.bak` sibling and returning a sentinel empty config.\n * Callers can then prompt the user to re-add their repos.\n * 3. **Write atomically** — always write to `<target>.tmp-<rand>` first\n * and `rename` over the target. A crash mid-write leaves the previous\n * good file intact instead of a half-written JSON.\n *\n * `fs.watch`-based change detection lives in `store.ts`. This module is\n * deliberately pure I/O + validation so it can be exercised from tests\n * without spinning up watchers.\n */\n\nconst ISO_TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:?\\d{2})$/;\n\nconst repoIdSchema = z\n\t.string()\n\t.min(1)\n\t.max(64)\n\t.regex(SAFE_REPO_ID_PATTERN, \"repo id must match /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/\");\n\nconst isoTimestampSchema = z.string().regex(ISO_TIMESTAMP_PATTERN, {\n\tmessage: \"must be an ISO-8601 timestamp\",\n});\n\nconst baseRepoFields = {\n\tid: repoIdSchema,\n\tname: z.string().min(1).max(200),\n\turl: z.string().url(),\n\tbranch: z.string().min(1).max(200),\n\tenabled: z.boolean(),\n\tuseProxy: z.boolean().optional().default(true),\n\twriteEnabled: z.boolean(),\n\taddedAt: isoTimestampSchema,\n\tlastSyncAt: isoTimestampSchema.optional(),\n\tlastSyncCommitSha: z.string().min(1).max(80).optional(),\n\tlastSyncStatus: z.enum([\"ok\", \"error\"]).optional(),\n\tlastSyncError: z.string().max(2_000).optional(),\n};\n\nconst defaultRepoSchema = z.object({\n\t...baseRepoFields,\n\tsource: z.literal(\"default\"),\n\tdescription: z.string().min(1).max(2_000),\n});\n\nconst userRepoSchema = z.object({\n\t...baseRepoFields,\n\tsource: z.literal(\"user\"),\n});\n\nconst repoSchema = z.discriminatedUnion(\"source\", [defaultRepoSchema, userRepoSchema]);\n\nexport const reposFileSchema = z\n\t.object({\n\t\tversion: z.literal(1),\n\t\t// Empty string is the sentinel for \"no repos yet\" (see `repos: []`\n\t\t// below); a populated file must use a real `repoIdSchema`-shaped id,\n\t\t// checked in `superRefine` below.\n\t\tdefaultRepoId: z.string(),\n\t\trepos: z.array(repoSchema),\n\t})\n\t.superRefine((value, ctx) => {\n\t\tconst ids = new Set<string>();\n\t\tfor (const repo of value.repos) {\n\t\t\tif (ids.has(repo.id)) {\n\t\t\t\tctx.addIssue({\n\t\t\t\t\tcode: z.ZodIssueCode.custom,\n\t\t\t\t\tpath: [\"repos\"],\n\t\t\t\t\tmessage: `Duplicate repo id: ${repo.id}`,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tids.add(repo.id);\n\t\t}\n\t\tif (value.repos.length === 0) {\n\t\t\tif (value.defaultRepoId !== \"\") {\n\t\t\t\tctx.addIssue({\n\t\t\t\t\tcode: z.ZodIssueCode.custom,\n\t\t\t\t\tpath: [\"defaultRepoId\"],\n\t\t\t\t\tmessage: \"defaultRepoId must be '' when repos[] is empty\",\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (!ids.has(value.defaultRepoId)) {\n\t\t\tctx.addIssue({\n\t\t\t\tcode: z.ZodIssueCode.custom,\n\t\t\t\tpath: [\"defaultRepoId\"],\n\t\t\t\tmessage: `defaultRepoId '${value.defaultRepoId}' not present in repos[]`,\n\t\t\t});\n\t\t}\n\t});\n\n/** Inferred Zod type — identical to our hand-written `ReposFile`. */\nexport type ReposFileInput = z.infer<typeof reposFileSchema>;\n\n/** Result of a load attempt — either a parsed file or a recovery notice. */\nexport interface LoadResult {\n\t/** Parsed + validated repos file (always populated, even on recovery). */\n\tconfig: ReposFile;\n\t/**\n\t * Non-null when the existing file on disk was unreadable / invalid and\n\t * was moved aside as a `.bak`. The store can use this to surface a\n\t * warning in the UI.\n\t */\n\trecoveredFromBackup: string | null;\n}\n\n/** Dependencies — defaults to the real fs, tests inject a fake. */\nexport interface ReposLoaderFileSystem {\n\treadFile: typeof fs.readFile;\n\twriteFile: typeof fs.writeFile;\n\trename: typeof fs.rename;\n\tmkdir: typeof fs.mkdir;\n\tstat: typeof fs.stat;\n\tunlink?: typeof fs.unlink;\n}\n\nexport interface ReposLoaderOptions {\n\t/** Absolute path of the `repos.json` file. */\n\tconfigPath: string;\n\t/** Custom time provider — defaults to `() => new Date().toISOString()`. */\n\tnow?: () => string;\n\t/** Random suffix provider for atomic-write temp files. */\n\trandomSuffix?: () => string;\n\t/** Filesystem shim for tests. */\n\tfileSystem?: ReposLoaderFileSystem;\n}\n\nconst DEFAULT_RANDOM_SUFFIX_LENGTH = 8;\n\nfunction defaultRandomSuffix(): string {\n\t// Avoid pulling `crypto.randomUUID` (Node 14.17+) here so we stay\n\t// compatible with the wider node range this package targets. A short\n\t// alphanumeric suffix is enough — collisions are vanishingly rare and\n\t// the temp file is removed right after rename.\n\tconst alphabet = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n\tlet out = \"\";\n\tfor (let i = 0; i < DEFAULT_RANDOM_SUFFIX_LENGTH; i++) {\n\t\tout += alphabet[Math.floor(Math.random() * alphabet.length)];\n\t}\n\treturn out;\n}\n\nexport class ReposLoader {\n\tprivate readonly configPath: string;\n\tprivate readonly now: () => string;\n\tprivate readonly randomSuffix: () => string;\n\tprivate readonly fileSystem: ReposLoaderFileSystem;\n\n\tconstructor(options: ReposLoaderOptions) {\n\t\tthis.configPath = options.configPath;\n\t\tthis.now = options.now ?? (() => new Date().toISOString());\n\t\tthis.randomSuffix = options.randomSuffix ?? defaultRandomSuffix;\n\t\tthis.fileSystem = options.fileSystem ?? fs;\n\t}\n\n\t/** Absolute path of the file this loader reads/writes. */\n\tgetConfigPath(): string {\n\t\treturn this.configPath;\n\t}\n\n\t/**\n\t * Read + validate the on-disk config. When the file is missing, returns a\n\t * sentinel empty config (no backup is taken — that's expected on first\n\t * run). When the file is corrupted, moves it to `<name>.<ts>.bak` and\n\t * returns an empty config with `recoveredFromBackup` populated.\n\t */\n\tasync load(): Promise<LoadResult> {\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await this.fileSystem.readFile(this.configPath, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tif (isErrnoCode(error, \"ENOENT\")) {\n\t\t\t\treturn {\n\t\t\t\t\tconfig: this.buildEmptyConfig(),\n\t\t\t\t\trecoveredFromBackup: null,\n\t\t\t\t};\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst parsed = safeParseJson(raw);\n\t\tif (parsed === undefined) {\n\t\t\tconst backupPath = await this.quarantine(\"json-parse-failed\");\n\t\t\treturn {\n\t\t\t\tconfig: this.buildEmptyConfig(),\n\t\t\t\trecoveredFromBackup: backupPath,\n\t\t\t};\n\t\t}\n\n\t\tconst result = reposFileSchema.safeParse(parsed);\n\t\tif (!result.success) {\n\t\t\tconst backupPath = await this.quarantine(\"schema-invalid\");\n\t\t\treturn {\n\t\t\t\tconfig: this.buildEmptyConfig(),\n\t\t\t\trecoveredFromBackup: backupPath,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tconfig: result.data as ReposFile,\n\t\t\trecoveredFromBackup: null,\n\t\t};\n\t}\n\n\t/**\n\t * Write the config to disk atomically. Writes to\n\t * `<target>.tmp-<random>` then `rename`s over the target. The parent\n\t * directory is created on demand.\n\t */\n\tasync save(config: ReposFile): Promise<void> {\n\t\tconst validated = reposFileSchema.parse(config) as ReposFile;\n\n\t\tconst dir = path.dirname(this.configPath);\n\t\tawait this.fileSystem.mkdir(dir, { recursive: true });\n\n\t\tconst serialized = `${JSON.stringify(validated, null, 2)}\\n`;\n\t\tconst tempPath = `${this.configPath}.tmp-${this.randomSuffix()}`;\n\n\t\tawait this.fileSystem.writeFile(tempPath, serialized, \"utf-8\");\n\t\ttry {\n\t\t\tawait this.fileSystem.rename(tempPath, this.configPath);\n\t\t} catch (error) {\n\t\t\t// Best-effort cleanup of the orphaned temp file.\n\t\t\tconst unlink = this.fileSystem.unlink ?? fs.unlink;\n\t\t\tawait unlink(tempPath).catch(() => undefined);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Move the existing file to a timestamped `.bak` sibling and return the\n\t * backup path. Used when we detect a corrupted / unparseable file and\n\t * need to make room for a fresh write.\n\t */\n\tprivate async quarantine(reason: string): Promise<string> {\n\t\tconst stamp = sanitizeForFilename(this.now());\n\t\tconst backupPath = `${this.configPath}.${stamp}.${reason}.bak`;\n\t\ttry {\n\t\t\tawait this.fileSystem.rename(this.configPath, backupPath);\n\t\t} catch (error) {\n\t\t\tif (!isErrnoCode(error, \"ENOENT\")) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\treturn backupPath;\n\t}\n\n\t/**\n\t * Empty config used when no file exists or the existing file is corrupt.\n\t * The caller (e.g. `ensureDefaultsInstalled`) is expected to populate the\n\t * real default repo set before persisting.\n\t */\n\tprivate buildEmptyConfig(): ReposFile {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tdefaultRepoId: \"\",\n\t\t\trepos: [],\n\t\t};\n\t}\n}\n\nfunction safeParseJson(raw: string): unknown | undefined {\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isErrnoCode(error: unknown, code: string): boolean {\n\tif (typeof error !== \"object\" || error === null) {\n\t\treturn false;\n\t}\n\treturn (error as { code?: unknown }).code === code;\n}\n\nfunction sanitizeForFilename(iso: string): string {\n\treturn iso.replace(/[^0-9T]/g, \"-\").replace(/-+/g, \"-\");\n}\n\n/** Re-export the Zod schemas so other modules can introspect them. */\nexport {\n\tdefaultRepoSchema as defaultRepoConfigSchema,\n\tuserRepoSchema as userRepoConfigSchema,\n\trepoSchema as anyRepoConfigSchema,\n};\n\n/** Helper: parse + validate a config object (e.g. freshly built in memory). */\nexport function validateReposFile(input: unknown): ReposFile {\n\treturn reposFileSchema.parse(input) as ReposFile;\n}\n\n/** Helper: discriminate a `RepoConfig` between default and user flavours. */\nexport function narrowRepoConfig(repo: AnyRepoConfig): DefaultRepoConfig | UserRepoConfig {\n\treturn repo.source === \"default\" ? (repo as DefaultRepoConfig) : (repo as UserRepoConfig);\n}\n","import { EventEmitter } from \"node:events\";\nimport * as fs from \"node:fs\";\n\nimport { ensureDefaultsInstalled } from \"./default-repos\";\nimport { type LoadResult, ReposLoader, validateReposFile } from \"./loader\";\nimport {\n\ttype AnyRepoConfig,\n\tisDefaultRepo,\n\ttype RepoConfig,\n\ttype ReposFile,\n\ttype UserRepoConfig,\n} from \"./types\";\n\n/**\n * In-memory CRUD store for `repos.json`.\n *\n * Responsibilities:\n * - Hold the authoritative `ReposFile` state in memory.\n * - Re-load on `fs.watch` events so external writes (CLI edit, user edit)\n * propagate to in-process consumers (extension UI, bridge calls).\n * - Emit `change` events so consumers can re-render without polling.\n * - Validate every write through {@link ReposLoader} so a programmatic\n * `addRepo` cannot smuggle in a malformed entry.\n *\n * The store deliberately does **not** own `fs.watch` resources by default.\n * Callers opt in via {@link ReposStore.startWatching} / {@link stopWatching}\n * so tests can run without lingering watchers.\n */\n\n/** Mutating event from the store. */\nexport type ReposStoreChange =\n\t| { kind: \"load\"; config: ReposFile }\n\t| { kind: \"add\"; repo: AnyRepoConfig }\n\t| { kind: \"update\"; repo: AnyRepoConfig }\n\t| { kind: \"remove\"; repoId: string }\n\t| { kind: \"default-change\"; defaultRepoId: string }\n\t| { kind: \"error\"; error: Error };\n\n/** Subscriber callback. */\nexport type ReposStoreListener = (change: ReposStoreChange) => void;\n\n/** Dependencies — defaults to the real fs, tests inject a fake. */\nexport interface ReposStoreFileSystem {\n\twatch: typeof fs.watch;\n}\n\nexport interface ReposStoreOptions {\n\t/** Use an explicit loader (defaults to one wrapping the resolved configPath). */\n\tloader?: ReposLoader;\n\t/** File-system watch implementation — defaults to `fs.watch`. */\n\tfileSystem?: ReposStoreFileSystem;\n\t/** Time provider for `addedAt` stamping. */\n\tnow?: () => string;\n\t/**\n\t * Debounce window for `fs.watch` callbacks. Editors sometimes emit\n\t * multiple `change` events for a single logical write — coalesce them.\n\t * Defaults to 50ms.\n\t */\n\tdebounceMs?: number;\n\t/**\n\t * Watch implementation factory — defaults to a no-deps wrapper around\n\t * `fs.watch`. Tests inject a fake watcher here. The store wires the\n\t * returned handle to its own schedule/error flow via the supplied\n\t * callbacks.\n\t */\n\tcreateFsWatcher?: (configPath: string, callbacks: FsWatcherCallbacks) => FsWatcherHandle;\n}\n\n/**\n * Minimal subset of `fs.FSWatcher` that the store depends on. Defining our\n * own type lets tests inject a fake without dragging in real fs handles.\n */\nexport interface FsWatcherHandle {\n\tclose(): void;\n}\n\n/**\n * Callbacks the store wires to a watcher. The default `fs.watch` factory\n * uses these to forward events into `scheduleReload` / `error` emission;\n * tests can ignore them when they only care about lifecycle.\n */\nexport interface FsWatcherCallbacks {\n\tonChange?: () => void;\n\tonRename?: () => void;\n\tonError?: (err: Error) => void;\n}\n\nexport class ReposStore {\n\tprivate readonly loader: ReposLoader;\n\tprivate readonly fileSystem: ReposStoreFileSystem;\n\tprivate readonly now: () => string;\n\tprivate readonly debounceMs: number;\n\tprivate readonly createFsWatcher: (\n\t\tconfigPath: string,\n\t\tcallbacks: FsWatcherCallbacks\n\t) => FsWatcherHandle;\n\n\tprivate config: ReposFile | null = null;\n\tprivate readonly emitter = new EventEmitter();\n\tprivate watcher: FsWatcherHandle | null = null;\n\tprivate reloadTimer: NodeJS.Timeout | null = null;\n\tprivate lastLoadResult: LoadResult | null = null;\n\n\tconstructor(options: ReposStoreOptions = {}) {\n\t\tthis.loader = options.loader ?? new ReposLoader({ configPath: \"\" });\n\t\tthis.fileSystem = options.fileSystem ?? { watch: fs.watch };\n\t\tthis.now = options.now ?? (() => new Date().toISOString());\n\t\tthis.debounceMs = options.debounceMs ?? 50;\n\t\tthis.createFsWatcher =\n\t\t\toptions.createFsWatcher ?? ((p, cb) => this.defaultCreateFsWatcher(p, cb));\n\t}\n\n\t/** Currently held config, or `null` if `load()` has not been called yet. */\n\tgetConfig(): ReposFile | null {\n\t\treturn this.config;\n\t}\n\n\t/** Path of the underlying `repos.json` file (via the loader). */\n\tgetConfigPath(): string {\n\t\treturn this.loader.getConfigPath();\n\t}\n\n\t/** Subscribe to store mutations. Returns an unsubscribe function. */\n\tsubscribe(listener: ReposStoreListener): () => void {\n\t\tthis.emitter.on(\"change\", listener);\n\t\treturn () => {\n\t\t\tthis.emitter.off(\"change\", listener);\n\t\t};\n\t}\n\n\t/**\n\t * Read the config from disk (delegating to the loader) and seed the\n\t * in-memory state. Safe to call repeatedly.\n\t */\n\tasync load(): Promise<LoadResult> {\n\t\tconst result = await this.loader.load();\n\t\tthis.lastLoadResult = result;\n\t\tthis.config = result.config;\n\t\tthis.emit({ kind: \"load\", config: result.config });\n\t\treturn result;\n\t}\n\n\t/** Convenience getter — wraps `load()` and returns the config. */\n\tasync ensureLoaded(): Promise<ReposFile> {\n\t\tconst result = await this.load();\n\t\treturn result.config;\n\t}\n\n\t/** The most recent `LoadResult`, useful for surfacing recovery warnings. */\n\tgetLastLoadResult(): LoadResult | null {\n\t\treturn this.lastLoadResult;\n\t}\n\n\t/**\n\t * Start watching the config file for external changes. Subsequent writes\n\t * (or edits from another process) trigger a debounced re-load.\n\t *\n\t * No-op when already watching.\n\t */\n\tstartWatching(): void {\n\t\tif (this.watcher) {\n\t\t\treturn;\n\t\t}\n\t\tconst configPath = this.loader.getConfigPath();\n\t\tif (!configPath) {\n\t\t\tthrow new Error(\"Cannot watch: loader has no configPath.\");\n\t\t}\n\t\tthis.watcher = this.createFsWatcher(configPath, {\n\t\t\tonChange: () => this.scheduleReload(),\n\t\t\tonRename: () => this.scheduleReload(),\n\t\t\tonError: (err) => this.emit({ kind: \"error\", error: err }),\n\t\t});\n\t}\n\n\t/** Stop watching and release any pending reload timers. */\n\tstopWatching(): void {\n\t\tif (this.reloadTimer) {\n\t\t\tclearTimeout(this.reloadTimer);\n\t\t\tthis.reloadTimer = null;\n\t\t}\n\t\tif (this.watcher) {\n\t\t\tthis.watcher.close();\n\t\t\tthis.watcher = null;\n\t\t}\n\t}\n\n\t// ───────────────────────────── CRUD operations ─────────────────────────────\n\n\tlist(): AnyRepoConfig[] {\n\t\treturn this.config?.repos.slice() ?? [];\n\t}\n\n\tlistDefault(): AnyRepoConfig[] {\n\t\treturn this.list().filter(isDefaultRepo);\n\t}\n\n\tlistUser(): UserRepoConfig[] {\n\t\treturn this.list().filter((r): r is UserRepoConfig => r.source === \"user\");\n\t}\n\n\tget(repoId: string): AnyRepoConfig | undefined {\n\t\treturn this.config?.repos.find((r) => r.id === repoId);\n\t}\n\n\t/**\n\t * Add a user repo. Throws when the id already exists. The `addedAt`\n\t * timestamp defaults to `now()` when not provided. Persists to disk.\n\t */\n\tasync addUserRepo(input: Omit<UserRepoConfig, \"source\">): Promise<UserRepoConfig> {\n\t\tconst enriched: UserRepoConfig = {\n\t\t\t...input,\n\t\t\tsource: \"user\",\n\t\t\taddedAt: input.addedAt ?? this.now(),\n\t\t};\n\t\tawait this.mutate((current) => {\n\t\t\tif (current.repos.some((r) => r.id === enriched.id)) {\n\t\t\t\tthrow new RepoAlreadyExistsError(enriched.id);\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t...current,\n\t\t\t\trepos: [...current.repos, enriched],\n\t\t\t};\n\t\t});\n\t\tthis.emit({ kind: \"add\", repo: enriched });\n\t\treturn enriched;\n\t}\n\n\t/**\n\t * Update an existing repo by id (partial). Throws when not found.\n\t * Persists to disk.\n\t */\n\tasync updateRepo(repoId: string, patch: Partial<RepoConfig>): Promise<AnyRepoConfig> {\n\t\tconst updated = await this.mutate((current) => {\n\t\t\tconst idx = current.repos.findIndex((r) => r.id === repoId);\n\t\t\tif (idx === -1) {\n\t\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t\t}\n\t\t\tconst previous = current.repos[idx];\n\t\t\tif (!previous) {\n\t\t\t\t// Unreachable: idx === -1 caught above.\n\t\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t\t}\n\t\t\tconst merged: AnyRepoConfig = {\n\t\t\t\t...previous,\n\t\t\t\t...patch,\n\t\t\t\tid: previous.id,\n\t\t\t\tsource: previous.source,\n\t\t\t} as AnyRepoConfig;\n\t\t\tconst nextRepos = current.repos.slice();\n\t\t\tnextRepos[idx] = merged;\n\t\t\treturn { ...current, repos: nextRepos };\n\t\t});\n\t\tconst targetRepo = updated.repos.find((r) => r.id === repoId);\n\t\tif (!targetRepo) {\n\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t}\n\t\tthis.emit({ kind: \"update\", repo: targetRepo });\n\t\treturn targetRepo;\n\t}\n\n\t/**\n\t * Remove a user repo. Throws when the repo is a default or does not exist.\n\t * Persists to disk.\n\t */\n\tasync removeUserRepo(repoId: string): Promise<void> {\n\t\tconst existed = this.get(repoId);\n\t\tif (!existed) {\n\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t}\n\t\tif (existed.source === \"default\") {\n\t\t\tthrow new CannotRemoveDefaultRepoError(repoId);\n\t\t}\n\t\tawait this.mutate((current) => ({\n\t\t\t...current,\n\t\t\trepos: current.repos.filter((r) => r.id !== repoId),\n\t\t}));\n\t\tthis.emit({ kind: \"remove\", repoId });\n\t}\n\n\t/**\n\t * Disable a repo (any source). Useful for the \"Disable\" button on default\n\t * repos. Persists to disk.\n\t */\n\tasync disableRepo(repoId: string): Promise<AnyRepoConfig> {\n\t\treturn this.updateRepo(repoId, { enabled: false });\n\t}\n\n\t/** Inverse of {@link disableRepo}. */\n\tasync enableRepo(repoId: string): Promise<AnyRepoConfig> {\n\t\treturn this.updateRepo(repoId, { enabled: true });\n\t}\n\n\t/** Change which repo the UI surfaces as the default. Persists to disk. */\n\tasync setDefaultRepoId(repoId: string): Promise<void> {\n\t\tconst target = this.get(repoId);\n\t\tif (!target) {\n\t\t\tthrow new RepoNotFoundError(repoId);\n\t\t}\n\t\tawait this.mutate((current) => ({\n\t\t\t...current,\n\t\t\tdefaultRepoId: repoId,\n\t\t}));\n\t\tthis.emit({ kind: \"default-change\", defaultRepoId: repoId });\n\t}\n\n\tgetDefaultRepoId(): string | null {\n\t\treturn this.config?.defaultRepoId ?? null;\n\t}\n\n\t/** Returns the configured `now` provider — exposed for `bootstrapDefaults`. */\n\tgetNow(): () => string {\n\t\treturn this.now;\n\t}\n\n\t/**\n\t * Replace the in-memory config and persist it. Used by helpers like\n\t * `bootstrapDefaults` that build a new config wholesale (rather than\n\t * mutating a single field).\n\t */\n\tasync replaceConfig(next: ReposFile): Promise<ReposFile> {\n\t\tconst validated = validateReposFile(next);\n\t\tawait this.loader.save(validated);\n\t\tthis.config = validated;\n\t\treturn validated;\n\t}\n\n\t// ────────────────────────── Internal helpers ──────────────────────────\n\n\tprivate async mutate(updater: (current: ReposFile) => ReposFile): Promise<ReposFile> {\n\t\tconst current = this.config;\n\t\tif (!current) {\n\t\t\tthrow new Error(\"ReposStore.mutate called before load().\");\n\t\t}\n\t\tconst next = updater(current);\n\t\tconst validated = validateReposFile(next);\n\t\tawait this.loader.save(validated);\n\t\tthis.config = validated;\n\t\treturn validated;\n\t}\n\n\tprivate emit(change: ReposStoreChange): void {\n\t\tthis.emitter.emit(\"change\", change);\n\t}\n\n\t/**\n\t * Default `fs.watch` adapter. Public so tests can wrap a real watcher\n\t * with a debounce trampoline if they need to.\n\t */\n\tprivate defaultCreateFsWatcher(\n\t\tconfigPath: string,\n\t\tcallbacks: FsWatcherCallbacks\n\t): FsWatcherHandle {\n\t\tconst handle = this.fileSystem.watch(configPath, { persistent: false });\n\t\tif (callbacks.onChange) {\n\t\t\thandle.on(\"change\", () => callbacks.onChange?.());\n\t\t}\n\t\tif (callbacks.onRename) {\n\t\t\thandle.on(\"rename\", () => callbacks.onRename?.());\n\t\t}\n\t\tif (callbacks.onError) {\n\t\t\thandle.on(\"error\", (err: Error) => callbacks.onError?.(err));\n\t\t}\n\t\treturn {\n\t\t\tclose: () => handle.close(),\n\t\t};\n\t}\n\n\t// Override createFsWatcher with the debounced default if no factory was supplied.\n\tprivate scheduleReload(): void {\n\t\tif (this.reloadTimer) {\n\t\t\tclearTimeout(this.reloadTimer);\n\t\t}\n\t\tthis.reloadTimer = setTimeout(() => {\n\t\t\tthis.reloadTimer = null;\n\t\t\tthis.load().catch((err: unknown) => {\n\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\t\t\t\tthis.emit({ kind: \"error\", error });\n\t\t\t});\n\t\t}, this.debounceMs);\n\t}\n}\n\n/** Factory: creates a `ReposStore` bound to a specific config path. */\nexport function createReposStore(\n\tconfigPath: string,\n\toptions: Omit<ReposStoreOptions, \"loader\"> = {}\n): ReposStore {\n\treturn new ReposStore({\n\t\tloader: new ReposLoader({ configPath }),\n\t\t...options,\n\t});\n}\n\n/** Error: tried to add a repo whose id already exists. */\nexport class RepoAlreadyExistsError extends Error {\n\tconstructor(public readonly repoId: string) {\n\t\tsuper(`Repo already exists: ${repoId}`);\n\t\tthis.name = \"RepoAlreadyExistsError\";\n\t}\n}\n\n/** Error: looked up a repo id that isn't present in the current config. */\nexport class RepoNotFoundError extends Error {\n\tconstructor(public readonly repoId: string) {\n\t\tsuper(`Repo not found: ${repoId}`);\n\t\tthis.name = \"RepoNotFoundError\";\n\t}\n}\n\n/** Error: tried to remove a default repo (defaults are disable-only). */\nexport class CannotRemoveDefaultRepoError extends Error {\n\tconstructor(public readonly repoId: string) {\n\t\tsuper(`Cannot remove default repo (use disableRepo instead): ${repoId}`);\n\t\tthis.name = \"CannotRemoveDefaultRepoError\";\n\t}\n}\n\n/**\n * Bootstrap helper — invokes `ensureDefaultsInstalled` on the store to\n * guarantee that all default repos are present. Returns the resulting\n * config. Lives here (not in `default-repos.ts`) to keep the default-repo\n * module free of store concerns.\n */\nexport async function bootstrapDefaults(store: ReposStore): Promise<ReposFile> {\n\tconst config = store.getConfig() ?? (await store.ensureLoaded());\n\tconst result = ensureDefaultsInstalled(config, store.getNow());\n\tif (result.installed.length === 0) {\n\t\treturn config;\n\t}\n\tawait store.replaceConfig(result.config);\n\treturn store.getConfig() ?? result.config;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst CONFIG_DIR = \".serviceme\";\nconst LOG_FILE = \"scheduler.log\";\nconst MAX_LOG_SIZE = 1024 * 1024; // 1MB\n\nexport class DaemonLogger {\n\tprivate readonly logPath: string;\n\n\tconstructor(workspacePath: string, options: { logPath?: string } = {}) {\n\t\tthis.logPath = options.logPath ?? path.join(workspacePath, CONFIG_DIR, LOG_FILE);\n\t\tconst dir = path.dirname(this.logPath);\n\t\tif (!fs.existsSync(dir)) {\n\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t}\n\t}\n\n\tgetLogPath(): string {\n\t\treturn this.logPath;\n\t}\n\n\tlog(level: \"info\" | \"warn\" | \"error\", message: string): void {\n\t\tconst ts = new Date().toISOString();\n\t\tconst line = `[${ts}] [${level.toUpperCase()}] ${message}\\n`;\n\t\tthis.rotateIfNeeded();\n\t\tfs.appendFileSync(this.logPath, line, \"utf-8\");\n\t}\n\n\tprivate rotateIfNeeded(): void {\n\t\ttry {\n\t\t\tconst stats = fs.statSync(this.logPath);\n\t\t\tif (stats.size > MAX_LOG_SIZE) {\n\t\t\t\t// Keep last half of the file\n\t\t\t\tconst content = fs.readFileSync(this.logPath, \"utf-8\");\n\t\t\t\tconst halfIdx = content.indexOf(\"\\n\", Math.floor(content.length / 2));\n\t\t\t\tif (halfIdx > 0) {\n\t\t\t\t\tfs.writeFileSync(this.logPath, content.slice(halfIdx + 1), \"utf-8\");\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// File doesn't exist yet — fine\n\t\t}\n\t}\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst CONFIG_DIR = \".serviceme\";\nconst PID_FILE = \"scheduler.pid\";\n\nexport interface PidManagerOptions {\n\t/**\n\t * Override the on-disk PID path. Defaults to\n\t * `<workspacePath>/.serviceme/scheduler.pid` for backward compat.\n\t * V2 callers pass an absolute path (e.g. `getSchedulerPidPath()`)\n\t * to read the global daemon PID file.\n\t */\n\tpidPath?: string;\n}\n\nexport class PidManager {\n\tprivate readonly pidPath: string;\n\n\tconstructor(workspacePath: string, options: PidManagerOptions = {}) {\n\t\tthis.pidPath = options.pidPath ?? path.join(workspacePath, CONFIG_DIR, PID_FILE);\n\t}\n\n\tgetPidPath(): string {\n\t\treturn this.pidPath;\n\t}\n\n\twritePid(pid: number): void {\n\t\tconst dir = path.dirname(this.pidPath);\n\t\tif (!fs.existsSync(dir)) {\n\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t}\n\t\tfs.writeFileSync(this.pidPath, String(pid), \"utf-8\");\n\t}\n\n\treadPid(): number | null {\n\t\t// statSync (vs existsSync + readFileSync) so that a path that\n\t\t// points at a directory — or any non-file — is treated as\n\t\t// \"no pid\" instead of crashing with EISDIR. Activation paths\n\t\t// hit this code path on every reload; one stale directory\n\t\t// shell from a half-removed daemon would otherwise throw and\n\t\t// take down extension activation. See ms-devtools-vscode\n\t\t// agent memory \"PidManager EISDIR regression\".\n\t\tlet stat: fs.Stats;\n\t\ttry {\n\t\t\tstat = fs.statSync(this.pidPath);\n\t\t} catch {\n\t\t\t// ENOENT / EACCES / EPERM / etc. — all treated as no pid.\n\t\t\treturn null;\n\t\t}\n\t\tif (!stat.isFile()) return null;\n\t\tconst raw = fs.readFileSync(this.pidPath, \"utf-8\").trim();\n\t\tconst pid = Number.parseInt(raw, 10);\n\t\treturn Number.isNaN(pid) ? null : pid;\n\t}\n\n\tremovePid(): void {\n\t\tif (fs.existsSync(this.pidPath)) {\n\t\t\tfs.unlinkSync(this.pidPath);\n\t\t}\n\t}\n\n\tisProcessRunning(pid: number): boolean {\n\t\ttry {\n\t\t\tprocess.kill(pid, 0);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tgetRunningPid(): number | null {\n\t\tconst pid = this.readPid();\n\t\tif (pid === null) return null;\n\t\tif (this.isProcessRunning(pid)) return pid;\n\t\t// Stale PID file — remove\n\t\tthis.removePid();\n\t\treturn null;\n\t}\n}\n","import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ScheduledTask } from \"@serviceme/devtools-protocol\";\nimport { getExecutor } from \"../executors\";\nimport { TaskConfigManager, validateTaskPayload } from \"../TaskConfigManager\";\nimport { resolveTaskExecutionPayload } from \"../TaskExecutionEngine\";\nimport { TaskLogManager } from \"../TaskLogManager\";\nimport { DaemonLogger } from \"./DaemonLogger\";\nimport { PidManager } from \"./PidManager\";\n\nconst TICK_INTERVAL = 1000; // Check every second\nconst MIN_SCHEDULE_INTERVAL = 1_000; // 1 second minimum\n\nexport interface SchedulerDaemonOptions {\n\t/**\n\t * Override the executor factory. Production callers should leave this\n\t * undefined and rely on the default `getExecutor` from `../executors`;\n\t * tests inject a fake to drive the execution branch without spawning\n\t * real subprocesses.\n\t */\n\tgetExecutor?: typeof getExecutor;\n}\n\n/**\n * @deprecated v1 per-workspace daemon. Superseded by `SchedulerDaemonV2` in\n * `daemon/SchedulerDaemonV2.ts` for the global single-instance model. Kept\n * temporarily so callers can roll back to v1 behaviour during the M1.5\n * transition. New code should use SchedulerDaemonV2.\n */\nexport class SchedulerDaemon {\n\tprivate readonly workspacePath: string;\n\tprivate readonly configManager: TaskConfigManager;\n\tprivate readonly logManager: TaskLogManager;\n\tprivate readonly pidManager: PidManager;\n\tprivate readonly logger: DaemonLogger;\n\tprivate readonly getExecutor: typeof getExecutor;\n\n\tprivate tickTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate watcher: fs.FSWatcher | null = null;\n\tprivate running = false;\n\tprivate startTime: number = 0;\n\n\t// Track last execution time and running state per task\n\tprivate lastRun: Map<string, number> = new Map();\n\tprivate taskRunning: Set<string> = new Set();\n\n\tconstructor(workspacePath: string, options: SchedulerDaemonOptions = {}) {\n\t\tthis.workspacePath = workspacePath;\n\t\t// Per-workspace overrides keep v1 semantics for now. M1.5.6 will\n\t\t// switch the daemon to the global config + per-task workspace cwd model.\n\t\tconst configDir = path.join(workspacePath, \".serviceme\");\n\t\tthis.configManager = new TaskConfigManager({\n\t\t\tconfigPath: path.join(configDir, \"scheduled-tasks.json\"),\n\t\t});\n\t\tthis.logManager = new TaskLogManager({\n\t\t\tlogPath: path.join(configDir, \"scheduled-tasks-log.json\"),\n\t\t});\n\t\tthis.pidManager = new PidManager(workspacePath);\n\t\tthis.logger = new DaemonLogger(workspacePath);\n\t\tthis.getExecutor = options.getExecutor ?? getExecutor;\n\t}\n\n\tstart(): void {\n\t\tif (this.running) return;\n\t\tthis.running = true;\n\t\tthis.startTime = Date.now();\n\n\t\tthis.pidManager.writePid(process.pid);\n\t\tconst configPath = this.configManager.getConfigPath();\n\t\tconst logPath = this.logger.getLogPath();\n\t\tthis.logger.log(\"info\", \"=\".repeat(60));\n\t\tthis.logger.log(\n\t\t\t\"info\",\n\t\t\t`Scheduler daemon started\n PID: ${process.pid}\n Node: ${process.version}\n OS: ${os.type()} ${os.release()} (${process.arch})\n Platform: ${process.platform}\n Hostname: ${os.hostname()}\n User: ${os.userInfo().username}\n Workspace: ${this.workspacePath}\n Config: ${configPath}\n LogPath: ${logPath}\n ExecPath: ${process.execPath}`\n\t\t);\n\n\t\t// Start tick loop\n\t\tthis.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);\n\n\t\t// Watch config for changes\n\t\tthis.setupConfigWatch();\n\n\t\t// Handle signals\n\t\tprocess.on(\"SIGTERM\", () => this.stop());\n\t\tprocess.on(\"SIGINT\", () => this.stop());\n\t}\n\n\tstop(): void {\n\t\tif (!this.running) return;\n\t\tthis.running = false;\n\n\t\tif (this.tickTimer) {\n\t\t\tclearInterval(this.tickTimer);\n\t\t\tthis.tickTimer = null;\n\t\t}\n\t\tif (this.watcher) {\n\t\t\tthis.watcher.close();\n\t\t\tthis.watcher = null;\n\t\t}\n\n\t\tthis.pidManager.removePid();\n\t\tthis.logger.log(\"info\", \"Scheduler daemon stopped\");\n\t\tprocess.exit(0);\n\t}\n\n\tprivate setupConfigWatch(): void {\n\t\tconst configPath = this.configManager.getConfigPath();\n\t\tconst dir = configPath.substring(0, configPath.lastIndexOf(\"/\"));\n\t\ttry {\n\t\t\tif (fs.existsSync(dir)) {\n\t\t\t\tthis.watcher = fs.watch(dir, (_eventType, filename) => {\n\t\t\t\t\tif (filename === \"scheduled-tasks.json\") {\n\t\t\t\t\t\tthis.logger.log(\"info\", \"Config file changed, reconciling...\");\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t} catch {\n\t\t\tthis.logger.log(\"warn\", \"Could not watch config directory\");\n\t\t}\n\t}\n\n\tprivate tick(): void {\n\t\tconst config = this.configManager.readConfig();\n\t\tconst now = Date.now();\n\n\t\tfor (const task of config.tasks) {\n\t\t\tif (!task.enabled) continue;\n\t\t\tif (this.taskRunning.has(task.id)) continue; // Concurrency guard\n\n\t\t\t// First encounter: mark \"last run\" as now so the task waits a full interval\n\t\t\tlet lastExec = this.lastRun.get(task.id);\n\t\t\tif (lastExec === undefined) {\n\t\t\t\tthis.lastRun.set(task.id, now);\n\t\t\t\tlastExec = now;\n\t\t\t}\n\t\t\tif (this.shouldRun(task, lastExec, now)) {\n\t\t\t\tthis.executeTask(task, now);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate shouldRun(task: ScheduledTask, lastExec: number, now: number): boolean {\n\t\tif (task.scheduleType === \"interval\") {\n\t\t\tconst intervalMs = parseIntervalMs(task.schedule);\n\t\t\tif (intervalMs < MIN_SCHEDULE_INTERVAL) return false;\n\t\t\treturn now - lastExec >= intervalMs;\n\t\t}\n\n\t\tif (task.scheduleType === \"cron\") {\n\t\t\t// Simple cron check: use interval of 60s minimum (cron granularity is minutes)\n\t\t\tif (now - lastExec < 60_000) return false;\n\t\t\treturn matchesCron(task.schedule, new Date(now));\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate async executeTask(task: ScheduledTask, now: number): Promise<void> {\n\t\tthis.taskRunning.add(task.id);\n\t\tthis.lastRun.set(task.id, now);\n\n\t\tconst startedAt = new Date(now).toISOString();\n\t\tconst startMs = Date.now();\n\t\tthis.logger.log(\n\t\t\t\"info\",\n\t\t\t`Executing task: ${task.name} (${task.id})\n type: ${task.taskType}\n schedule: ${task.schedule} (${task.scheduleType})\n enabled: ${task.enabled}`\n\t\t);\n\n\t\ttry {\n\t\t\tconst executionPayload = resolveTaskExecutionPayload(\n\t\t\t\ttask.taskType,\n\t\t\t\ttask.payload,\n\t\t\t\tthis.workspacePath\n\t\t\t);\n\t\t\tvalidateTaskPayload(task.taskType, executionPayload);\n\t\t\tconst executor = this.getExecutor(task.taskType);\n\t\t\tconst result = await executor.execute(executionPayload);\n\t\t\tconst finishedAt = new Date().toISOString();\n\t\t\tconst durationMs = Date.now() - startMs;\n\n\t\t\tthis.logManager.appendLog({\n\t\t\t\ttaskId: task.id,\n\t\t\t\ttaskName: task.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt,\n\t\t\t\tstatus: result.status === \"running\" ? \"failure\" : result.status,\n\t\t\t\toutput: result.output,\n\t\t\t\terror: result.error,\n\t\t\t});\n\n\t\t\tconst outputBytes = result.output ? Buffer.byteLength(result.output) : 0;\n\t\t\tthis.logger.log(\n\t\t\t\t\"info\",\n\t\t\t\t`Task completed: ${task.name} (${task.id})\n status: ${result.status}\n duration: ${durationMs}ms\n outputBytes: ${outputBytes}`\n\t\t\t);\n\t\t} catch (err: unknown) {\n\t\t\tconst finishedAt = new Date().toISOString();\n\t\t\tconst durationMs = Date.now() - startMs;\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tthis.logManager.appendLog({\n\t\t\t\ttaskId: task.id,\n\t\t\t\ttaskName: task.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt,\n\t\t\t\tstatus: \"failure\",\n\t\t\t\terror: message,\n\t\t\t});\n\t\t\tthis.logger.log(\n\t\t\t\t\"error\",\n\t\t\t\t`Task failed: ${task.name} (${task.id})\n duration: ${durationMs}ms\n error: ${message}`\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.taskRunning.delete(task.id);\n\t\t}\n\t}\n\n\tgetStatus() {\n\t\tconst config = this.configManager.readConfig();\n\t\treturn {\n\t\t\trunning: this.running,\n\t\t\tpid: process.pid,\n\t\t\tuptimeSeconds: this.running ? Math.floor((Date.now() - this.startTime) / 1000) : null,\n\t\t\ttasksRegistered: config.tasks.length,\n\t\t\ttasksEnabled: config.tasks.filter((t) => t.enabled).length,\n\t\t\tworkspacePath: this.workspacePath,\n\t\t\tpidFile: this.pidManager.getPidPath(),\n\t\t};\n\t}\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction parseIntervalMs(schedule: string): number {\n\tconst match = /^every\\s+(\\d+)\\s*(s|sec|m|min|h|hr|d|day)s?$/i.exec(schedule);\n\tif (!match) return 0;\n\tconst [, numStr, unit] = match;\n\tconst num = Number.parseInt(numStr ?? \"0\", 10);\n\tswitch (unit?.toLowerCase()) {\n\t\tcase \"s\":\n\t\tcase \"sec\":\n\t\t\treturn num * 1000;\n\t\tcase \"m\":\n\t\tcase \"min\":\n\t\t\treturn num * 60 * 1000;\n\t\tcase \"h\":\n\t\tcase \"hr\":\n\t\t\treturn num * 60 * 60 * 1000;\n\t\tcase \"d\":\n\t\tcase \"day\":\n\t\t\treturn num * 24 * 60 * 60 * 1000;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nfunction matchesCron(expression: string, date: Date): boolean {\n\t// Simplified cron matching: minute hour day month weekday\n\tconst parts = expression.trim().split(/\\s+/);\n\tif (parts.length < 5) return false;\n\tconst [minPart, hourPart, dayPart, monthPart, weekdayPart] = parts;\n\tif (!minPart || !hourPart || !dayPart || !monthPart || !weekdayPart) return false;\n\n\treturn (\n\t\tmatchCronField(minPart, date.getMinutes()) &&\n\t\tmatchCronField(hourPart, date.getHours()) &&\n\t\tmatchCronField(dayPart, date.getDate()) &&\n\t\tmatchCronField(monthPart, date.getMonth() + 1) &&\n\t\tmatchCronField(weekdayPart, date.getDay())\n\t);\n}\n\nfunction matchCronField(field: string, value: number): boolean {\n\tif (field === \"*\") return true;\n\n\t// Handle */n (step)\n\tif (field.startsWith(\"*/\")) {\n\t\tconst step = Number.parseInt(field.slice(2), 10);\n\t\treturn step > 0 && value % step === 0;\n\t}\n\n\t// Handle comma-separated values\n\tconst values = field.split(\",\");\n\treturn values.some((v) => Number.parseInt(v, 10) === value);\n}\n\n// Export helpers for testing\nexport { parseIntervalMs, matchesCron };\n","import { spawn } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport type { GithubCopilotCliPayload } from \"@serviceme/devtools-protocol\";\nimport { isCopilotAuthenticated } from \"../../copilot/doctor\";\nimport { resolveConfiguredTimeoutMs } from \"./timeout\";\nimport type {\n\tExecutorResult,\n\tOutputEventCallback,\n\tStreamingExecutorHandle,\n\tStreamingTaskExecutor,\n} from \"./types\";\n\nconst MAX_OUTPUT_BYTES = 2 * 1024 * 1024; // 2 MB\nconst DEFAULT_TIMEOUT_MS = 300_000;\n\nexport interface ResolvedGithubCopilotCliExecution {\n\tcommand: \"serviceme\";\n\targs: string[];\n\tcwd?: string;\n\ttimeoutMs: number | undefined;\n\tdiagnosticArgs: string[];\n\tpromptLen: number;\n}\n\nfunction redactArgs(args: string[]): string[] {\n\tconst redacted: string[] = [];\n\tlet redactNext = false;\n\tfor (const arg of args) {\n\t\tif (redactNext) {\n\t\t\tredacted.push(\"<redacted>\");\n\t\t\tredactNext = false;\n\t\t\tcontinue;\n\t\t}\n\t\tredacted.push(arg);\n\t\tif (arg === \"--prompt\") {\n\t\t\tredactNext = true;\n\t\t}\n\t}\n\treturn redacted;\n}\n\nfunction writeDiagnostic(message: string): void {\n\tconst logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;\n\tif (logPath) {\n\t\tfs.appendFileSync(logPath, message);\n\t\treturn;\n\t}\n\tprocess.stderr.write(message);\n}\n\nexport function resolveGithubCopilotCliExecution(\n\tpayload: GithubCopilotCliPayload\n): ResolvedGithubCopilotCliExecution {\n\tconst args: string[] = [\"copilot\", \"prompt\", \"--prompt\", payload.prompt];\n\tif (payload.autopilot) {\n\t\targs.push(\"--autopilot\");\n\t}\n\tif (payload.allowTools && payload.allowTools.length > 0) {\n\t\targs.push(\"--allow-tools\", payload.allowTools.join(\",\"));\n\t}\n\tif (payload.model) {\n\t\targs.push(\"--model\", payload.model);\n\t}\n\tif (payload.agent) {\n\t\targs.push(\"--agent\", payload.agent);\n\t}\n\targs.push(\"--timeout\", String(payload.timeout ?? 0));\n\n\treturn {\n\t\tcommand: \"serviceme\",\n\t\targs,\n\t\tcwd: payload.workspace,\n\t\ttimeoutMs: resolveConfiguredTimeoutMs(payload.timeout, DEFAULT_TIMEOUT_MS),\n\t\tdiagnosticArgs: redactArgs(args),\n\t\tpromptLen: payload.prompt.length,\n\t};\n}\n\nexport class GithubCopilotCliExecutor implements StreamingTaskExecutor {\n\tasync execute(payload: unknown, abortSignal?: AbortSignal): Promise<ExecutorResult> {\n\t\t// Pre-flight auth check\n\t\tconst authenticated = await isCopilotAuthenticated();\n\t\tif (!authenticated) {\n\t\t\treturn {\n\t\t\t\tstatus: \"failure\",\n\t\t\t\terror:\n\t\t\t\t\t\"GitHub Copilot CLI authentication failed. Please run `gh auth login` to re-authenticate.\",\n\t\t\t};\n\t\t}\n\n\t\tlet output = \"\";\n\t\tconst handle = this.executeStreaming(\n\t\t\tpayload,\n\t\t\t(_stream, data) => {\n\t\t\t\toutput += data;\n\t\t\t},\n\t\t\tabortSignal\n\t\t);\n\t\tconst result = await handle.result;\n\t\treturn { ...result, output: output || result.output };\n\t}\n\n\texecuteStreaming(\n\t\tpayload: unknown,\n\t\tonOutput: OutputEventCallback,\n\t\tabortSignal?: AbortSignal\n\t): StreamingExecutorHandle {\n\t\tconst p = payload as GithubCopilotCliPayload;\n\t\tconst execution = resolveGithubCopilotCliExecution(p);\n\n\t\tlet resolve: (value: ExecutorResult) => void;\n\t\tconst resultPromise = new Promise<ExecutorResult>((r) => {\n\t\t\tresolve = r;\n\t\t});\n\t\tlet settled = false;\n\n\t\tconst settle = (result: ExecutorResult) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tresolve?.(result);\n\t\t};\n\n\t\tif (abortSignal?.aborted) {\n\t\t\treturn {\n\t\t\t\tresult: Promise.resolve({\n\t\t\t\t\tstatus: \"cancelled\" as const,\n\t\t\t\t\terror: \"Execution aborted\",\n\t\t\t\t}),\n\t\t\t\tcancel: () => {},\n\t\t\t};\n\t\t}\n\n\t\twriteDiagnostic(\n\t\t\t`[GithubCopilotCliExecutor] spawn: command=${execution.command}, args=${JSON.stringify(execution.diagnosticArgs)}, promptLen=${execution.promptLen}, cwd=${execution.cwd ?? \"(default)\"}, platform=${process.platform}, windowsHide=true, pid=${process.pid}\\n`\n\t\t);\n\n\t\tconst child = spawn(execution.command, execution.args, {\n\t\t\tcwd: execution.cwd,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t});\n\n\t\tif (child.pid) {\n\t\t\twriteDiagnostic(`[GithubCopilotCliExecutor] spawned child PID=${child.pid}\\n`);\n\t\t}\n\n\t\tconst timeoutMs = execution.timeoutMs;\n\t\tconst timer =\n\t\t\ttimeoutMs != null\n\t\t\t\t? setTimeout(() => {\n\t\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tif (!child.killed) child.kill(\"SIGKILL\");\n\t\t\t\t\t\t}, 5000);\n\t\t\t\t\t\tsettle({\n\t\t\t\t\t\t\tstatus: \"timeout\",\n\t\t\t\t\t\t\terror: `Copilot CLI execution timed out after ${timeoutMs / 1000}s`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}, timeoutMs)\n\t\t\t\t: undefined;\n\n\t\tlet stdoutBuf = \"\";\n\t\tlet stderrBuf = \"\";\n\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\tconst data = chunk.toString();\n\t\t\tstdoutBuf += data;\n\t\t\tif (stdoutBuf.length > MAX_OUTPUT_BYTES) stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);\n\t\t\tonOutput(\"stdout\", data);\n\t\t});\n\n\t\tchild.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\tconst data = chunk.toString();\n\t\t\tstderrBuf += data;\n\t\t\tif (stderrBuf.length > MAX_OUTPUT_BYTES) stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);\n\t\t\tonOutput(\"stderr\", data);\n\t\t});\n\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tsettle({ status: \"success\", output: stdoutBuf || undefined });\n\t\t\t} else {\n\t\t\t\tsettle({\n\t\t\t\t\tstatus: \"failure\",\n\t\t\t\t\toutput: stdoutBuf || undefined,\n\t\t\t\t\terror: stderrBuf || `Process exited with code ${code}`,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tchild.on(\"error\", (err) => {\n\t\t\tsettle({ status: \"failure\", error: err.message });\n\t\t});\n\n\t\tconst cancelFn = () => {\n\t\t\tchild.kill(\"SIGTERM\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (!child.killed) child.kill(\"SIGKILL\");\n\t\t\t}, 5000);\n\t\t\tsettle({ status: \"cancelled\", error: \"Execution cancelled\" });\n\t\t};\n\n\t\tabortSignal?.addEventListener(\"abort\", () => cancelFn(), { once: true });\n\n\t\treturn { result: resultPromise, cancel: cancelFn };\n\t}\n}\n","export function resolveConfiguredTimeoutMs(\n\ttimeoutSeconds: number | undefined,\n\tdefaultTimeoutMs: number\n): number | undefined {\n\tif (timeoutSeconds === 0) {\n\t\treturn undefined;\n\t}\n\tif (\n\t\ttypeof timeoutSeconds !== \"number\" ||\n\t\t!Number.isFinite(timeoutSeconds) ||\n\t\ttimeoutSeconds < 0\n\t) {\n\t\treturn defaultTimeoutMs;\n\t}\n\treturn timeoutSeconds * 1000;\n}\n","import type { HttpRequestPayload } from \"@serviceme/devtools-protocol\";\nimport { resolveConfiguredTimeoutMs } from \"./timeout\";\nimport type { ExecutorResult, TaskExecutor } from \"./types\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport class HttpRequestExecutor implements TaskExecutor {\n\tasync execute(payload: unknown, abortSignal?: AbortSignal): Promise<ExecutorResult> {\n\t\tconst p = payload as HttpRequestPayload;\n\t\tconst timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS);\n\n\t\tconst ac = new AbortController();\n\t\tlet timedOut = false;\n\t\tconst timer =\n\t\t\ttimeoutMs != null\n\t\t\t\t? setTimeout(() => {\n\t\t\t\t\t\ttimedOut = true;\n\t\t\t\t\t\tac.abort();\n\t\t\t\t\t}, timeoutMs)\n\t\t\t\t: undefined;\n\n\t\tif (abortSignal?.aborted) {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\treturn { status: \"cancelled\", error: \"Execution aborted\" };\n\t\t}\n\n\t\tlet externalAbort = false;\n\t\tabortSignal?.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\texternalAbort = true;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tac.abort();\n\t\t\t},\n\t\t\t{ once: true }\n\t\t);\n\n\t\ttry {\n\t\t\tconst response = await fetch(p.url, {\n\t\t\t\tmethod: p.method,\n\t\t\t\theaders: p.headers,\n\t\t\t\tbody: p.body,\n\t\t\t\tsignal: ac.signal,\n\t\t\t});\n\t\t\tif (timer) clearTimeout(timer);\n\n\t\t\tconst body = await response.text();\n\t\t\tif (response.ok) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"success\",\n\t\t\t\t\toutput: `${response.status} ${response.statusText}\\n${body}`.trim(),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstatus: \"failure\",\n\t\t\t\terror: `HTTP ${response.status} ${response.statusText}\\n${body}`.trim(),\n\t\t\t};\n\t\t} catch (err: unknown) {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tif (err instanceof Error && err.name === \"AbortError\") {\n\t\t\t\tif (externalAbort) {\n\t\t\t\t\treturn { status: \"cancelled\", error: \"Execution cancelled\" };\n\t\t\t\t}\n\t\t\t\tif (!timedOut || timeoutMs == null) {\n\t\t\t\t\treturn { status: \"failure\", error: err.message };\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"timeout\",\n\t\t\t\t\terror: `HTTP request timed out after ${timeoutMs / 1000}s`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\treturn { status: \"failure\", error: message };\n\t\t}\n\t}\n}\n","import { type SpawnOptions, spawn } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { ShellPayload } from \"@serviceme/devtools-protocol\";\nimport { resolveConfiguredTimeoutMs } from \"./timeout\";\nimport type {\n\tExecutorResult,\n\tOutputEventCallback,\n\tStreamingExecutorHandle,\n\tStreamingTaskExecutor,\n} from \"./types\";\n\nconst MAX_OUTPUT_BYTES = 1024 * 1024; // 1 MB\nconst DEFAULT_TIMEOUT_MS = 60_000;\nconst POSIX_SHELL_CANDIDATES = [\"bash.exe\", \"sh.exe\"];\n\nexport interface ShellExecutionResolutionOptions {\n\tplatform?: NodeJS.Platform;\n\tenv?: NodeJS.ProcessEnv;\n\tfileExists?: (candidate: string) => boolean;\n}\n\nexport interface ResolvedShellExecution {\n\tcommand: string;\n\targs: string[];\n\tstdinScript?: string;\n\toutputEncoding: BufferEncoding;\n\tshellKind: \"cmd\" | \"posix\";\n}\n\nexport function resolveShellExecution(\n\tscript: string,\n\toptions: ShellExecutionResolutionOptions = {}\n): ResolvedShellExecution {\n\tconst platform = options.platform ?? process.platform;\n\tconst env = options.env ?? process.env;\n\tconst fileExists = options.fileExists ?? fs.existsSync;\n\n\tif (platform === \"win32\") {\n\t\tconst posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;\n\t\tif (posixShell) {\n\t\t\treturn {\n\t\t\t\tcommand: posixShell,\n\t\t\t\targs: [\"-s\"],\n\t\t\t\tstdinScript: script,\n\t\t\t\toutputEncoding: \"utf8\",\n\t\t\t\tshellKind: \"posix\",\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tcommand: env.ComSpec ?? env.COMSPEC ?? \"cmd.exe\",\n\t\t\targs: [\"/d\", \"/s\", \"/c\", script],\n\t\t\toutputEncoding: \"utf8\",\n\t\t\tshellKind: \"cmd\",\n\t\t};\n\t}\n\n\treturn {\n\t\tcommand: \"sh\",\n\t\targs: [\"-s\"],\n\t\tstdinScript: script,\n\t\toutputEncoding: \"utf8\",\n\t\tshellKind: \"posix\",\n\t};\n}\n\nfunction usesPosixShellSyntax(script: string): boolean {\n\treturn /\\$\\([^)]*\\)|`[^`]*`/.test(script);\n}\n\nfunction findWindowsPosixShell(\n\tenv: NodeJS.ProcessEnv,\n\tfileExists: (candidate: string) => boolean\n): string | null {\n\tconst explicitShell = env.SERVICEME_POSIX_SHELL;\n\tif (explicitShell && fileExists(explicitShell)) {\n\t\treturn explicitShell;\n\t}\n\n\tconst knownGitBashPaths = [\n\t\t\"C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe\",\n\t\t\"C:\\\\Program Files\\\\Git\\\\usr\\\\bin\\\\bash.exe\",\n\t\t\"C:\\\\Program Files (x86)\\\\Git\\\\bin\\\\bash.exe\",\n\t\t\"C:\\\\Program Files (x86)\\\\Git\\\\usr\\\\bin\\\\bash.exe\",\n\t];\n\tfor (const candidate of knownGitBashPaths) {\n\t\tif (fileExists(candidate)) return candidate;\n\t}\n\n\tconst pathValue = env.Path ?? env.PATH ?? \"\";\n\tfor (const dir of pathValue.split(path.win32.delimiter)) {\n\t\tif (!dir) continue;\n\t\tfor (const executable of POSIX_SHELL_CANDIDATES) {\n\t\t\tconst candidate = path.win32.join(dir, executable);\n\t\t\tif (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction isWindowsWslLauncher(candidate: string): boolean {\n\tconst normalized = path.win32.normalize(candidate).toLowerCase();\n\treturn (\n\t\tnormalized.endsWith(\"\\\\windows\\\\system32\\\\bash.exe\") ||\n\t\tnormalized.endsWith(\"\\\\windows\\\\syswow64\\\\bash.exe\")\n\t);\n}\n\nfunction writeDiagnostic(message: string): void {\n\tconst logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;\n\tif (logPath) {\n\t\tfs.appendFileSync(logPath, message);\n\t\treturn;\n\t}\n\tprocess.stderr.write(message);\n}\n\nexport class ShellExecutor implements StreamingTaskExecutor {\n\tasync execute(payload: unknown, abortSignal?: AbortSignal): Promise<ExecutorResult> {\n\t\tlet output = \"\";\n\t\tconst handle = this.executeStreaming(\n\t\t\tpayload,\n\t\t\t(_stream, data) => {\n\t\t\t\toutput += data;\n\t\t\t},\n\t\t\tabortSignal\n\t\t);\n\t\tconst result = await handle.result;\n\t\treturn { ...result, output: output || result.output };\n\t}\n\n\texecuteStreaming(\n\t\tpayload: unknown,\n\t\tonOutput: OutputEventCallback,\n\t\tabortSignal?: AbortSignal\n\t): StreamingExecutorHandle {\n\t\tconst p = payload as ShellPayload;\n\t\tconst timeoutMs = resolveConfiguredTimeoutMs(p.timeout, DEFAULT_TIMEOUT_MS);\n\n\t\tlet resolve: (value: ExecutorResult) => void;\n\t\tconst resultPromise = new Promise<ExecutorResult>((r) => {\n\t\t\tresolve = r;\n\t\t});\n\t\tlet settled = false;\n\n\t\tconst settle = (result: ExecutorResult) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tresolve?.(result);\n\t\t};\n\n\t\tif (abortSignal?.aborted) {\n\t\t\treturn {\n\t\t\t\tresult: Promise.resolve({\n\t\t\t\t\tstatus: \"cancelled\" as const,\n\t\t\t\t\terror: \"Execution aborted\",\n\t\t\t\t}),\n\t\t\t\tcancel: () => {},\n\t\t\t};\n\t\t}\n\n\t\tconst shellExecution = resolveShellExecution(p.script);\n\n\t\tconst spawnOpts: SpawnOptions = {\n\t\t\tcwd: p.cwd,\n\t\t\tstdio: [shellExecution.stdinScript ? \"pipe\" : \"ignore\", \"pipe\", \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t};\n\n\t\twriteDiagnostic(\n\t\t\t`[ShellExecutor] spawn:\n platform=${process.platform}\n shell=${shellExecution.shellKind}\n command=${shellExecution.command}\n args=${JSON.stringify(shellExecution.args.filter((a) => a !== p.script))}\n stdin=${shellExecution.stdinScript ? \"true\" : \"false\"}\n cwd=${p.cwd ?? \"(default)\"}\n timeout=${timeoutMs != null ? `${timeoutMs}ms` : \"unlimited\"}\n scriptLen=${p.script.length}\n windowsHide=true\n daemonPid=${process.pid}\\n`\n\t\t);\n\n\t\tconst child = spawn(shellExecution.command, shellExecution.args, spawnOpts);\n\n\t\tif (shellExecution.stdinScript && child.stdin) {\n\t\t\tchild.stdin.end(shellExecution.stdinScript);\n\t\t}\n\n\t\tif (child.pid) {\n\t\t\twriteDiagnostic(`[ShellExecutor] spawned child PID=${child.pid}\\n`);\n\t\t}\n\n\t\tconst timer =\n\t\t\ttimeoutMs != null\n\t\t\t\t? setTimeout(() => {\n\t\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tif (!child.killed) child.kill(\"SIGKILL\");\n\t\t\t\t\t\t}, 5000);\n\t\t\t\t\t\tsettle({\n\t\t\t\t\t\t\tstatus: \"timeout\",\n\t\t\t\t\t\t\terror: `Shell execution timed out after ${timeoutMs / 1000}s`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}, timeoutMs)\n\t\t\t\t: undefined;\n\n\t\tlet stdoutBuf = \"\";\n\t\tlet stderrBuf = \"\";\n\n\t\tchild.stdout?.on(\"data\", (chunk: Buffer) => {\n\t\t\tconst data = chunk.toString(shellExecution.outputEncoding);\n\t\t\tstdoutBuf += data;\n\t\t\tif (stdoutBuf.length > MAX_OUTPUT_BYTES) stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);\n\t\t\tonOutput(\"stdout\", data);\n\t\t});\n\n\t\tchild.stderr?.on(\"data\", (chunk: Buffer) => {\n\t\t\tconst data = chunk.toString(shellExecution.outputEncoding);\n\t\t\tstderrBuf += data;\n\t\t\tif (stderrBuf.length > MAX_OUTPUT_BYTES) stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);\n\t\t\tonOutput(\"stderr\", data);\n\t\t});\n\n\t\tchild.on(\"close\", (code: number | null) => {\n\t\t\twriteDiagnostic(`[ShellExecutor] child PID=${child.pid} exited: code=${code}\\n`);\n\t\t\tif (code === 0) {\n\t\t\t\tsettle({ status: \"success\", output: stdoutBuf || undefined });\n\t\t\t} else {\n\t\t\t\tsettle({\n\t\t\t\t\tstatus: \"failure\",\n\t\t\t\t\toutput: stdoutBuf || undefined,\n\t\t\t\t\terror: stderrBuf || `Process exited with code ${code}`,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tchild.on(\"error\", (err: Error) => {\n\t\t\tsettle({ status: \"failure\", error: err.message });\n\t\t});\n\n\t\tconst cancelFn = () => {\n\t\t\tchild.kill(\"SIGTERM\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (!child.killed) child.kill(\"SIGKILL\");\n\t\t\t}, 5000);\n\t\t\tsettle({ status: \"cancelled\", error: \"Execution cancelled\" });\n\t\t};\n\n\t\tabortSignal?.addEventListener(\"abort\", () => cancelFn(), { once: true });\n\n\t\treturn { result: resultPromise, cancel: cancelFn };\n\t}\n}\n","import type { TaskExecutionStatus } from \"@serviceme/devtools-protocol\";\n\nexport interface ExecutorResult {\n\tstatus: TaskExecutionStatus;\n\toutput?: string;\n\terror?: string;\n}\n\nexport interface TaskExecutor {\n\texecute(payload: unknown, abortSignal?: AbortSignal): Promise<ExecutorResult>;\n}\n\n// Streaming interfaces for Bridge task execution\n\nexport type OutputEventCallback = (stream: \"stdout\" | \"stderr\", data: string) => void;\n\nexport interface StreamingExecutorHandle {\n\tresult: Promise<ExecutorResult>;\n\tcancel: () => void;\n}\n\nexport interface StreamingTaskExecutor extends TaskExecutor {\n\texecuteStreaming(\n\t\tpayload: unknown,\n\t\tonOutput: OutputEventCallback,\n\t\tabortSignal?: AbortSignal\n\t): StreamingExecutorHandle;\n}\n\nexport function isStreamingTaskExecutor(executor: TaskExecutor): executor is StreamingTaskExecutor {\n\treturn (\n\t\t\"executeStreaming\" in executor &&\n\t\ttypeof (executor as StreamingTaskExecutor).executeStreaming === \"function\"\n\t);\n}\n","import type { ScheduledTaskType } from \"@serviceme/devtools-protocol\";\nimport { GithubCopilotCliExecutor } from \"./GithubCopilotCliExecutor\";\nimport { HttpRequestExecutor } from \"./HttpRequestExecutor\";\nimport { ShellExecutor } from \"./ShellExecutor\";\nimport type { TaskExecutor } from \"./types\";\n\n/** Task types executable by the bridge (excludes \"command\" which runs in-extension) */\ntype BridgeTaskType = Exclude<ScheduledTaskType, \"command\">;\n\nconst executors: Record<BridgeTaskType, TaskExecutor> = {\n\tshell: new ShellExecutor(),\n\thttp_request: new HttpRequestExecutor(),\n\tgithub_copilot_cli: new GithubCopilotCliExecutor(),\n};\n\nexport function getExecutor(taskType: ScheduledTaskType): TaskExecutor {\n\tconst executor = executors[taskType as BridgeTaskType];\n\tif (!executor) {\n\t\tthrow new Error(`Unsupported bridge task type: ${taskType}`);\n\t}\n\treturn executor;\n}\n\nexport { ShellExecutor, HttpRequestExecutor, GithubCopilotCliExecutor };\nexport type {\n\tExecutorResult,\n\tOutputEventCallback,\n\tStreamingExecutorHandle,\n\tStreamingTaskExecutor,\n\tTaskExecutor,\n} from \"./types\";\nexport { isStreamingTaskExecutor } from \"./types\";\n","import { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type {\n\tScheduledTask,\n\tScheduledTasksConfig,\n\tScheduledTasksConfigV1,\n\tScheduledTaskType,\n\tTaskPayload,\n\tTaskWorkspaceRef,\n} from \"@serviceme/devtools-protocol\";\nimport {\n\tcreateServicemeError,\n\tisScheduledTasksConfig,\n\tisScheduledTasksConfigV1,\n\tisScheduledTaskV1,\n\tmigrateV1ToV2,\n} from \"@serviceme/devtools-protocol\";\nimport { getMigrationFailuresPath, getScheduledTasksConfigPath } from \"../paths/userHome\";\n\nfunction emptyConfig(): ScheduledTasksConfig {\n\treturn { version: 2, tasks: [] };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Detect the \"v2 container with v1-shaped tasks\" hybrid: a config object\n * whose top-level `version` is `2` and whose `tasks` array is non-empty,\n * but where every task fails the v2 validator specifically because of the\n * missing required `workspace` field. The task entries must still pass\n * the v1 validator (otherwise the file is genuinely corrupt, not just\n * stale).\n */\nfunction isV2ContainerWithV1Tasks(value: unknown): value is {\n\tversion: 2;\n\ttasks: unknown[];\n} {\n\tif (!isRecord(value)) return false;\n\tif (value.version !== 2) return false;\n\tif (!Array.isArray(value.tasks) || value.tasks.length === 0) return false;\n\tconst allValidV2 = value.tasks.every((t) => !isScheduledTaskV1(t) || tIsValidV2(t));\n\tif (allValidV2) return false;\n\t// All tasks pass v1 — exactly the v2-container-with-v1-tasks hybrid.\n\treturn value.tasks.every((t) => isScheduledTaskV1(t));\n}\n\nfunction tIsValidV2(t: unknown): boolean {\n\treturn isRecord(t) && isRecord((t as Record<string, unknown>).workspace);\n}\n\n/**\n * Cast a v2-container-with-v1-tasks hybrid back to a v1 `ScheduledTasksConfigV1`\n * so we can feed it through the existing `migrateV1ToV2` helper. No data is\n * transformed — only the type assertion; the tasks inside are already\n * v1-shaped.\n */\nfunction v1ContainerShape(value: { tasks: unknown[] }): ScheduledTasksConfigV1 {\n\treturn { version: 1, tasks: value.tasks.filter(isScheduledTaskV1) };\n}\n\n/**\n * Fallback workspace context used when the manager has to auto-migrate a\n * v1-shaped task and the caller did not supply one. Points at the user's\n * home directory; the user can always re-edit the task in the webview to\n * point at the real workspace afterwards. This is intentionally\n * pessimistic — surfacing the task at all beats the previous behaviour\n * of silently dropping it.\n */\nfunction defaultWorkspaceContext(): TaskWorkspaceRef {\n\tconst home = os.homedir() || \"/\";\n\treturn { path: home, name: path.basename(home) || home };\n}\n\nfunction requireNonEmptyString(\n\tpayload: TaskPayload,\n\tfield: string,\n\ttaskType: ScheduledTaskType\n): void {\n\tif (!isRecord(payload) || typeof payload[field] !== \"string\" || !payload[field].trim()) {\n\t\tthrow createServicemeError(\"invalid_payload\", `${taskType} payload ${field} is required`);\n\t}\n}\n\nexport function validateTaskPayload(taskType: ScheduledTaskType, payload: TaskPayload): void {\n\tswitch (taskType) {\n\t\tcase \"command\":\n\t\t\trequireNonEmptyString(payload, \"command\", taskType);\n\t\t\treturn;\n\t\tcase \"shell\":\n\t\t\trequireNonEmptyString(payload, \"script\", taskType);\n\t\t\treturn;\n\t\tcase \"http_request\":\n\t\t\trequireNonEmptyString(payload, \"url\", taskType);\n\t\t\trequireNonEmptyString(payload, \"method\", taskType);\n\t\t\treturn;\n\t\tcase \"github_copilot_cli\":\n\t\t\trequireNonEmptyString(payload, \"prompt\", taskType);\n\t\t\treturn;\n\t}\n}\n\nexport interface CreateTaskInput {\n\tname: string;\n\tdescription?: string;\n\tscheduleType: \"cron\" | \"interval\";\n\tschedule: string;\n\ttaskType: ScheduledTaskType;\n\tpayload: TaskPayload;\n\tworkspace: TaskWorkspaceRef;\n\tenabled?: boolean;\n}\n\nexport interface EditTaskInput {\n\tname?: string;\n\tdescription?: string;\n\tscheduleType?: \"cron\" | \"interval\";\n\tschedule?: string;\n\ttaskType?: ScheduledTaskType;\n\tpayload?: TaskPayload;\n\tworkspace?: TaskWorkspaceRef;\n\tenabled?: boolean;\n}\n\nexport interface TaskConfigManagerOptions {\n\t/**\n\t * Override the on-disk config path. Defaults to the global\n\t * `~/.serviceme/scheduled-tasks.json`. Tests use this to point at a tmp\n\t * directory; production code should leave it unset.\n\t */\n\tconfigPath?: string;\n\t/**\n\t * Workspace context used when a legacy v1 config is read. v1 tasks had no\n\t * per-task workspace, so the manager needs a fallback to attach when\n\t * auto-migrating. Required only if the underlying file might still be v1.\n\t */\n\tworkspaceContext?: TaskWorkspaceRef;\n\t/**\n\t * Override the path where malformed-config diagnostic entries are appended.\n\t * Defaults to `~/.serviceme/migration-failures.json`. Tests can point at a\n\t * tmp file to assert failures were recorded.\n\t */\n\tmigrationFailuresPath?: string;\n\t/**\n\t * Inject a logger for the manager's recovery / migration warnings. Defaults\n\t * to `console.warn`. Tests pass a `vi.fn`/`mock.fn` to assert the warnings\n\t * were emitted.\n\t */\n\tlogger?: (message: string) => void;\n}\n\nexport class TaskConfigManager {\n\tprivate readonly configPath: string;\n\tprivate readonly workspaceContext?: TaskWorkspaceRef;\n\tprivate readonly migrationFailuresPath?: string;\n\tprivate readonly logger?: (message: string) => void;\n\n\tconstructor(options: TaskConfigManagerOptions = {}) {\n\t\tthis.configPath = options.configPath ?? getScheduledTasksConfigPath();\n\t\tthis.workspaceContext = options.workspaceContext;\n\t\tthis.migrationFailuresPath = options.migrationFailuresPath;\n\t\tthis.logger = options.logger;\n\t}\n\n\tgetConfigPath(): string {\n\t\treturn this.configPath;\n\t}\n\n\treadConfig(): ScheduledTasksConfig {\n\t\tif (!fs.existsSync(this.configPath)) {\n\t\t\treturn emptyConfig();\n\t\t}\n\t\tconst raw = fs.readFileSync(this.configPath, \"utf-8\");\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(raw);\n\t\t} catch (error) {\n\t\t\t// Corrupted file — record failure + warn, then treat as empty.\n\t\t\t// Mirrors the log manager's repair-on-read policy.\n\t\t\tthis.recordMalformedConfigFailure(\"parse_error\", String(error), raw);\n\t\t\treturn emptyConfig();\n\t\t}\n\n\t\t// v2 (current): return as-is.\n\t\tif (isScheduledTasksConfig(parsed)) {\n\t\t\treturn parsed;\n\t\t}\n\n\t\t// Mixed / recoverable: a v2 container holding v1-shaped tasks (no\n\t\t// `workspace` field). Happens when a tool writes the header\n\t\t// `\"version\": 2` but each task is missing the v2-required `workspace`\n\t\t// ref — observed when users hand-edit `~/.serviceme/scheduled-tasks.json`\n\t\t// or when older bugged writers stamped v2 onto v1 task shapes. Treat\n\t\t// as v1 + auto-migrate using either the explicit `workspaceContext`\n\t\t// (preferred) or a sensible default (the user's home dir) so the\n\t\t// tasks stay visible instead of being silently dropped. Repairs the\n\t\t// on-disk file on success.\n\t\tif (isV2ContainerWithV1Tasks(parsed)) {\n\t\t\tconst v1 = v1ContainerShape(parsed);\n\t\t\tconst ctx = this.workspaceContext ?? defaultWorkspaceContext();\n\t\t\tconst { config, issues } = migrateV1ToV2(v1, ctx);\n\t\t\tthis.warn(\n\t\t\t\t`TaskConfigManager: detected v2 container with v1-shaped tasks in ${this.configPath} — auto-migrated in place. ` +\n\t\t\t\t\t`Tasks are now bound to workspace \"${ctx.path}\". Pass TaskConfigManager({ workspaceContext }) to override.`\n\t\t\t);\n\t\t\tfor (const issue of issues) this.warn(`TaskConfigManager migration issue: ${issue}`);\n\t\t\ttry {\n\t\t\t\tthis.writeConfig(config);\n\t\t\t} catch (writeError) {\n\t\t\t\tthis.warn(\n\t\t\t\t\t`TaskConfigManager: failed to rewrite repaired config to ${this.configPath}: ${String(writeError)}`\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn config;\n\t\t}\n\n\t\t// v1 (legacy): auto-migrate in memory. The MigrateToGlobal scanner is\n\t\t// the long-term path forward; this branch is the safety net for any\n\t\t// global config file that was somehow written in v1 format during the\n\t\t// migration window.\n\t\tif (isScheduledTasksConfigV1(parsed)) {\n\t\t\tif (!this.workspaceContext) {\n\t\t\t\tthrow createServicemeError(\n\t\t\t\t\t\"invalid_params\",\n\t\t\t\t\t\"TaskConfigManager: v1 config found but no workspaceContext provided. \" +\n\t\t\t\t\t\t\"Pass TaskConfigManager({ workspaceContext }) when reading a legacy v1 file.\"\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst v1 = parsed as ScheduledTasksConfigV1;\n\t\t\treturn migrateV1ToV2(v1, this.workspaceContext).config;\n\t\t}\n\n\t\t// Unknown shape — record a failure entry so the user can debug, then\n\t\t// treat as empty. Don't return silently: a previous version's CLI\n\t\t// sometimes left an unrecoverable file that ate tasks invisibly.\n\t\tthis.recordMalformedConfigFailure(\"unknown_shape\", \"Config did not match v1 or v2 schema\", raw);\n\t\treturn emptyConfig();\n\t}\n\n\tprivate warn(message: string): void {\n\t\tif (this.logger) this.logger(message);\n\t\telse console.warn(`[TaskConfigManager] ${message}`);\n\t}\n\n\tprivate recordMalformedConfigFailure(reason: string, detail: string, raw: string): void {\n\t\tthis.warn(\n\t\t\t`TaskConfigManager: malformed config at ${this.configPath} (${reason}): ${detail}. ` +\n\t\t\t\t`Tasks in this file are invisible until repaired. A diagnostic entry is written to ${\n\t\t\t\t\tthis.migrationFailuresPath ?? getMigrationFailuresPath()\n\t\t\t\t}.`\n\t\t);\n\t\ttry {\n\t\t\tconst target = this.migrationFailuresPath ?? getMigrationFailuresPath();\n\t\t\tconst prior = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn JSON.parse(fs.readFileSync(target, \"utf-8\")) as unknown;\n\t\t\t\t} catch {\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t})();\n\t\t\tconst failures = Array.isArray(prior) ? (prior as unknown[]) : [];\n\t\t\tfailures.push({\n\t\t\t\tpath: this.configPath,\n\t\t\t\treason,\n\t\t\t\tdetail,\n\t\t\t\tsnippet: raw.slice(0, 500),\n\t\t\t\trecordedAt: new Date().toISOString(),\n\t\t\t});\n\t\t\tfs.mkdirSync(path.dirname(target), { recursive: true });\n\t\t\tfs.writeFileSync(target, JSON.stringify(failures, null, \"\\t\"), \"utf-8\");\n\t\t} catch (writeError) {\n\t\t\tthis.warn(\n\t\t\t\t`TaskConfigManager: also failed to write migration-failures log: ${String(writeError)}`\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate writeConfig(config: ScheduledTasksConfig): void {\n\t\tconst dir = path.dirname(this.configPath);\n\t\tif (!fs.existsSync(dir)) {\n\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t}\n\t\t// Atomic write: write to temp then rename\n\t\tconst tmp = `${this.configPath}.tmp`;\n\t\tfs.writeFileSync(tmp, JSON.stringify(config, null, \"\\t\"), \"utf-8\");\n\t\tfs.renameSync(tmp, this.configPath);\n\t}\n\n\tlistTasks(): ScheduledTask[] {\n\t\treturn this.readConfig().tasks;\n\t}\n\n\tgetTask(id: string): ScheduledTask | undefined {\n\t\treturn this.readConfig().tasks.find((t) => t.id === id);\n\t}\n\n\tgetTaskByName(name: string): ScheduledTask | undefined {\n\t\treturn this.readConfig().tasks.find((t) => t.name === name);\n\t}\n\n\tcreateTask(input: CreateTaskInput): ScheduledTask {\n\t\tif (!input.workspace || !input.workspace.path || !input.workspace.name) {\n\t\t\tthrow createServicemeError(\n\t\t\t\t\"invalid_params\",\n\t\t\t\t\"createTask: workspace with path and name is required (v2 schema)\"\n\t\t\t);\n\t\t}\n\t\tvalidateTaskPayload(input.taskType, input.payload);\n\t\tconst config = this.readConfig();\n\t\tconst now = new Date().toISOString();\n\t\tconst task: ScheduledTask = {\n\t\t\tid: randomUUID(),\n\t\t\tname: input.name,\n\t\t\tdescription: input.description,\n\t\t\tenabled: input.enabled ?? true,\n\t\t\tscheduleType: input.scheduleType,\n\t\t\tschedule: input.schedule,\n\t\t\ttaskType: input.taskType,\n\t\t\tpayload: input.payload,\n\t\t\tworkspace: input.workspace,\n\t\t\tcreatedAt: now,\n\t\t\tupdatedAt: now,\n\t\t};\n\t\tconfig.tasks.push(task);\n\t\tthis.writeConfig(config);\n\t\treturn task;\n\t}\n\n\teditTask(id: string, input: EditTaskInput): ScheduledTask {\n\t\tconst config = this.readConfig();\n\t\tconst idx = config.tasks.findIndex((t) => t.id === id);\n\t\tif (idx === -1) {\n\t\t\tthrow new Error(`Task '${id}' not found`);\n\t\t}\n\t\tconst existing = config.tasks[idx];\n\t\tif (!existing) {\n\t\t\tthrow new Error(`Task '${id}' not found`);\n\t\t}\n\t\tconst nextTaskType = input.taskType ?? existing.taskType;\n\t\tconst nextPayload = input.payload ?? existing.payload;\n\t\tconst nextWorkspace = input.workspace ?? existing.workspace;\n\t\tvalidateTaskPayload(nextTaskType, nextPayload);\n\t\tconst updated: ScheduledTask = {\n\t\t\t...existing,\n\t\t\t...(input.name !== undefined && { name: input.name }),\n\t\t\t...(input.description !== undefined && {\n\t\t\t\tdescription: input.description,\n\t\t\t}),\n\t\t\t...(input.scheduleType !== undefined && {\n\t\t\t\tscheduleType: input.scheduleType,\n\t\t\t}),\n\t\t\t...(input.schedule !== undefined && { schedule: input.schedule }),\n\t\t\t...(input.taskType !== undefined && { taskType: input.taskType }),\n\t\t\t...(input.payload !== undefined && { payload: input.payload }),\n\t\t\t...(input.workspace !== undefined && { workspace: nextWorkspace }),\n\t\t\t...(input.enabled !== undefined && { enabled: input.enabled }),\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t};\n\t\tconfig.tasks[idx] = updated;\n\t\tthis.writeConfig(config);\n\t\treturn updated;\n\t}\n\n\tdeleteTask(id: string): ScheduledTask {\n\t\tconst config = this.readConfig();\n\t\tconst idx = config.tasks.findIndex((t) => t.id === id);\n\t\tif (idx === -1) {\n\t\t\tthrow new Error(`Task '${id}' not found`);\n\t\t}\n\t\tconst removed = config.tasks[idx];\n\t\tif (!removed) {\n\t\t\tthrow new Error(`Task '${id}' not found`);\n\t\t}\n\t\tconfig.tasks.splice(idx, 1);\n\t\tthis.writeConfig(config);\n\t\treturn removed;\n\t}\n\n\ttoggleTask(id: string, enabled: boolean): ScheduledTask {\n\t\treturn this.editTask(id, { enabled });\n\t}\n}\n","import type {\n\tConcurrencyPolicy,\n\tGithubCopilotCliPayload,\n\tRunningTaskInfo,\n\tScheduledTaskType,\n\tShellPayload,\n\tTaskCancelledEventParams,\n\tTaskCompletedEventParams,\n\tTaskExecutionLog,\n\tTaskExecutionSnapshot,\n\tTaskFailedEventParams,\n\tTaskOutputEventParams,\n\tTaskPayload,\n\tTaskStartedEventParams,\n} from \"@serviceme/devtools-protocol\";\nimport { type ExecutorResult, isStreamingTaskExecutor, type TaskExecutor } from \"./executors/types\";\nimport { validateTaskPayload } from \"./TaskConfigManager\";\n\nexport interface TaskEventListener {\n\tonStarted(params: TaskStartedEventParams): void;\n\tonOutput(params: TaskOutputEventParams): void;\n\tonCompleted(params: TaskCompletedEventParams): void;\n\tonFailed(params: TaskFailedEventParams): void;\n\tonCancelled(params: TaskCancelledEventParams): void;\n}\n\ninterface RunningExecution {\n\texecutionId: string;\n\ttaskId: string;\n\tstartedAt: string;\n\tcancel: () => void;\n}\n\nfunction hasNonEmptyString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.trim().length > 0;\n}\n\nexport function resolveTaskExecutionPayload(\n\ttaskType: ScheduledTaskType,\n\tpayload: TaskPayload,\n\tworkspacePath: string\n): TaskPayload {\n\tif (!hasNonEmptyString(workspacePath)) {\n\t\treturn payload;\n\t}\n\n\tif (taskType === \"shell\") {\n\t\tconst shellPayload = payload as ShellPayload;\n\t\tif (hasNonEmptyString(shellPayload.cwd)) {\n\t\t\treturn shellPayload;\n\t\t}\n\t\treturn { ...shellPayload, cwd: workspacePath };\n\t}\n\n\tif (taskType === \"github_copilot_cli\") {\n\t\tconst copilotPayload = payload as GithubCopilotCliPayload;\n\t\tif (hasNonEmptyString(copilotPayload.workspace)) {\n\t\t\treturn copilotPayload;\n\t\t}\n\t\treturn { ...copilotPayload, workspace: workspacePath };\n\t}\n\n\treturn payload;\n}\n\nexport class TaskExecutionEngine {\n\tprivate readonly running = new Map<string, RunningExecution>();\n\tprivate readonly taskExecutions = new Map<string, Set<string>>();\n\tprivate listener: TaskEventListener | null = null;\n\n\tconstructor(private readonly getExecutor: (taskType: string) => TaskExecutor) {}\n\n\tsetListener(listener: TaskEventListener): void {\n\t\tthis.listener = listener;\n\t}\n\n\tasync execute(snapshot: TaskExecutionSnapshot): Promise<void> {\n\t\tconst policy: ConcurrencyPolicy = snapshot.concurrencyPolicy ?? \"reject\";\n\n\t\t// Enforce concurrency policy\n\t\tif (policy === \"reject\") {\n\t\t\tconst existingIds = this.taskExecutions.get(snapshot.taskId);\n\t\t\tif (existingIds && existingIds.size > 0) {\n\t\t\t\tthrow new Error(`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`);\n\t\t\t}\n\t\t}\n\n\t\tconst startedAt = new Date().toISOString();\n\n\t\t// Track by taskId → executionId\n\t\tif (!this.taskExecutions.has(snapshot.taskId)) {\n\t\t\tthis.taskExecutions.set(snapshot.taskId, new Set());\n\t\t}\n\t\tthis.taskExecutions.get(snapshot.taskId)?.add(snapshot.executionId);\n\n\t\t// Set up cancellation\n\t\tconst ac = new AbortController();\n\t\tconst runningExec: RunningExecution = {\n\t\t\texecutionId: snapshot.executionId,\n\t\t\ttaskId: snapshot.taskId,\n\t\t\tstartedAt,\n\t\t\tcancel: () => ac.abort(),\n\t\t};\n\t\tthis.running.set(snapshot.executionId, runningExec);\n\n\t\tthis.listener?.onStarted({\n\t\t\texecutionId: snapshot.executionId,\n\t\t\ttaskId: snapshot.taskId,\n\t\t\tstartedAt,\n\t\t});\n\n\t\ttry {\n\t\t\tconst executionPayload = resolveTaskExecutionPayload(\n\t\t\t\tsnapshot.type,\n\t\t\t\tsnapshot.payload,\n\t\t\t\tsnapshot.workspacePath\n\t\t\t);\n\t\t\tvalidateTaskPayload(snapshot.type, executionPayload);\n\t\t\tconst executor = this.getExecutor(snapshot.type);\n\t\t\tlet result: ExecutorResult;\n\t\t\tif (isStreamingTaskExecutor(executor)) {\n\t\t\t\tconst handle = executor.executeStreaming(\n\t\t\t\t\texecutionPayload,\n\t\t\t\t\t(stream, data) => {\n\t\t\t\t\t\tthis.listener?.onOutput({\n\t\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\t\tstream,\n\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\tac.signal\n\t\t\t\t);\n\t\t\t\trunningExec.cancel = () => {\n\t\t\t\t\thandle.cancel();\n\t\t\t\t\tac.abort();\n\t\t\t\t};\n\t\t\t\tresult = await handle.result;\n\t\t\t} else {\n\t\t\t\tresult = await executor.execute(executionPayload, ac.signal);\n\t\t\t}\n\n\t\t\tconst log: TaskExecutionLog = {\n\t\t\t\tid: snapshot.executionId,\n\t\t\t\ttaskId: snapshot.taskId,\n\t\t\t\ttaskName: snapshot.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt: new Date().toISOString(),\n\t\t\t\tstatus: result.status === \"running\" ? \"failure\" : result.status,\n\t\t\t\toutput: result.output,\n\t\t\t\terror: result.error,\n\t\t\t};\n\n\t\t\tswitch (log.status) {\n\t\t\t\tcase \"success\":\n\t\t\t\t\tthis.listener?.onCompleted({\n\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\tlog,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"cancelled\":\n\t\t\t\t\tthis.listener?.onCancelled({\n\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\tlog,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"timeout\":\n\t\t\t\t\tthis.listener?.onFailed({\n\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\tstatus: \"timeout\",\n\t\t\t\t\t\treason: \"timeout\",\n\t\t\t\t\t\tlog,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tthis.listener?.onFailed({\n\t\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\t\tstatus: \"failure\",\n\t\t\t\t\t\treason: \"error\",\n\t\t\t\t\t\tlog,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst log: TaskExecutionLog = {\n\t\t\t\tid: snapshot.executionId,\n\t\t\t\ttaskId: snapshot.taskId,\n\t\t\t\ttaskName: snapshot.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt: new Date().toISOString(),\n\t\t\t\tstatus: ac.signal.aborted ? \"cancelled\" : \"failure\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t};\n\n\t\t\tif (ac.signal.aborted) {\n\t\t\t\tthis.listener?.onCancelled({\n\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\tlog,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.listener?.onFailed({\n\t\t\t\t\texecutionId: snapshot.executionId,\n\t\t\t\t\tstatus: \"failure\",\n\t\t\t\t\treason: \"error\",\n\t\t\t\t\tlog,\n\t\t\t\t});\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.running.delete(snapshot.executionId);\n\t\t\tconst taskExecIds = this.taskExecutions.get(snapshot.taskId);\n\t\t\tif (taskExecIds) {\n\t\t\t\ttaskExecIds.delete(snapshot.executionId);\n\t\t\t\tif (taskExecIds.size === 0) {\n\t\t\t\t\tthis.taskExecutions.delete(snapshot.taskId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tcancel(executionId: string): boolean {\n\t\tconst exec = this.running.get(executionId);\n\t\tif (!exec) return false;\n\t\texec.cancel();\n\t\treturn true;\n\t}\n\n\tlistRunning(): RunningTaskInfo[] {\n\t\treturn Array.from(this.running.values()).map((e) => ({\n\t\t\texecutionId: e.executionId,\n\t\t\ttaskId: e.taskId,\n\t\t\tstartedAt: e.startedAt,\n\t\t}));\n\t}\n\n\tdispose(): void {\n\t\tfor (const exec of this.running.values()) {\n\t\t\texec.cancel();\n\t\t}\n\t\tthis.running.clear();\n\t\tthis.taskExecutions.clear();\n\t}\n}\n","import { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n\tScheduledTasksLogFile,\n\tTaskExecutionLog,\n\tTaskExecutionStatus,\n} from \"@serviceme/devtools-protocol\";\nimport { getScheduledTasksLogPath } from \"../paths/userHome\";\n\nconst MAX_LOGS = 200;\n\nfunction emptyLogFile(): ScheduledTasksLogFile {\n\treturn { logs: [] };\n}\n\nfunction isValidLogEntry(entry: unknown): entry is TaskExecutionLog {\n\tif (!entry || typeof entry !== \"object\") return false;\n\tconst e = entry as Record<string, unknown>;\n\treturn (\n\t\ttypeof e.id === \"string\" &&\n\t\ttypeof e.taskId === \"string\" &&\n\t\ttypeof e.taskName === \"string\" &&\n\t\ttypeof e.startedAt === \"string\" &&\n\t\ttypeof e.finishedAt === \"string\" &&\n\t\ttypeof e.status === \"string\"\n\t);\n}\n\nfunction validateAndRepairLogFile(raw: unknown): ScheduledTasksLogFile {\n\tif (!raw || typeof raw !== \"object\") {\n\t\treturn emptyLogFile();\n\t}\n\tconst file = raw as Record<string, unknown>;\n\tif (!Array.isArray(file.logs)) {\n\t\treturn emptyLogFile();\n\t}\n\tconst validLogs = file.logs.filter(isValidLogEntry);\n\treturn { logs: validLogs };\n}\n\nexport interface AppendLogInput {\n\ttaskId: string;\n\ttaskName: string;\n\tstartedAt: string;\n\tfinishedAt: string;\n\tstatus: Exclude<TaskExecutionStatus, \"running\">;\n\toutput?: string;\n\terror?: string;\n}\n\nexport interface TaskLogManagerOptions {\n\t/**\n\t * Override the on-disk log path. Defaults to the global\n\t * `~/.serviceme/scheduled-tasks-log.json`. Tests use this to point at a\n\t * tmp directory; production code should leave it unset.\n\t */\n\tlogPath?: string;\n}\n\nexport class TaskLogManager {\n\tprivate readonly logPath: string;\n\n\tconstructor(options: TaskLogManagerOptions = {}) {\n\t\tthis.logPath = options.logPath ?? getScheduledTasksLogPath();\n\t}\n\n\tgetLogPath(): string {\n\t\treturn this.logPath;\n\t}\n\n\tprivate readLogFile(): ScheduledTasksLogFile {\n\t\tif (!fs.existsSync(this.logPath)) {\n\t\t\treturn emptyLogFile();\n\t\t}\n\t\ttry {\n\t\t\tconst raw = fs.readFileSync(this.logPath, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\tconst file = validateAndRepairLogFile(parsed);\n\t\t\t// If repair removed entries or structure was invalid, rewrite the file\n\t\t\tif (\n\t\t\t\t!parsed ||\n\t\t\t\ttypeof parsed !== \"object\" ||\n\t\t\t\t!Array.isArray(parsed.logs) ||\n\t\t\t\tparsed.logs.length !== file.logs.length\n\t\t\t) {\n\t\t\t\tthis.writeLogFile(file);\n\t\t\t}\n\t\t\treturn file;\n\t\t} catch {\n\t\t\t// JSON parse failed — file is corrupted, back it up and start fresh\n\t\t\tthis.backupCorruptedFile();\n\t\t\tconst fresh = emptyLogFile();\n\t\t\tthis.writeLogFile(fresh);\n\t\t\treturn fresh;\n\t\t}\n\t}\n\n\tprivate backupCorruptedFile(): void {\n\t\ttry {\n\t\t\tif (fs.existsSync(this.logPath)) {\n\t\t\t\tconst backupPath = `${this.logPath}.corrupted.${Date.now()}`;\n\t\t\t\tfs.copyFileSync(this.logPath, backupPath);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Best-effort backup\n\t\t}\n\t}\n\n\tprivate writeLogFile(file: ScheduledTasksLogFile): void {\n\t\tconst dir = path.dirname(this.logPath);\n\t\tif (!fs.existsSync(dir)) {\n\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t}\n\t\tconst tmp = `${this.logPath}.tmp`;\n\t\tfs.writeFileSync(tmp, JSON.stringify(file, null, \"\\t\"), \"utf-8\");\n\t\tfs.renameSync(tmp, this.logPath);\n\t}\n\n\tappendLog(input: AppendLogInput): TaskExecutionLog {\n\t\tconst file = this.readLogFile();\n\t\tconst log: TaskExecutionLog = {\n\t\t\tid: randomUUID(),\n\t\t\ttaskId: input.taskId,\n\t\t\ttaskName: input.taskName,\n\t\t\tstartedAt: input.startedAt,\n\t\t\tfinishedAt: input.finishedAt,\n\t\t\tstatus: input.status,\n\t\t\toutput: input.output,\n\t\t\terror: input.error,\n\t\t};\n\t\tfile.logs.push(log);\n\t\t// FIFO trim\n\t\tif (file.logs.length > MAX_LOGS) {\n\t\t\tfile.logs = file.logs.slice(file.logs.length - MAX_LOGS);\n\t\t}\n\t\tthis.writeLogFile(file);\n\t\treturn log;\n\t}\n\n\tgetLogs(options?: { taskId?: string; limit?: number }): {\n\t\tlogs: TaskExecutionLog[];\n\t\ttotal: number;\n\t} {\n\t\tconst file = this.readLogFile();\n\t\tlet logs = file.logs;\n\t\tif (options?.taskId) {\n\t\t\tlogs = logs.filter((l) => l.taskId === options.taskId);\n\t\t}\n\t\tconst total = logs.length;\n\t\t// Return newest first\n\t\tlogs = logs.slice().reverse();\n\t\tif (options?.limit && options.limit > 0) {\n\t\t\tlogs = logs.slice(0, options.limit);\n\t\t}\n\t\treturn { logs, total };\n\t}\n\n\tclearLogs(taskId?: string): number {\n\t\tconst file = this.readLogFile();\n\t\tconst before = file.logs.length;\n\t\tif (taskId) {\n\t\t\tfile.logs = file.logs.filter((l) => l.taskId !== taskId);\n\t\t} else {\n\t\t\tfile.logs = [];\n\t\t}\n\t\tthis.writeLogFile(file);\n\t\treturn before - file.logs.length;\n\t}\n}\n","// SchedulerDaemonV2 — global single-instance scheduler.\n//\n// Differences from the v1 SchedulerDaemon (see daemon/SchedulerDaemon.ts,\n// @deprecated):\n// - Reads the global ~/.serviceme/scheduled-tasks.json (v2 schema).\n// - One process for the whole machine, guarded by PID file + startup lock.\n// - Per-workspace in-memory mutex: same workspace runs serially, different\n// workspaces run in parallel.\n// - Switches cwd to task.workspace.path on every execute (no implicit\n// \"current workspace\" — daemon is machine-scoped).\n// - Disables tasks whose workspace path is missing.\n//\n// See docs/architecture/skill-agent-v2-repo.md §14.3.b + §14.4.\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ScheduledTask, ScheduledTasksConfig } from \"@serviceme/devtools-protocol\";\nimport { getSchedulerPidPath } from \"../../paths/userHome\";\nimport { getExecutor } from \"../executors\";\nimport { TaskConfigManager, validateTaskPayload } from \"../TaskConfigManager\";\nimport { resolveTaskExecutionPayload } from \"../TaskExecutionEngine\";\nimport { TaskLogManager } from \"../TaskLogManager\";\nimport { DaemonLogger } from \"./DaemonLogger\";\nimport { PidManager } from \"./PidManager\";\n\nconst TICK_INTERVAL = 1000; // Check every second\nconst MIN_SCHEDULE_INTERVAL = 1_000; // 1 second minimum\nconst SCHEDULER_LOG_FILENAME = \"scheduler.log\";\n\nexport interface SchedulerDaemonV2Options {\n\t/**\n\t * Override the executor factory. Production callers should leave this\n\t * undefined; tests inject a fake to drive the execution branch without\n\t * spawning real subprocesses.\n\t */\n\tgetExecutor?: typeof getExecutor;\n\t/** Override the config manager. Tests inject a fake pointing at a tmp dir. */\n\tconfigManager?: TaskConfigManager;\n\t/** Override the log manager. */\n\tlogManager?: TaskLogManager;\n\t/** Override the PID manager. */\n\tpidManager?: PidManager;\n\t/** Override the daemon logger (for tests; the default writes to ~/.serviceme/scheduler.log). */\n\tlogger?: DaemonLogger;\n\t/** Override the startup lock acquisition (tests use this to inject \"lock held\" / \"lock free\"). */\n\ttryAcquireLock?: () => boolean;\n\t/** Override the startup lock release. */\n\treleaseLock?: () => void;\n\t/**\n\t * Override the PID file existence + process liveness check. Production\n\t * reads PID file + `process.kill(pid, 0)`; tests inject a stub.\n\t */\n\tgetRunningPid?: () => number | null;\n}\n\nexport class SchedulerDaemonV2 {\n\tprivate readonly configManager: TaskConfigManager;\n\tprivate readonly logManager: TaskLogManager;\n\tprivate readonly pidManager: PidManager;\n\tprivate readonly logger: DaemonLogger;\n\tprivate readonly getExecutor: typeof getExecutor;\n\tprivate readonly tryAcquireLock: () => boolean;\n\tprivate readonly releaseLock: () => void;\n\tprivate readonly getRunningPid: () => number | null;\n\n\tprivate tickTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate running = false;\n\t// Stable references to our own signal listeners so stop() can detach\n\t// them — otherwise repeated start()/stop() cycles (especially in tests)\n\t// leak listeners and keep the event loop alive past the daemon's lifetime.\n\tprivate readonly sigtermHandler: () => void;\n\tprivate readonly sigintHandler: () => void;\n\tprivate startTime: number = 0;\n\n\t// Per-task state (machines are global, but the daemon only knows about\n\t// tasks currently in the config — `lastRun` keys are task IDs).\n\tprivate lastRun: Map<string, number> = new Map();\n\n\t// Per-workspace mutex queue: same workspacePath runs serially, different\n\t// workspacePaths can run in parallel. Each entry is a promise that\n\t// resolves when the previous + this task has finished.\n\tprivate workspaceLocks: Map<string, Promise<unknown>> = new Map();\n\n\tconstructor(options: SchedulerDaemonV2Options = {}) {\n\t\tthis.configManager = options.configManager ?? new TaskConfigManager();\n\t\tthis.logManager = options.logManager ?? new TaskLogManager();\n\t\tthis.pidManager = options.pidManager ?? new PidManager(\"\", { pidPath: getSchedulerPidPath() });\n\t\t// Daemon log lives at ~/.serviceme/scheduler.log (global, not per-ws).\n\t\t// Computed from the PID file's directory to stay in sync with the\n\t\t// user-home resolution.\n\t\tthis.logger =\n\t\t\toptions.logger ??\n\t\t\tnew DaemonLogger(os.homedir(), {\n\t\t\t\tlogPath: path.join(path.dirname(this.pidManager.getPidPath()), SCHEDULER_LOG_FILENAME),\n\t\t\t});\n\t\tthis.getExecutor = options.getExecutor ?? getExecutor;\n\t\tthis.tryAcquireLock = options.tryAcquireLock ?? (() => true);\n\t\tthis.releaseLock = options.releaseLock ?? (() => {});\n\t\tthis.getRunningPid = options.getRunningPid ?? (() => this.pidManager.getRunningPid());\n\t\t// Bind the signal handlers once so stop() can detach the SAME\n\t\t// function references later.\n\t\tthis.sigtermHandler = () => this.stop();\n\t\tthis.sigintHandler = () => this.stop();\n\t}\n\n\tstart(): void {\n\t\tif (this.running) return;\n\n\t\t// 1. Try to acquire the startup flock. If we can't, another instance\n\t\t// is mid-startup — exit 0 (silent, not an error).\n\t\tif (!this.tryAcquireLock()) {\n\t\t\tthis.logger.log(\"info\", \"Another daemon instance holds the lock; exiting\");\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\t// 2. Check existing PID file. If a live process holds the slot, also\n\t\t// exit 0. (Recoverable if the recorded PID is stale.)\n\t\tconst existingPid = this.getRunningPid();\n\t\tif (existingPid !== null && existingPid !== process.pid) {\n\t\t\tthis.logger.log(\"info\", `Another daemon already running (pid: ${existingPid}); exiting`);\n\t\t\tthis.releaseLock();\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\tthis.running = true;\n\t\tthis.startTime = Date.now();\n\t\tthis.pidManager.writePid(process.pid);\n\n\t\tthis.logger.log(\n\t\t\t\"info\",\n\t\t\t`SchedulerDaemonV2 started\n PID: ${process.pid}\n Node: ${process.version}\n OS: ${os.type()} ${os.release()} (${process.arch})\n Config: ${this.configManager.getConfigPath()}\n LogPath: ${this.logger.getLogPath()}\n PidPath: ${this.pidManager.getPidPath()}`\n\t\t);\n\n\t\tthis.tickTimer = setInterval(() => this.tick(), TICK_INTERVAL);\n\t\tprocess.on(\"SIGTERM\", this.sigtermHandler);\n\t\tprocess.on(\"SIGINT\", this.sigintHandler);\n\t}\n\n\tstop(): void {\n\t\tif (!this.running) return;\n\t\tthis.running = false;\n\t\tif (this.tickTimer) {\n\t\t\tclearInterval(this.tickTimer);\n\t\t\tthis.tickTimer = null;\n\t\t}\n\t\tthis.pidManager.removePid();\n\t\tthis.releaseLock();\n\t\t// Detach our own signal handlers so multiple start()/stop() cycles\n\t\t// (especially in test scenarios) don't leak listeners and keep the\n\t\t// event loop alive past the daemon's lifetime.\n\t\tprocess.removeListener(\"SIGTERM\", this.sigtermHandler);\n\t\tprocess.removeListener(\"SIGINT\", this.sigintHandler);\n\t\tthis.logger.log(\"info\", \"SchedulerDaemonV2 stopped\");\n\t\tprocess.exit(0);\n\t}\n\n\tprivate tick(): void {\n\t\tconst config = this.configManager.readConfig();\n\t\tconst now = Date.now();\n\n\t\tfor (const task of config.tasks) {\n\t\t\tif (!task.enabled) continue;\n\n\t\t\t// Workspace-missing: auto-disable + log. Done outside the mutex\n\t\t\t// chain because no execution is going to happen anyway.\n\t\t\tif (!fs.existsSync(task.workspace.path)) {\n\t\t\t\tthis.disableTaskForMissingWorkspace(task, config);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// First-encounter: seed lastRun to the current tick so the task\n\t\t\t// waits a full interval before firing.\n\t\t\tlet lastExec = this.lastRun.get(task.id);\n\t\t\tif (lastExec === undefined) {\n\t\t\t\tthis.lastRun.set(task.id, now);\n\t\t\t\tlastExec = now;\n\t\t\t}\n\t\t\tif (!this.shouldRun(task, lastExec, now)) continue;\n\n\t\t\t// Per-workspace mutex: chain onto the existing promise for that\n\t\t\t// workspace path. Different workspaces run independently.\n\t\t\tconst wsPath = task.workspace.path;\n\t\t\tconst prev = this.workspaceLocks.get(wsPath) ?? Promise.resolve();\n\t\t\tconst next = prev\n\t\t\t\t.catch(() => undefined)\n\t\t\t\t.then(() => this.executeTask(task, now))\n\t\t\t\t.catch((err: unknown) => {\n\t\t\t\t\tthis.logger.log(\"error\", `Task ${task.id} execution failed: ${String(err)}`);\n\t\t\t\t});\n\t\t\tthis.workspaceLocks.set(wsPath, next);\n\t\t}\n\t}\n\n\tprivate disableTaskForMissingWorkspace(task: ScheduledTask, config: ScheduledTasksConfig): void {\n\t\tconst idx = config.tasks.findIndex((t) => t.id === task.id);\n\t\tif (idx === -1) return;\n\t\tconst existing = config.tasks[idx];\n\t\tif (!existing) return;\n\t\tif (existing.enabled === false && existing.lastRunError === \"workspace missing\") return;\n\t\tthis.logger.log(\n\t\t\t\"warn\",\n\t\t\t`Disabling task ${task.name} (${task.id}): workspace ${task.workspace.path} not found`\n\t\t);\n\t\tconfig.tasks[idx] = {\n\t\t\t...existing,\n\t\t\tenabled: false,\n\t\t\tlastRunError: \"workspace missing\",\n\t\t};\n\t\tthis.configManager.editTask(task.id, {\n\t\t\tenabled: false,\n\t\t\tworkspace: config.tasks[idx].workspace,\n\t\t});\n\t}\n\n\tprivate shouldRun(task: ScheduledTask, lastExec: number, now: number): boolean {\n\t\tif (task.scheduleType === \"interval\") {\n\t\t\tconst intervalMs = parseIntervalMs(task.schedule);\n\t\t\tif (intervalMs < MIN_SCHEDULE_INTERVAL) return false;\n\t\t\treturn now - lastExec >= intervalMs;\n\t\t}\n\t\tif (task.scheduleType === \"cron\") {\n\t\t\tif (now - lastExec < 60_000) return false;\n\t\t\treturn matchesCron(task.schedule, new Date(now));\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate async executeTask(task: ScheduledTask, now: number): Promise<void> {\n\t\tthis.lastRun.set(task.id, now);\n\t\tconst startedAt = new Date(now).toISOString();\n\t\tconst startMs = Date.now();\n\t\tconst origCwd = process.cwd();\n\n\t\tthis.logger.log(\"info\", `Executing task: ${task.name} (${task.id}) in ${task.workspace.path}`);\n\n\t\ttry {\n\t\t\tprocess.chdir(task.workspace.path);\n\t\t\tconst executionPayload = resolveTaskExecutionPayload(\n\t\t\t\ttask.taskType,\n\t\t\t\ttask.payload,\n\t\t\t\ttask.workspace.path\n\t\t\t);\n\t\t\tvalidateTaskPayload(task.taskType, executionPayload);\n\t\t\tconst executor = this.getExecutor(task.taskType);\n\t\t\tconst result = await executor.execute(executionPayload);\n\t\t\tconst finishedAt = new Date().toISOString();\n\t\t\tconst durationMs = Date.now() - startMs;\n\n\t\t\tthis.logManager.appendLog({\n\t\t\t\ttaskId: task.id,\n\t\t\t\ttaskName: task.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt,\n\t\t\t\tstatus: result.status === \"running\" ? \"failure\" : result.status,\n\t\t\t\toutput: result.output,\n\t\t\t\terror: result.error,\n\t\t\t});\n\n\t\t\t// Update runtime metadata on the task itself.\n\t\t\tthis.configManager.editTask(task.id, {\n\t\t\t\tenabled: task.enabled,\n\t\t\t\tworkspace: task.workspace,\n\t\t\t});\n\n\t\t\tthis.logger.log(\n\t\t\t\t\"info\",\n\t\t\t\t`Task completed: ${task.name} status=${result.status} duration=${durationMs}ms`\n\t\t\t);\n\t\t} catch (err: unknown) {\n\t\t\tconst finishedAt = new Date().toISOString();\n\t\t\tconst durationMs = Date.now() - startMs;\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tthis.logManager.appendLog({\n\t\t\t\ttaskId: task.id,\n\t\t\t\ttaskName: task.name,\n\t\t\t\tstartedAt,\n\t\t\t\tfinishedAt,\n\t\t\t\tstatus: \"failure\",\n\t\t\t\terror: message,\n\t\t\t});\n\t\t\tthis.logger.log(\n\t\t\t\t\"error\",\n\t\t\t\t`Task failed: ${task.name} (${task.id}) duration=${durationMs}ms error=${message}`\n\t\t\t);\n\t\t} finally {\n\t\t\tprocess.chdir(origCwd);\n\t\t}\n\t}\n\n\tgetStatus() {\n\t\tconst config = this.configManager.readConfig();\n\t\treturn {\n\t\t\trunning: this.running,\n\t\t\tpid: process.pid,\n\t\t\tuptimeSeconds: this.running ? Math.floor((Date.now() - this.startTime) / 1000) : null,\n\t\t\ttasksRegistered: config.tasks.length,\n\t\t\ttasksEnabled: config.tasks.filter((t) => t.enabled).length,\n\t\t\tpidFile: this.pidManager.getPidPath(),\n\t\t};\n\t}\n}\n\n// ─── Helpers (duplicated from v1 to keep v1 untouched) ─────────────────────\n\nfunction parseIntervalMs(schedule: string): number {\n\tconst match = /^every\\s+(\\d+)\\s*(s|sec|m|min|h|hr|d|day)s?$/i.exec(schedule);\n\tif (!match) return 0;\n\tconst [, numStr, unit] = match;\n\tconst num = Number.parseInt(numStr ?? \"0\", 10);\n\tswitch (unit?.toLowerCase()) {\n\t\tcase \"s\":\n\t\tcase \"sec\":\n\t\t\treturn num * 1000;\n\t\tcase \"m\":\n\t\tcase \"min\":\n\t\t\treturn num * 60 * 1000;\n\t\tcase \"h\":\n\t\tcase \"hr\":\n\t\t\treturn num * 60 * 60 * 1000;\n\t\tcase \"d\":\n\t\tcase \"day\":\n\t\t\treturn num * 24 * 60 * 60 * 1000;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nfunction matchesCron(expression: string, date: Date): boolean {\n\tconst parts = expression.trim().split(/\\s+/);\n\tif (parts.length < 5) return false;\n\tconst [minPart, hourPart, dayPart, monthPart, weekdayPart] = parts;\n\tif (!minPart || !hourPart || !dayPart || !monthPart || !weekdayPart) return false;\n\treturn (\n\t\tmatchCronField(minPart, date.getMinutes()) &&\n\t\tmatchCronField(hourPart, date.getHours()) &&\n\t\tmatchCronField(dayPart, date.getDate()) &&\n\t\tmatchCronField(monthPart, date.getMonth() + 1) &&\n\t\tmatchCronField(weekdayPart, date.getDay())\n\t);\n}\n\nfunction matchCronField(field: string, value: number): boolean {\n\tif (field === \"*\") return true;\n\tif (field.startsWith(\"*/\")) {\n\t\tconst step = Number.parseInt(field.slice(2), 10);\n\t\treturn step > 0 && value % step === 0;\n\t}\n\tconst values = field.split(\",\");\n\treturn values.some((v) => Number.parseInt(v, 10) === value);\n}\n\n// Export helpers for testing.\nexport { matchesCron, parseIntervalMs };\n","// MigrateToGlobal — scan known workspaces for legacy per-workspace\n// <workspace>/.serviceme/scheduled-tasks.json, migrate v1 → v2, append\n// to the global ~/.serviceme/scheduled-tasks.json, and DELETE the\n// original file. Failures (parse errors, schema issues) are recorded\n// in ~/.serviceme/migration-failures.json and the original is also\n// deleted — no half-migration state is left on disk.\n// See docs/architecture/skill-agent-v2-repo.md §14.3.a + §14.4 + §14.5.\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n\tScheduledTask,\n\tScheduledTasksConfig,\n\tScheduledTasksConfigV1,\n\tTaskWorkspaceRef,\n} from \"@serviceme/devtools-protocol\";\nimport { isScheduledTasksConfigV1, migrateV1ToV2 } from \"@serviceme/devtools-protocol\";\nimport { getMigrationFailuresPath, getScheduledTasksConfigPath } from \"../../paths/userHome\";\n\nexport interface MigrateToGlobalOptions {\n\t/**\n\t * Workspace paths to scan for legacy v1 files. May include the current\n\t * workspace + any persisted in `~/.serviceme/known-workspaces.json`.\n\t */\n\tworkspacePaths: string[];\n\t/**\n\t * Workspace context to apply to each scanned file. The scanner calls the\n\t * probe for each candidate path to populate path/name/git metadata.\n\t * Tests can inject a stub that returns synthetic data.\n\t */\n\tprobe?: (workspacePath: string) => Promise<TaskWorkspaceRef> | TaskWorkspaceRef;\n\t/**\n\t * Override the global config path. Tests use this; production should\n\t * leave it unset to get the standard ~/.serviceme/scheduled-tasks.json.\n\t */\n\tglobalConfigPath?: string;\n\t/**\n\t * Override the migration-failures path. Same convention.\n\t */\n\tmigrationFailuresPath?: string;\n}\n\nexport interface MigrationResult {\n\t/** Number of v1 files successfully migrated into the global config. */\n\tmigrated: number;\n\t/** Number of v1 files that failed to parse / migrate. */\n\tfailed: number;\n\t/** Task names that had to be disambiguated because they appeared in multiple workspaces. */\n\tconflicts: string[];\n\t/** Soft issues surfaced by migrateV1ToV2 (e.g. unknown taskType). */\n\tissues: string[];\n}\n\ninterface FailureEntry {\n\tworkspacePath: string;\n\tv1Path: string;\n\terror: string;\n\tat: string; // ISO timestamp\n}\n\nconst WORKSPACE_DIR = \".serviceme\";\nconst V1_FILENAME = \"scheduled-tasks.json\";\n\n/**\n * Default workspace probe: returns a minimal TaskWorkspaceRef derived from\n * the path (name = basename). The extension injects the real WorkspaceProbe\n * at startup; tests inject a stub.\n */\nfunction defaultProbe(workspacePath: string): TaskWorkspaceRef {\n\treturn {\n\t\tpath: workspacePath,\n\t\tname: path.basename(workspacePath) || workspacePath,\n\t};\n}\n\nfunction readV1Config(\n\tv1Path: string\n): { ok: true; config: ScheduledTasksConfigV1 } | { ok: false; error: string } {\n\tlet raw: string;\n\ttry {\n\t\traw = fs.readFileSync(v1Path, \"utf-8\");\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `read failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t};\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `JSON parse failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t};\n\t}\n\tif (!isScheduledTasksConfigV1(parsed)) {\n\t\treturn { ok: false, error: \"not a valid v1 scheduledTasksConfig\" };\n\t}\n\treturn { ok: true, config: parsed };\n}\n\nfunction safeDelete(filePath: string): void {\n\ttry {\n\t\tfs.unlinkSync(filePath);\n\t} catch {\n\t\t// Best-effort: file may have been already removed or never existed.\n\t}\n}\n\nfunction ensureDir(filePath: string): void {\n\tconst dir = path.dirname(filePath);\n\tif (!fs.existsSync(dir)) {\n\t\tfs.mkdirSync(dir, { recursive: true });\n\t}\n}\n\nfunction readJsonFile<T>(filePath: string): T | null {\n\tif (!fs.existsSync(filePath)) return null;\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(filePath, \"utf-8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction writeJsonFile(filePath: string, data: unknown): void {\n\tensureDir(filePath);\n\tfs.writeFileSync(filePath, JSON.stringify(data, null, \"\\t\"), \"utf-8\");\n}\n\n/**\n * Disambiguate task names that collide across workspaces by suffixing the\n * workspace name in brackets. Mutates a copy of `task` and returns it.\n */\nfunction disambiguateName(\n\ttask: ScheduledTask,\n\texistingNames: Set<string>,\n\tworkspaceName: string\n): ScheduledTask {\n\tif (!existingNames.has(task.name)) {\n\t\texistingNames.add(task.name);\n\t\treturn task;\n\t}\n\tconst base = `${task.name} [${workspaceName}]`;\n\tlet candidate = base;\n\tlet counter = 2;\n\twhile (existingNames.has(candidate)) {\n\t\tcandidate = `${base} (${counter})`;\n\t\tcounter += 1;\n\t}\n\texistingNames.add(candidate);\n\treturn { ...task, name: candidate };\n}\n\n/**\n * Run the migration. Idempotent: each v1 file is deleted after processing,\n * so re-running this on a partially-migrated state is safe (the\n * already-migrated files no longer exist; only the remainder is processed).\n */\nexport async function migrateToGlobal(options: MigrateToGlobalOptions): Promise<MigrationResult> {\n\tconst globalConfigPath = options.globalConfigPath ?? getScheduledTasksConfigPath();\n\tconst migrationFailuresPath = options.migrationFailuresPath ?? getMigrationFailuresPath();\n\tconst probe = options.probe ?? defaultProbe;\n\n\t// Load or seed the global config.\n\tconst existing = readJsonFile<ScheduledTasksConfig>(globalConfigPath);\n\tconst baseConfig: ScheduledTasksConfig =\n\t\texisting && existing.version === 2 ? existing : { version: 2, tasks: [] };\n\tconst existingNames = new Set(baseConfig.tasks.map((t) => t.name));\n\n\t// Read prior failures (so we accumulate, not overwrite).\n\tconst priorFailures = readJsonFile<FailureEntry[]>(migrationFailuresPath) ?? [];\n\tconst failures: FailureEntry[] = [...priorFailures];\n\n\tlet migrated = 0;\n\tconst conflicts: string[] = [];\n\tconst issues: string[] = [];\n\n\tfor (const workspacePath of options.workspacePaths) {\n\t\tconst v1Path = path.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);\n\t\tif (!fs.existsSync(v1Path)) continue;\n\n\t\tconst v1 = readV1Config(v1Path);\n\t\tif (!v1.ok) {\n\t\t\tfailures.push({\n\t\t\t\tworkspacePath,\n\t\t\t\tv1Path,\n\t\t\t\terror: v1.error,\n\t\t\t\tat: new Date().toISOString(),\n\t\t\t});\n\t\t\t// DELETE the original — no half-migration state.\n\t\t\tsafeDelete(v1Path);\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet workspaceRef: TaskWorkspaceRef;\n\t\ttry {\n\t\t\tworkspaceRef = await probe(workspacePath);\n\t\t} catch (err) {\n\t\t\tfailures.push({\n\t\t\t\tworkspacePath,\n\t\t\t\tv1Path,\n\t\t\t\terror: `probe failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\tat: new Date().toISOString(),\n\t\t\t});\n\t\t\tsafeDelete(v1Path);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { config, issues: taskIssues } = migrateV1ToV2(v1.config, workspaceRef);\n\t\tissues.push(...taskIssues);\n\n\t\t// Disambiguate name conflicts.\n\t\tconst newTasks: ScheduledTask[] = config.tasks.map((t) => {\n\t\t\tif (existingNames.has(t.name)) {\n\t\t\t\tconflicts.push(t.name);\n\t\t\t\treturn disambiguateName(t, existingNames, workspaceRef.name);\n\t\t\t}\n\t\t\texistingNames.add(t.name);\n\t\t\treturn t;\n\t\t});\n\n\t\tbaseConfig.tasks.push(...newTasks);\n\t\tmigrated += 1;\n\n\t\t// DELETE the original — no half-migration state.\n\t\tsafeDelete(v1Path);\n\t}\n\n\t// Persist the new global config + failures.\n\tif (migrated > 0) {\n\t\t// Use TaskConfigManager's atomic write semantics via the public\n\t\t// listTasks + createTask path would lose ordering and re-validate\n\t\t// everything, which is wasteful. Write directly using the same\n\t\t// tmp → rename pattern the manager uses.\n\t\tensureDir(globalConfigPath);\n\t\tconst tmp = `${globalConfigPath}.tmp`;\n\t\tfs.writeFileSync(tmp, JSON.stringify(baseConfig, null, \"\\t\"), \"utf-8\");\n\t\tfs.renameSync(tmp, globalConfigPath);\n\t}\n\tif (failures.length > priorFailures.length) {\n\t\twriteJsonFile(migrationFailuresPath, failures);\n\t} else if (failures.length === 0 && priorFailures.length > 0) {\n\t\t// All prior failures cleared; remove the file.\n\t\tsafeDelete(migrationFailuresPath);\n\t}\n\n\treturn {\n\t\tmigrated,\n\t\tfailed: failures.length - priorFailures.length,\n\t\tconflicts,\n\t\tissues,\n\t};\n}\n","// WorkspaceProbe — auto-detects git metadata for a workspace path.\n// Used by extension startup and task creation to populate TaskWorkspaceRef.\n// See docs/architecture/skill-agent-v2-repo.md §14.3.c (workspace auto-probe).\n\nimport { spawn } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { TaskWorkspaceRef } from \"@serviceme/devtools-protocol\";\n\n/** Result of probing a single workspace. */\nexport interface ProbeResult {\n\t/** Always populated; empty fields are left undefined. */\n\tworkspace: TaskWorkspaceRef;\n\t/** null on success; one of the {@link ProbeError} tags on failure. */\n\terror: ProbeError | null;\n}\n\nexport type ProbeError = \"path-not-found\" | \"not-a-git-repo\" | \"git-timeout\" | \"git-error\";\n\nexport interface ProbeOptions {\n\t/** Timeout for each git invocation in ms. Default: 2000. */\n\ttimeoutMs?: number;\n\t/** Override the git binary (default: 'git'). Tests inject a stub. */\n\tgitBinary?: string;\n\t/**\n\t * Override the underlying runner. Receives the args + cwd and returns\n\t * { stdout, stderr, code } on success, or throws on timeout. The default\n\t * implementation spawns `gitBinary` with the given args.\n\t */\n\trunGit?: (\n\t\targs: string[],\n\t\tcwd: string\n\t) => Promise<{\n\t\tstdout: string;\n\t\tstderr: string;\n\t\tcode: number;\n\t}>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 2000;\n\nclass GitTimeoutError extends Error {\n\tconstructor() {\n\t\tsuper(\"git-timeout\");\n\t\tthis.name = \"GitTimeoutError\";\n\t}\n}\n\nfunction isTimeout(err: unknown): boolean {\n\tif (err instanceof GitTimeoutError) return true;\n\tif (err instanceof Error) {\n\t\treturn err.name === \"GitTimeoutError\" || err.message === \"git-timeout\";\n\t}\n\treturn false;\n}\n\nfunction defaultRunGit(\n\tgitBinary: string,\n\targs: string[],\n\tcwd: string,\n\ttimeoutMs: number\n): Promise<{ stdout: string; stderr: string; code: number }> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst child = spawn(gitBinary, args, {\n\t\t\tcwd,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tchild.kill(\"SIGTERM\");\n\t\t\treject(new GitTimeoutError());\n\t\t}, timeoutMs);\n\n\t\tchild.stdout.on(\"data\", (chunk) => {\n\t\t\tstdout += chunk.toString(\"utf-8\");\n\t\t});\n\t\tchild.stderr.on(\"data\", (chunk) => {\n\t\t\tstderr += chunk.toString(\"utf-8\");\n\t\t});\n\t\tchild.on(\"error\", (err) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\treject(err);\n\t\t});\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tresolve({ stdout, stderr, code: code ?? 0 });\n\t\t});\n\t});\n}\n\n/**\n * Probes a workspace for git metadata. Returns a {@link ProbeResult} with\n * `error: null` on success and an error tag on failure — the workspace\n * descriptor is always populated so callers can still surface a partial\n * record.\n */\nexport class WorkspaceProbe {\n\tprivate readonly timeoutMs: number;\n\tprivate readonly gitBinary: string;\n\tprivate readonly runGitFn: (\n\t\targs: string[],\n\t\tcwd: string\n\t) => Promise<{ stdout: string; stderr: string; code: number }>;\n\n\tconstructor(options: ProbeOptions = {}) {\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\tthis.gitBinary = options.gitBinary ?? \"git\";\n\t\tif (options.runGit) {\n\t\t\tthis.runGitFn = options.runGit;\n\t\t} else {\n\t\t\tconst binary = this.gitBinary;\n\t\t\tconst timeoutMs = this.timeoutMs;\n\t\t\tthis.runGitFn = (args, cwd) => defaultRunGit(binary, args, cwd, timeoutMs);\n\t\t}\n\t}\n\n\tasync probe(workspacePath: string): Promise<ProbeResult> {\n\t\tconst name = path.basename(workspacePath) || workspacePath;\n\n\t\tif (!workspacePath || !fs.existsSync(workspacePath)) {\n\t\t\treturn {\n\t\t\t\tworkspace: { path: workspacePath, name },\n\t\t\t\terror: \"path-not-found\",\n\t\t\t};\n\t\t}\n\n\t\tlet gitRemote: string | undefined;\n\t\ttry {\n\t\t\tconst remote = await this.runGitFn([\"remote\", \"get-url\", \"origin\"], workspacePath);\n\t\t\tif (remote.code === 0 && remote.stdout.trim()) {\n\t\t\t\tgitRemote = remote.stdout.trim();\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (isTimeout(err)) {\n\t\t\t\treturn {\n\t\t\t\t\tworkspace: { path: workspacePath, name, gitRemote },\n\t\t\t\t\terror: \"git-timeout\",\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tworkspace: { path: workspacePath, name },\n\t\t\t\terror: \"not-a-git-repo\",\n\t\t\t};\n\t\t}\n\n\t\tlet gitBranch: string | undefined;\n\t\ttry {\n\t\t\tconst branch = await this.runGitFn([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], workspacePath);\n\t\t\tif (branch.code === 0 && branch.stdout.trim()) {\n\t\t\t\tgitBranch = branch.stdout.trim();\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (isTimeout(err)) {\n\t\t\t\treturn {\n\t\t\t\t\tworkspace: { path: workspacePath, name, gitRemote, gitBranch },\n\t\t\t\t\terror: \"git-timeout\",\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tworkspace: { path: workspacePath, name, gitRemote },\n\t\t\t\terror: \"not-a-git-repo\",\n\t\t\t};\n\t\t}\n\n\t\t// If neither command produced output, treat as not-a-git-repo.\n\t\tif (!gitRemote && !gitBranch) {\n\t\t\treturn {\n\t\t\t\tworkspace: { path: workspacePath, name },\n\t\t\t\terror: \"not-a-git-repo\",\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tworkspace: {\n\t\t\t\tpath: workspacePath,\n\t\t\t\tname,\n\t\t\t\tgitRemote,\n\t\t\t\tgitBranch,\n\t\t\t\tlastSeenAt: new Date().toISOString(),\n\t\t\t},\n\t\t\terror: null,\n\t\t};\n\t}\n}\n","import type { SkillMarketplaceEntry } from \"@serviceme/devtools-protocol\";\nimport { createServicemeError } from \"@serviceme/devtools-protocol\";\nimport type { SkillDownloadFile } from \"./types\";\n\nexport interface SkillCatalog {\n\tskills: SkillMarketplaceEntry[];\n\tfetchedAt: string;\n}\n\nexport interface SkillCatalogClientOptions {\n\tfetchImpl?: typeof fetch;\n\tbaseUrl?: string;\n}\n\nexport class SkillCatalogClient {\n\tprivate readonly fetchImpl: typeof fetch;\n\tprivate readonly baseUrl?: string;\n\n\tconstructor(options: SkillCatalogClientOptions = {}) {\n\t\tthis.fetchImpl = options.fetchImpl ?? fetch;\n\t\tthis.baseUrl = options.baseUrl;\n\t}\n\n\tasync getCatalog(): Promise<SkillCatalog> {\n\t\tif (!this.baseUrl) {\n\t\t\tthrow createServicemeError(\"workspace_not_found\", \"Skill catalog baseUrl is not configured.\");\n\t\t}\n\n\t\tconst response = await this.fetchImpl(`${this.baseUrl}/api/v1/marketplace/skills`);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to fetch skills catalog: ${response.status}`);\n\t\t}\n\n\t\tconst data = (await response.json()) as {\n\t\t\tskills?: SkillMarketplaceEntry[];\n\t\t};\n\t\treturn {\n\t\t\tskills: data.skills ?? [],\n\t\t\tfetchedAt: new Date().toISOString(),\n\t\t};\n\t}\n\n\tasync downloadSkill(remoteId: string): Promise<SkillDownloadFile[]> {\n\t\tif (!this.baseUrl) {\n\t\t\tthrow createServicemeError(\"workspace_not_found\", \"Skill catalog baseUrl is not configured.\");\n\t\t}\n\n\t\tconst response = await this.fetchImpl(\n\t\t\t`${this.baseUrl}/api/v1/marketplace/skills/download/${remoteId}`\n\t\t);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 404) {\n\t\t\t\tthrow createServicemeError(\"not_found\", `Skill '${remoteId}' not found`);\n\t\t\t}\n\t\t\tthrow new Error(`Failed to download skill ${remoteId}: ${response.status}`);\n\t\t}\n\n\t\tconst payload = (await response.json()) as {\n\t\t\tdata?: { files?: SkillDownloadFile[] };\n\t\t};\n\t\treturn payload.data?.files ?? [];\n\t}\n}\n","import type { SkillMutationRequest } from \"@serviceme/devtools-protocol\";\n\ninterface CatalogSkillLike {\n\tid: string;\n\thasScripts?: boolean;\n\thasHooks?: boolean;\n}\n\ninterface CatalogLike {\n\tskills: CatalogSkillLike[];\n\tfetchedAt: string;\n}\n\ninterface SkillStoreLike {\n\tnormalizeRemoteSkillId(remoteId: string): string;\n\tlistWorkspaceSkillIds(): Promise<string[]>;\n\tlistUserSkillIds(): Promise<string[]>;\n}\n\ninterface SkillCatalogClientLike {\n\tgetCatalog(): Promise<CatalogLike>;\n}\n\nexport interface SkillReconcilerDependencies {\n\tskillStore: SkillStoreLike;\n\tcatalogClient: SkillCatalogClientLike;\n}\n\nexport interface SkillMutateResult {\n\tstatus: \"success\" | \"blocked\" | \"requires_confirmation\";\n\tchanged: boolean;\n\tmessage?: string;\n\thasScripts?: boolean;\n\thasHooks?: boolean;\n}\n\nexport class SkillReconciler {\n\tconstructor(private readonly deps: SkillReconcilerDependencies) {}\n\n\tasync mutate(request: SkillMutationRequest): Promise<SkillMutateResult> {\n\t\tif (request.targetScope !== \"workspace\" && request.targetScope !== \"user\") {\n\t\t\tthrow new Error(`Invalid target scope: ${String(request.targetScope)}`);\n\t\t}\n\n\t\tif (\n\t\t\trequest.action === \"uninstall\" ||\n\t\t\trequest.action === \"move\" ||\n\t\t\trequest.action === \"removeExternal\"\n\t\t) {\n\t\t\treturn {\n\t\t\t\tstatus: \"success\",\n\t\t\t\tchanged: true,\n\t\t\t\tmessage: `Skill ${request.action} completed.`,\n\t\t\t};\n\t\t}\n\n\t\tif (request.action !== \"install\") {\n\t\t\treturn {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: `Skill action is not supported by bridge reconciler: ${request.action}`,\n\t\t\t};\n\t\t}\n\n\t\tconst catalog = await this.deps.catalogClient.getCatalog();\n\t\tconst remoteSkill = catalog.skills.find(\n\t\t\t(skill) => this.deps.skillStore.normalizeRemoteSkillId(skill.id) === request.skillId\n\t\t);\n\n\t\tif (!remoteSkill) {\n\t\t\treturn {\n\t\t\t\tstatus: \"blocked\",\n\t\t\t\tchanged: false,\n\t\t\t\tmessage: \"Skill not found in catalog.\",\n\t\t\t};\n\t\t}\n\n\t\tif (!request.confirmed && (remoteSkill.hasScripts || remoteSkill.hasHooks)) {\n\t\t\treturn {\n\t\t\t\tstatus: \"requires_confirmation\",\n\t\t\t\tchanged: false,\n\t\t\t\thasScripts: Boolean(remoteSkill.hasScripts),\n\t\t\t\thasHooks: Boolean(remoteSkill.hasHooks),\n\t\t\t\tmessage: \"This skill contains executable scripts that require confirmation.\",\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: \"success\",\n\t\t\tchanged: true,\n\t\t\tmessage: \"Skill installed.\",\n\t\t};\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { SkillDownloadFile, SkillStoreFileSystem, SkillStoreOptions } from \"./types\";\n\nconst USER_SKILL_MARKER_FILE = \".serviceme-skill.json\";\nconst LEGACY_USER_SKILL_MARKER_FILE = \".ms-devtools-skill.json\";\nconst WORKSPACE_SKILLS_ROOT_RELATIVE = \".github/skills\";\nconst WORKSPACE_SKILLS_MARKER_RELATIVE = \".github/.serviceme-skills.yml\";\nconst SAFE_LOCAL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;\n\nfunction assertSafeLocalSkillId(skillId: string): string {\n\tif (\n\t\ttypeof skillId !== \"string\" ||\n\t\tskillId.length === 0 ||\n\t\tskillId === \".\" ||\n\t\tskillId === \"..\" ||\n\t\tskillId.includes(\"/\") ||\n\t\tskillId.includes(\"\\\\\") ||\n\t\t!SAFE_LOCAL_ID_PATTERN.test(skillId)\n\t) {\n\t\tthrow new Error(`Invalid skill id: ${skillId}`);\n\t}\n\n\treturn skillId;\n}\n\nexport class SkillStore {\n\tprivate readonly workspacePath: string;\n\tprivate readonly userSkillsRoot: string;\n\tprivate readonly fileSystem: SkillStoreFileSystem;\n\n\tconstructor(options: SkillStoreOptions) {\n\t\tthis.workspacePath = options.workspacePath;\n\t\tthis.userSkillsRoot = options.userSkillsRoot;\n\t\tthis.fileSystem = options.fileSystem ?? fs;\n\t}\n\n\tnormalizeRemoteSkillId(remoteId: string): string {\n\t\tif (remoteId.startsWith(\"official/\")) {\n\t\t\treturn assertSafeLocalSkillId(remoteId.slice(\"official/\".length));\n\t\t}\n\t\tif (remoteId.startsWith(\"community/\")) {\n\t\t\tconst lastSlash = remoteId.lastIndexOf(\"/\");\n\t\t\treturn assertSafeLocalSkillId(remoteId.slice(lastSlash + 1));\n\t\t}\n\t\treturn assertSafeLocalSkillId(remoteId);\n\t}\n\n\tgetWorkspaceSkillPath(skillId: string): string {\n\t\treturn `${WORKSPACE_SKILLS_ROOT_RELATIVE}/${skillId}`;\n\t}\n\n\tgetWorkspaceMarkerPath(): string {\n\t\treturn WORKSPACE_SKILLS_MARKER_RELATIVE;\n\t}\n\n\tgetUserSkillPath(skillId: string): string {\n\t\treturn path.join(this.userSkillsRoot, skillId);\n\t}\n\n\tasync listWorkspaceSkillIds(): Promise<string[]> {\n\t\tconst skillsRootPath = path.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE);\n\t\ttry {\n\t\t\tconst entries = await this.fileSystem.readdir(skillsRootPath, {\n\t\t\t\twithFileTypes: true,\n\t\t\t});\n\t\t\treturn entries\n\t\t\t\t.filter((entry) => entry.isDirectory() && !entry.name.startsWith(\".\"))\n\t\t\t\t.map((entry) => entry.name)\n\t\t\t\t.sort();\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tasync listUserSkillIds(): Promise<string[]> {\n\t\ttry {\n\t\t\tconst entries = await this.fileSystem.readdir(this.userSkillsRoot, {\n\t\t\t\twithFileTypes: true,\n\t\t\t});\n\t\t\treturn entries\n\t\t\t\t.filter((entry) => entry.isDirectory() && !entry.name.startsWith(\".\"))\n\t\t\t\t.map((entry) => entry.name)\n\t\t\t\t.sort();\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tasync writeManagedUserSkillMarker(skillId: string): Promise<void> {\n\t\tconst targetDir = this.getUserSkillPath(skillId);\n\t\tawait this.fileSystem.mkdir(targetDir, { recursive: true });\n\t\tawait this.fileSystem.writeFile(\n\t\t\tpath.join(targetDir, USER_SKILL_MARKER_FILE),\n\t\t\tJSON.stringify({ skillId, installedBy: \"serviceme\" }, null, 2),\n\t\t\t\"utf-8\"\n\t\t);\n\t}\n\n\tasync isManagedUserSkill(skillId: string): Promise<boolean> {\n\t\tawait this.migrateLegacyUserSkillMarker(skillId);\n\t\ttry {\n\t\t\tconst marker = await this.fileSystem.readFile(\n\t\t\t\tpath.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),\n\t\t\t\t\"utf-8\"\n\t\t\t);\n\t\t\tconst parsed = JSON.parse(marker) as { skillId?: string };\n\t\t\treturn parsed.skillId === skillId;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * One-time migration: the user-scope skill marker used to be named\n\t * `.ms-devtools-skill.json`. If the new `.serviceme-skill.json` doesn't\n\t * exist yet but the legacy marker does, copy it forward so an existing\n\t * skill doesn't lose its \"managed\" status.\n\t */\n\tprivate async migrateLegacyUserSkillMarker(skillId: string): Promise<void> {\n\t\tconst targetDir = this.getUserSkillPath(skillId);\n\t\tconst newPath = path.join(targetDir, USER_SKILL_MARKER_FILE);\n\t\tconst legacyPath = path.join(targetDir, LEGACY_USER_SKILL_MARKER_FILE);\n\t\ttry {\n\t\t\tawait this.fileSystem.readFile(newPath, \"utf-8\");\n\t\t\treturn;\n\t\t} catch {\n\t\t\t// new marker missing — check the legacy marker below\n\t\t}\n\t\ttry {\n\t\t\tconst legacyContent = await this.fileSystem.readFile(legacyPath, \"utf-8\");\n\t\t\tawait this.fileSystem.writeFile(newPath, legacyContent, \"utf-8\");\n\t\t} catch {\n\t\t\t// legacy marker doesn't exist either — nothing to migrate\n\t\t}\n\t}\n\n\tasync writeSkillFiles(\n\t\tskillId: string,\n\t\tscope: \"workspace\" | \"user\",\n\t\tfiles: SkillDownloadFile[]\n\t): Promise<void> {\n\t\tconst root =\n\t\t\tscope === \"workspace\"\n\t\t\t\t? path.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE)\n\t\t\t\t: this.userSkillsRoot;\n\t\tconst targetDir = path.join(root, skillId);\n\t\tawait this.fileSystem.mkdir(targetDir, { recursive: true });\n\n\t\tfor (const file of files) {\n\t\t\tconst filePath = path.join(targetDir, file.path);\n\t\t\tawait this.fileSystem.mkdir(path.dirname(filePath), { recursive: true });\n\t\t\tawait this.fileSystem.writeFile(filePath, file.content, \"utf-8\");\n\t\t\tif (file.executable) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.fileSystem.chmod(filePath, 0o755);\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore chmod failures on unsupported environments\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { GitClient } from \"../git-client\";\nimport { getRepoDir } from \"../paths/userHome\";\nimport type { SkillFile } from \"../skill-store/types\";\nimport type { SubmitValidationRequest, SubmitValidationResponse } from \"./types\";\nimport { SubmitError } from \"./types\";\n\n// Re-export so consumers (bridge handlers, CLI commands) can detect\n// SubmitError via `instanceof` without reaching into the internal\n// `./types` module.\nexport { SubmitError } from \"./types\";\n\n/**\n * Skill & Agent v2 — SubmitClient (M4)\n *\n * SubmitClient orchestrates the \"validate then push\" flow described\n * in docs/architecture/skill-agent-v2-repo.md §5.7:\n *\n * 1. **validate** — POST the candidate files to the server's\n * `/api/v1/skills/validate` endpoint (5 deny reasons: repo_not_\n * writable, file_too_large, path_traversal, invalid_frontmatter,\n * name_conflict).\n * 2. **write** — materialize the files into the local repo clone at\n * `~/.serviceme/repos/<repoId>/skills/<name>/` (or `agents/`).\n * 3. **commit** — `git add . && git commit -m \"feat(skills): add <name>\"`.\n * 4. **push** — `git push origin <branch>` via the server proxy.\n *\n * The client is intentionally thin: it does NOT do its own validation\n * (the server is the gate), and it does NOT cache anything across\n * calls. The single source of truth for \"is this submission allowed?\"\n * is the server's validate endpoint.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §5.7 SubmitClient\n */\n\nexport interface SubmitOptions {\n\t/** Override the server's validate URL. Defaults to `http://localhost:3000/api/v1`. */\n\tserverBaseUrl?: string;\n\t/** Override the branch to push. Defaults to the repo's `branch` field. */\n\tbranch?: string;\n\t/** Skip the actual push (for tests + dry-runs). When true, step 4 returns a synthetic PushResult. */\n\tskipPush?: boolean;\n}\n\nexport interface SubmitResult {\n\trepoId: string;\n\tskillName: string;\n\tcommitSha: string;\n\tpushedRef?: string;\n\tpushedSha?: string;\n}\n\nexport interface SubmitClientOptions {\n\tgitClient: GitClient;\n\t/** Lookup the default branch for a repo (config-driven). */\n\tgetRepoBranch?: (repoId: string) => string | undefined;\n\t/** HTTP fetch impl (defaults to the global `fetch`). */\n\tfetcher?: typeof fetch;\n\t/** Default server base URL when no override is supplied. */\n\tdefaultServerBaseUrl?: string;\n}\n\nexport class SubmitClient {\n\tprivate readonly git: GitClient;\n\tprivate readonly getRepoBranch: (repoId: string) => string | undefined;\n\tprivate readonly fetcher: typeof fetch;\n\tprivate readonly defaultServerBaseUrl: string;\n\n\tconstructor(opts: SubmitClientOptions) {\n\t\tthis.git = opts.gitClient;\n\t\tthis.getRepoBranch = opts.getRepoBranch ?? (() => undefined);\n\t\tthis.fetcher = opts.fetcher ?? (globalThis.fetch as typeof fetch);\n\t\tthis.defaultServerBaseUrl = opts.defaultServerBaseUrl ?? \"http://localhost:3000\";\n\t}\n\n\t/**\n\t * Validate-only path. Useful for the UI's \"Save Draft\" flow which\n\t * wants to surface validation errors without committing or pushing.\n\t */\n\tasync validate(req: SubmitValidationRequest): Promise<SubmitValidationResponse> {\n\t\tconst url = `${this.defaultServerBaseUrl}/api/v1/skills/validate`;\n\t\tconst res = await this.fetcher(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(req),\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tthrow new SubmitError(\"network_error\", `validate request failed: HTTP ${res.status}`);\n\t\t}\n\t\treturn (await res.json()) as SubmitValidationResponse;\n\t}\n\n\t/**\n\t * Full submit pipeline. Throws `SubmitError` on:\n\t * - validate deny (reason echoed)\n\t * - network failure (network_error)\n\t * - local write failure\n\t * - commit/push failure\n\t */\n\tasync submit(\n\t\trepoId: string,\n\t\tskillName: string,\n\t\tfiles: SkillFile[],\n\t\topts: SubmitOptions = {}\n\t): Promise<SubmitResult> {\n\t\t// (1) Validate\n\t\tconst v = await this.validate({ repoId, skillName, files });\n\t\tif (!v.allow) {\n\t\t\tthrow new SubmitError(v.reason ?? \"unknown\", v.detail ?? \"validation denied\");\n\t\t}\n\n\t\t// (2) Write files into the local repo clone\n\t\tconst localRepoPath = getRepoDir(repoId);\n\t\tconst targetDir = path.join(localRepoPath, \"skills\", skillName);\n\t\tawait fs.mkdir(targetDir, { recursive: true });\n\t\tfor (const f of files) {\n\t\t\tconst full = path.join(targetDir, f.path);\n\t\t\tawait fs.mkdir(path.dirname(full), { recursive: true });\n\t\t\tconst tmp = `${full}.${process.pid}.${Date.now()}.tmp`;\n\t\t\tawait fs.writeFile(tmp, f.content, \"utf8\");\n\t\t\tawait fs.rename(tmp, full);\n\t\t}\n\n\t\t// (3) Commit (convention: `feat(skills): add <name>`)\n\t\tconst commitMessage = `feat(skills): add ${skillName}`;\n\t\tconst { commitSha } = await this.git.commit(localRepoPath, commitMessage);\n\n\t\t// (4) Push\n\t\tlet pushedRef: string | undefined;\n\t\tlet pushedSha: string | undefined;\n\t\tif (!opts.skipPush) {\n\t\t\tconst branch = opts.branch ?? this.getRepoBranch(repoId) ?? \"main\";\n\t\t\tconst pushResult = await this.git.push(repoId, localRepoPath, branch);\n\t\t\tpushedRef = pushResult.ref;\n\t\t\tpushedSha = pushResult.commitSha;\n\t\t}\n\n\t\treturn {\n\t\t\trepoId,\n\t\t\tskillName,\n\t\t\tcommitSha,\n\t\t\tpushedRef,\n\t\t\tpushedSha,\n\t\t};\n\t}\n}\n","/**\n * Skill & Agent v2 — SubmitClient Types (M4)\n *\n * Mirrors the server's POST /api/v1/skills/validate contract (M2\n * SubmitApi). Defined here as a separate types file so the test\n * fixtures + the client can both import without circular deps.\n */\n\n/** Reasons the server may deny a submission. */\nexport type DenyReason =\n\t| \"repo_not_writable\"\n\t| \"file_too_large\"\n\t| \"path_traversal\"\n\t| \"invalid_frontmatter\"\n\t| \"name_conflict\"\n\t/** Local-client-side errors that don't come from the server. */\n\t| \"network_error\"\n\t| \"write_error\"\n\t| \"commit_error\"\n\t| \"push_error\"\n\t| \"unknown\";\n\n/** Request payload. Identical to the server's SubmitValidationRequest. */\nexport interface SubmitValidationRequest {\n\trepoId: string;\n\tskillName: string;\n\tfiles: Array<{ path: string; content: string }>;\n}\n\n/** Response payload. Identical to the server's SubmitValidationResponse. */\nexport interface SubmitValidationResponse {\n\tallow: boolean;\n\treason?: DenyReason;\n\tdetail?: string;\n}\n\n/** Sentinel error thrown by SubmitClient.submit() / validate(). */\nexport class SubmitError extends Error {\n\treadonly reason: DenyReason;\n\treadonly detail: string;\n\treadonly status?: number;\n\n\tconstructor(reason: DenyReason, detail: string, options?: { status?: number }) {\n\t\tsuper(`submit failed (${reason}): ${detail}`);\n\t\tthis.name = \"SubmitError\";\n\t\tthis.reason = reason;\n\t\tthis.detail = detail;\n\t\tthis.status = options?.status;\n\t}\n}\n","/**\n * toolbox sort + dedup — pure helpers, no I/O.\n *\n * The Extension's `ToolBoxService` keeps a stable `order` index and\n * surfaces \"recently used\" via the `lastUsedAt` ISO timestamp. These\n * helpers codify that algorithm so the CLI and Extension agree on the\n * \"最近使用置顶\" UX.\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `sort.ts 排序与去重纯函数`\n */\n\nimport type { ExternalTool } from \"@serviceme/devtools-protocol\";\n\n/**\n * Parsed timestamp from `lastUsedAt`. Returns 0 when the field is\n * missing or unparseable (so the entry falls below entries with a\n * real timestamp).\n */\nfunction lastUsedTimestamp(tool: ExternalTool): number {\n\tif (!tool.lastUsedAt) return 0;\n\tconst ms = Date.parse(tool.lastUsedAt);\n\treturn Number.isFinite(ms) ? ms : 0;\n}\n\n/**\n * Sort by \"recently used\" — entries with newer `lastUsedAt` float to\n * the top, entries with no `lastUsedAt` sort to the bottom (preserving\n * their relative `order` index when both are absent).\n *\n * Ties (same `lastUsedAt` or both missing) are broken by `order`, then\n * by `id` for determinism.\n */\nexport function sortByRecentFirst(tools: readonly ExternalTool[]): ExternalTool[] {\n\treturn [...tools].sort((a, b) => {\n\t\tconst tsA = lastUsedTimestamp(a);\n\t\tconst tsB = lastUsedTimestamp(b);\n\t\tif (tsA !== tsB) return tsB - tsA;\n\t\tconst orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n\t\tconst orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n\t\tif (orderA !== orderB) return orderA - orderB;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n}\n\n/**\n * Sort by user-defined `order` field. Entries without `order` are\n * appended in their input order (stable sort via `id` tiebreaker).\n */\nexport function sortByUserOrder(tools: readonly ExternalTool[]): ExternalTool[] {\n\treturn [...tools].sort((a, b) => {\n\t\tconst orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n\t\tconst orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n\t\tif (orderA !== orderB) return orderA - orderB;\n\t\treturn a.id.localeCompare(b.id);\n\t});\n}\n\n/**\n * Deduplicate by `id`. The first occurrence wins; later ones are\n * discarded. Used when merging built-in + user seeds so duplicates\n * from the on-disk file don't shadow the built-in defaults.\n */\nexport function dedupeById(tools: readonly ExternalTool[]): ExternalTool[] {\n\tconst seen = new Set<string>();\n\tconst out: ExternalTool[] = [];\n\tfor (const tool of tools) {\n\t\tif (seen.has(tool.id)) continue;\n\t\tseen.add(tool.id);\n\t\tout.push(tool);\n\t}\n\treturn out;\n}\n\n/**\n * Merge built-in defaults with user-stored tools. Defaults keep\n * `isDefault: true` and their `order`; user tools are appended with\n * their stored metadata. Output is sorted by user order.\n */\nexport function mergeWithDefaults(\n\tdefaults: readonly ExternalTool[],\n\tuserTools: readonly ExternalTool[]\n): ExternalTool[] {\n\tconst defaultIds = new Set(defaults.map((d) => d.id));\n\tconst uniqueUserTools = userTools.filter((t) => !defaultIds.has(t.id));\n\treturn sortByUserOrder([...defaults, ...uniqueUserTools]);\n}\n\n/**\n * Re-number `order` for a list of tool ids. Used by\n * `toolbox.update` when the user drags-and-drops entries in the UI.\n */\nexport function reindexOrder(\n\ttools: ExternalTool[],\n\tnewOrderIds: readonly string[]\n): ExternalTool[] {\n\tconst orderMap = new Map<string, number>();\n\tfor (const [index, id] of newOrderIds.entries()) {\n\t\torderMap.set(id, index);\n\t}\n\tfor (const tool of tools) {\n\t\tconst next = orderMap.get(tool.id);\n\t\tif (next !== undefined) tool.order = next;\n\t}\n\treturn tools;\n}\n\n/**\n * Stamp `lastUsedAt` on the targeted tool (clones the array). Returns\n * a new array; the input is left untouched.\n */\nexport function touchLastUsedAt(\n\ttools: readonly ExternalTool[],\n\tid: string,\n\twhen: Date = new Date()\n): ExternalTool[] {\n\treturn tools.map((tool) => (tool.id === id ? { ...tool, lastUsedAt: when.toISOString() } : tool));\n}\n","/**\n * ToolboxStore — JSON persistence for the toolbox entries.\n *\n * Two scopes:\n * - `user` → `~/.serviceme/toolbox.json`\n * - `workspace` → `<cwd>/.github/.serviceme-toolbox.json`\n *\n * Both files share the `PersistedToolbox` shape and the same atomic\n * write pattern (tmp + rename + fsync) as `IdentityStore`. Concurrent\n * writers are serialized via a mkdir-based file lock (Phase 6+ may\n * upgrade to `proper-lockfile`).\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `ToolboxStore.ts JSON 持久化(user + workspace scope)`\n * - `3.功能拆分.md` §3 — toolbox wire shape\n */\n\nimport * as fsp from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nimport type { ExternalTool, ToolboxScope } from \"@serviceme/devtools-protocol\";\n\nimport { getToolboxJsonPath } from \"../paths/userHome\";\n\nimport { mergeWithDefaults, sortByUserOrder } from \"./sort\";\nimport {\n\tBUILTIN_DEFAULT_TOOLS,\n\ttype DefaultToolSeed,\n\ttype PersistedToolbox,\n\ttype ResolvedToolbox,\n\tTOOLBOX_JSON_SCHEMA_VERSION,\n} from \"./types\";\n\nconst FILE_MODE = 0o600;\nconst LOCK_DIR_MODE = 0o700;\nconst DEFAULT_LOCK_TIMEOUT_MS = 5000;\nconst DEFAULT_LOCK_RETRY_MS = 25;\nconst TMP_SUFFIX = \".tmp\";\nexport const WORKSPACE_TOOLBOX_RELATIVE_PATH = path.join(\".github\", \".serviceme-toolbox.json\");\nconst LEGACY_WORKSPACE_TOOLBOX_FILENAME = \".ms-devtools-toolbox.json\";\n\n/**\n * One-time migration: the workspace-scope toolbox file used to be named\n * `.ms-devtools-toolbox.json`. If the new `.serviceme-toolbox.json` doesn't\n * exist yet but the legacy file does (in the same directory), rename it\n * forward so existing toolbox entries aren't silently lost.\n */\nasync function migrateLegacyWorkspaceToolboxFile(filePath: string | null): Promise<void> {\n\tif (!filePath) return;\n\tconst legacyPath = path.join(path.dirname(filePath), LEGACY_WORKSPACE_TOOLBOX_FILENAME);\n\tif (legacyPath === filePath) return;\n\ttry {\n\t\tawait fsp.access(filePath);\n\t\treturn;\n\t} catch {\n\t\t// new file missing — check the legacy path below\n\t}\n\ttry {\n\t\tawait fsp.rename(legacyPath, filePath);\n\t} catch {\n\t\t// legacy file doesn't exist either — nothing to migrate\n\t}\n}\n\nexport interface ToolboxFileBackend {\n\tread(filePath: string): Promise<PersistedToolbox | null>;\n\twrite(filePath: string, payload: PersistedToolbox): Promise<void>;\n\texists(filePath: string): Promise<boolean>;\n}\n\nexport interface ToolboxStoreOptions {\n\t/** Override the user-scope file path (default: `getToolboxJsonPath()`). */\n\tuserFilePath?: string;\n\t/** Override the workspace-scope file path resolver (default: cwd-relative). */\n\tresolveWorkspacePath?: () => string | null;\n\t/** Injectable built-in tool seeds (default: `BUILTIN_DEFAULT_TOOLS`). */\n\tdefaultTools?: readonly DefaultToolSeed[];\n\thooks?: ToolboxStoreHooks;\n\tbackend?: ToolboxFileBackend;\n\tlockTimeoutMs?: number;\n\tlockRetryMs?: number;\n}\n\nexport interface ToolboxStoreHooks {\n\tbeforeWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;\n\tafterWrite?: (scope: ToolboxScope, payload: PersistedToolbox) => void | Promise<void>;\n}\n\nexport class FsToolboxFileBackend implements ToolboxFileBackend {\n\tasync exists(filePath: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fsp.access(filePath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync read(filePath: string): Promise<PersistedToolbox | null> {\n\t\tlet buf: string;\n\t\ttry {\n\t\t\tbuf = await fsp.readFile(filePath, \"utf8\");\n\t\t} catch (err) {\n\t\t\tif (isNodeError(err) && err.code === \"ENOENT\") return null;\n\t\t\tthrow err;\n\t\t}\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(buf) as unknown;\n\t\t\treturn coercePersistedToolbox(parsed);\n\t\t} catch {\n\t\t\t// File exists but is unparseable JSON or fails schema validation\n\t\t\t// (e.g. hand-edited, written by an older/incompatible schema, or\n\t\t\t// truncated by a crash). Quarantine it as a timestamped `.bak`\n\t\t\t// sibling and fall back to an empty toolbox (defaults still\n\t\t\t// merge in via `ToolboxStore.read()`) rather than crashing the\n\t\t\t// whole \"get external tools\" request.\n\t\t\tawait this.backupCorruptedFile(filePath);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate async backupCorruptedFile(filePath: string): Promise<void> {\n\t\ttry {\n\t\t\tconst backupPath = `${filePath}.corrupted.${Date.now()}.bak`;\n\t\t\tawait fsp.copyFile(filePath, backupPath);\n\t\t} catch {\n\t\t\t// Best-effort backup — never let backup failure mask the real recovery.\n\t\t}\n\t}\n\n\tasync write(filePath: string, payload: PersistedToolbox): Promise<void> {\n\t\tawait fsp.mkdir(path.dirname(filePath), { recursive: true });\n\t\tconst tmpPath = `${filePath}${TMP_SUFFIX}`;\n\t\tconst bytes = Buffer.from(JSON.stringify(payload, null, \"\\t\"), \"utf8\");\n\t\tawait fsp.rm(tmpPath, { force: true });\n\t\tconst handle = await fsp.open(tmpPath, \"w\", FILE_MODE);\n\t\ttry {\n\t\t\tawait handle.writeFile(bytes);\n\t\t\tawait handle.sync();\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait fsp.rename(tmpPath, filePath);\n\t\tawait fsp.chmod(filePath, FILE_MODE).catch(() => undefined);\n\t}\n}\n\nfunction coercePersistedToolbox(parsed: unknown): PersistedToolbox {\n\tif (typeof parsed !== \"object\" || parsed === null) {\n\t\tthrow new Error(\"toolbox.json: top-level must be an object\");\n\t}\n\tconst obj = parsed as Record<string, unknown>;\n\tconst version = obj.version;\n\tif (version !== TOOLBOX_JSON_SCHEMA_VERSION) {\n\t\tthrow new Error(`toolbox.json: unsupported schema version ${String(version)}`);\n\t}\n\tif (!Array.isArray(obj.tools)) {\n\t\tthrow new Error(\"toolbox.json: 'tools' must be an array\");\n\t}\n\treturn { version, tools: obj.tools as ExternalTool[] };\n}\n\nfunction isNodeError(value: unknown): value is NodeJS.ErrnoException {\n\treturn value instanceof Error && typeof (value as { code?: unknown }).code === \"string\";\n}\n\nclass ToolboxFileLock {\n\tprivate readonly dirPath: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly retryMs: number;\n\tprivate acquired = false;\n\n\tconstructor(filePath: string, timeoutMs: number, retryMs: number) {\n\t\tthis.dirPath = `${filePath}.lock`;\n\t\tthis.timeoutMs = timeoutMs;\n\t\tthis.retryMs = retryMs;\n\t}\n\n\tasync acquire(): Promise<void> {\n\t\tconst start = Date.now();\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tawait fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });\n\t\t\t\tthis.acquired = true;\n\t\t\t\treturn;\n\t\t\t} catch (err) {\n\t\t\t\tif (!isNodeError(err) || err.code !== \"EEXIST\") throw err;\n\t\t\t\tif (Date.now() - start >= this.timeoutMs) {\n\t\t\t\t\tthrow new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);\n\t\t\t\t}\n\t\t\t\tawait delay(this.retryMs);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync release(): Promise<void> {\n\t\tif (!this.acquired) return;\n\t\tthis.acquired = false;\n\t\tawait fsp.rm(this.dirPath, { recursive: true, force: true });\n\t}\n}\n\nfunction defaultWorkspacePath(): string | null {\n\t// The store caller (ToolboxCore) supplies a cwd, but tests want a\n\t// stable default. Return null when the env var says \"no workspace\".\n\tif (process.env.SERVICEME_NO_WORKSPACE_TOOLBOX === \"1\") return null;\n\treturn path.join(process.cwd(), WORKSPACE_TOOLBOX_RELATIVE_PATH);\n}\n\nexport class ToolboxStore {\n\tprivate readonly userFilePath: string;\n\tprivate readonly resolveWorkspacePath: () => string | null;\n\tprivate readonly defaults: readonly DefaultToolSeed[];\n\tprivate readonly hooks: ToolboxStoreHooks;\n\tprivate readonly backend: ToolboxFileBackend;\n\tprivate readonly lockTimeoutMs: number;\n\tprivate readonly lockRetryMs: number;\n\n\tconstructor(opts: ToolboxStoreOptions = {}) {\n\t\tthis.userFilePath = opts.userFilePath ?? getToolboxJsonPath();\n\t\tthis.resolveWorkspacePath = opts.resolveWorkspacePath ?? defaultWorkspacePath;\n\t\tthis.defaults = opts.defaultTools ?? BUILTIN_DEFAULT_TOOLS;\n\t\tthis.hooks = opts.hooks ?? {};\n\t\tthis.backend = opts.backend ?? new FsToolboxFileBackend();\n\t\tthis.lockTimeoutMs = opts.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;\n\t\tthis.lockRetryMs = opts.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;\n\t}\n\n\t/** Read a scope; returns the resolved toolbox (with defaults merged when empty). */\n\tasync read(scope: ToolboxScope): Promise<ResolvedToolbox> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (scope === \"workspace\") {\n\t\t\tawait migrateLegacyWorkspaceToolboxFile(filePath);\n\t\t}\n\t\tconst stored = filePath ? await this.backend.read(filePath) : null;\n\t\tconst storedTools = stored?.tools ?? [];\n\t\tconst defaults = this.defaults.map((seed, index) => toDefaultTool(seed, index));\n\t\tconst merged = mergeWithDefaults(defaults, storedTools);\n\t\tconst userOnly = storedTools.filter((t) => !this.defaults.some((d) => d.id === t.id));\n\t\tvoid userOnly;\n\t\treturn { scope, tools: merged };\n\t}\n\n\t/**\n\t * Atomic write under a file lock. Replaces the entire `tools` array\n\t * with the provided snapshot.\n\t */\n\tasync write(scope: ToolboxScope, tools: readonly ExternalTool[]): Promise<void> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (!filePath) {\n\t\t\tthrow new Error(`Cannot write toolbox scope '${scope}': file path is unavailable`);\n\t\t}\n\t\tif (scope === \"workspace\") {\n\t\t\tawait migrateLegacyWorkspaceToolboxFile(filePath);\n\t\t}\n\t\tconst payload: PersistedToolbox = {\n\t\t\tversion: TOOLBOX_JSON_SCHEMA_VERSION,\n\t\t\ttools: sortByUserOrder([...tools]),\n\t\t};\n\t\tawait this.hooks.beforeWrite?.(scope, payload);\n\t\tconst lock = new ToolboxFileLock(filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tawait this.backend.write(filePath, payload);\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t\tawait this.hooks.afterWrite?.(scope, payload);\n\t}\n\n\t/**\n\t * Read-modify-write under the file lock. The mutator receives the\n\t * current user-only list (defaults not included) and returns the\n\t * replacement list. Throwing inside the mutator aborts the write.\n\t */\n\tasync mutate(\n\t\tscope: ToolboxScope,\n\t\tmutator: (current: ExternalTool[]) => Promise<ExternalTool[]>\n\t): Promise<ExternalTool[]> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (!filePath) {\n\t\t\tthrow new Error(`Cannot mutate toolbox scope '${scope}': file path is unavailable`);\n\t\t}\n\t\tif (scope === \"workspace\") {\n\t\t\tawait migrateLegacyWorkspaceToolboxFile(filePath);\n\t\t}\n\t\tconst lock = new ToolboxFileLock(filePath, this.lockTimeoutMs, this.lockRetryMs);\n\t\tawait lock.acquire();\n\t\ttry {\n\t\t\tconst stored = await this.backend.read(filePath);\n\t\t\tconst storedTools = stored?.tools ?? [];\n\t\t\tconst defaultIds = new Set(this.defaults.map((d) => d.id));\n\t\t\tconst userTools = storedTools.filter((t) => !defaultIds.has(t.id));\n\t\t\tconst next = await mutator(userTools);\n\t\t\tconst payload: PersistedToolbox = {\n\t\t\t\tversion: TOOLBOX_JSON_SCHEMA_VERSION,\n\t\t\t\ttools: sortByUserOrder([...next]),\n\t\t\t};\n\t\t\tawait this.hooks.beforeWrite?.(scope, payload);\n\t\t\tawait this.backend.write(filePath, payload);\n\t\t\tawait this.hooks.afterWrite?.(scope, payload);\n\t\t\treturn next;\n\t\t} finally {\n\t\t\tawait lock.release();\n\t\t}\n\t}\n\n\t/** Wipe a scope entirely (used by `toolbox.remove --all` extensions). */\n\tasync clear(scope: ToolboxScope): Promise<void> {\n\t\tconst filePath = this.filePathFor(scope);\n\t\tif (!filePath) return;\n\t\tawait fsp.rm(filePath, { force: true });\n\t}\n\n\t/** Test seam — resolve the user-scope file path. */\n\tgetUserFilePath(): string {\n\t\treturn this.userFilePath;\n\t}\n\n\t/** Test seam — resolve the workspace-scope file path (or null when disabled). */\n\tgetWorkspaceFilePath(): string | null {\n\t\treturn this.resolveWorkspacePath();\n\t}\n\n\tprivate filePathFor(scope: ToolboxScope): string | null {\n\t\treturn scope === \"user\" ? this.userFilePath : this.resolveWorkspacePath();\n\t}\n}\n\nfunction toDefaultTool(seed: DefaultToolSeed, order: number): ExternalTool {\n\treturn {\n\t\tid: seed.id,\n\t\tname: seed.name,\n\t\tdescription: seed.description,\n\t\ticon: seed.icon,\n\t\turl: seed.url,\n\t\tisDefault: true,\n\t\torder,\n\t\tscope: \"user\",\n\t};\n}\n","/**\n * toolbox types — internal-only data shapes.\n *\n * The public data model lives in `@serviceme/devtools-protocol/toolbox`\n * (`ExternalTool`, `ToolboxScope`, `ToolboxList`). The types below are\n * the persisted on-disk shape and the patch helpers, scoped to the\n * toolbox domain only.\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `toolbox/types.ts`\n */\n\nimport type { ExternalTool, ToolboxScope } from \"@serviceme/devtools-protocol\";\n\n/** Schema version of the on-disk toolbox JSON files. Bumped on breaking changes. */\nexport const TOOLBOX_JSON_SCHEMA_VERSION = 1;\n\nexport interface PersistedToolbox {\n\tversion: number;\n\ttools: ExternalTool[];\n}\n\n/** Patch payload accepted by `ToolboxCore.update` (mirrors `ExternalToolPatch`). */\nexport type ToolboxPatch = Partial<Omit<ExternalTool, \"id\" | \"isDefault\">>;\n\n/**\n * Built-in (default) tool seeds. Mirrors the Extension's\n * `apps/extension/src/config/external-tools.ts`. The Core stores these\n * verbatim and never mutates them — the Extension's `ToolBoxService`\n * runs the same merge on top of the Core's view.\n */\nexport interface DefaultToolSeed {\n\tid: string;\n\tname: string;\n\tdescription: string;\n\ticon: string;\n\turl: string;\n}\n\n/** Built-in tools rendered when the JSON file is missing or empty. */\nexport const BUILTIN_DEFAULT_TOOLS: readonly DefaultToolSeed[] = [\n\t{\n\t\tid: \"builtin-docs\",\n\t\tname: \"SERVICEME Docs\",\n\t\tdescription: \"Official documentation portal\",\n\t\ticon: \"book\",\n\t\turl: \"https://docs.medalsoft.com/serviceme\",\n\t},\n\t{\n\t\tid: \"builtin-issues\",\n\t\tname: \"Issue Tracker\",\n\t\tdescription: \"Report bugs and feature requests\",\n\t\ticon: \"bug\",\n\t\turl: \"https://github.com/medalsoftchina/ms-devtools-vscode/issues\",\n\t},\n\t{\n\t\tid: \"builtin-changelog\",\n\t\tname: \"Changelog\",\n\t\tdescription: \"Release notes for every published version\",\n\t\ticon: \"history\",\n\t\turl: \"https://github.com/medalsoftchina/ms-devtools-vscode/releases\",\n\t},\n];\n\n/** Resolved toolbox shape returned by `ToolboxStore.read()`. */\nexport interface ResolvedToolbox {\n\tscope: ToolboxScope;\n\ttools: ExternalTool[];\n}\n","/**\n * ToolboxCore — Main entry for the toolbox domain.\n *\n * Orchestrates the user + workspace scopes via `ToolboxStore`, applies\n * the \"最近使用置顶\" sort, and exposes a small surface that mirrors\n * the Phase 5.1 bridge methods:\n *\n * - `list(scope?)` → `toolbox.list`\n * - `add(tool, scope)` → `toolbox.add`\n * - `remove(id, scope?)` → `toolbox.remove`\n * - `update(id, patch, scope?)` → `toolbox.update`\n *\n * Default tools (built-in seeds) are never user-deletable. `remove()`\n * is a no-op for default tools and surfaces a structured error so\n * callers can map it to `TOOLBOX_DEFAULT_IMMUTABLE` (Phase 5.3 error\n * code).\n *\n * Refs:\n * - 4.功能规划.md §2.3 — `ToolboxCore.ts 主入口`\n * - `3.功能拆分.md` §3 — wire shape\n */\n\nimport type { ExternalTool, ToolboxList, ToolboxScope } from \"@serviceme/devtools-protocol\";\n\nimport { sortByRecentFirst, sortByUserOrder, touchLastUsedAt } from \"./sort\";\nimport { ToolboxStore } from \"./ToolboxStore\";\nimport { BUILTIN_DEFAULT_TOOLS, type DefaultToolSeed, type ToolboxPatch } from \"./types\";\n\n/** Sentinel — caller tried to remove a built-in (immutable) tool. */\nexport class DefaultToolImmutableError extends Error {\n\tconstructor(public readonly toolId: string) {\n\t\tsuper(`Cannot remove default toolbox entry: ${toolId}`);\n\t\tthis.name = \"DefaultToolImmutableError\";\n\t}\n}\n\nexport interface ToolboxCoreOptions {\n\tstore?: ToolboxStore;\n\tdefaultTools?: readonly DefaultToolSeed[];\n\t/** Sort applied to the merged list returned by `list()`. Default: `sortByUserOrder`. */\n\tlistSort?: (tools: readonly ExternalTool[]) => ExternalTool[];\n}\n\nexport class ToolboxCore {\n\tprivate readonly store: ToolboxStore;\n\tprivate readonly defaults: readonly DefaultToolSeed[];\n\tprivate readonly listSort: (tools: readonly ExternalTool[]) => ExternalTool[];\n\n\tconstructor(opts: ToolboxCoreOptions = {}) {\n\t\tthis.store = opts.store ?? new ToolboxStore();\n\t\tthis.defaults = opts.defaultTools ?? BUILTIN_DEFAULT_TOOLS;\n\t\tthis.listSort = opts.listSort ?? sortByUserOrder;\n\t}\n\n\t/** List all tools in a scope. Built-in defaults are merged in. */\n\tasync list(scope: ToolboxScope = \"user\"): Promise<ToolboxList> {\n\t\tconst resolved = await this.store.read(scope);\n\t\treturn {\n\t\t\tscope,\n\t\t\ttools: this.listSort(resolved.tools),\n\t\t};\n\t}\n\n\t/**\n\t * Append a new tool to the requested scope. Default tools are\n\t * rejected (they are seeds, not user entries).\n\t */\n\tasync add(tool: ExternalTool, scope: ToolboxScope = \"user\"): Promise<ToolboxList> {\n\t\tif (this.isDefaultId(tool.id)) {\n\t\t\tthrow new DefaultToolImmutableError(tool.id);\n\t\t}\n\t\tconst next = await this.store.mutate(scope, async (current) => {\n\t\t\tconst filtered = current.filter((t) => t.id !== tool.id);\n\t\t\treturn [\n\t\t\t\t...filtered,\n\t\t\t\t{\n\t\t\t\t\t...tool,\n\t\t\t\t\tscope,\n\t\t\t\t\tisDefault: false,\n\t\t\t\t\torder: tool.order ?? filtered.length,\n\t\t\t\t},\n\t\t\t];\n\t\t});\n\t\treturn { scope, tools: next };\n\t}\n\n\t/**\n\t * Remove a tool by id. Returns `success: false` when the id is a\n\t * built-in default (idempotent, never throws on missing entries).\n\t */\n\tasync remove(\n\t\tid: string,\n\t\tscope: ToolboxScope = \"user\"\n\t): Promise<{ scope: ToolboxScope; toolId: string; success: boolean }> {\n\t\tif (this.isDefaultId(id)) {\n\t\t\tthrow new DefaultToolImmutableError(id);\n\t\t}\n\t\tconst removed = await this.store.mutate(scope, async (current) =>\n\t\t\tcurrent.filter((t) => t.id !== id)\n\t\t);\n\t\treturn { scope, toolId: id, success: !removed.some((t) => t.id === id) };\n\t}\n\n\t/**\n\t * Patch a tool by id. Default tools can only have their `order`\n\t * updated; other patches are silently ignored for default entries\n\t * (callers can compare before/after to detect the ignore).\n\t */\n\tasync update(\n\t\tid: string,\n\t\tpatch: ToolboxPatch,\n\t\tscope: ToolboxScope = \"user\"\n\t): Promise<ToolboxList> {\n\t\tconst isDefault = this.isDefaultId(id);\n\t\tconst next = await this.store.mutate(scope, async (current) => {\n\t\t\tif (isDefault) {\n\t\t\t\t// Default tools are not stored on disk — silently ignore\n\t\t\t\t// non-order patches. The order field is also not persisted\n\t\t\t\t// (default order is set in memory at merge time), so the\n\t\t\t\t// caller can compare before/after to detect the ignore.\n\t\t\t\treturn current;\n\t\t\t}\n\t\t\treturn current.map((tool) =>\n\t\t\t\ttool.id === id ? { ...tool, ...patch, scope, isDefault: false } : tool\n\t\t\t);\n\t\t});\n\t\treturn { scope, tools: next };\n\t}\n\n\t/**\n\t * Stamp `lastUsedAt` on the targeted tool. This is the \"recently\n\t * used\" hook the Extension's webview uses when a user clicks a\n\t * toolbox entry.\n\t */\n\tasync recordUsage(\n\t\tid: string,\n\t\tscope: ToolboxScope = \"user\",\n\t\twhen: Date = new Date()\n\t): Promise<ExternalTool | null> {\n\t\tif (this.isDefaultId(id)) {\n\t\t\t// Defaults live in memory only — return a synthetic record so\n\t\t\t// callers can render the click without persisting anything.\n\t\t\tconst seed = this.defaults.find((d) => d.id === id);\n\t\t\tif (!seed) return null;\n\t\t\treturn { ...seed, scope, isDefault: true, order: 0, lastUsedAt: when.toISOString() };\n\t\t}\n\t\tlet updated: ExternalTool | null = null;\n\t\tawait this.store.mutate(scope, async (current) => {\n\t\t\tconst next = touchLastUsedAt(current, id, when);\n\t\t\tupdated = next.find((t) => t.id === id) ?? null;\n\t\t\treturn next;\n\t\t});\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Combined view: user + workspace scopes merged, sorted by recent\n\t * usage. Workspace tools overlay user tools (workspace entries win\n\t * on `id` collision).\n\t */\n\tasync listMerged(): Promise<ToolboxList> {\n\t\tconst [user, workspace] = await Promise.all([\n\t\t\tthis.store.read(\"user\"),\n\t\t\tthis.store.read(\"workspace\"),\n\t\t]);\n\t\tconst seen = new Set<string>();\n\t\tconst merged: ExternalTool[] = [];\n\t\tfor (const tool of workspace.tools) {\n\t\t\tseen.add(tool.id);\n\t\t\tmerged.push({ ...tool, scope: \"workspace\" });\n\t\t}\n\t\tfor (const tool of user.tools) {\n\t\t\tif (seen.has(tool.id)) continue;\n\t\t\tmerged.push({ ...tool, scope: \"user\" });\n\t\t}\n\t\treturn { scope: \"user\", tools: sortByRecentFirst(merged) };\n\t}\n\n\t/** Expose the underlying store (CLI / Bridge use it for path-level access). */\n\tgetStore(): ToolboxStore {\n\t\treturn this.store;\n\t}\n\n\tprivate isDefaultId(id: string): boolean {\n\t\treturn this.defaults.some((d) => d.id === id);\n\t}\n}\n"],"mappings":";;;;;;;;AACA,SAAS,4BAA4B;AAa9B,IAAM,qBAAN,MAAyB;AAAA,EAI/B,YAAY,UAAqC,CAAC,GAAG;AACpD,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,UAAU,QAAQ;AAAA,EACxB;AAAA,EAEA,MAAM,aAAoC;AACzC,QAAI,CAAC,KAAK,SAAS;AAClB,YAAM,qBAAqB,uBAAuB,0CAA0C;AAAA,IAC7F;AAEA,UAAM,WAAW,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,4BAA4B;AACjF,QAAI,CAAC,SAAS,IAAI;AACjB,YAAM,IAAI,MAAM,mCAAmC,SAAS,MAAM,EAAE;AAAA,IACrE;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAGlC,WAAO;AAAA,MACN,QAAQ,KAAK,UAAU,CAAC;AAAA,MACxB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC;AAAA,EACD;AAAA,EAEA,MAAM,cAAc,UAAgD;AACnE,QAAI,CAAC,KAAK,SAAS;AAClB,YAAM,qBAAqB,uBAAuB,0CAA0C;AAAA,IAC7F;AAEA,UAAM,WAAW,MAAM,KAAK;AAAA,MAC3B,GAAG,KAAK,OAAO,uCAAuC,QAAQ;AAAA,IAC/D;AACA,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI,SAAS,WAAW,KAAK;AAC5B,cAAM,qBAAqB,aAAa,UAAU,QAAQ,aAAa;AAAA,MACxE;AACA,YAAM,IAAI,MAAM,4BAA4B,QAAQ,KAAK,SAAS,MAAM,EAAE;AAAA,IAC3E;AAEA,UAAM,UAAW,MAAM,SAAS,KAAK;AAGrC,WAAO,QAAQ,MAAM,SAAS,CAAC;AAAA,EAChC;AACD;;;AC5DO,IAAM,gBAAoD;AAAA,EAChE,OAAO;AAAA,EACP,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,eAAe;AAAA,EACf,aAAa;AAAA,EACb,wBAAwB;AAAA,EACxB,8BAA8B;AAAA,EAC9B,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,UAAU;AACX;AAEA,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAC3B,IAAM,kBAAkB;AAEjB,SAAS,0BAA0B,SAAwC;AACjF,QAAM,UAAU,QAAQ,MAAM,iBAAiB;AAC/C,MAAI,CAAC,UAAU,CAAC,EAAG,QAAO,CAAC;AAE3B,QAAM,cAAc,QAAQ,CAAC;AAE7B,QAAM,cAAc,YAAY,MAAM,kBAAkB;AACxD,MAAI,cAAc,CAAC,KAAK,MAAM;AAC7B,UAAM,MAAM,YAAY,CAAC;AACzB,WAAO,IACL,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,EACd,IAAI,CAAC,UAAU;AAAA,MACf;AAAA,MACA,WAAW,cAAc,IAAI,KAAK;AAAA,IACnC,EAAE;AAAA,EACJ;AAEA,QAAM,aAAa,YAAY,MAAM,gBAAgB;AACrD,MAAI,CAAC,aAAa,CAAC,EAAG,QAAO,CAAC;AAE9B,QAAM,kBAAkB,YAAY,QAAQ,WAAW,CAAC,CAAC,IAAI,WAAW,CAAC,EAAE;AAC3E,QAAM,YAAY,YAAY,MAAM,eAAe;AACnD,QAAM,QAAQ,UAAU,MAAM,OAAO;AACrC,QAAM,QAA+B,CAAC;AAEtC,aAAW,QAAQ,OAAO;AACzB,UAAM,YAAY,KAAK,MAAM,eAAe;AAC5C,QAAI,YAAY,CAAC,GAAG;AACnB,YAAM,OAAO,UAAU,CAAC,EAAE,KAAK;AAC/B,YAAM,KAAK,EAAE,MAAM,WAAW,cAAc,IAAI,KAAK,SAAS,CAAC;AAAA,IAChE,WAAW,KAAK,KAAK,MAAM,MAAM,CAAC,KAAK,WAAW,GAAG,KAAK,CAAC,KAAK,WAAW,GAAI,GAAG;AACjF;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;;;AC9BO,IAAM,kBAAN,MAAsB;AAAA,EAC5B,YAA6B,MAAmC;AAAnC;AAAA,EAAoC;AAAA,EAEjE,MAAM,OAAO,SAA2D;AACvE,QAAI,QAAQ,gBAAgB,eAAe,QAAQ,gBAAgB,QAAQ;AAC1E,YAAM,IAAI,MAAM,yBAAyB,OAAO,QAAQ,WAAW,CAAC,EAAE;AAAA,IACvE;AAEA,QACC,QAAQ,WAAW,eACnB,QAAQ,WAAW,UACnB,QAAQ,WAAW,kBAClB;AACD,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS,SAAS,QAAQ,MAAM;AAAA,MACjC;AAAA,IACD;AAEA,QAAI,QAAQ,WAAW,WAAW;AACjC,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS,uDAAuD,QAAQ,MAAM;AAAA,MAC/E;AAAA,IACD;AAEA,UAAM,UAAU,MAAM,KAAK,KAAK,cAAc,WAAW;AACzD,UAAM,cAAc,QAAQ,OAAO;AAAA,MAClC,CAAC,UAAU,KAAK,KAAK,WAAW,uBAAuB,MAAM,EAAE,MAAM,QAAQ;AAAA,IAC9E;AAEA,QAAI,CAAC,aAAa;AACjB,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS;AAAA,MACV;AAAA,IACD;AAEA,QAAI,CAAC,QAAQ,aAAa,KAAK,gBAAgB,YAAY,KAAK,GAAG;AAClE,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO,YAAY;AAAA,MACpB;AAAA,IACD;AAEA,WAAO;AAAA,MACN,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AAAA,EACD;AAAA,EAEA,qBACC,SACA,WACA,SACyB;AACzB,UAAM,QAAQ,0BAA0B,OAAO;AAC/C,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,MAAM,OAAO,CAAC,SAAS,KAAK,cAAc,MAAM,EAAE;AAAA,MACjE,iBAAiB,MAAM,OAAO,CAAC,SAAS,KAAK,cAAc,QAAQ,EAAE;AAAA,MACrE,cAAc,MAAM,OAAO,CAAC,SAAS,KAAK,cAAc,KAAK,EAAE;AAAA,IAChE;AAAA,EACD;AAAA,EAEQ,gBAAgB,OAA0B;AACjD,WAAO,MAAM,KAAK,CAAC,SAAS,cAAc,IAAI,MAAM,MAAM;AAAA,EAC3D;AACD;;;AC9GA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAStB,IAAM,iCAAiC;AACvC,IAAM,kCAAkC;AACxC,IAAM,yCAAyC;AAC/C,IAAM,wBAAwB;AAE9B,SAAS,uBAAuB,SAAyB;AACxD,MACC,OAAO,YAAY,YACnB,QAAQ,WAAW,KACnB,YAAY,OACZ,YAAY,QACZ,QAAQ,SAAS,GAAG,KACpB,QAAQ,SAAS,IAAI,KACrB,CAAC,sBAAsB,KAAK,OAAO,GAClC;AACD,UAAM,IAAI,MAAM,qBAAqB,OAAO,EAAE;AAAA,EAC/C;AAEA,SAAO;AACR;AAEO,IAAM,aAAN,MAAiB;AAAA,EAMvB,YAAY,SAA4B;AACvC,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,gBAAgB,QAAQ,iBAAiB;AAAA,EAC/C;AAAA,EAEA,uBAAuB,UAA0B;AAChD,QAAI,SAAS,WAAW,WAAW,GAAG;AACrC,aAAO,uBAAuB,SAAS,MAAM,YAAY,MAAM,CAAC;AAAA,IACjE;AACA,QAAI,SAAS,WAAW,YAAY,GAAG;AACtC,YAAM,YAAY,SAAS,YAAY,GAAG;AAC1C,aAAO,uBAAuB,SAAS,MAAM,YAAY,CAAC,CAAC;AAAA,IAC5D;AACA,WAAO,uBAAuB,QAAQ;AAAA,EACvC;AAAA,EAEA,6BAAqC;AACpC,WAAO;AAAA,EACR;AAAA,EAEA,4BAAoC;AACnC,WAAO;AAAA,EACR;AAAA,EAEA,wBAAgC;AAC/B,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,MAAM,wBAA2C;AAChD,WAAO,KAAK,aAAkB,UAAK,KAAK,eAAe,8BAA8B,CAAC;AAAA,EACvF;AAAA,EAEA,MAAM,mBAAsC;AAC3C,WAAO,KAAK,aAAa,KAAK,cAAc;AAAA,EAC7C;AAAA,EAEA,MAAM,YAA6C;AAClD,UAAM,KAAK,mBAAmB;AAC9B,QAAI;AACH,YAAM,MAAM,MAAM,KAAK,WAAW;AAAA,QAC5B,UAAK,KAAK,eAAe,+BAA+B;AAAA,QAC7D;AAAA,MACD;AACA,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAI,OAAO,OAAO,kBAAkB,YAAY,CAAC,MAAM,QAAQ,OAAO,eAAe,GAAG;AACvF,eAAO;AAAA,MACR;AACA,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,qBAAoC;AACjD,UAAM,UAAe,UAAK,KAAK,eAAe,+BAA+B;AAC7E,UAAM,aAAkB,UAAK,KAAK,eAAe,sCAAsC;AACvF,QAAI;AACH,YAAM,KAAK,WAAW,SAAS,SAAS,OAAO;AAC/C;AAAA,IACD,QAAQ;AAAA,IAER;AACA,QAAI;AACH,YAAM,gBAAgB,MAAM,KAAK,WAAW,SAAS,YAAY,OAAO;AACxE,YAAM,KAAK,WAAW,MAAW,aAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AACtE,YAAM,KAAK,WAAW,UAAU,SAAS,eAAe,OAAO;AAAA,IAChE,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EAEA,MAAM,WAAW,OAAuC;AACvD,UAAM,YAAiB,UAAK,KAAK,eAAe,+BAA+B;AAC/E,UAAM,KAAK,WAAW,MAAW,aAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AACxE,UAAM,KAAK,WAAW,UAAU,WAAW,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,OAAO;AAAA,EACnF;AAAA,EAEA,MAAM,kBAAkB,OAAsC;AAC7D,UAAM,QACJ,MAAM,KAAK,UAAU,KACrB;AAAA,MACA,eAAe,KAAK;AAAA,MACpB,iBAAiB,CAAC;AAAA,IACnB;AACD,UAAM,kBAAkB,MAAM,gBAAgB,OAAO,CAAC,UAAU,MAAM,OAAO,MAAM,EAAE;AACrF,UAAM,gBAAgB,KAAK,KAAK;AAChC,UAAM,KAAK,WAAW,KAAK;AAAA,EAC5B;AAAA,EAEA,MAAM,qBAAqB,SAAgC;AAC1D,UAAM,QAAQ,MAAM,KAAK,UAAU;AACnC,QAAI,CAAC,OAAO;AACX;AAAA,IACD;AACA,UAAM,kBAAkB,MAAM,gBAAgB,OAAO,CAAC,UAAU,MAAM,OAAO,OAAO;AACpF,UAAM,KAAK,WAAW,KAAK;AAAA,EAC5B;AAAA,EAEA,MAAM,gBACL,SACA,OACA,OACgB;AAChB,UAAM,OACL,UAAU,cACF,UAAK,KAAK,eAAe,8BAA8B,IAC5D,KAAK;AACT,UAAM,YAAY,MAAM,CAAC;AACzB,UAAM,mBACL,MAAM,WAAW,KACjB,cAAc,UACd,UAAU,SAAS,GAAG,OAAO,eAC7B,CAAC,UAAU,KAAK,SAAS,GAAG;AAC7B,UAAM,YAAY,mBAAmB,OAAY,UAAK,MAAM,OAAO;AACnE,UAAM,KAAK,WAAW,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAE1D,eAAW,QAAQ,OAAO;AACzB,YAAM,WAAgB,UAAK,WAAW,KAAK,IAAI;AAC/C,YAAM,KAAK,WAAW,MAAW,aAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACvE,YAAM,KAAK,WAAW,UAAU,UAAU,KAAK,SAAS,OAAO;AAC/D,UAAI,KAAK,YAAY;AACpB,YAAI;AACH,gBAAM,KAAK,WAAW,MAAM,UAAU,GAAK;AAAA,QAC5C,QAAQ;AAAA,QAER;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,aAAa,KAAgC;AAC1D,QAAI;AACH,YAAM,UAAU,MAAM,KAAK,WAAW,QAAQ,KAAK;AAAA,QAClD,eAAe;AAAA,MAChB,CAAC;AACD,YAAM,MAAgB,CAAC;AACvB,iBAAW,SAAS,SAAS;AAC5B,YAAI,MAAM,KAAK,WAAW,GAAG,GAAG;AAC/B;AAAA,QACD;AACA,YAAI,MAAM,YAAY,GAAG;AACxB,cAAI,KAAK,MAAM,IAAI;AACnB;AAAA,QACD;AACA,YAAI,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,WAAW,GAAG;AACvD,cAAI,KAAK,MAAM,KAAK,QAAQ,gBAAgB,EAAE,CAAC;AAAA,QAChD;AAAA,MACD;AACA,aAAO,IAAI,KAAK;AAAA,IACjB,QAAQ;AACP,aAAO,CAAC;AAAA,IACT;AAAA,EACD;AACD;;;ACpIA,IAAM,uBAAuB,KAAK,KAAK;AACvC,IAAM,kCAAkC,KAAK,KAAK,KAAK;AACvD,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAC7B,IAAM,oBAAoB;AAWnB,IAAM,gBAAN,MAAoB;AAAA,EAa1B,YACkB,IACjB,SACC;AAFgB;AAblB,SAAiB,WAAW,oBAAI,IAAwB;AACxD,SAAiB,iBAAiB,oBAAI,IAAqB;AAC3D,SAAiB,YAAY,oBAAI,IAAgB;AAchD,SAAK,OAAO;AAAA,MACX,KAAK,QAAQ;AAAA,MACb,cAAc,QAAQ;AAAA,MACtB,YAAY,QAAQ;AAAA,MACpB,uBAAuB,QAAQ,yBAAyB,CAAC;AAAA,MACzD,YAAY,QAAQ,cAAc;AAAA,MAClC,sBAAsB,QAAQ,wBAAwB;AAAA,MACtD,KAAK,QAAQ,QAAQ,MAAM,KAAK,IAAI;AAAA,IACrC;AAEA,UAAM,uBAAuB,KAAK,GAAG,IAA6B,iBAAiB;AACnF,QAAI,sBAAsB;AACzB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,oBAAoB,GAAG;AAChE,aAAK,eAAe,IAAI,KAAK,KAAK;AAAA,MACnC;AAAA,IACD;AAAA,EACD;AAAA;AAAA,EAGA,YAAY,UAAkC;AAC7C,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,EAC5C;AAAA;AAAA,EAGA,MAAM,YAAY,MAA0D;AAC3E,QAAI,CAAC,MAAM;AACV,aAAO,EAAE,SAAS,OAAO,QAAQ,oBAAoB;AAAA,IACtD;AAGA,QAAI,KAAK,aAAa,eAAe,KAAK,OAAO;AAChD,YAAM,QAAQ,KAAK,eAAe,IAAI,KAAK,MAAM,YAAY,CAAC;AAC9D,UAAI,UAAU,KAAM,QAAO,EAAE,SAAS,KAAK;AAC3C,UAAI,UAAU,OAAO;AACpB,eAAO,EAAE,SAAS,OAAO,QAAQ,cAAc,UAAU,KAAK,MAAM;AAAA,MACrE;AAAA,IACD;AAGA,UAAM,gBAAgB,KAAK,aAAa,WAAW,OAAO;AAC1D,QAAI,CAAC,iBAAiB,KAAK,aAAa,eAAe,KAAK,OAAO;AAClE,YAAM,CAAC,EAAE,SAAS,EAAE,IAAI,KAAK,MAAM,MAAM,GAAG;AAC5C,YAAM,iBACL,OAAO,SAAS,KAAK,KAAK,KAAK,sBAAsB,SAAS,OAAO,YAAY,CAAC;AACnF,UAAI,eAAgB,QAAO,EAAE,SAAS,KAAK;AAE3C,aAAO,EAAE,SAAS,OAAO,QAAQ,cAAc,UAAU,KAAK,MAAM;AAAA,IACrE;AACA,QAAI,CAAC,eAAe;AAEnB,aAAO,EAAE,SAAS,KAAK;AAAA,IACxB;AAEA,UAAM,iBAAiB,cAAc,SAAS,KAAK,SAAS;AAC5D,QAAI,eAAe,WAAW,GAAG;AAChC,aAAO,EAAE,SAAS,OAAO,QAAQ,cAAc,UAAU,GAAG;AAAA,IAC7D;AAGA,UAAM,UAAU,KAAK,gBAAgB;AACrC,QAAI,QAAQ,SAAS,eAAe,YAAY,CAAC,GAAG;AACnD,aAAO,EAAE,SAAS,KAAK;AAAA,IACxB;AAEA,UAAM,WAAW,MAAM,KAAK,mBAAmB,cAAc;AAC7D,WAAO,WACJ,EAAE,SAAS,KAAK,IAChB,EAAE,SAAS,OAAO,QAAQ,cAAc,UAAU,eAAe;AAAA,EACrE;AAAA;AAAA,EAGA,MAAM,YAAY,UAAiC;AAClD,UAAM,OAAO,KAAK,gBAAgB;AAClC,UAAM,MAAM,SAAS,YAAY;AACjC,QAAI,CAAC,KAAK,SAAS,GAAG,GAAG;AACxB,WAAK,KAAK,GAAG;AACb,YAAM,KAAK,GAAG,OAAO,mBAAmB,IAAI;AAAA,IAC7C;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,aAAa,UAAiC;AACnD,UAAM,UAAU,KAAK,gBAAgB,EAAE,OAAO,CAAC,MAAM,MAAM,SAAS,YAAY,CAAC;AACjF,UAAM,KAAK,GAAG,OAAO,mBAAmB,OAAO;AAAA,EAChD;AAAA;AAAA,EAGA,kBAA4B;AAC3B,WAAO,KAAK,GAAG,IAAc,iBAAiB,KAAK,CAAC;AAAA,EACrD;AAAA;AAAA,EAGA,MAAM,mBAAmB,UAAoC;AAC5D,UAAM,MAAM,KAAK,KAAK,IAAI;AAG1B,UAAM,QAAQ,KAAK,SAAS,IAAI,QAAQ;AACxC,QAAI,SAAS,MAAM,MAAM,KAAK,KAAK,KAAK,YAAY;AACnD,aAAO,MAAM;AAAA,IACd;AAGA,UAAM,aAAa,KAAK,GAAG,IAAqB,oBAAoB,KAAK,CAAC;AAC1E,UAAM,kBAAkB,WAAW,QAAQ;AAC3C,QAAI,mBAAmB,MAAM,gBAAgB,KAAK,KAAK,KAAK,sBAAsB;AACjF,WAAK,SAAS,IAAI,UAAU,eAAe;AAC3C,aAAO,gBAAgB;AAAA,IACxB;AAGA,UAAM,QAAQ,MAAM,KAAK,KAAK,aAAa,QAAQ;AACnD,QAAI,CAAC,MAAO,QAAO;AAEnB,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,KAAK,WAAW,OAAO,KAAK,KAAK,GAAG;AAC9D,YAAM,WAAW,KAAK,gBAAgB,UAAU,MAAM;AACtD,UAAI,OAAO,aAAa,WAAW;AAClC,aAAK,YAAY,UAAU,QAAQ;AACnC,eAAO;AAAA,MACR;AAEA,aAAO;AAAA,IACR,QAAQ;AAEP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA,EAGA,eAAe,UAAkB,OAAsB;AACtD,UAAM,MAAM,SAAS,YAAY;AACjC,SAAK,eAAe,IAAI,KAAK,KAAK;AAClC,UAAM,YAAY,KAAK,GAAG,IAA6B,iBAAiB,KAAK,CAAC;AAC9E,cAAU,GAAG,IAAI;AACjB,SAAK,KAAK,GAAG,OAAO,mBAAmB,SAAS;AAChD,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA,EAGA,eAAe,UAAuC;AACrD,WAAO,KAAK,eAAe,IAAI,SAAS,YAAY,CAAC;AAAA,EACtD;AAAA,EAEQ,gBACP,WACA,YACsB;AACtB,QAAI,WAAW,WAAW,YAAY,WAAW,WAAW,UAAW,QAAO;AAC9E,QAAI,WAAW,WAAW,aAAc,QAAO;AAC/C,WAAO;AAAA,EACR;AAAA,EAEQ,YAAY,UAAkB,UAAyB;AAC9D,UAAM,KAAK,KAAK,KAAK,IAAI;AACzB,SAAK,SAAS,IAAI,UAAU,EAAE,UAAU,GAAG,CAAC;AAC5C,UAAM,aAAa,KAAK,GAAG,IAAqB,oBAAoB,KAAK,CAAC;AAC1E,eAAW,QAAQ,IAAI,EAAE,UAAU,GAAG;AACtC,SAAK,KAAK,GAAG,OAAO,sBAAsB,UAAU;AAAA,EACrD;AAAA,EAEQ,kBAAwB;AAC/B,eAAW,YAAY,KAAK,WAAW;AACtC,UAAI;AACH,iBAAS;AAAA,MACV,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AACD;;;AC1PA,SAAS,oBAAoB;AAatB,IAAM,mBAAN,MAAuB;AAAA,EAO7B,YAAY,OAAgC,CAAC,GAAG;AANhD,SAAiB,UAAU,IAAI,aAAa;AAC5C,SAAQ,WAA8B,CAAC;AACvC,SAAQ,iBAAsC;AAC9C,SAAQ,kBAAiC;AACzC,SAAQ,YAA2B;AAGlC,QAAI,KAAK,iBAAiB,QAAW;AACpC,WAAK,QAAQ,gBAAgB,KAAK,YAAY;AAAA,IAC/C;AAAA,EACD;AAAA;AAAA,EAGA,YAAY,UAAkC;AAC7C,SAAK,QAAQ,GAAG,UAAU,QAAQ;AAClC,WAAO,MAAM,KAAK,QAAQ,IAAI,UAAU,QAAQ;AAAA,EACjD;AAAA;AAAA,EAGA,eAAkC;AACjC,WAAO,CAAC,GAAG,KAAK,QAAQ;AAAA,EACzB;AAAA;AAAA,EAGA,YAAY,UAAwB,WAA2C;AAC9E,WAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,YAAY,EAAE,OAAO,SAAS,KAAK;AAAA,EACpF;AAAA;AAAA,EAGA,qBAAqB,UAAgD;AACpE,WAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ,KAAK;AAAA,EAC9D;AAAA;AAAA,EAGA,cAAc,MAA6B;AAC1C,UAAM,MAAM,KAAK,SAAS,UAAU,CAAC,MAAM,EAAE,aAAa,KAAK,YAAY,EAAE,OAAO,KAAK,EAAE;AAC3F,QAAI,OAAO,GAAG;AACb,WAAK,SAAS,GAAG,IAAI;AAAA,IACtB,OAAO;AACN,WAAK,SAAS,QAAQ,IAAI;AAAA,IAC3B;AACA,SAAK,KAAK;AAAA,EACX;AAAA;AAAA,EAGA,cAAc,UAAwB,WAA4B;AACjE,UAAM,SAAS,KAAK,SAAS;AAC7B,SAAK,WAAW,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,EAAE,aAAa,YAAY,EAAE,OAAO,UAAU;AAC5F,UAAM,UAAU,KAAK,SAAS,WAAW;AAEzC,QAAI,WAAW,KAAK,mBAAmB,YAAY,KAAK,oBAAoB,WAAW;AACtF,YAAM,uBAAuB,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC9E,UAAI,CAAC,sBAAsB;AAC1B,aAAK,iBAAiB;AACtB,aAAK,kBAAkB;AAAA,MACxB,OAAO;AACN,cAAM,OAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC9D,YAAI,MAAM;AACT,eAAK,kBAAkB,KAAK;AAAA,QAC7B;AAAA,MACD;AAAA,IACD;AACA,QAAI,QAAS,MAAK,KAAK;AACvB,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,UAAU,UAAwB,WAA6B;AAC9D,UAAM,QACL,cAAc,SACX,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,YAAY,EAAE,OAAO,SAAS,IACvE,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AACrD,QAAI,CAAC,MAAO,QAAO;AACnB,SAAK,iBAAiB;AACtB,SAAK,kBAAkB,MAAM;AAC7B,SAAK,KAAK;AACV,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,oBAAyC;AACxC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,mBAA2C;AAC1C,QAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,gBAAiB,QAAO;AAC1D,WACC,KAAK,SAAS;AAAA,MACb,CAAC,MAAM,EAAE,aAAa,KAAK,kBAAkB,EAAE,OAAO,KAAK;AAAA,IAC5D,KAAK;AAAA,EAEP;AAAA;AAAA,EAGA,gBAAyB;AACxB,WAAO,KAAK,SAAS,SAAS,KAAK,KAAK,mBAAmB;AAAA,EAC5D;AAAA;AAAA,EAGA,YAAwB;AACvB,UAAM,UAA0D,CAAC;AACjE,eAAW,WAAW,KAAK,UAAU;AACpC,cAAQ,QAAQ,QAAQ,IAAI;AAAA,IAC7B;AACA,WAAO;AAAA,MACN,gBAAgB,KAAK;AAAA,MACrB,UAAU;AAAA,MACV,eAAe,KAAK,cAAc;AAAA,MAClC,WAAW,KAAK,aAAa;AAAA,IAC9B;AAAA,EACD;AAAA;AAAA,EAGA,YAAY,SAAuB;AAClC,SAAK,YAAY;AACjB,SAAK,KAAK;AAAA,EACX;AAAA;AAAA,EAGA,aAAmB;AAClB,QAAI,KAAK,cAAc,KAAM;AAC7B,SAAK,YAAY;AACjB,SAAK,KAAK;AAAA,EACX;AAAA;AAAA,EAGA,WAAiB;AAChB,SAAK,WAAW,CAAC;AACjB,SAAK,iBAAiB;AACtB,SAAK,kBAAkB;AACvB,SAAK,YAAY;AACjB,SAAK,KAAK;AAAA,EACX;AAAA,EAEQ,OAAa;AACpB,SAAK,QAAQ,KAAK,QAAQ;AAAA,EAC3B;AACD;;;ACzJO,IAAM,mBAAN,MAAuB;AAAA,EAAvB;AACN,SAAiB,YAAY,oBAAI,IAAiC;AAAA;AAAA;AAAA,EAGlE,SAAS,UAA+B;AACvC,SAAK,UAAU,IAAI,SAAS,YAAY,QAAQ;AAAA,EACjD;AAAA;AAAA,EAGA,IAAI,YAAyC;AAC5C,UAAM,IAAI,KAAK,UAAU,IAAI,UAAU;AACvC,QAAI,CAAC,GAAG;AACP,YAAM,IAAI,MAAM,iCAAiC,UAAU,EAAE;AAAA,IAC9D;AACA,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,OAAO,YAAqD;AAC3D,WAAO,KAAK,UAAU,IAAI,UAAU;AAAA,EACrC;AAAA;AAAA,EAGA,OAAuB;AACtB,WAAO,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,EACjC;AAAA;AAAA,EAGA,IAAI,YAAmC;AACtC,WAAO,KAAK,UAAU,IAAI,UAAU;AAAA,EACrC;AACD;;;ACaO,IAAM,WAAN,MAAe;AAAA,EAMrB,YAAY,MAAuB;AAClC,SAAK,WAAW,IAAI,iBAAiB;AACrC,eAAW,YAAY,KAAK,WAAW;AACtC,WAAK,SAAS,SAAS,QAAQ;AAAA,IAChC;AACA,SAAK,QAAQ,KAAK,gBAAgB,IAAI,iBAAiB;AACvD,SAAK,aAAa,KAAK;AACvB,SAAK,gBAAgB,KAAK;AAAA,EAC3B;AAAA;AAAA,EAGA,SAAqB;AACpB,WAAO,KAAK,MAAM,UAAU;AAAA,EAC7B;AAAA;AAAA,EAGA,eAAkC;AACjC,WAAO,KAAK,MAAM,aAAa;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,MACL,UACA,IACA,gBAC2B;AAC3B,UAAM,eAAe,KAAK,SAAS,IAAI,QAAQ;AAC/C,QAAI;AACH,YAAM,UAAU,MAAM,aAAa,kBAAkB;AACrD,YAAM,GAAG,OAAO;AAChB,YAAM,UAAU,MAAM,aAAa;AAAA,QAClC,QAAQ,WAAa,QAA+C,cAAc,KAAM;AAAA,QACxF;AAAA,MACD;AACA,aAAO,MAAM,KAAK,eAAe,cAAc,OAAO;AAAA,IACvD,SAAS,KAAK;AACb,WAAK,MAAM,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AACvE,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cACL,UACA,YACA,gBAC2B;AAC3B,UAAM,eAAe,KAAK,SAAS,IAAI,QAAQ;AAC/C,QAAI;AACH,YAAM,UAAU,MAAM,aAAa,mBAAmB,YAAY,cAAc;AAChF,aAAO,MAAM,KAAK,eAAe,cAAc,OAAO;AAAA,IACvD,SAAS,KAAK;AACb,WAAK,MAAM,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AACvE,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,qBAII;AACT,UAAM,WAAW,KAAK,MAAM,kBAAkB;AAC9C,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,QAAI,CAAC,QAAS,QAAO;AACrB,UAAM,WAAW,MAAM,KAAK,WAAW,IAAI,EAAE,UAAU,WAAW,QAAQ,GAAG,CAAC;AAC9E,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,EAAE,UAAU,SAAS,OAAO,SAAS,MAAM;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OACL,UAC+D;AAC/D,QAAI,CAAC,UAAU;AAEd,iBAAW,WAAW,KAAK,MAAM,aAAa,GAAG;AAChD,cAAM,KAAK,WAAW,OAAO,EAAE,UAAU,QAAQ,UAAU,WAAW,QAAQ,GAAG,CAAC;AAClF,aAAK,MAAM,cAAc,QAAQ,UAAU,QAAQ,EAAE;AAAA,MACtD;AACA,WAAK,MAAM,SAAS;AACpB,aAAO,EAAE,UAAU,MAAM,SAAS,KAAK;AAAA,IACxC;AAEA,UAAM,sBAAsB,KAAK,MAAM,aAAa,EAAE,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC3F,QAAI,UAAU;AACd,eAAW,WAAW,qBAAqB;AAC1C,YAAM,KAAK,WAAW,OAAO,EAAE,UAAU,WAAW,QAAQ,GAAG,CAAC;AAChE,YAAM,YAAY,KAAK,MAAM,cAAc,UAAU,QAAQ,EAAE;AAC/D,gBAAU,WAAW;AAAA,IACtB;AACA,QAAI,KAAK,MAAM,kBAAkB,MAAM,UAAU;AAEhD,YAAM,iBAAiB,KAAK,MAAM,aAAa,EAAE,CAAC;AAClD,UAAI,gBAAgB;AACnB,aAAK,MAAM,UAAU,eAAe,QAAQ;AAAA,MAC7C;AAAA,IACD;AACA,WAAO,EAAE,UAAU,SAAS,QAAQ;AAAA,EACrC;AAAA;AAAA,EAGA,MAAM,SAAoC;AACzC,UAAM,WAAW,KAAK,MAAM,kBAAkB;AAC9C,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,QAAI,CAAC,YAAY,CAAC,SAAS;AAC1B,aAAO,EAAE,UAAU,KAAK;AAAA,IACzB;AACA,WAAO;AAAA,MACN;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ;AAAA,MACd,WAAW,QAAQ;AAAA,MACnB,OAAO,QAAQ;AAAA,IAChB;AAAA,EACD;AAAA;AAAA,EAGA,eAAe,UAAwB,WAAsC;AAC5E,UAAM,KAAK,KAAK,MAAM,UAAU,UAAU,SAAS;AACnD,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,WAAO;AAAA,MACN,gBAAgB,KAAK,WAAW,KAAK,MAAM,kBAAkB;AAAA,MAC7D;AAAA,IACD;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,cAAiD;AACtD,QAAI,CAAC,KAAK,cAAe,QAAO;AAChC,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,WAAO,KAAK,cAAc,YAAY,OAAO;AAAA,EAC9C;AAAA;AAAA,EAGA,kBAAoC;AACnC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,sBAAwC;AACvC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,mBAA8C;AAC7C,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,MAAc,eACb,cACA,SAC2B;AAC3B,UAAM,YAAY,QAAQ,YAAY,KAAK,IAAI,IAAI,QAAQ,YAAY,MAAO;AAC9E,UAAM,OAAO,MAAM,aAAa,iBAAiB,QAAQ,KAAK;AAC9D,UAAM,cAA+B;AAAA,MACpC,IAAI,KAAK;AAAA,MACT,UAAU,KAAK;AAAA,MACf,aAAa,KAAK;AAAA,MAClB,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK,aAAa;AAAA,IAC9B;AAEA,UAAM,KAAK,WAAW,IAAI,EAAE,UAAU,KAAK,UAAU,WAAW,KAAK,GAAG,GAAG,QAAQ,OAAO;AAAA,MACzF;AAAA,IACD,CAAC;AACD,SAAK,MAAM,cAAc,WAAW;AACpC,SAAK,MAAM,UAAU,KAAK,UAAU,KAAK,EAAE;AAC3C,SAAK,MAAM,WAAW;AACtB,WAAO;AAAA,MACN,UAAU,KAAK;AAAA,MACf,SAAS,gBAAgB,KAAK,SAAS,KAAK,EAAE;AAAA,IAC/C;AAAA,EACD;AACD;;;ACvKO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EACnD,YAAY,SAAiB,OAAiB;AAU7C,UAAM,SAAS,UAAU,SAAY,EAAE,MAAM,IAAI,MAAS;AAC1D,SAAK,OAAO;AAAA,EACb;AACD;AAQO,IAAM,iCAAN,MAAuE;AAAA,EAAvE;AACN,SAAiB,UAAU,oBAAI,IAG7B;AAAA;AAAA,EAEM,aAAa,KAAiC;AACrD,WAAO,GAAG,IAAI,QAAQ,IAAI,IAAI,SAAS;AAAA,EACxC;AAAA,EAEA,MAAM,IACL,KACA,OACA,MACgB;AAChB,SAAK,QAAQ,IAAI,KAAK,aAAa,GAAG,GAAG;AAAA,MACxC;AAAA,MACA,WAAW,MAAM,aAAa;AAAA,MAC9B,UAAU,KAAK,IAAI;AAAA,IACpB,CAAC;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAAgE;AACzE,UAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,aAAa,GAAG,CAAC;AACrD,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO;AAAA,MACN,OAAO,MAAM;AAAA,MACb,UAAU;AAAA,QACT,UAAU,IAAI;AAAA,QACd,WAAW,IAAI;AAAA,QACf,WAAW,MAAM;AAAA,QACjB,UAAU,MAAM;AAAA,MACjB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,OAAO,KAAwC;AACpD,SAAK,QAAQ,OAAO,KAAK,aAAa,GAAG,CAAC;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAsC;AAC3C,UAAM,MAA4B,CAAC;AACnC,eAAW,aAAa,KAAK,QAAQ,KAAK,GAAG;AAC5C,YAAM,SAAS,UAAU,QAAQ,GAAG;AACpC,UAAI,UAAU,EAAG;AACjB,UAAI,KAAK;AAAA,QACR,UAAU,UAAU,MAAM,GAAG,MAAM;AAAA,QACnC,WAAW,UAAU,MAAM,SAAS,CAAC;AAAA,MACtC,CAAC;AAAA,IACF;AACA,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,cAAgC;AACrC,WAAO;AAAA,EACR;AACD;;;AC1JO,SAAS,mBAAmB,OAA2C;AAC7E,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,YAAY,EAAE,SAAS,eAAe;AACxF;AAGO,SAAS,sBAAsB,OAAuB;AAC5D,SAAO,GAAG,KAAK;AAChB;AAMO,SAAS,oBAAoB,OAAe,OAA0C;AAC5F,QAAM,kBAAkB,OAAO,KAAK;AACpC,SAAO,mBAAmB,sBAAsB,KAAK;AACtD;;;ACVA,IAAM,0BAA0B;AAChC,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AAGtB,IAAM,2BAA2B;AACjC,IAAM,0CAA0C;AAShD,SAAS,wBAAwB,OAAyB;AACzD,SAAO,iBAAiB;AACzB;AAgDO,IAAM,qBAAN,MAAkD;AAAA,EAOxD,YAAY,QAAkC;AAN9C,SAAS,aAA2B;AAOnC,QAAI,CAAC,OAAO,YAAY,OAAO,SAAS,WAAW,GAAG;AACrD,YAAM,IAAI,MAAM,oDAAoD;AAAA,IACrE;AACA,SAAK,MAAM;AAAA,MACV,UAAU,OAAO;AAAA,MACjB,eAAe,OAAO,iBAAiB;AAAA,MACvC,UAAU,OAAO,YAAY;AAAA,MAC7B,SAAS,OAAO,WAAW;AAAA,MAC3B,OAAO,OAAO,SAAS;AAAA,MACvB,mBAAmB,OAAO,qBAAqB;AAAA,MAC/C,mBAAmB,OAAO,qBAAqB;AAAA,MAC/C,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO,aAAa;AAAA,MAC/B,4BACC,OAAO,8BAA8B;AAAA,IACvC;AAAA,EACD;AAAA,EAEA,MAAM,kBAAkB,MAAqD;AAC5E,UAAM,OAAO,KAAK,UAAU;AAAA,MAC3B,WAAW,KAAK,IAAI;AAAA,MACpB,OAAO,MAAM,SAAS,KAAK,IAAI;AAAA,IAChC,CAAC;AAED,QAAI;AACJ,aAAS,UAAU,GAAG,WAAW,0BAA0B,WAAW;AACrE,UAAI;AACJ,UAAI;AACH,eAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,eAAe;AAAA,UACvD,QAAQ;AAAA,UACR,SAAS;AAAA,YACR,QAAQ;AAAA,YACR,gBAAgB;AAAA,YAChB,cAAc;AAAA,UACf;AAAA,UACA;AAAA,QACD,CAAC;AAAA,MACF,SAAS,OAAO;AACf,YAAI,CAAC,wBAAwB,KAAK,KAAK,YAAY,0BAA0B;AAC5E,gBAAM;AAAA,QACP;AACA,2BAAmB;AACnB,cAAM,MAAM,KAAK,IAAI,6BAA6B,MAAM,UAAU,EAAE;AACpE;AAAA,MACD;AACA,UAAI,CAAC,KAAK,IAAI;AACb,cAAM,IAAI,MAAM,2CAA2C,KAAK,MAAM,EAAE;AAAA,MACzE;AACA,YAAM,OAAQ,MAAM,KAAK,KAAK;AAC9B,UAAI,CAAC,KAAK,eAAe,CAAC,KAAK,aAAa,CAAC,KAAK,kBAAkB;AACnE,cAAM,IAAI,MAAM,qDAAqD;AAAA,MACtE;AACA,aAAO;AAAA,QACN,UAAU,KAAK;AAAA,QACf,UAAU,KAAK;AAAA,QACf,iBAAiB,KAAK;AAAA,QACtB,WAAW,KAAK,IAAI,IAAI,KAAK,aAAa;AAAA,QAC1C,SAAS,QAAQ,KAAK,gBAAgB,cAAc,KAAK,SAAS;AAAA,MACnE;AAAA,IACD;AAGA,UAAM,oBAAoB,IAAI,MAAM,mCAAmC;AAAA,EACxE;AAAA,EAEA,MAAM,mBACL,YACA,gBACgC;AAChC,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI,iBAAiB,KAAK,IAAI;AAC9B,QAAI,sBAAsB;AAE1B,WAAO,MAAM;AACZ,UAAI,kBAAkB,CAAC,eAAe,GAAG;AACxC,cAAM,IAAI,MAAM,wCAAwC;AAAA,MACzD;AACA,YAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,UAAI,KAAK,IAAI,cAAc,UAAa,UAAU,KAAK,IAAI,WAAW;AACrE,cAAM,IAAI,MAAM,2CAA2C;AAAA,MAC5D;AACA,YAAM,MAAM,cAAc;AAC1B,UAAI,kBAAkB,CAAC,eAAe,GAAG;AACxC,cAAM,IAAI,MAAM,wCAAwC;AAAA,MACzD;AAEA,UAAI;AACJ,UAAI;AACH,eAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU;AAAA,UAClD,QAAQ;AAAA,UACR,SAAS;AAAA,YACR,QAAQ;AAAA,YACR,gBAAgB;AAAA,YAChB,cAAc;AAAA,UACf;AAAA,UACA,MAAM,KAAK,UAAU;AAAA,YACpB,WAAW,KAAK,IAAI;AAAA,YACpB,aAAa;AAAA,YACb,YAAY;AAAA,UACb,CAAC;AAAA,QACF,CAAC;AAAA,MACF,SAAS,OAAO;AACf,YAAI,CAAC,wBAAwB,KAAK,GAAG;AACpC,gBAAM;AAAA,QACP;AAGA,yBAAiB,KAAK,IAAI,KAAK,MAAM,iBAAiB,GAAG,GAAG,KAAK,IAAI,iBAAiB;AACtF;AAAA,MACD;AACA,UAAI,CAAC,KAAK,IAAI;AAEb,yBAAiB,KAAK,IAAI,KAAK,MAAM,iBAAiB,GAAG,GAAG,KAAK,IAAI,iBAAiB;AACtF;AAAA,MACD;AACA,YAAM,OAAQ,MAAM,KAAK,KAAK;AAC9B,UAAI,KAAK,cAAc;AACtB,cAAM,UAAU,MAAM,KAAK,gBAAgB,KAAK,YAAY;AAC5D,cAAM,QAAQ,MAAM,KAAK,aAAa,KAAK,cAAc,OAAO;AAChE,eAAO;AAAA,UACN,OAAO,KAAK;AAAA,UACZ,cAAc,KAAK;AAAA,UACnB,WAAW,KAAK;AAAA,UAChB,MAAM;AAAA,YACL,IAAI,OAAO,QAAQ,EAAE;AAAA,YACrB,OAAO,QAAQ;AAAA,YACf,MAAM,QAAQ,QAAQ;AAAA,YACtB,OAAO,SAAS;AAAA,YAChB,WAAW,QAAQ,cAAc;AAAA,UAClC;AAAA,QACD;AAAA,MACD;AACA,UAAI,KAAK,UAAU,yBAAyB;AAE3C;AAAA,MACD;AACA,UAAI,KAAK,UAAU,aAAa;AAC/B;AACA,yBAAiB,KAAK;AAAA,UACrB,KAAK,MAAM,iBAAiB,MAAM,KAAK,IAAI,sBAAsB,KAAK,GAAI,CAAC;AAAA,UAC3E,KAAK,IAAI;AAAA,QACV;AACA;AAAA,MACD;AACA,UAAI,KAAK,UAAU,iBAAiB;AACnC,cAAM,IAAI,MAAM,2BAA2B;AAAA,MAC5C;AACA,UAAI,KAAK,UAAU,iBAAiB;AACnC,cAAM,IAAI,MAAM,oDAA+C;AAAA,MAChE;AACA,YAAM,IAAI,MAAM,6BAA6B,KAAK,SAAS,SAAS,EAAE;AAAA,IACvE;AAAA,EACD;AAAA,EAEA,MAAM,mBAAmB,cAAqD;AAC7E,QAAI,CAAC,cAAc;AAClB,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACxE;AACA,UAAM,OAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU;AAAA,MACxD,QAAQ;AAAA,MACR,SAAS;AAAA,QACR,QAAQ;AAAA,QACR,gBAAgB;AAAA,QAChB,cAAc;AAAA,MACf;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACpB,WAAW,KAAK,IAAI;AAAA,QACpB,YAAY;AAAA,QACZ,eAAe;AAAA,MAChB,CAAC;AAAA,IACF,CAAC;AACD,QAAI,CAAC,KAAK,IAAI;AACb,YAAM,IAAI,MAAM,+BAA+B,KAAK,MAAM,EAAE;AAAA,IAC7D;AACA,UAAM,OAAQ,MAAM,KAAK,KAAK;AAC9B,QAAI,CAAC,KAAK,cAAc;AACvB,YAAM,IAAI,MAAM,0BAA0B,KAAK,SAAS,iBAAiB,EAAE;AAAA,IAC5E;AACA,UAAM,UAAU,MAAM,KAAK,gBAAgB,KAAK,YAAY;AAC5D,UAAM,QAAQ,MAAM,KAAK,aAAa,KAAK,cAAc,OAAO;AAChE,WAAO;AAAA,MACN,OAAO,KAAK;AAAA,MACZ,cAAc,KAAK,iBAAiB;AAAA,MACpC,WAAW,KAAK;AAAA,MAChB,MAAM;AAAA,QACL,IAAI,OAAO,QAAQ,EAAE;AAAA,QACrB,OAAO,QAAQ;AAAA,QACf,MAAM,QAAQ,QAAQ;AAAA,QACtB,OAAO,SAAS;AAAA,QAChB,WAAW,QAAQ,cAAc;AAAA,MAClC;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,cAAc,OAAiC;AACpD,QAAI;AACH,YAAM,OAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,SAAS;AAAA,QACvD,SAAS;AAAA,UACR,QAAQ;AAAA,UACR,eAAe,UAAU,KAAK;AAAA,UAC9B,cAAc;AAAA,QACf;AAAA,MACD,CAAC;AACD,UAAI,KAAK,WAAW,IAAK,QAAO;AAChC,UAAI,CAAC,KAAK,IAAI;AACb,cAAM,IAAI,MAAM,8BAA8B,KAAK,MAAM,EAAE;AAAA,MAC5D;AACA,aAAO;AAAA,IACR,SAAS,KAAK;AAEb,UAAI,eAAe,SAAS,IAAI,QAAQ,SAAS,KAAK,EAAG,QAAO;AAChE,YAAM;AAAA,IACP;AAAA,EACD;AAAA,EAEA,MAAM,iBAAiB,OAAyC;AAC/D,UAAM,OAAO,MAAM,KAAK,gBAAgB,KAAK;AAC7C,UAAM,QAAQ,MAAM,KAAK,aAAa,OAAO,IAAI;AACjD,WAAO;AAAA,MACN,IAAI,OAAO,KAAK,EAAE;AAAA,MAClB,UAAU,KAAK;AAAA,MACf,aAAa,KAAK,QAAQ,KAAK;AAAA,MAC/B,OAAO,KAAK;AAAA,MACZ,OAAO,SAAS,sBAAsB,KAAK,KAAK;AAAA,MAChD,WAAW,KAAK,cAAc;AAAA,MAC9B,WAAW;AAAA,IACZ;AAAA,EACD;AAAA,EAEA,MAAc,gBAAgB,OAA4C;AACzE,UAAM,OAAO,MAAM,KAAK,IAAI,UAAU,KAAK,IAAI,SAAS;AAAA,MACvD,SAAS;AAAA,QACR,QAAQ;AAAA,QACR,eAAe,UAAU,KAAK;AAAA,QAC9B,cAAc;AAAA,MACf;AAAA,IACD,CAAC;AACD,QAAI,CAAC,KAAK,IAAI;AACb,YAAM,IAAI,MAAM,6BAA6B,KAAK,MAAM,EAAE;AAAA,IAC3D;AACA,WAAQ,MAAM,KAAK,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,aAAa,OAAe,MAAkD;AAC3F,QAAI,KAAK,MAAO,QAAO,KAAK;AAC5B,UAAM,aAAa,MAAM,KAAK,IAAI,UAAU,sCAAsC;AAAA,MACjF,SAAS;AAAA,QACR,QAAQ;AAAA,QACR,eAAe,UAAU,KAAK;AAAA,QAC9B,cAAc;AAAA,MACf;AAAA,IACD,CAAC;AACD,QAAI,CAAC,WAAW,IAAI;AACnB,aAAO;AAAA,IACR;AACA,UAAM,SAAU,MAAM,WAAW,KAAK;AAMtC,UAAM,UAAU,OAAO,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ;AAC1D,WAAO,SAAS,SAAS;AAAA,EAC1B;AACD;AAEA,SAAS,MAAM,IAA2B;AACzC,SAAO,IAAI,QAAQ,CAACA,aAAY,WAAWA,UAAS,EAAE,CAAC;AACxD;;;ACxVO,IAAM,kCAAN,cAA8C,MAAM;AAAA,EAC1D,YACC,UAAU,uFACT;AACD,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACb;AACD;AAEO,IAAM,wBAAN,MAAqD;AAAA,EAArD;AACN,SAAS,aAA2B;AAAA;AAAA,EAEpC,MAAM,oBAA8C;AACnD,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AAAA,EAEA,MAAM,mBACL,aACA,iBACgC;AAChC,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AAAA,EAEA,MAAM,mBAAmB,eAAsD;AAC9E,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AAAA,EAEA,MAAM,cAAc,QAAkC;AACrD,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AAAA,EAEA,MAAM,iBAAiB,QAA0C;AAChE,UAAM,IAAI,gCAAgC;AAAA,EAC3C;AACD;;;AC/DA;AAAA,EACC;AAAA,EAEA,wBAAAC;AAAA,OACM;;;ACJP,SAAS,aAAa;AAEtB,SAAS,wBAAwB,OAIxB;AACR,MAAI,MAAM,QAAQ;AACjB;AAAA,EACD;AAEA,MAAI,QAAQ,aAAa,WAAW,MAAM,KAAK;AAC9C,UAAM,cAAc,MAAM,YAAY,CAAC,QAAQ,OAAO,MAAM,GAAG,GAAG,MAAM,IAAI,GAAG;AAAA,MAC9E,OAAO;AAAA,MACP,aAAa;AAAA,IACd,CAAC;AAED,gBAAY,KAAK,SAAS,MAAM;AAC/B,YAAM,KAAK;AAAA,IACZ,CAAC;AACD;AAAA,EACD;AAEA,QAAM,KAAK,SAAS;AACrB;AAiBA,eAAsB,WACrB,SACA,UAA6B,CAAC,GACF;AAC5B,SAAO,IAAI,QAA0B,CAACC,UAAS,WAAW;AACzD,UAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ,CAAC,GAAG;AAAA,MAChD,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,OAAO,QAAQ;AAAA,MACf,OAAO;AAAA,MACP,aAAa;AAAA,IACd,CAAC;AAED,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,WAAW;AACf,QAAI;AAEJ,UAAM,SAAS,CAAC,YAAwB;AACvC,UAAI,UAAU;AACb;AAAA,MACD;AAEA,iBAAW;AACX,UAAI,WAAW;AACd,qBAAa,SAAS;AAAA,MACvB;AACA,cAAQ;AAAA,IACT;AAEA,UAAM,QAAQ,YAAY,MAAM;AAChC,UAAM,QAAQ,YAAY,MAAM;AAEhC,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC3C,gBAAU;AAAA,IACX,CAAC;AAED,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC3C,gBAAU;AAAA,IACX,CAAC;AAED,UAAM,KAAK,SAAS,CAAC,UAAU;AAC9B,aAAO,MAAM,OAAO,KAAK,CAAC;AAAA,IAC3B,CAAC;AAED,UAAM,KAAK,SAAS,CAAC,SAAS;AAC7B,aAAO,MAAM;AACZ,YAAI,SAAS,GAAG;AACf,UAAAA,SAAQ;AAAA,YACP;AAAA,YACA;AAAA,YACA,MAAM;AAAA,UACP,CAAC;AACD;AAAA,QACD;AAEA,cAAM,QAAQ,IAAI;AAAA,UACjB,UAAU,UAAU,iCAAiC,OAAO,QAAQ,CAAC,CAAC;AAAA,QACvE;AAKA,cAAM,OAAO,QAAQ;AACrB,cAAM,SAAS;AACf,cAAM,SAAS;AACf,eAAO,KAAK;AAAA,MACb,CAAC;AAAA,IACF,CAAC;AAED,QAAI,QAAQ,UAAU,QAAW;AAChC,YAAM,OAAO,IAAI,QAAQ,KAAK;AAAA,IAC/B,OAAO;AACN,YAAM,OAAO,IAAI;AAAA,IAClB;AAEA,QAAI,QAAQ,WAAW;AACtB,kBAAY,WAAW,MAAM;AAC5B,eAAO,MAAM;AACZ,kCAAwB,KAAK;AAC7B,gBAAM,QAAQ,IAAI;AAAA,YACjB,2BAA2B,OAAO,QAAQ,SAAS,CAAC;AAAA,UACrD;AAKA,gBAAM,OAAO;AACb,gBAAM,SAAS;AACf,gBAAM,SAAS;AACf,iBAAO,KAAK;AAAA,QACb,CAAC;AAAA,MACF,GAAG,QAAQ,SAAS;AAAA,IACrB;AAAA,EACD,CAAC;AACF;AAEA,eAAsB,cAAc,SAAmC;AACtE,QAAM,YAAY,QAAQ,aAAa;AAEvC,MAAI;AACH,UAAM,WAAW,YAAY,UAAU,SAAS;AAAA,MAC/C,MAAM,CAAC,OAAO;AAAA,MACd,WAAW;AAAA,IACZ,CAAC;AACD,WAAO;AAAA,EACR,QAAQ;AACP,WAAO;AAAA,EACR;AACD;;;AD/IA,IAAM,kBAAkB;AACxB,IAAM,aAAa;AAMnB,eAAsB,yBAA2C;AAChE,MAAI;AACH,UAAM,WAAW,YAAY;AAAA,MAC5B,MAAM,CAAC,QAAQ,QAAQ;AAAA,MACvB,WAAW;AAAA,IACZ,CAAC;AACD,WAAO;AAAA,EACR,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,eAAsB,gBAA8C;AACnE,QAAM,SAAS,MAAM,cAAc,eAAe;AAClD,MAAI,CAAC,QAAQ;AACZ,WAAO,EAAE,WAAW,OAAO,SAAS,MAAM,eAAe,MAAM;AAAA,EAChE;AAEA,MAAI,UAAyB;AAC7B,MAAI;AACH,UAAM,gBAAgB,MAAM,WAAW,iBAAiB;AAAA,MACvD,MAAM,CAAC,WAAW;AAAA,MAClB,WAAW;AAAA,IACZ,CAAC;AACD,cAAU,cAAc,OAAO,KAAK;AAAA,EACrC,QAAQ;AACP,WAAO,EAAE,WAAW,MAAM,SAAS,MAAM,eAAe,MAAM;AAAA,EAC/D;AAGA,QAAM,gBAAgB,MAAM,uBAAuB;AAEnD,SAAO,EAAE,WAAW,MAAM,SAAS,cAAc;AAClD;AAEO,SAAS,iCAAiC;AAChD,SAAOC;AAAA,IACN,oBAAoB;AAAA,IACpB;AAAA,EACD;AACD;AAEO,SAAS,iCAAiC;AAChD,SAAOA;AAAA,IACN,oBAAoB;AAAA,IACpB;AAAA,EACD;AACD;;;AE7DA;AAAA,EACC,uBAAAC;AAAA,EAGA,wBAAAC;AAAA,EACA;AAAA,OACM;AAGP,IAAMC,mBAAkB;AACxB,IAAM,qBAAqB;AAG3B,SAAS,UAAU,MAAsB;AAExC,SAAO,KAAK,QAAQ,0BAA0B,EAAE;AACjD;AAEA,eAAsB,cAAc,SAA6D;AAChG,QAAM,OAAO,CAAC,MAAM,QAAQ,MAAM;AAElC,MAAI,QAAQ,WAAW;AACtB,SAAK,KAAK,aAAa,QAAQ,SAAS;AAAA,EACzC;AAEA,MAAI,QAAQ,WAAW;AACtB,SAAK,KAAK,mBAAmB;AAAA,EAC9B;AAEA,MAAI,QAAQ,YAAY,QAAQ;AAC/B,eAAW,QAAQ,QAAQ,YAAY;AACtC,WAAK,KAAK,gBAAgB,IAAI;AAAA,IAC/B;AAAA,EACD;AAEA,MAAI,QAAQ,OAAO;AAClB,SAAK,KAAK,WAAW,QAAQ,KAAK;AAAA,EACnC;AAEA,MAAI,QAAQ,OAAO;AAClB,SAAK,KAAK,WAAW,QAAQ,KAAK;AAAA,EACnC;AAEA,QAAM,UAAU,QAAQ,WAAW;AAEnC,MAAI;AACH,UAAM,SAAS,MAAM,WAAWA,kBAAiB;AAAA,MAChD;AAAA,MACA,KAAK,QAAQ;AAAA,MACb,WAAW;AAAA,IACZ,CAAC;AAED,UAAM,SAAS,UAAU,OAAO,MAAM;AAGtC,QACC,OAAO,SAAS,gCAAgC,KAChD,OAAO,QAAQ,SAAS,qCAAqC,GAC5D;AACD,YAAMC;AAAA,QACLC,qBAAoB;AAAA,QACpB;AAAA,QACA,EAAE,UAAU,GAAG,QAAQ,QAAQ,OAAO,OAAO;AAAA,MAC9C;AAAA,IACD;AAEA,WAAO,EAAE,QAAQ,UAAU,EAAE;AAAA,EAC9B,SAAS,OAAgB;AAIxB,QAAI,iBAAiB,wBAAwB;AAC5C,YAAM;AAAA,IACP;AAEA,UAAM,MAAM;AAOZ,QAAI,IAAI,SAAS,aAAa;AAC7B,YAAMD;AAAA,QACLC,qBAAoB;AAAA,QACpB,kCAAkC,OAAO,OAAO,CAAC;AAAA,MAClD;AAAA,IACD;AAEA,UAAM,WAAW,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AAC3D,UAAM,SAAS,UAAU,IAAI,UAAU,EAAE;AACzC,UAAM,SAAS,IAAI,UAAU,IAAI,WAAW;AAG5C,QACC,OAAO,SAAS,gCAAgC,KAChD,OAAO,SAAS,qCAAqC,GACpD;AACD,YAAMD;AAAA,QACLC,qBAAoB;AAAA,QACpB;AAAA,QACA,EAAE,UAAU,QAAQ,OAAO;AAAA,MAC5B;AAAA,IACD;AAEA,QAAI,aAAa,KAAK,QAAQ;AAC7B,aAAO,EAAE,QAAQ,SAAS;AAAA,IAC3B;AAEA,UAAMD;AAAA,MACLC,qBAAoB;AAAA,MACpB,UAAU,4BAA4B,OAAO,QAAQ,CAAC;AAAA,MACtD,EAAE,UAAU,OAAO;AAAA,IACpB;AAAA,EACD;AACD;;;ACnGA,SAAS,kBAAkB;AAGpB,IAAM,oBAAoB;AAAA,EAChC,UAAU;AAAA,EACV,cAAc;AAAA,EACd,WAAW;AAAA,EACX,WAAW;AAAA,EACX,eAAe;AAChB;AAiBO,SAAS,6BAA6B,QAA8C;AAC1F,QAAM,QAAQ;AAAA,IACb,OAAO,OAAO,YAAY;AAAA,IAC1B,OAAO;AAAA,IACP,OAAO,OAAO,SAAS;AAAA,IACvB,OAAO;AAAA,IACP,OAAO;AAAA,EACR,EAAE,KAAK,IAAI;AACX,SAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AACvD;AA2BO,SAAS,mBAAmB,QAAuD;AACzF,QAAM,YAAY,OAAO,aAAa,KAAK,IAAI;AAC/C,QAAM,YAAY,6BAA6B;AAAA,IAC9C,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,IACb;AAAA,IACA,MAAM,OAAO;AAAA,IACb,QAAQ,OAAO;AAAA,EAChB,CAAC;AACD,SAAO;AAAA,IACN,CAAC,kBAAkB,QAAQ,GAAG,OAAO;AAAA,IACrC,CAAC,kBAAkB,YAAY,GAAG,OAAO;AAAA,IACzC,CAAC,kBAAkB,SAAS,GAAG;AAAA,IAC/B,CAAC,kBAAkB,SAAS,GAAG,OAAO,SAAS;AAAA,IAC/C,CAAC,kBAAkB,aAAa,GAAG,OAAO,OAAO,aAAa;AAAA,EAC/D;AACD;;;AChEA,SAAS,mBAAmB;;;ACR5B,SAAS,cAAAC,aAAY,kBAAkB;AACvC,YAAY,QAAQ;AAGpB,SAAS,sBAA8B;AAItC,MAAI,WAAW;AACf,MAAI;AACH,eAAc,YAAS,EAAE;AAAA,EAC1B,QAAQ;AACP,eAAW,QAAQ,IAAI,QAAQ,QAAQ,IAAI,YAAY;AAAA,EACxD;AACA,SAAO;AAAA,IACH,YAAS;AAAA,IACZ;AAAA,IACG,YAAS;AAAA,IACT,QAAK;AAAA,IACR,QAAQ,SAAS,QAAQ;AAAA,EAC1B,EAAE,KAAK,GAAG;AACX;AAOO,SAAS,uBAA+B;AAC9C,QAAM,WAAW,oBAAoB;AACrC,QAAM,SAASA,YAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAK;AAIjE,SAAO,WAAW,OAAO,MAAM,GAAG,EAAE,CAAC;AACtC;AAOO,SAAS,uBAA+B;AAC9C,SAAO,WAAW;AACnB;AAGO,SAAS,oBAA4B;AAC3C,SAAO,oBAAoB;AAC5B;AAEA,SAAS,WAAW,OAAuB;AAG1C,QAAM,QAAQ,MAAM,MAAM,EAAE;AAE5B,QAAM,aAAa;AACnB,QAAM,aAAa;AACnB,QAAM,cAAe,SAAS,MAAM,UAAU,KAAK,KAAK,EAAE,IAAI,IAAO;AACrE,QAAM,UAAU,IAAI,YAAY,SAAS,EAAE;AAE3C,QAAM,cAAe,SAAS,MAAM,UAAU,KAAK,KAAK,EAAE,IAAI,IAAO;AACrE,QAAM,UAAU,IAAI,YAAY,SAAS,EAAE;AAC3C,QAAM,YAAY,MAAM,KAAK,EAAE;AAC/B,SAAO,GAAG,UAAU,MAAM,GAAG,CAAC,CAAC,IAAI,UAAU,MAAM,GAAG,EAAE,CAAC,IAAI,UAAU,MAAM,IAAI,EAAE,CAAC,IAAI,UAAU,MAAM,IAAI,EAAE,CAAC,IAAI,UAAU,MAAM,IAAI,EAAE,CAAC;AAC3I;;;ADjDA,IAAM,eAAe;AAErB,IAAM,kBAAkB;AAIxB,IAAM,qBAAoC,CAAC,SAAS;AACnD,SAAO,YAAY,IAAI;AACxB;AA8BO,IAAM,kCAAN,cAA8C,MAAM;AAAA,EAC1D,YACC,UAAU,uFACT;AACD,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACb;AACD;AAGO,IAAM,mCAAN,cAA+C,MAAM;AAAA,EAC3D,YAAY,UAAU,gFAA2E;AAChG,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACb;AACD;AAEO,IAAM,WAAN,MAAe;AAAA,EAOrB,YAAY,MAAuB;AAFnC,SAAQ,WAA+C;AAGtD,SAAK,WAAW,KAAK;AACrB,SAAK,MAAM,KAAK,QAAQ,MAAM,oBAAI,KAAK;AACvC,SAAK,SAAS,KAAK,eAAe;AAClC,SAAK,gBAAgB,KAAK;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAA4C;AACjD,UAAM,SAAS,MAAM,KAAK,SAAS,KAAK;AACxC,WAAO,QAAQ,gBAAgB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,oBAAmC;AACxC,QAAI,KAAK,UAAU;AAClB,YAAM,KAAK;AAAA,IACZ;AAAA,EACD;AAAA,EAEA,MAAM,OAAO,OAAmD,CAAC,GAAgC;AAEhG,QAAI,KAAK,UAAU;AAClB,aAAO,KAAK;AAAA,IACb;AACA,UAAM,UAAU,KAAK,UAAU,IAAI;AACnC,SAAK,WAAW;AAChB,QAAI;AACH,aAAO,MAAM;AAAA,IACd,UAAE;AACD,UAAI,KAAK,aAAa,QAAS,MAAK,WAAW;AAAA,IAChD;AAAA,EACD;AAAA;AAAA,EAGA,mBAAkC;AACjC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,cAAuB;AACtB,WAAO,KAAK,aAAa;AAAA,EAC1B;AAAA,EAEA,MAAc,UAAU,MAGQ;AAC/B,UAAM,EAAE,QAAQ,IAAI,MAAM,KAAK,SAAS,OAAO,OAAO,YAAY;AACjE,YAAM,WAAW,KAAK,QAAQ,OAAO;AAErC,UAAI,CAAC,KAAK,SAAS,SAAS;AAC3B,YAAI,QAAQ,iBAAiB,WAAW;AAAA,QAExC,WACC,KAAK,gBACJ,QAAQ,iBAAiB,aAAa,QAAQ,iBAAiB,YAC/D;AAID,gBAAM,IAAI,gCAAgC;AAAA,QAC3C;AAAA,MACD;AAEA,YAAM,iBAAiB,SAAS,kBAAkB,qBAAqB;AACvE,YAAM,YAAY,SAAS,aAAa;AACxC,YAAMC,YAAW,SAAS,YAAY;AAEtC,UAAI;AACJ,UAAI,KAAK,eAAe;AACvB,mBAAW,MAAM,KAAK,cAAc;AAAA,UACnC;AAAA,UACA;AAAA,UACA,UAAAA;AAAA,UACA;AAAA,UACA,OAAO,QAAQ,KAAK,KAAK;AAAA,UACzB,aAAa,QAAQ,KAAK,WAAW;AAAA,QACtC,CAAC;AAAA,MACF,OAAO;AAIN,mBAAW,yBAAyB,KAAK,QAAQ,QAAQ;AAAA,MAC1D;AAEA,YAAM,OAAgC;AAAA,QACrC,SAAS,SAAS,WAAW;AAAA,QAC7B;AAAA,QACA;AAAA,QACA,UAAAA;AAAA,QACA,UAAU,SAAS;AAAA,QACnB,UAAU,SAAS;AAAA,QACnB,eAAe,SAAS;AAAA,QACxB,cAAc,SAAS;AAAA,QACvB,cAAc,SAAS;AAAA,QACvB,sBAAsB,UAAU;AAAA,QAChC,yBACC,KAAK,SAAS,SAAS,mBAAmB,UAAU,iBAAiB,KAAK,IACvE,SACA,UAAU;AAAA,QACd,cAAc,KAAK,IAAI,EAAE,YAAY;AAAA,QACrC,YAAY,UAAU;AAAA,QACtB,eAAe;AAAA,MAChB;AACA,aAAO,EAAE,KAAK;AAAA,IACf,CAAC;AAED,WAAO;AAAA,MACN,UAAU,QAAQ;AAAA,MAClB,cAAc,QAAQ;AAAA,MACtB,WAAW,gBAAgB,SAAS,KAAK,GAAG;AAAA,IAC7C;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aACL,OAAqC,CAAC,GAC0C;AAChF,UAAM,kBAAkB,KAAK,mBAAmB;AAChD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,YAAY,KAAK,OAAO,YAAY,EAAE,SAAS,KAAK;AAE1D,UAAM,EAAE,QAAQ,IAAI,MAAM,KAAK,SAAS,OAAO,OAAO,YAAY;AACjE,UAAI,CAAC,SAAS;AACb,cAAM,IAAI,MAAM,iDAAiD;AAAA,MAClE;AACA,YAAM,iBAAiB,IAAI,KAAK,IAAI,QAAQ,IAAI,kBAAkB,KAAK,KAAK,KAAK,GAAI;AACrF,YAAM,OAAgC;AAAA,QACrC,GAAG;AAAA,QACH,cAAc;AAAA,QACd,sBAAsB,QAAQ;AAAA,QAC9B,yBAAyB,eAAe,YAAY;AAAA,QACpD,eAAe,QAAQ,gBAAgB;AAAA,QACvC,cAAc,IAAI,YAAY;AAAA,MAC/B;AACA,aAAO,EAAE,KAAK;AAAA,IACf,CAAC;AAED,WAAO;AAAA,MACN,UAAU,QAAQ;AAAA,MAClB,eAAe,QAAQ;AAAA,MACvB;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAA6B;AAClC,UAAM,KAAK,SAAS,OAAO,OAAO,YAAY;AAC7C,UAAI,CAAC,SAAS;AAEb,eAAO,EAAE,MAAM,WAAY,MAAM,cAAc,KAAK,MAAM,GAAI,QAAQ,OAAU;AAAA,MACjF;AACA,YAAM,OAAgC,EAAE,GAAG,SAAS,cAAc,UAAU;AAC5E,aAAO,EAAE,KAAK;AAAA,IACf,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAA6B;AAClC,UAAM,KAAK,SAAS,OAAO,OAAO,YAAY;AAC7C,UAAI,CAAC,SAAS;AACb,cAAM,IAAI,MAAM,gDAAgD;AAAA,MACjE;AACA,YAAM,OAAgC;AAAA,QACrC,GAAG;AAAA,QACH,cAAc;AAAA,QACd,YAAY,KAAK,IAAI,EAAE,YAAY;AAAA,QACnC,eAAe;AAAA,MAChB;AACA,aAAO,EAAE,KAAK;AAAA,IACf,CAAC;AAAA,EACF;AACD;AAEA,SAAS,gBAAgB,WAAoC,MAAsC;AAKlG,SAAO;AACR;AAEA,SAAS,yBACR,QACA,UACiB;AACjB,QAAM,WAAW,UAAU,YAAY,OAAO,eAAe,EAAE,SAAS,KAAK;AAC7E,QAAM,iBAAiB,UAAU,iBAAiB,KAAK;AACvD,SAAO;AAAA,IACN;AAAA,IACA,cAAc,OAAO,YAAY,EAAE,SAAS,KAAK;AAAA,IACjD;AAAA,IACA,cAAc,UAAU,iBAAiB,YAAY,YAAY;AAAA,EAClE;AACD;AAEA,eAAe,cAAc,QAAyD;AACrF,SAAO;AAAA,IACN,SAAS;AAAA,IACT,gBAAgB,qBAAqB;AAAA,IACrC,WAAW;AAAA,IACX,UAAU;AAAA,IACV,UAAU,OAAO,eAAe,EAAE,SAAS,KAAK;AAAA,IAChD,eAAe;AAAA,IACf,cAAc;AAAA,IACd,cAAc,OAAO,YAAY,EAAE,SAAS,KAAK;AAAA,IACjD,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtC;AACD;;;AErTA,YAAY,SAAS;AACrB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AACtB,SAAS,cAAc,aAAa;;;AC/BpC,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AAef,IAAM,qBAAqB;AAC3B,IAAM,eAAe;AACrB,IAAM,eAAe;AACrB,IAAM,gBAAgB;AACtB,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAC5B,IAAM,wBAAwB;AAO9B,IAAM,uBAAuB;AAG7B,IAAM,qBAAqB;AAalC,IAAI,kBAAqC,CAAC;AAEnC,SAAS,qBAAqB,WAAoC;AACxE,oBAAkB,EAAE,GAAG,UAAU;AAClC;AAEO,SAAS,yBAA+B;AAC9C,oBAAkB,CAAC;AACpB;AAEA,SAAS,iBAAyB;AACjC,QAAM,WAAW,gBAAgB;AACjC,MAAI,aAAa,QAAW;AAC3B,WAAO;AAAA,EACR;AACA,SAAU,YAAQ;AACnB;AAEA,SAAS,0BAA8C;AACtD,QAAM,WAAW,gBAAgB;AACjC,MAAI,aAAa,QAAW;AAG3B,WAAO,SAAS,SAAS,IAAI,WAAW;AAAA,EACzC;AACA,QAAM,WAAW,QAAQ,IAAI,kBAAkB;AAC/C,SAAO,YAAY,SAAS,SAAS,IAAI,WAAW;AACrD;AAEA,SAAS,kBAAmC;AAC3C,SAAO,gBAAgB,YAAY,QAAQ;AAC5C;AAEO,SAAS,iBAAiB,QAAwB;AACxD,MAAI,OAAO,WAAW,YAAY,OAAO,WAAW,KAAK,CAAC,qBAAqB,KAAK,MAAM,GAAG;AAC5F,UAAM,IAAI;AAAA,MACT,oBAAoB,KAAK,UAAU,MAAM,CAAC,gBAC3B,oBAAoB;AAAA,IAEpC;AAAA,EACD;AACA,SAAO;AACR;AASO,SAAS,aAAqB;AACpC,SAAO,eAAe;AACvB;AAOO,SAAS,mBAA2B;AAC1C,QAAM,WAAW,wBAAwB;AACzC,MAAI,aAAa,QAAW;AAC3B,WAAY,cAAQ,QAAQ;AAAA,EAC7B;AACA,SAAY,WAAK,eAAe,GAAG,kBAAkB;AACtD;AAGO,SAAS,cAAsB;AACrC,SAAY,WAAK,iBAAiB,GAAG,YAAY;AAClD;AAGO,SAAS,WAAW,QAAwB;AAClD,SAAY,WAAK,YAAY,GAAG,iBAAiB,MAAM,CAAC;AACzD;AAGO,SAAS,cAAsB;AACrC,SAAY,WAAK,iBAAiB,GAAG,YAAY;AAClD;AAGO,SAAS,gBAAgB,QAAwB;AACvD,SAAY,WAAK,YAAY,GAAG,iBAAiB,MAAM,CAAC;AACzD;AAGO,SAAS,eAAuB;AACtC,SAAY,WAAK,iBAAiB,GAAG,aAAa;AACnD;AAGO,SAAS,oBAA4B;AAC3C,SAAY,WAAK,aAAa,GAAG,mBAAmB;AACrD;AAGO,SAAS,oBAA4B;AAC3C,SAAY,WAAK,aAAa,GAAG,mBAAmB;AACrD;AAOO,SAAS,qBAA6B;AAC5C,SAAY,WAAK,iBAAiB,GAAG,qBAAqB;AAC3D;AAOO,SAAS,kBAAmC;AAClD,SAAO,gBAAgB;AACxB;AAMO,IAAM,kCAAkC;AACxC,IAAM,+BAA+B;AACrC,IAAM,yBAAyB;AAC/B,IAAM,0BAA0B;AAChC,IAAM,yBAAyB;AAC/B,IAAM,8BAA8B;AACpC,IAAM,4BAA4B;AAGlC,SAAS,8BAAsC;AACrD,SAAY,WAAK,iBAAiB,GAAG,+BAA+B;AACrE;AAGO,SAAS,2BAAmC;AAClD,SAAY,WAAK,iBAAiB,GAAG,4BAA4B;AAClE;AAGO,SAAS,sBAA8B;AAC7C,SAAY,WAAK,iBAAiB,GAAG,sBAAsB;AAC5D;AAGO,SAAS,uBAA+B;AAC9C,SAAY,WAAK,iBAAiB,GAAG,uBAAuB;AAC7D;AAGO,SAAS,sBAA8B;AAC7C,SAAY,WAAK,iBAAiB,GAAG,sBAAsB;AAC5D;AAGO,SAAS,2BAAmC;AAClD,SAAY,WAAK,iBAAiB,GAAG,2BAA2B;AACjE;AAGO,SAAS,yBAAiC;AAChD,SAAY,WAAK,iBAAiB,GAAG,yBAAyB;AAC/D;AASO,IAAM,8BAA8B;AACpC,IAAM,uBAAuB;AAC7B,IAAM,wBAAwB;AAC9B,IAAM,sBAAsB;AAC5B,IAAM,yBAAyB;AAG/B,SAAS,2BAAmC;AAClD,SAAY,WAAK,iBAAiB,GAAG,2BAA2B;AACjE;AAGO,SAAS,oBAA4B;AAC3C,SAAY,WAAK,iBAAiB,GAAG,oBAAoB;AAC1D;AAGO,SAAS,qBAA6B;AAC5C,SAAY,WAAK,iBAAiB,GAAG,qBAAqB;AAC3D;AAGO,SAAS,mBAA2B;AAC1C,SAAY,WAAK,iBAAiB,GAAG,mBAAmB;AACzD;AAGO,SAAS,sBAA8B;AAC7C,SAAY,WAAK,iBAAiB,GAAG,sBAAsB;AAC5D;;;ACtOO,IAAM,6BAA6B;;;AFuB1C,IAAM,YAAY;AAClB,IAAM,gBAAgB;AACtB,IAAM,0BAA0B;AAChC,IAAM,wBAAwB;AAC9B,IAAM,aAAa;AA6BZ,IAAM,wBAAN,MAA2D;AAAA,EACjE,MAAM,OAAO,UAAoC;AAChD,QAAI;AACH,YAAU,WAAO,QAAQ;AACzB,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAM,KAAK,UAA2D;AACrE,QAAI;AACH,YAAM,MAAM,MAAU,aAAS,UAAU,MAAM;AAC/C,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,aAAO,yBAAyB,MAAM;AAAA,IACvC,SAAS,KAAK;AACb,UAAI,YAAY,GAAG,KAAK,IAAI,SAAS,SAAU,QAAO;AACtD,YAAM;AAAA,IACP;AAAA,EACD;AAAA,EAEA,MAAM,MAAM,UAAkB,SAA8D;AAC3F,UAAU,UAAW,cAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,UAAM,UAAU,GAAG,QAAQ,GAAG,UAAU;AACxC,UAAM,QAAQ,OAAO,KAAK,KAAK,UAAU,SAAS,MAAM,GAAI,GAAG,MAAM;AAErE,UAAU,OAAG,SAAS,EAAE,OAAO,KAAK,CAAC;AACrC,UAAM,SAAS,MAAU,SAAK,SAAS,KAAK,SAAS;AACrD,QAAI;AACH,YAAM,OAAO,UAAU,KAAK;AAC5B,YAAM,OAAO,KAAK;AAAA,IACnB,UAAE;AACD,YAAM,OAAO,MAAM;AAAA,IACpB;AACA,UAAU,WAAO,SAAS,QAAQ;AAElC,UAAU,UAAM,UAAU,SAAS,EAAE,MAAM,MAAM,MAAS;AAC1D,WAAO,EAAE,cAAc,MAAM,YAAY,QAAQ;AAAA,EAClD;AAAA,EAEA,MAAM,OAAO,UAAiC;AAC7C,UAAU,OAAG,UAAU,EAAE,OAAO,KAAK,CAAC;AAAA,EACvC;AACD;AAWA,SAAS,yBAAyB,QAAiD;AAClF,MAAI,CAAC,SAAS,MAAM,GAAG;AACtB,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC3D;AACA,QAAM,UAAU,OAAO;AACvB,MAAI,YAAY,4BAA4B;AAI3C,QACC,OAAO,aAAa,UACpB,OAAO,iBAAiB,WACvB,aAAa,UAAU,0BAA0B,SACjD;AACD,aAAO;AAAA,IACR;AAEA,WAAO;AAAA,EACR;AACA,MAAI,OAAO,YAAY,YAAY,UAAU,4BAA4B;AAGxE,WAAO;AAAA,EACR;AACA,QAAM,IAAI,MAAM,2CAA2C,OAAO,OAAO,CAAC,EAAE;AAC7E;AAEA,SAAS,SAAS,OAAkD;AACnE,SAAO,OAAO,UAAU,YAAY,UAAU;AAC/C;AAEA,SAAS,YAAY,OAAgD;AACpE,SAAO,iBAAiB,SAAS,OAAQ,MAA6B,SAAS;AAChF;AAQA,IAAM,WAAN,MAAe;AAAA,EAMd,YAAY,UAAkB,WAAmB,SAAiB;AAFlE,SAAQ,WAAW;AAGlB,SAAK,UAAU,GAAG,QAAQ;AAC1B,SAAK,YAAY;AACjB,SAAK,UAAU;AAAA,EAChB;AAAA,EAEA,MAAM,UAAyB;AAC9B,UAAM,QAAQ,KAAK,IAAI;AACvB,WAAO,MAAM;AACZ,UAAI;AACH,cAAU,UAAM,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACrD,aAAK,WAAW;AAChB;AAAA,MACD,SAAS,KAAK;AACb,YAAI,CAAC,YAAY,GAAG,KAAK,IAAI,SAAS,UAAU;AAC/C,gBAAM;AAAA,QACP;AACA,YAAI,KAAK,IAAI,IAAI,SAAS,KAAK,WAAW;AACzC,gBAAM,IAAI,MAAM,gDAAgD,KAAK,OAAO,EAAE;AAAA,QAC/E;AACA,cAAM,MAAM,KAAK,OAAO;AAAA,MACzB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,UAAyB;AAC9B,QAAI,CAAC,KAAK,SAAU;AACpB,SAAK,WAAW;AAChB,UAAU,OAAG,KAAK,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC5D;AACD;AAEO,IAAM,gBAAN,MAAoB;AAAA,EAQ1B,YAAY,OAA6B,CAAC,GAAG;AAC5C,SAAK,WAAW,KAAK,YAAY,kBAAkB;AACnD,SAAK,UAAU,KAAK,WAAW,IAAI,sBAAsB;AACzD,SAAK,QAAQ,KAAK,SAAS,CAAC;AAC5B,SAAK,gBAAgB,KAAK,iBAAiB;AAC3C,SAAK,cAAc,KAAK,eAAe;AACvC,SAAK,MAAM,KAAK,QAAQ,MAAM,oBAAI,KAAK;AAAA,EACxC;AAAA;AAAA,EAGA,cAAsB;AACrB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,MAAM,SAA2B;AAChC,WAAO,KAAK,QAAQ,OAAO,KAAK,QAAQ;AAAA,EACzC;AAAA;AAAA,EAGA,MAAM,OAAgD;AACrD,WAAO,KAAK,QAAQ,KAAK,KAAK,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAM,MAA2D;AACtE,UAAM,KAAK,MAAM,cAAc,IAAI;AACnC,UAAM,OAAO,IAAI,SAAS,KAAK,UAAU,KAAK,eAAe,KAAK,WAAW;AAC7E,UAAM,KAAK,QAAQ;AACnB,QAAI;AACH,YAAM,UAAmC;AAAA,QACxC,GAAG;AAAA,QACH,SAAS;AAAA,MACV;AACA,YAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,KAAK,UAAU,OAAO;AAC9D,YAAM,KAAK,MAAM,aAAa,OAAO;AACrC,aAAO;AAAA,IACR,UAAE;AACD,YAAM,KAAK,QAAQ;AAAA,IACpB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OACL,SAGuE;AACvE,UAAM,OAAO,IAAI,SAAS,KAAK,UAAU,KAAK,eAAe,KAAK,WAAW;AAC7E,UAAM,KAAK,QAAQ;AACnB,QAAI;AACH,YAAM,UAAU,MAAM,KAAK,QAAQ,KAAK,KAAK,QAAQ;AACrD,YAAM,EAAE,MAAM,OAAO,IAAI,MAAM,QAAQ,OAAO;AAC9C,YAAM,UAAmC;AAAA,QACxC,GAAG;AAAA,QACH,SAAS;AAAA,MACV;AACA,YAAM,KAAK,MAAM,cAAc,OAAO;AACtC,YAAM,KAAK,QAAQ,MAAM,KAAK,UAAU,OAAO;AAC/C,YAAM,KAAK,MAAM,aAAa,OAAO;AACrC,aAAO,EAAE,QAAQ,SAAS,QAAQ;AAAA,IACnC,UAAE;AACD,YAAM,KAAK,QAAQ;AAAA,IACpB;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC5B,UAAM,KAAK,QAAQ,OAAO,KAAK,QAAQ;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,8BAGE;AACD,UAAM,YAAe,aAAS;AAC9B,UAAMC,YAAc,aAAS;AAI7B,WAAO;AAAA,MACN,gBAAgB;AAAA;AAAA,MAChB;AAAA,MACA,UAAAA;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAA4B;AACjC,UAAU,UAAM,iBAAiB,GAAG,EAAE,WAAW,KAAK,CAAC;AACvD,UAAU,UAAW,cAAQ,KAAK,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,EACjE;AACD;;;AGhSO,IAAM,aAAN,MAAiB;AAAA,EAKvB,YAAY,OAA0B,CAAC,GAAG;AACzC,SAAK,WAAW,KAAK,iBAAiB,IAAI,cAAc;AACxD,SAAK,WAAW,IAAI,SAAS;AAAA,MAC5B,eAAe,KAAK;AAAA,MACpB,eAAe,KAAK;AAAA,MACpB,KAAK,KAAK;AAAA,IACX,CAAC;AACD,SAAK,wBAAwB,KAAK,yBAAyB;AAAA,EAC5D;AAAA;AAAA,EAGA,MAAM,SAAgC;AACrC,UAAM,SAAS,MAAM,KAAK,SAAS,KAAK;AACxC,WAAO,SACJ;AAAA,MACA,cAAc,OAAO;AAAA,MACrB,UAAU,gBAAgB,MAAM;AAAA,MAChC,UAAU,gBAAgB,MAAM;AAAA,MAChC,YAAY,OAAO;AAAA,MACnB,eAAe,OAAO;AAAA,IACvB,IACC,EAAE,cAAc,YAAY;AAAA,EAChC;AAAA;AAAA,EAGA,MAAM,OAAO,OAAmD,CAAC,GAAgC;AAChG,WAAO,KAAK,SAAS,OAAO,IAAI;AAAA,EACjC;AAAA;AAAA,EAGA,MAAM,oBAAmC;AACxC,UAAM,KAAK,SAAS,kBAAkB;AAAA,EACvC;AAAA;AAAA,EAGA,cAAuB;AACtB,WAAO,KAAK,SAAS,YAAY;AAAA,EAClC;AAAA;AAAA,EAGA,MAAM,aAAa,OAAqC,CAAC,GAAsC;AAC9F,UAAM,SAAS,MAAM,KAAK,SAAS,aAAa,IAAI;AACpD,UAAM,SAAS,MAAM,KAAK,SAAS,KAAK;AACxC,WAAO;AAAA,MACN,GAAG;AAAA,MACH,mBAAmB,QAAQ;AAAA,IAC5B;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,mBAAmB,OAIe;AACvC,UAAM,SAAS,MAAM,KAAK,SAAS,KAAK;AACxC,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,mBAAmB;AAAA,MACzB,QAAQ,MAAM;AAAA,MACd,MAAM,MAAM;AAAA,MACZ,MAAM,MAAM;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB,cAAc,OAAO;AAAA,MACrB,eAAe,OAAO;AAAA,IACvB,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,eAAwD;AAC7D,WAAO,KAAK,SAAS,KAAK;AAAA,EAC3B;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC5B,UAAM,KAAK,SAAS,MAAM;AAAA,EAC3B;AAAA;AAAA,EAGA,MAAM,cAA6B;AAClC,UAAM,KAAK,SAAS,YAAY;AAAA,EACjC;AAAA;AAAA,EAGA,MAAM,cAA6B;AAClC,UAAM,KAAK,SAAS,YAAY;AAAA,EACjC;AAAA;AAAA,EAGA,mBAAkC;AACjC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,cAAwB;AACvB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,iBAA2C;AAC1C,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,oBAA4B;AAC3B,WAAO,KAAK,sBAAsB;AAAA,EACnC;AACD;AAEA,SAAS,gBAAgB,QAAsD;AAC9E,SAAO;AAAA,IACN,UAAU,OAAO;AAAA,IACjB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,EACtB;AACD;AAEA,SAAS,gBAAgB,QAAiD;AACzE,SAAO;AAAA,IACN,gBAAgB,OAAO;AAAA,IACvB,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,UAAU,OAAO;AAAA,EAClB;AACD;;;ACnKA,YAAY,YAAY;AACxB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;;;ACFtB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;;;AC2BtB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;;;ADff,SAAS,mBACf,KACyD;AACzD,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,QAAQ,IAAI,MAAM,6CAA6C;AACrE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,WAAW,MAAM,CAAC,KAAK;AAC7B,QAAM,OAAO,MAAM,CAAC,KAAK;AACzB,QAAM,OAAgC,CAAC;AACvC,aAAW,QAAQ,SAAS,MAAM,OAAO,GAAG;AAC3C,QAAI,KAAK,KAAK,EAAE,WAAW,EAAG;AAC9B,QAAI,KAAK,KAAK,EAAE,WAAW,GAAG,EAAG;AACjC,UAAM,KAAK,KAAK,MAAM,gCAAgC;AACtD,QAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAG;AACnB,QAAI,SAAkB,GAAG,CAAC,KAAK,IAAI,KAAK;AACxC,QAAI,OAAO,UAAU,UAAU;AAC9B,UACE,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,KAC3C,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAC3C;AACD,gBAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,MAC1B;AAAA,IACD;AACA,SAAK,GAAG,CAAC,CAAC,IAAI;AAAA,EACf;AACA,SAAO,EAAE,MAAM,KAAK;AACrB;AAyGA,IAAM,YAAY,oBAAI,IAAI,CAAC,QAAQ,gBAAgB,WAAW,QAAQ,SAAS,KAAK,CAAC;AAqBrF,IAAM,2BAA2B,oBAAI,IAAI,CAAC,GAAG,WAAW,SAAS,CAAC;;;ADzG3D,IAAM,cAAN,cAA0B,MAAM;AAAA,EACtC,YAAY,SAAiB;AAC5B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACb;AACD;AACO,IAAM,qBAAN,cAAiC,YAAY;AAAA,EACnD,YACiB,MACA,IACf;AACD,UAAM,oBAAoB,IAAI,IAAI,EAAE,EAAE;AAHtB;AACA;AAGhB,SAAK,OAAO;AAAA,EACb;AACD;AACO,IAAM,oBAAN,cAAgC,YAAY;AAAA,EAClD,YAAY,SAAiB;AAC5B,UAAM,kBAAkB,OAAO,EAAE;AACjC,SAAK,OAAO;AAAA,EACb;AACD;AAQO,SAAS,kBAA0B;AACzC,SAAc,mBAAY,CAAC,EAAE,SAAS,KAAK;AAC5C;AAOO,SAAS,gBAAgB,MAAiB,IAAoB;AACpE,QAAM,OAAO,SAAS,UAAU,kBAAkB,IAAI,kBAAkB;AACxE,SAAY,WAAK,MAAM,EAAE;AAC1B;AAMO,IAAM,cAAN,MAAkB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKxB,MAAM,KAAK,MAA0C;AACpD,UAAM,OAAO,SAAS,UAAU,kBAAkB,IAAI,kBAAkB;AACxE,QAAI;AACJ,QAAI;AACH,cAAQ,MAAS,YAAQ,IAAI;AAAA,IAC9B,SAAS,KAAK;AACb,UAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,YAAM;AAAA,IACP;AACA,UAAM,MAAsB,CAAC;AAC7B,eAAW,QAAQ,OAAO;AACzB,YAAM,UAAU,MAAM,KAAK,eAAe,MAAM,IAAI;AACpD,UAAI,QAAS,KAAI,KAAK,OAAO;AAAA,IAC9B;AAEA,QAAI,KAAK,CAAC,GAAG,MAAO,EAAE,aAAa,EAAE,aAAa,IAAI,EAAG;AACzD,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,MAAM,eAAe,MAAiB,IAA0C;AAC/E,UAAM,MAAM,gBAAgB,MAAM,EAAE;AACpC,UAAM,mBAAmB,SAAS,UAAU,aAAa;AACzD,UAAM,eAAoB,WAAK,KAAK,gBAAgB;AACpD,QAAIC;AACJ,QAAI;AACH,MAAAA,QAAO,MAAS,SAAK,YAAY;AAAA,IAClC,QAAQ;AACP,aAAO;AAAA,IACR;AACA,QAAI,UAAU;AACd,QAAI;AACH,gBAAU,MAAS,aAAS,cAAc,MAAM;AAAA,IACjD,QAAQ;AAAA,IAER;AACA,UAAM,SAAS,mBAAmB,OAAO;AACzC,UAAM,OAAO,OAAO,QAAQ,KAAK,SAAS,WAAW,OAAO,KAAK,OAAO;AACxE,UAAM,cAAc,OAAO,QAAQ,KAAK,gBAAgB,WAAW,OAAO,KAAK,cAAc;AAC7F,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAYA,MAAK,MAAM,YAAY;AAAA,IACpC;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,IAAI,MAAiB,IAAkC;AAC5D,UAAM,UAAU,MAAM,KAAK,eAAe,MAAM,EAAE;AAClD,QAAI,CAAC,QAAS,OAAM,IAAI,mBAAmB,MAAM,EAAE;AACnD,UAAM,QAAQ,MAAM,KAAK,SAAS,MAAM,EAAE;AAC1C,WAAO,EAAE,GAAG,SAAS,MAAM;AAAA,EAC5B;AAAA;AAAA,EAGA,MAAM,SAAS,MAAiB,IAAkC;AACjE,UAAM,MAAM,gBAAgB,MAAM,EAAE;AACpC,UAAM,MAAmB,CAAC;AAC1B,UAAM,iBAAiB,KAAK,KAAK,GAAG;AACpC,QAAI,KAAK,CAAC,GAAG,MAAM;AAClB,YAAM,cAAc,EAAE,SAAS,cAAc,EAAE,SAAS;AACxD,YAAM,cAAc,EAAE,SAAS,cAAc,EAAE,SAAS;AACxD,UAAI,eAAe,CAAC,YAAa,QAAO;AACxC,UAAI,eAAe,CAAC,YAAa,QAAO;AACxC,aAAO,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,IACnC,CAAC;AACD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KAAK,MAAyC;AACnD,UAAM,mBAAmB,KAAK,SAAS,UAAU,aAAa;AAC9D,QAAI,CAAC,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,gBAAgB,GAAG;AACzD,YAAM,IAAI,kBAAkB,GAAG,gBAAgB,cAAc;AAAA,IAC9D;AACA,UAAM,KAAK,KAAK,MAAM,gBAAgB;AACtC,UAAM,MAAM,gBAAgB,KAAK,MAAM,EAAE;AAGzC,UAAS,OAAG,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACjD,UAAS,UAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAEvC,eAAW,KAAK,KAAK,OAAO;AAC3B,YAAM,OAAY,WAAK,KAAK,EAAE,IAAI;AAClC,YAAS,UAAW,cAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,YAAM,MAAM,GAAG,IAAI,IAAI,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;AAChD,YAAS,cAAU,KAAK,EAAE,SAAS,MAAM;AACzC,YAAS,WAAO,KAAK,IAAI;AAAA,IAC1B;AAEA,UAAM,KAAK,SAAS,GAAG;AACvB,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,MAAM,OAAO,MAAiB,IAA2B;AACxD,UAAM,MAAM,gBAAgB,MAAM,EAAE;AACpC,QAAI;AACH,YAAS,OAAG,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAClD,SAAS,KAAK;AACb,UAAK,IAA8B,SAAS,SAAU;AACtD,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA,EAGA,MAAc,SAAS,KAA4B;AAClD,QAAI;AACJ,QAAI;AACH,cAAQ,MAAS,YAAQ,GAAG;AAAA,IAC7B,QAAQ;AACP;AAAA,IACD;AACA,eAAW,KAAK,OAAO;AACtB,UAAI,EAAE,SAAS,MAAM,GAAG;AACvB,cAAS,OAAQ,WAAK,KAAK,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,MACtE;AAAA,IACD;AAAA,EACD;AACD;AAEA,eAAe,iBAAiB,QAAgB,MAAc,KAAiC;AAC9F,MAAI;AACJ,MAAI;AACH,cAAU,MAAS,YAAQ,QAAQ,EAAE,eAAe,KAAK,CAAC;AAAA,EAC3D,QAAQ;AACP;AAAA,EACD;AACA,aAAW,KAAK,SAAS;AACxB,QAAI,EAAE,KAAK,SAAS,MAAM,EAAG;AAC7B,UAAM,OAAY,WAAK,QAAQ,EAAE,IAAI;AACrC,QAAI,EAAE,YAAY,GAAG;AACpB,YAAM,iBAAiB,MAAM,MAAM,GAAG;AAAA,IACvC,WAAW,EAAE,OAAO,GAAG;AACtB,YAAM,UAAU,MAAS,aAAS,MAAM,MAAM;AAC9C,UAAI,KAAK,EAAE,MAAW,eAAS,MAAM,IAAI,GAAG,QAAQ,CAAC;AAAA,IACtD;AAAA,EACD;AACD;;;AGlQA;AAAA,EACC,wBAAAC;AAAA,EAEA;AAAA,OAGM;AAGP,IAAM,gCAAgC;AACtC,IAAM,wBAAuE;AAAA,EAC5E,OAAO;AAAA,EACP,KAAK;AAAA,EACL,QAAQ;AAAA;AAAA;AAAA;AAAA,EAIR,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AACN;AACA,IAAM,uBAAuB;AAC7B,IAAM,qBAAqB;AAwBpB,IAAM,uBAAN,MAA2B;AAAA,EAIjC,YAAY,UAAuC,CAAC,GAAG;AACtD,SAAK,eAAe,QAAQ,cAAc;AAC1C,SAAK,WAAW,QAAQ,YAAY,QAAQ;AAAA,EAC7C;AAAA,EAEA,MAAM,mBAAoD;AACzD,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC7B,wBAAwB,IAAI,OAAO,SAAS,CAAC,MAAM,MAAM,KAAK,UAAU,IAAI,CAAC,CAAU;AAAA,IACxF;AAEA,WAAO,OAAO,YAAY,OAAO;AAAA,EAClC;AAAA,EAEA,MAAM,UAAU,UAA0D;AACzE,QAAI,CAAC,kBAAkB,KAAK,QAAQ,GAAG;AACtC,YAAMC,sBAAqB,kBAAkB,oBAAoB;AAAA,IAClE;AAEA,QAAI;AACH,UAAI,aAAa,OAAO;AACvB,eAAO,MAAM,KAAK,SAAS;AAAA,MAC5B;AAEA,UAAI,aAAa,SAAS;AACzB,eAAO,MAAM,KAAK,WAAW;AAAA,MAC9B;AAEA,YAAM,WAAW,MAAM,KAAK,YAAY,QAAQ;AAChD,YAAM,UAAU,MAAM,KAAK,eAAe,QAAQ;AAElD,aAAO;AAAA,QACN,WAAW;AAAA,QACX;AAAA,QACA,MAAM;AAAA,MACP;AAAA,IACD,SAAS,OAAO;AACf,aAAO,KAAK,qBAAqB,KAAK;AAAA,IACvC;AAAA,EACD;AAAA,EAEA,MAAc,YAAY,UAA+C;AACxE,QAAI;AACH,YAAM,YAAY,KAAK,aAAa;AACpC,YAAM,SAAS,MAAM,KAAK,aAAa,YAAY,UAAU,SAAS;AAAA,QACrE,MAAM,CAAC,QAAQ;AAAA,QACf,WAAW,KAAK,eAAe,QAAgC;AAAA,MAChE,CAAC;AAED,aAAO,OAAO,OACZ,MAAM,OAAO,EACb,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC;AAAA,IACjC,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,gBAAgB,UAA+C;AAC5E,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,aAAa,SAAS;AAAA,QAC/C,MAAM,CAAC,QAAQ;AAAA,QACf,WAAW,KAAK,eAAe,QAAgC;AAAA,MAChE,CAAC;AAED,YAAM,aAAa,OAAO,OACxB,MAAM,OAAO,EACb,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AAElC,YAAM,UAAU,WAAW,KAAK,CAAC,SAAS,KAAK,YAAY,EAAE,SAAS,MAAM,CAAC;AAC7E,aAAO,WAAW,WAAW,CAAC;AAAA,IAC/B,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAc,eAAe,UAAmC;AAC/D,UAAM,aAAa,MAAM,KAAK,qBAAqB,QAAQ;AAC3D,UAAM,SAAS,MAAM,KAAK,aAAa,WAAW,SAAS;AAAA,MAC1D,MAAM,WAAW;AAAA,MACjB,WAAW,KAAK,eAAe,QAAgC;AAAA,IAChE,CAAC;AAED,WAAO,KAAK,aAAa,UAAU,OAAO,UAAU,OAAO,MAAM;AAAA,EAClE;AAAA,EAEA,MAAc,WAAqC;AAClD,QAAI,KAAK,aAAa,SAAS;AAC9B,UAAI;AACH,cAAM,SAAS,MAAM,KAAK,aAAa,WAAW;AAAA,UACjD,MAAM,CAAC,MAAM,aAAa;AAAA,UAC1B,WAAW,KAAK,eAAe,KAAK;AAAA,QACrC,CAAC;AAED,eAAO;AAAA,UACN,WAAW;AAAA,UACX,SAAS,OAAO,OAAO,KAAK;AAAA,UAC5B,MAAM,MAAM,KAAK,YAAY,KAAK;AAAA,QACnC;AAAA,MACD,QAAQ;AACP,eAAO;AAAA,UACN,WAAW;AAAA,UACX,OAAO;AAAA,QACR;AAAA,MACD;AAAA,IACD;AAEA,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,aAAa,aAAa;AAAA,QACnD,MAAM;AAAA,UACL;AAAA,UACA;AAAA,QACD;AAAA,QACA,WAAW,KAAK,eAAe,KAAK;AAAA,MACrC,CAAC;AAED,aAAO;AAAA,QACN,WAAW;AAAA,QACX,SAAS,OAAO,OAAO,KAAK;AAAA,QAC5B,MAAM;AAAA,MACP;AAAA,IACD,QAAQ;AACP,aAAO;AAAA,QACN,WAAW;AAAA,QACX,OAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,aAAuC;AACpD,UAAM,aAAa,MAAM,KAAK,YAAY,QAAQ;AAClD,QAAI,CAAC,YAAY;AAChB,aAAO;AAAA,QACN,WAAW;AAAA,QACX,OAAO;AAAA,MACR;AAAA,IACD;AAEA,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,aAAa,UAAU;AAAA,QAChD,MAAM,CAAC,SAAS,QAAQ,QAAQ;AAAA,QAChC,WAAW,KAAK,eAAe,OAAO;AAAA,MACvC,CAAC;AAED,YAAM,mBAAmB;AACzB,UAAI,OAAO,OAAO,SAAS,gBAAgB,GAAG;AAC7C,eAAO;AAAA,UACN,WAAW;AAAA,UACX,SAAS;AAAA,UACT,MAAM;AAAA,QACP;AAAA,MACD;AAEA,aAAO;AAAA,QACN,WAAW;AAAA,QACX,OAAO;AAAA,MACR;AAAA,IACD,SAAS,OAAO;AACf,aAAO,KAAK,qBAAqB,KAAK;AAAA,IACvC;AAAA,EACD;AAAA,EAEA,MAAc,qBAAqB,UAGhC;AACF,UAAM,YAAY,KAAK,aAAa;AACpC,QAAI,aAAa,CAAC,OAAO,QAAQ,KAAK,EAAE,SAAS,QAAQ,GAAG;AAa3D,YAAM,OAAO,MAAM,KAAK,gBAAgB,QAAQ;AAChD,UAAI,MAAM;AACT,eAAO;AAAA,UACN,SAAS;AAAA,UACT,MAAM,CAAC,MAAM,MAAM,WAAW;AAAA,QAC/B;AAAA,MACD;AAEA,aAAO;AAAA,QACN,SAAS;AAAA,QACT,MAAM,CAAC,MAAM,UAAU,WAAW;AAAA,MACnC;AAAA,IACD;AAEA,UAAM,WAAgE;AAAA,MACrE,KAAK,EAAE,SAAS,OAAO,MAAM,CAAC,WAAW,EAAE;AAAA,MAC3C,MAAM,EAAE,SAAS,QAAQ,MAAM,CAAC,WAAW,EAAE;AAAA,MAC7C,KAAK,EAAE,SAAS,OAAO,MAAM,CAAC,WAAW,EAAE;AAAA,MAC3C,MAAM,EAAE,SAAS,QAAQ,MAAM,CAAC,WAAW,EAAE;AAAA,MAC7C,KAAK,EAAE,SAAS,OAAO,MAAM,CAAC,WAAW,EAAE;AAAA,MAC3C,KAAK,EAAE,SAAS,OAAO,MAAM,CAAC,WAAW,EAAE;AAAA,MAC3C,KAAK,EAAE,SAAS,OAAO,MAAM,CAAC,WAAW,EAAE;AAAA,MAC3C,QAAQ,EAAE,SAAS,UAAU,MAAM,CAAC,WAAW,EAAE;AAAA,IAClD;AAEA,WAAO,SAAS,QAAQ,KAAK,EAAE,SAAS,UAAU,MAAM,CAAC,WAAW,EAAE;AAAA,EACvE;AAAA,EAEQ,eAAe,UAAwC;AAC9D,WAAO,sBAAsB,QAAQ,KAAK;AAAA,EAC3C;AAAA,EAEQ,aAAa,UAAkB,QAAwB;AAC9D,UAAM,UAAU,OAAO,KAAK;AAE5B,YAAQ,UAAU;AAAA,MACjB,KAAK,OAAO;AACX,cAAM,QAAQ,QAAQ,MAAM,6BAA6B;AACzD,eAAO,QAAQ,CAAC,KAAK;AAAA,MACtB;AAAA,MACA,KAAK,QAAQ;AACZ,cAAM,QAAQ,QAAQ,MAAM,mBAAmB;AAC/C,eAAO,QAAQ,CAAC,KAAK;AAAA,MACtB;AAAA,MACA,SAAS;AACR,cAAM,SAAS,QAAQ,MAAM,iBAAiB;AAC9C,YAAI,SAAS,CAAC,GAAG;AAChB,iBAAO,OAAO,CAAC;AAAA,QAChB;AAEA,cAAM,SAAS,QAAQ,MAAM,YAAY;AACzC,eAAO,SAAS,CAAC,KAAK;AAAA,MACvB;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,qBAAqB,OAAiC;AAC7D,UAAM,YAAY;AAClB,UAAM,UAAU,UAAU,WAAW,OAAO,KAAK;AACjD,UAAM,OAAO,UAAU;AAEvB,UAAM,aACL,QAAQ,SAAS,mBAAmB,KACpC,QAAQ,SAAS,gBAAgB,KACjC,QAAQ,SAAS,QAAQ,KACzB,QAAQ,SAAS,QAAQ,KACzB,SAAS,YACT,SAAS,YACT,SAAS;AACV,UAAMC,aAAY,SAAS,sBAAsB,QAAQ,YAAY,EAAE,SAAS,WAAW;AAE3F,WAAO;AAAA,MACN,WAAW;AAAA,MACX,OAAO,aAAa,kBAAkBA,aAAY,oBAAoB;AAAA,IACvE;AAAA,EACD;AACD;;;ACxTA,SAAS,SAAAC,cAAa;AACtB,YAAYC,WAAU;;;ACiEf,IAAM,WAAN,cAAuB,MAAM;AAAA,EAKnC,YAAY,MAAgB,QAAwB;AACnD,UAAM,OAAO,KAAK,KAAK,GAAG,CAAC,iBAAiB,OAAO,IAAI,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG,CAAC,EAAE;AAC1F,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,OAAO,OAAO;AACnB,SAAK,SAAS,OAAO;AACrB,SAAK,SAAS,OAAO;AAAA,EACtB;AACD;;;AD5CO,IAAM,YAAN,MAAgB;AAAA,EAItB,YAAY,MAAwB;AAEnC,SAAK,kBAAkB,KAAK,gBAAgB,QAAQ,QAAQ,EAAE;AAC9D,SAAK,UAAU,KAAK,WAAW,IAAI,eAAe;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,iBAAiB,SAAiB,aAAqB,WAAW,MAAc;AAC/E,QAAI,CAAC,UAAU;AACd,aAAO;AAAA,IACR;AACA,WAAO,GAAG,KAAK,eAAe,IAAI,OAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,MACL,QACA,aACA,WACA,QACA,WAAW,MACK;AAChB,UAAM,WAAW,KAAK,iBAAiB,QAAQ,aAAa,QAAQ;AACpE,UAAM,OAAO,SACV,CAAC,SAAS,MAAM,QAAQ,MAAM,UAAU,SAAS,IACjD,CAAC,SAAS,MAAM,UAAU,SAAS;AACtC,UAAM,KAAK,WAAW,MAAM,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,KACL,QACA,WACA,QACA,WAAW,MACX,aACsB;AAItB,UAAM,YAAY,MAAM,KAAK,aAAa,WAAW,QAAQ;AAC7D,QAAI,CAAC,WAAW;AACf,YAAM,IAAI,MAAM,oCAAoC,SAAS,EAAE;AAAA,IAChE;AACA,UAAM,YAAY,WAAW,YAAa,eAAe;AACzD,UAAM,WAAW,KAAK,iBAAiB,QAAQ,WAAW,QAAQ;AAGlE,UAAM,KAAK,WAAW,CAAC,UAAU,WAAW,UAAU,QAAQ,GAAG,EAAE,KAAK,UAAU,CAAC;AAInF,UAAM,SAAS,MAAM,KAAK,aAAa,SAAS;AAChD,UAAM,KAAK,WAAW,CAAC,SAAS,QAAQ,GAAG,EAAE,KAAK,UAAU,CAAC;AAC7D,UAAM,QAAQ,MAAM,KAAK,aAAa,SAAS;AAM/C,QAAI,eAAe;AACnB,QAAI,CAAC,cAAc;AAClB,qBAAe,MAAM,KAAK,cAAc,SAAS;AAAA,IAClD,OAAO;AACN,YAAM,UAAU,MAAM,KAAK,cAAc,SAAS;AAClD,UAAI,YAAY,cAAc;AAC7B,cAAM,KAAK,WAAW,CAAC,YAAY,YAAY,GAAG,EAAE,KAAK,UAAU,CAAC;AAAA,MACrE;AAAA,IACD;AACA,QAAI,CAAC,cAAc;AAClB,YAAM,IAAI,MAAM,yCAAyC,SAAS,EAAE;AAAA,IACrE;AAGA,UAAM,KAAK,WAAW,CAAC,SAAS,aAAa,UAAU,YAAY,EAAE,GAAG;AAAA,MACvE,KAAK;AAAA,IACN,CAAC;AAED,WAAO;AAAA,MACN,SAAS,WAAW;AAAA,MACpB,WAAW;AAAA,MACX,QAAQ;AAAA,IACT;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KACL,QACA,WACA,QACA,WAAW,MACW;AACtB,UAAM,YAAY,MAAM,KAAK,aAAa,WAAW,QAAQ;AAC7D,QAAI,CAAC,WAAW;AACf,YAAM,IAAI,MAAM,oCAAoC,SAAS,EAAE;AAAA,IAChE;AACA,UAAM,WAAW,KAAK,iBAAiB,QAAQ,WAAW,QAAQ;AAClE,UAAM,KAAK,WAAW,CAAC,UAAU,WAAW,UAAU,QAAQ,GAAG,EAAE,KAAK,UAAU,CAAC;AAEnF,UAAM,SAAS,MAAM,KAAK,QAAQ;AAAA,MACjC,CAAC,QAAQ,UAAU,cAAc,MAAM,eAAe,MAAM,EAAE;AAAA,MAC9D,EAAE,KAAK,UAAU;AAAA,IAClB;AACA,QAAI,OAAO,SAAS,GAAG;AACtB,YAAM,IAAI,SAAS,CAAC,MAAM,GAAG,MAAM;AAAA,IACpC;AAGA,UAAM,IAAI,OAAO,OAAO,MAAM,wCAAwC;AACtE,UAAM,YAAY,IAAI,CAAC,KAAK;AAE5B,WAAO;AAAA,MACN,KAAK,cAAc,MAAM;AAAA,MACzB;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAS,QAAgB,aAAqB,WAAW,MAA+B;AAC7F,UAAM,WAAW,KAAK,iBAAiB,QAAQ,aAAa,QAAQ;AACpE,UAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,CAAC,aAAa,WAAW,MAAM,QAAQ,GAAG;AAAA,MACjF,KAAK,QAAQ,IAAI;AAAA,IAClB,CAAC;AACD,QAAI,OAAO,SAAS,GAAG;AACtB,YAAM,IAAI,SAAS,CAAC,WAAW,GAAG,MAAM;AAAA,IACzC;AACA,UAAM,WAA2B,CAAC;AAClC,eAAW,QAAQ,OAAO,OAAO,MAAM,IAAI,GAAG;AAC7C,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,QAAS;AAEd,YAAM,MAAM,QAAQ,QAAQ,GAAI;AAChC,UAAI,QAAQ,GAAI;AAChB,YAAM,MAAM,QAAQ,MAAM,GAAG,GAAG;AAChC,YAAM,MAAM,QAAQ,MAAM,MAAM,CAAC;AACjC,eAAS,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,IAC3B;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,OAAO,WAAmB,SAAiB,UAAU,KAAqC;AAC/F,UAAM,KAAK,WAAW,CAAC,OAAO,MAAM,OAAO,GAAG,EAAE,KAAK,UAAU,CAAC;AAChE,UAAM,KAAK,WAAW,CAAC,UAAU,MAAM,OAAO,GAAG,EAAE,KAAK,UAAU,CAAC;AACnE,UAAM,MAAM,MAAM,KAAK,aAAa,SAAS;AAC7C,WAAO,EAAE,WAAW,IAAI;AAAA,EACzB;AAAA,EAEA,MAAc,WAAW,MAAgB,MAAiD;AACzF,UAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,MAAM,EAAE,KAAK,KAAK,OAAO,QAAQ,IAAI,EAAE,CAAC;AAChF,QAAI,OAAO,SAAS,GAAG;AACtB,YAAM,IAAI,SAAS,MAAM,MAAM;AAAA,IAChC;AACA,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,aAAa,WAAmB,QAA6C;AAC1F,UAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,CAAC,UAAU,WAAW,MAAM,GAAG;AAAA,MACtE,KAAK;AAAA,IACN,CAAC;AACD,QAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,UAAM,MAAM,OAAO,OAAO,KAAK;AAC/B,WAAO,IAAI,SAAS,IAAI,MAAM;AAAA,EAC/B;AAAA,EAEA,MAAc,aAAa,WAAoC;AAC9D,UAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,CAAC,aAAa,MAAM,GAAG,EAAE,KAAK,UAAU,CAAC;AACjF,QAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,WAAO,OAAO,OAAO,KAAK;AAAA,EAC3B;AAAA,EAEA,MAAc,cAAc,WAAgD;AAC3E,UAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,CAAC,aAAa,gBAAgB,MAAM,GAAG;AAAA,MAC9E,KAAK;AAAA,IACN,CAAC;AACD,QAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,UAAM,SAAS,OAAO,OAAO,KAAK;AAClC,QAAI,CAAC,UAAU,WAAW,OAAQ,QAAO;AACzC,WAAO;AAAA,EACR;AACD;AAUO,IAAM,iBAAN,MAA2C;AAAA,EACjD,MAAM,MAAM,MAAgB,MAAiD;AAC5E,WAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACvC,YAAM,QAAQC,OAAM,OAAO,MAAM;AAAA,QAChC,KAAK,KAAK;AAAA,QACV,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,QAChC,OAAO;AAAA,MACR,CAAC;AACD,YAAM,eAAyB,CAAC;AAChC,YAAM,eAAyB,CAAC;AAChC,YAAM,QAAQ,GAAG,QAAQ,CAAC,MAAc,aAAa,KAAK,CAAC,CAAC;AAC5D,YAAM,QAAQ,GAAG,QAAQ,CAAC,MAAc,aAAa,KAAK,CAAC,CAAC;AAC5D,YAAM,GAAG,SAAS,MAAM;AACxB,YAAM,GAAG,SAAS,CAAC,SAAS;AAC3B,QAAAD,SAAQ;AAAA,UACP,QAAQ,OAAO,OAAO,YAAY,EAAE,SAAS,MAAM;AAAA,UACnD,QAAQ,OAAO,OAAO,YAAY,EAAE,SAAS,MAAM;AAAA,UACnD,MAAM,QAAQ;AAAA,QACf,CAAC;AAAA,MACF,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AACD;AAGO,IAAM,iBAAN,MAA2C;AAAA,EAMjD,YAAY,QAA0B;AAFtC;AAAA,SAAS,QAA4D,CAAC;AAGrE,SAAK,SAAS,CAAC,GAAG,MAAM;AAAA,EACzB;AAAA,EAEA,MAAM,MAAM,MAAgB,MAAiD;AAC5E,SAAK,MAAM,KAAK,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AACvC,UAAM,OAAO,KAAK,OAAO,MAAM;AAC/B,QAAI,CAAC,MAAM;AACV,aAAO,EAAE,QAAQ,IAAI,QAAQ,kCAAkC,KAAK,KAAK,GAAG,CAAC,IAAI,MAAM,EAAE;AAAA,IAC1F;AACA,WAAO;AAAA,EACR;AACD;AAOO,SAAS,UAAU,cAA8B;AACvD,QAAM,aAAkB,cAAQ,YAAY;AAC5C,MAAI,QAAQ,aAAa,SAAS;AACjC,WAAO,WAAW,WAAW,QAAQ,OAAO,GAAG,CAAC;AAAA,EACjD;AACA,SAAO,UAAU,UAAU;AAC5B;;;AEhVA,YAAYE,SAAQ;AACpB,YAAYC,WAAU;AACtB;AAAA,EACC,wBAAAC;AAAA,OAKM;AAmBP,IAAM,oBAAoB,CAAC,QAAQ,SAAS,QAAQ,SAAS,QAAQ,QAAQ,OAAO;AACpF,IAAM,oCAAoC;AAEnC,IAAM,aAAN,MAAiB;AAAA,EACvB,sBAAgC;AAC/B,WAAO,CAAC,GAAG,iBAAiB;AAAA,EAC7B;AAAA,EAEA,MAAM,SACL,UACA,iBAC0C;AAC1C,QAAI;AACH,UAAI,CAAE,MAAM,KAAK,WAAW,QAAQ,GAAI;AACvC,eAAO,EAAE,OAAO,MAAM;AAAA,MACvB;AAEA,UAAI,CAAC,kBAAkB,SAAc,cAAQ,QAAQ,EAAE,YAAY,CAAC,GAAG;AACtE,eAAO,EAAE,OAAO,MAAM;AAAA,MACvB;AAEA,YAAM,KAAK,QAAQ,UAAU,eAAe;AAC5C,aAAO,EAAE,OAAO,KAAK;AAAA,IACtB,QAAQ;AACP,aAAO,EAAE,OAAO,MAAM;AAAA,IACvB;AAAA,EACD;AAAA,EAEA,MAAM,QAAQ,WAAmB,iBAAuD;AACvF,UAAM,QAAQ,KAAK,UAAU,eAAe;AAC5C,UAAM,WAAW,MAAM,MAAM,SAAS,EAAE,SAAS;AACjD,UAAM,QAAQ,MAAS,SAAK,SAAS;AAErC,WAAO;AAAA,MACN,OAAO,SAAS,SAAS;AAAA,MACzB,QAAQ,SAAS,UAAU;AAAA,MAC3B,QAAQ,SAAS,UAAU;AAAA,MAC3B,MAAM,MAAM;AAAA,MACZ,YAAY,SAAS;AAAA,IACtB;AAAA,EACD;AAAA,EAEA,MAAM,SACL,WACA,SACwC;AACxC,UAAM,aAAa,MAAM,KAAK,SAAS,WAAW,QAAQ,eAAe;AACzE,QAAI,CAAC,WAAW,OAAO;AACtB,YAAMC,sBAAqB,kBAAkB,uBAAuB,SAAS,EAAE;AAAA,IAChF;AAEA,UAAM,gBAAgB,MAAS,SAAK,SAAS;AAC7C,UAAM,eAAe,cAAc;AACnC,UAAM,aAAa,KAAK,cAAc,WAAW,OAAO;AACxD,UAAM,mBAAmB,MAAM,KAAK,kBAAkB,WAAW,OAAO;AACxE,UAAM,iBAAiB,iBAAiB;AACxC,UAAM,oBAAqB,eAAe,kBAAkB,eAAgB;AAC5E,UAAM,0BACL,QAAQ,2BAA2B;AAEpC,QAAI,mBAAmB,yBAAyB;AAC/C,aAAO;AAAA,QACN;AAAA,QACA,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,QAClB,YAAY;AAAA,MACb;AAAA,IACD;AAEA,UAAS,cAAU,YAAY,gBAAgB;AAE/C,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,kBACb,WACA,SACkB;AAClB,UAAM,QAAQ,KAAK,UAAU,QAAQ,eAAe;AACpD,QAAI,WAAW,MAAM,SAAS;AAE9B,YAAQ,QAAQ,UAAe,cAAQ,SAAS,EAAE,YAAY,EAAE,MAAM,CAAC,GAAG;AAAA,MACzE,KAAK;AAAA,MACL,KAAK;AACJ,mBAAW,SAAS,KAAK,EAAE,SAAS,QAAQ,QAAQ,CAAC;AACrD;AAAA,MACD,KAAK;AACJ,mBAAW,SAAS,IAAI;AAAA,UACvB,SAAS,QAAQ;AAAA,UACjB,kBAAkB;AAAA,QACnB,CAAC;AACD;AAAA,MACD,KAAK;AACJ,mBAAW,SAAS,KAAK,EAAE,SAAS,QAAQ,QAAQ,CAAC;AACrD;AAAA,MACD;AACC,mBAAW,SAAS,KAAK,EAAE,SAAS,QAAQ,QAAQ,CAAC;AAAA,IACvD;AAEA,WAAO,SAAS,SAAS;AAAA,EAC1B;AAAA,EAEQ,cAAc,WAAmB,SAAgD;AACxF,QAAI,QAAQ,YAAY;AACvB,aAAO,QAAQ;AAAA,IAChB;AAEA,QAAI,QAAQ,oBAAoB;AAC/B,aAAO;AAAA,IACR;AAEA,UAAM,MAAW,cAAQ,SAAS;AAClC,UAAM,MAAW,cAAQ,SAAS;AAClC,UAAM,OAAY,eAAS,WAAW,GAAG;AACzC,WAAY,WAAK,KAAK,GAAG,IAAI,cAAc,GAAG,EAAE;AAAA,EACjD;AAAA,EAEA,MAAc,WAAW,YAAsC;AAC9D,QAAI;AACH,YAAS,WAAO,UAAU;AAC1B,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEQ,UAAU,iBAAuC;AACxD,QAAI;AACH,aAAQ,kBAAkB,UAAQ,eAAe,IAAI,UAAQ,OAAO;AAAA,IACrE,SAAS,OAAO;AACf,YAAMA;AAAA,QACL;AAAA,QACA,2CACC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CACtD;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;AAEO,SAAS,mBAA+B;AAC9C,SAAO,IAAI,WAAW;AACvB;;;AC9KA;AAAA,EACC,wBAAAC;AAAA,EACA;AAAA,OAGM;AACP,SAAS,SAAS,wBAAwB;AAC1C,OAAO,WAAW;AASX,SAAS,kBAA6B;AAC5C,SAAO;AAAA,IACN,KAAK,MAAc,SAA4C;AAC9D,YAAM,eAAe,EAAE,GAAG,2BAA2B,GAAG,QAAQ;AAChE,YAAM,OAAO,UAAU,IAAI;AAC3B,YAAM,SAAS,WAAW,MAAM,YAAY;AAC5C,aAAO,KAAK,UAAU,QAAQ,MAAM,aAAa,IAAI,CAAC;AAAA,IACvD;AAAA,IACA,OAAO,MAAc,SAAS,GAAW;AACxC,aAAO,KAAK,UAAU,UAAU,IAAI,GAAG,MAAM,MAAM;AAAA,IACpD;AAAA,IACA,SAAS,MAAoC;AAC5C,UAAI;AACH,kBAAU,IAAI;AACd,eAAO,EAAE,OAAO,KAAK;AAAA,MACtB,SAAS,OAAO;AACf,eAAO;AAAA,UACN,OAAO;AAAA,UACP,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,QAC7D;AAAA,MACD;AAAA,IACD;AAAA,IACA,OAAO,MAAsB;AAC5B,aAAO,KAAK,UAAU,UAAU,IAAI,CAAC;AAAA,IACtC;AAAA,EACD;AACD;AAEA,SAAS,UAAU,MAAuB;AACzC,QAAM,UAAU,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,MAAM,iBAAiB,IAAI,GAAG,MAAM,MAAM,MAAM,IAAI,CAAC;AAE9F,aAAW,UAAU,SAAS;AAC7B,QAAI;AACH,aAAO,OAAO;AAAA,IACf,QAAQ;AAAA,IAER;AAAA,EACD;AAEA,QAAMA,sBAAqB,sBAAsB,sBAAsB;AACxE;AAEA,SAAS,WAAW,KAAc,SAAmC;AACpE,MAAI,MAAM,QAAQ,GAAG,GAAG;AACvB,WAAO,IAAI,IAAI,CAAC,SAAS,WAAW,MAAM,OAAO,CAAC;AAAA,EACnD;AAEA,MAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC5C,UAAM,SAAS;AACf,UAAM,aAAa,SAAS,OAAO,KAAK,MAAM,GAAG,OAAO;AACxD,UAAM,SAAkC,CAAC;AAEzC,eAAW,OAAO,YAAY;AAC7B,aAAO,GAAG,IAAI,WAAW,OAAO,GAAG,GAAG,OAAO;AAAA,IAC9C;AAEA,WAAO;AAAA,EACR;AAEA,SAAO;AACR;AAEA,SAAS,SAAS,MAAgB,SAAoC;AACrE,MAAI;AAEJ,UAAQ,QAAQ,UAAU;AAAA,IACzB,KAAK;AACJ,kBAAY,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE;AACnC;AAAA,IACD,KAAK;AACJ,kBAAY,CAAC,GAAG,MAAM,EAAE,cAAc,GAAG,QAAW,EAAE,SAAS,KAAK,CAAC;AACrE;AAAA,IACD;AACC,kBAAY,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC;AACvC;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,SAAS;AACvC,SAAO,QAAQ,cAAc,SAAS,OAAO,QAAQ,IAAI;AAC1D;AAEA,SAAS,aAAa,MAAsB;AAC3C,aAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACpC,UAAM,QAAQ,KAAK,MAAM,QAAQ;AACjC,QAAI,QAAQ,CAAC,GAAG;AACf,aAAO,MAAM,CAAC,EAAE;AAAA,IACjB;AAAA,EACD;AAEA,SAAO;AACR;;;ACnGO,IAAM,aAA8B;AAAA,EAC1C,QAAQ;AAAA,EAAC;AAAA,EACT,OAAO;AAAA,EAAC;AAAA,EACR,OAAO;AAAA,EAAC;AAAA,EACR,QAAQ;AAAA,EAAC;AACV;AAEA,SAAS,WAAW,MAAyB;AAC5C,SAAO,KACL,IAAI,CAAC,QAAQ;AACb,QAAI,OAAO,QAAQ,UAAU;AAC5B,aAAO;AAAA,IACR;AAEA,QAAI;AACH,aAAO,KAAK,UAAU,GAAG;AAAA,IAC1B,QAAQ;AACP,aAAO,OAAO,GAAG;AAAA,IAClB;AAAA,EACD,CAAC,EACA,KAAK,GAAG;AACX;AAEO,SAAS,oBAAoB,SAAS,aAA8B;AAC1E,SAAO;AAAA,IACN,MAAM,YAAoB,MAAiB;AAC1C,cAAQ,OAAO,MAAM,IAAI,MAAM,WAAW,OAAO,IAAI,WAAW,IAAI,CAAC;AAAA,CAAI;AAAA,IAC1E;AAAA,IACA,KAAK,YAAoB,MAAiB;AACzC,cAAQ,OAAO,MAAM,IAAI,MAAM,UAAU,OAAO,IAAI,WAAW,IAAI,CAAC;AAAA,CAAI;AAAA,IACzE;AAAA,IACA,KAAK,YAAoB,MAAiB;AACzC,cAAQ,OAAO,MAAM,IAAI,MAAM,UAAU,OAAO,IAAI,WAAW,IAAI,CAAC;AAAA,CAAI;AAAA,IACzE;AAAA,IACA,MAAM,YAAoB,MAAiB;AAC1C,cAAQ,OAAO,MAAM,IAAI,MAAM,WAAW,OAAO,IAAI,WAAW,IAAI,CAAC;AAAA,CAAI;AAAA,IAC1E;AAAA,EACD;AACD;;;ACtBA,SAAS,cAAAC,mBAAkB;AAC3B,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AAkBtB,SAAS,qBAAuC;AAC/C,SAAO;AAAA,IACN;AAAA,MACC,MAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA,MAI/B,gBAAgB,KAAK,UAAU,EAAE,SAAS,GAAG,UAAU,CAAC,EAAE,GAAG,MAAM,GAAI;AAAA,IACxE;AAAA,IACA;AAAA,MACC,MAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA,MAIxB,gBAAgB,KAAK;AAAA,QACpB,EAAE,SAAS,GAAG,SAAS,OAAO,sBAAsB,KAAK;AAAA,QACzD;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC,MAAM,mBAAmB;AAAA;AAAA;AAAA,MAGzB,gBAAgB,KAAK,UAAU,EAAE,SAAS,GAAG,WAAW,CAAC,GAAG,aAAa,CAAC,EAAE,GAAG,MAAM,GAAI;AAAA,IAC1F;AAAA,IACA;AAAA,MACC,MAAM,iBAAiB;AAAA;AAAA;AAAA,MAGvB,gBAAgBC,YAAW;AAAA,IAC5B;AAAA,IACA;AAAA,MACC,MAAM,oBAAoB;AAAA;AAAA;AAAA,MAG1B,gBAAgB,KAAK,UAAU,EAAE,SAAS,GAAG,UAAU,CAAC,EAAE,GAAG,MAAM,GAAI;AAAA,IACxE;AAAA,EACD;AACD;AAgBA,eAAsB,8BAA8D;AACnF,QAAM,SAAgC,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE;AAC7E,QAAM,OAAO,iBAAiB;AAK9B,MAAI;AACH,UAAS,UAAM,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,EACzC,SAAS,KAAK;AACb,WAAO,OAAO,KAAK,EAAE,MAAM,MAAM,QAAS,IAAc,QAAQ,CAAC;AACjE,WAAO;AAAA,EACR;AAEA,aAAW,QAAQ,mBAAmB,GAAG;AACxC,QAAI;AACH,YAAS,WAAO,KAAK,IAAI;AACzB,aAAO,QAAQ,KAAK,KAAK,IAAI;AAAA,IAC9B,QAAQ;AAEP,UAAI;AACH,cAAS,UAAW,cAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,cAAS,cAAU,KAAK,MAAM,KAAK,gBAAgB,MAAM;AACzD,eAAO,QAAQ,KAAK,KAAK,IAAI;AAAA,MAC9B,SAAS,UAAU;AAClB,eAAO,OAAO,KAAK,EAAE,MAAM,KAAK,MAAM,QAAS,SAAmB,QAAQ,CAAC;AAAA,MAC5E;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;;;ACjIA,YAAYC,SAAQ;AACpB,YAAYC,YAAU;AACtB;AAAA,EACC,wBAAAC;AAAA,OAOM;;;ACVP,SAAS,WAAW,yBAAyB;AAC7C,SAAS,UAAAC,SAAQ,UAAU,OAAO,SAAAC,QAAO,WAAAC,UAAS,UAAAC,SAAQ,MAAAC,WAAU;AACpE,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAE9B,OAAO,WAAW;AAEX,IAAM,YAAY,CAAC,SAAiB,SAAgC;AAC1E,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACvC,UAAM,KAAK,SAAS,EAAE,aAAa,KAAK,GAAG,CAAC,KAAmB,YAA4B;AAC1F,UAAI,IAAK,QAAO,OAAO,GAAG;AAC1B,UAAI,CAAC,QAAS,QAAO,OAAO,IAAI,MAAM,0BAA0B,CAAC;AAEjE,cAAQ,UAAU;AAClB,cAAQ,GAAG,SAAS,CAAC,UAAuB;AAC3C,YAAI,MAAM,KAAK,MAAM,QAAQ,GAAG;AAC/B,eAAKN,OAAMK,MAAK,MAAM,MAAM,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC,EACxD,KAAK,MAAM;AACX,oBAAQ,UAAU;AAAA,UACnB,CAAC,EACA,MAAM,MAAM;AAAA,QACf,OAAO;AACN,gBAAM,aAAaA,MAAK,MAAM,MAAM,QAAQ;AAC5C,eAAKL,OAAMI,SAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC,EACjD,KAAK,MAAM;AACX,oBAAQ;AAAA,cACP;AAAA,cACA,CAAC,aAA2B,eAAgC;AAC3D,oBAAI,YAAa,QAAO,OAAO,WAAW;AAC1C,oBAAI,CAAC,WAAY,QAAO,OAAO,IAAI,MAAM,kCAAkC,CAAC;AAE5E,sBAAM,cAAc,kBAAkB,UAAU;AAChD,2BAAW,GAAG,SAAS,MAAM;AAC7B,4BAAY,GAAG,SAAS,MAAM;AAC9B,4BAAY,GAAG,SAAS,MAAM;AAC7B,0BAAQ,UAAU;AAAA,gBACnB,CAAC;AAED,2BAAW,KAAK,WAAW;AAAA,cAC5B;AAAA,YACD;AAAA,UACD,CAAC,EACA,MAAM,MAAM;AAAA,QACf;AAAA,MACD,CAAC;AAED,cAAQ,GAAG,OAAO,MAAM;AACvB,QAAAE,SAAQ;AAAA,MACT,CAAC;AAED,cAAQ,GAAG,SAAS,CAAC,aAAoB;AACxC,eAAO,QAAQ;AAAA,MAChB,CAAC;AAAA,IACF,CAAC;AAAA,EACF,CAAC;AACF;AAEA,IAAM,WAAW,OAAO,eAAuB;AAC9C,MAAI;AACH,WAAO,MAAM,MAAM,UAAU;AAAA,EAC9B,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,IAAM,aAAa,OAClB,YACA,UACA,cACmB;AACnB,QAAM,aAAa,MAAM,MAAM,UAAU;AACzC,QAAM,WAAW,MAAM,SAAS,QAAQ;AAExC,MAAI,WAAW,YAAY,GAAG;AAC7B,QAAI,YAAY,CAAC,SAAS,YAAY,GAAG;AACxC,UAAI,CAAC,WAAW;AACf,cAAMH,IAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD;AAAA,MACD;AAEA,YAAMA,IAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACpD;AAEA,UAAMH,OAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AACzC,UAAM,WAAW,MAAMC,SAAQ,UAAU;AAEzC,eAAW,SAAS,UAAU;AAC7B,YAAM,WAAWI,MAAK,YAAY,KAAK,GAAGA,MAAK,UAAU,KAAK,GAAG,SAAS;AAAA,IAC3E;AAEA,UAAMF,IAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD;AAAA,EACD;AAEA,MAAI,UAAU;AACb,QAAI,CAAC,WAAW;AACf,YAAMA,IAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD;AAAA,IACD;AAEA,UAAMA,IAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACpD;AAEA,MAAI;AACH,UAAMD,QAAO,YAAY,QAAQ;AAAA,EAClC,QAAQ;AACP,UAAM,SAAS,YAAY,QAAQ;AACnC,UAAMC,IAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACtD;AACD;AAEO,IAAM,YAAY,OACxB,WACA,SACA,YAAY,UACO;AACnB,QAAMH,OAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AACxC,QAAM,QAAQ,MAAMC,SAAQ,SAAS;AAErC,aAAW,QAAQ,OAAO;AACzB,UAAM,aAAaI,MAAK,WAAW,IAAI;AACvC,UAAM,WAAWA,MAAK,SAAS,IAAI;AAEnC,QAAI,CAAC,WAAW;AACf,UAAI;AACH,cAAMN,QAAO,UAAU,UAAU,IAAI;AACrC,cAAMI,IAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD;AAAA,MACD,QAAQ;AAAA,MAER;AAAA,IACD;AAEA,UAAM,WAAW,YAAY,UAAU,SAAS;AAAA,EACjD;AACD;;;ADxHO,IAAM,eAAN,MAAmB;AAAA,EACzB,MAAM,gBACL,SACA,eACA,gBACA,OACiD;AACjD,UAAM,UAAU,SAAS,cAAc;AAEvC,QAAI,YAAiB,YAAK,gBAAgB,MAAM,gBAAgB;AAChE,QAAI,gBAAgB,MAAM;AAE1B,QAAI,CAAE,MAAM,KAAK,WAAW,SAAS,GAAI;AACxC,YAAM,UAAU,MAAS,YAAQ,gBAAgB,EAAE,eAAe,KAAK,CAAC;AACxE,YAAM,cAAc,QAAQ;AAAA,QAC3B,CAAC,UAAU,MAAM,YAAY,KAAK,CAAC,MAAM,KAAK,WAAW,GAAG;AAAA,MAC7D;AAEA,YAAM,oBAAoB,MAAM,KAAK;AAAA,QACpC,YAAY,IAAI,CAAC,cAAc,UAAU,IAAI;AAAA,QAC7C;AAAA,QACA,MAAM;AAAA,QACN,MAAM;AAAA,MACP;AAEA,UAAI,mBAAmB;AACtB,wBAAgB;AAChB,oBAAiB,YAAK,gBAAgB,aAAa;AAAA,MACpD,WAAW,YAAY,WAAW,GAAG;AACpC,cAAM,IAAI;AAAA,UACT,4DAA4D,MAAM,gBAAgB;AAAA,QACnF;AAAA,MACD,OAAO;AACN,cAAM,IAAI;AAAA,UACT,gDAAgD,YAC9C,IAAI,CAAC,cAAc,UAAU,IAAI,EACjC,KAAK,IAAI,CAAC,eAAe,MAAM,gBAAgB;AAAA,QAClD;AAAA,MACD;AAAA,IACD;AAEA,QAAI,CAAE,MAAM,KAAK,WAAW,SAAS,GAAI;AACxC,YAAM,IAAI,MAAM,+BAA+B,SAAS,EAAE;AAAA,IAC3D;AAEA,UAAM,UAAU,WAAW,eAAe,IAAI;AAE9C,WAAO;AAAA,MACN;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,oBACL,eACA,SAC6C;AAC7C,QAAI,CAAC,SAAS;AACb,YAAMI,sBAAqB,kBAAkB,2BAA2B;AAAA,IACzE;AAEA,UAAM,WAAW,SAAS;AAAA,MACzB,KAAK;AAAA,MACL,OAAO;AAAA,IACR,CAAC;AAED,WAAO;AAAA,MACN,WAAW;AAAA,MACX;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,sBACL,eACuD;AACvD,UAAM,YAAY,QAAQ,aAAa;AACvC,UAAM,UAAU,MAAM,KAAK,YAAY,eAAe,YAAY,CAAC,QAAQ,MAAM,IAAI,CAAC,KAAK,CAAC;AAE5F,QAAI,eAAe;AAEnB,QAAI,WAAW;AACd,iBAAW,cAAc,QAAQ,OAAO,CAAC,WAAW,OAAO,SAAS,MAAM,CAAC,GAAG;AAC7E,YAAI;AACH,gBAAM,WAAW,4CAA4C,UAAU,MAAM;AAAA,YAC5E,KAAK;AAAA,YACL,OAAO;AAAA,UACR,CAAC;AACD,0BAAgB;AAAA,QACjB,QAAQ;AAAA,QAER;AAAA,MACD;AAAA,IACD,OAAO;AACN,iBAAW,cAAc,SAAS;AACjC,YAAI;AACH,gBAAS,UAAM,YAAY,GAAK;AAChC,0BAAgB;AAAA,QACjB,QAAQ;AAAA,QAER;AAAA,MACD;AAAA,IACD;AAEA,WAAO;AAAA,MACN,gBAAgB,QAAQ;AAAA,MACxB;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,cAAc,eAA+D;AAClF,UAAM,UAAU;AAChB,UAAM,WAAW,SAAS;AAAA,MACzB,KAAK;AAAA,MACL,OAAO;AAAA,IACR,CAAC;AAED,WAAO;AAAA,MACN,aAAa;AAAA,MACb;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,iBACL,eACA,QAC+C;AAC/C,QAAI,CAAC,QAAQ;AACZ,YAAMA,sBAAqB,kBAAkB,2BAA2B;AAAA,IACzE;AAEA,UAAM,KAAK,qBAAqB,eAAe,MAAM;AAErD,UAAM,eAAe,uCAAuC,MAAM;AAClE,UAAM,sBAAsB,+CAA+C,MAAM;AAEjF,UAAM,WAAW,CAAC,cAAc,mBAAmB;AAEnD,eAAW,WAAW,UAAU;AAC/B,UAAI,YAAY,qBAAqB;AAGpC,cAAM,KAAK,qBAAqB,eAAe,MAAM;AAAA,MACtD;AAEA,YAAM,WAAW,SAAS;AAAA,QACzB,KAAK;AAAA,QACL,OAAO;AAAA,MACR,CAAC;AAAA,IACF;AAEA,WAAO;AAAA,MACN,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,qBAAqB,eAAuB,QAA+B;AACxF,UAAM,qBAA0B;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG,MAAM;AAAA,IACV;AAEA,QAAI,MAAM,KAAK,WAAW,kBAAkB,GAAG;AAC9C;AAAA,IACD;AAEA,UAAM,kBAAuB,YAAK,eAAe,gBAAgB,mBAAmB;AACpF,QAAI,CAAE,MAAM,KAAK,WAAW,eAAe,GAAI;AAC9C;AAAA,IACD;AAEA,UAAM,iBAAiB,MAAS,aAAS,iBAAiB,MAAM;AAChE,UAAM,cAAc,KAAK,MAAM,cAAc;AAK7C,UAAM,oBAAoB;AAAA,MACzB;AAAA,MACA,iBAAiB,MAAM,QAAQ,YAAY,eAAe,IACvD,YAAY,kBACZ,CAAC;AAAA,MACJ,eAAe,MAAM,QAAQ,YAAY,aAAa,IAAI,YAAY,gBAAgB,CAAC;AAAA,MACvF,SAAS,CAAC;AAAA,MACV,sBAAsB,CAAC;AAAA,MACvB,cAAc,CAAC;AAAA,MACf,mBAAmB,CAAC;AAAA,MACpB,gBAAgB,CAAC;AAAA,IAClB;AAEA,UAAS,UAAW,eAAQ,kBAAkB,GAAG,EAAE,WAAW,KAAK,CAAC;AACpE,UAAS;AAAA,MACR;AAAA,MACA,GAAG,KAAK,UAAU,mBAAmB,MAAM,CAAC,CAAC;AAAA;AAAA,MAC7C;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,YAAY,KAAa,YAAyC;AAC/E,UAAM,UAAoB,CAAC;AAC3B,QAAI;AAEJ,QAAI;AACH,gBAAU,MAAS,YAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IACxD,QAAQ;AACP,aAAO;AAAA,IACR;AAEA,eAAW,SAAS,SAAS;AAC5B,YAAM,WAAgB,YAAK,KAAK,MAAM,IAAI;AAC1C,UAAI,MAAM,YAAY,KAAK,MAAM,SAAS,kBAAkB,CAAC,MAAM,KAAK,WAAW,GAAG,GAAG;AACxF,gBAAQ,KAAK,GAAI,MAAM,KAAK,YAAY,UAAU,UAAU,CAAE;AAAA,MAC/D,WAAW,MAAM,OAAO,KAAK,WAAW,KAAK,CAAC,QAAQ,MAAM,KAAK,SAAS,GAAG,CAAC,GAAG;AAChF,gBAAQ,KAAK,QAAQ;AAAA,MACtB;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,WAAW,YAAsC;AAC9D,QAAI;AACH,YAAS,WAAO,UAAU;AAC1B,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAc,yBACb,gBACA,gBACA,oBACA,iBACyB;AACzB,QAAI,eAAe,WAAW,GAAG;AAChC,aAAO,eAAe,CAAC,KAAK;AAAA,IAC7B;AAEA,UAAM,aAAa,eAAe,KAAK,CAAC,kBAAkB,kBAAkB,eAAe;AAC3F,QAAI,YAAY;AACf,aAAO;AAAA,IACR;AAEA,UAAM,UAAoB,CAAC;AAC3B,eAAW,iBAAiB,gBAAgB;AAC3C,UACC,MAAM,KAAK;AAAA,QACL,YAAK,gBAAgB,aAAa;AAAA,QACvC;AAAA,MACD,GACC;AACD,gBAAQ,KAAK,aAAa;AAAA,MAC3B;AAAA,IACD;AAEA,QAAI,QAAQ,WAAW,GAAG;AACzB,aAAO,QAAQ,CAAC,KAAK;AAAA,IACtB;AAEA,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,+BACb,eACA,oBACmB;AACnB,UAAM,UAAU,MAAS,YAAQ,aAAa;AAC9C,QAAI,mBAAmB,SAAS,GAAG,GAAG;AACrC,YAAM,QAAQ,IAAI,OAAO,IAAI,mBAAmB,QAAQ,KAAK,IAAI,CAAC,GAAG;AACrE,aAAO,QAAQ,KAAK,CAAC,UAAU,MAAM,KAAK,KAAK,CAAC;AAAA,IACjD;AAEA,WAAO,QAAQ,SAAS,kBAAkB;AAAA,EAC3C;AACD;AAEO,SAAS,qBAAmC;AAClD,SAAO,IAAI,aAAa;AACzB;;;AEzSA,YAAYC,SAAQ;AACpB,YAAYC,YAAU;;;AC0Ff,SAAS,cAAc,MAA6C;AAC1E,SAAO,KAAK,WAAW;AACxB;AAGO,SAAS,WAAW,MAA0C;AACpE,SAAO,KAAK,WAAW;AACxB;;;ADvBA,SAAS,UAAU,KAAqB;AACvC,QAAM,UAAU,IACd,KAAK,EACL,QAAQ,QAAQ,EAAE,EAClB,QAAQ,UAAU,EAAE;AAEtB,QAAM,WAAW,CAAC,UACjB,MACE,KAAK,EACL,YAAY,EACZ,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,OAAO,GAAG,EAClB,QAAQ,YAAY,EAAE;AAEzB,MAAI,QAAQ;AACZ,MAAI,OAAO;AAEX,MAAI,gBAAgB,KAAK,OAAO,GAAG;AAClC,UAAM,SAAS,IAAI,IAAI,OAAO;AAC9B,UAAM,WAAW,OAAO,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AAC1D,WAAO,SAAS,GAAG,EAAE,KAAK;AAC1B,YAAQ,SAAS,UAAU,IAAK,SAAS,GAAG,EAAE,KAAK,KAAM;AAAA,EAC1D,WAAW,QAAQ,WAAW,MAAM,GAAG;AACtC,UAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,UAAM,WAAW,SAAS,IAAI,QAAQ,MAAM,QAAQ,CAAC,IAAI;AACzD,UAAM,WAAW,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AACnD,WAAO,SAAS,GAAG,EAAE,KAAK;AAC1B,YAAQ,SAAS,UAAU,IAAK,SAAS,GAAG,EAAE,KAAK,KAAM;AAAA,EAC1D,OAAO;AACN,WAAO,QAAQ,MAAM,GAAG,EAAE,IAAI,KAAK;AAAA,EACpC;AAEA,QAAM,WAAW,SAAS,IAAI;AAC9B,QAAM,YAAY,SAAS,KAAK;AAChC,QAAM,OAAO,YAAY,GAAG,SAAS,IAAI,QAAQ,KAAK;AACtD,MAAI,CAAC,qBAAqB,KAAK,IAAI,GAAG;AACrC,UAAM,IAAI,oBAAoB,2CAA2C,GAAG,EAAE;AAAA,EAC/E;AACA,SAAO;AACR;AAEA,SAAS,YAAY,KAAqB;AACzC,QAAM,UAAU,OAAO,KAAK,IAAI,KAAK,GAAG,MAAM,EAAE,SAAS,WAAW;AACpE,SAAO,QAAQ,OAAO;AACvB;AAGO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC5B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACb;AACD;AACO,IAAM,sBAAN,cAAkC,iBAAiB;AAAC;AACpD,IAAM,yBAAN,cAAqC,iBAAiB;AAAC;AACvD,IAAM,sBAAN,cAAkC,iBAAiB;AAAC;AAEpD,IAAM,cAAN,MAAkB;AAAA,EAMxB,YAAY,MAA0B;AACrC,SAAK,QAAQ,KAAK;AAClB,SAAK,MAAM,KAAK;AAChB,SAAK,MAAM,KAAK,QAAQ,OAAM,oBAAI,KAAK,GAAE,YAAY;AACrD,SAAK,YAAY,KAAK,aAAa;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,iBAAsC;AAC3C,UAAM,SAAqB,EAAE,OAAO,CAAC,EAAE;AACvC,UAAM,WAAW,KAAK,MAAM,YAAY;AACxC,eAAW,QAAQ,UAAU;AAC5B,UAAI,CAAC,KAAK,QAAS;AACnB,YAAM,YAAY,WAAW,KAAK,EAAE;AACpC,YAAM,SAAS,MAAM,KAAK,WAAW,SAAS;AAC9C,UAAI,QAAQ;AAGX,YAAI,MAAM,KAAK,eAAe,SAAS,EAAG;AAC1C,cAAS,OAAG,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MACxD;AACA,UAAI;AACH,YAAI,CAAC,KAAK,WAAW;AACpB,gBAAS,UAAW,eAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,gBAAM,KAAK,IAAI,MAAM,KAAK,IAAI,KAAK,KAAK,WAAW,KAAK,QAAQ,IAAI;AAAA,QACrE;AACA,cAAM,KAAK,MAAM,WAAW,KAAK,IAAI;AAAA,UACpC,gBAAgB;AAAA,UAChB,YAAY,KAAK,IAAI;AAAA,QACtB,CAAC;AACD,eAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,QAAQ,KAAK,CAAC;AAAA,MACpD,SAAS,KAAK;AACb,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,cAAM,KAAK,MAAM,WAAW,KAAK,IAAI;AAAA,UACpC,gBAAgB;AAAA,UAChB,YAAY,KAAK,IAAI;AAAA,UACrB,eAAe;AAAA,QAChB,CAAC;AACD,eAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,QAAQ,SAAS,OAAO,QAAQ,CAAC;AAAA,MACvE;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,QAAQ,QAAqC;AAClD,UAAM,OAAO,KAAK,MAAM,IAAI,MAAM;AAClC,QAAI,CAAC,KAAM,OAAM,IAAI,oBAAoB,MAAM;AAC/C,UAAM,WAAW,KAAK,YAAY;AAClC,UAAM,UAAU,WAAW,YAAY,KAAK,GAAG,IAAI,KAAK;AACxD,UAAM,YAAY,WAAW,MAAM;AACnC,UAAM,SAAS,MAAM,KAAK,WAAW,SAAS;AAG9C,QAAI,UAAU,CAAE,MAAM,KAAK,eAAe,SAAS,GAAI;AACtD,YAAS,OAAG,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACxD;AACA,QAAI,CAAC,UAAU,CAAE,MAAM,KAAK,WAAW,SAAS,GAAI;AAEnD,YAAS,UAAW,eAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,UAAI,CAAC,KAAK,WAAW;AACpB,cAAM,KAAK,IAAI,MAAM,SAAS,KAAK,KAAK,WAAW,KAAK,QAAQ,QAAQ;AAAA,MACzE;AACA,YAAM,SAAqB;AAAA,QAC1B,SAAS;AAAA,QACT,WAAW;AAAA,QACX,QAAQ,KAAK;AAAA,MACd;AACA,YAAM,KAAK,MAAM,WAAW,QAAQ;AAAA,QACnC,gBAAgB;AAAA,QAChB,YAAY,KAAK,IAAI;AAAA,MACtB,CAAC;AACD,aAAO;AAAA,IACR;AACA,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,IAAI,KAAK,SAAS,WAAW,KAAK,QAAQ,UAAU,KAAK,GAAG;AACtF,YAAM,KAAK,MAAM,WAAW,QAAQ;AAAA,QACnC,gBAAgB;AAAA,QAChB,YAAY,KAAK,IAAI;AAAA,QACrB,mBAAmB,OAAO;AAAA,MAC3B,CAAC;AACD,aAAO;AAAA,IACR,SAAS,KAAK;AACb,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAM,KAAK,MAAM,WAAW,QAAQ;AAAA,QACnC,gBAAgB;AAAA,QAChB,YAAY,KAAK,IAAI;AAAA,QACrB,eAAe;AAAA,MAChB,CAAC;AACD,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,UAA+B;AACpC,UAAM,SAAqB,EAAE,OAAO,CAAC,EAAE;AACvC,UAAM,UAAU,KAAK,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO;AACzD,eAAW,QAAQ,SAAS;AAC3B,UAAI;AACH,cAAM,IAAI,MAAM,KAAK,QAAQ,KAAK,EAAE;AACpC,eAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,QAAQ,MAAM,QAAQ,EAAE,CAAC;AAAA,MAC/D,SAAS,KAAK;AACb,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,eAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,QAAQ,SAAS,OAAO,QAAQ,CAAC;AAAA,MACvE;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,YAAY,OAAqD;AACtE,UAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,QAAI,CAAC,eAAe,KAAK,GAAG,KAAK,CAAC,IAAI,WAAW,MAAM,GAAG;AACzD,YAAM,IAAI,oBAAoB,uCAAuC,GAAG,EAAE;AAAA,IAC3E;AACA,UAAM,KAAK,UAAU,GAAG;AACxB,UAAM,WAAW,MAAM,YAAY;AACnC,UAAM,cAAc,WAAW,YAAY,GAAG,IAAI;AAClD,qBAAiB,EAAE;AAGnB,QAAI,SAAS,MAAM,QAAQ,KAAK;AAChC,QAAI,CAAC,QAAQ;AACZ,YAAM,WAAW,MAAM,KAAK,IAAI,SAAS,aAAa,KAAK,QAAQ;AACnE,YAAM,OACL,SAAS,KAAK,CAAC,MAAM,EAAE,QAAQ,iBAAiB,KAChD,SAAS,KAAK,CAAC,MAAM,EAAE,QAAQ,mBAAmB,KAClD,SAAS,KAAK,CAAC,MAAM,EAAE,IAAI,SAAS,KAAK,CAAC;AAC3C,UAAI,CAAC,MAAM;AACV,cAAM,IAAI;AAAA,UACT,uCAAuC,GAAG;AAAA,QAC3C;AAAA,MACD;AACA,eAAS,KAAK,IAAI,QAAQ,kBAAkB,EAAE;AAAA,IAC/C;AAIA,QAAI,KAAK,MAAM,IAAI,EAAE,GAAG;AACvB,YAAM,IAAI,uBAAuB,YAAY,EAAE,kBAAkB;AAAA,IAClE;AAEA,UAAM,OAAuB;AAAA,MAC5B;AAAA,MACA,MAAM,MAAM,MAAM,KAAK,KAAK;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,cAAc;AAAA,MACd,QAAQ;AAAA,MACR,SAAS,KAAK,IAAI;AAAA,IACnB;AACA,UAAM,KAAK,MAAM,YAAY,IAAI;AAGjC,QAAI,SAAS;AACb,QAAI,CAAC,KAAK,WAAW;AACpB,YAAM,YAAY,WAAW,EAAE;AAC/B,YAAS,UAAW,eAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,YAAM,KAAK,IAAI,MAAM,aAAa,KAAK,WAAW,QAAQ,QAAQ;AAClE,eAAS;AAAA,IACV;AAEA,WAAO,EAAE,MAAM,QAAQ,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,eAAe,QAA+B;AACnD,UAAM,OAAO,KAAK,MAAM,IAAI,MAAM;AAClC,QAAI,CAAC,KAAM,OAAM,IAAI,oBAAoB,MAAM;AAC/C,QAAI,cAAc,IAAI,GAAG;AACxB,YAAM,IAAI;AAAA,QACT,+BAA+B,MAAM;AAAA,MACtC;AAAA,IACD;AAGA,UAAM,KAAK,MAAM,eAAe,MAAM;AACtC,UAAM,YAAY,WAAW,MAAM;AACnC,QAAI;AACH,YAAS,OAAG,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACxD,SAAS,KAAK;AAEb,UAAK,IAA8B,SAAS,SAAU,OAAM;AAAA,IAC7D;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,QAAwC;AACzD,WAAO,KAAK,MAAM,YAAY,MAAM;AAAA,EACrC;AAAA,EAEA,MAAM,WAAW,QAAwC;AACxD,WAAO,KAAK,MAAM,WAAW,MAAM;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAAc,QAAkC;AACrD,WAAO,KAAK,WAAW,WAAW,MAAM,CAAC;AAAA,EAC1C;AAAA;AAAA,EAGA,MAAM,aAA4B;AACjC,UAAS,UAAM,iBAAiB,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,eAAe,GAA6B;AACzD,WAAO,KAAK,WAAgB,YAAK,GAAG,MAAM,CAAC;AAAA,EAC5C;AAAA,EAEA,MAAc,WAAW,GAA6B;AACrD,QAAI;AACH,YAAS,SAAK,CAAC;AACf,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AACD;;;AEhZO,IAAM,kBAAkB;AAE/B,IAAM,iBAAiB;AAQvB,IAAM,qBAAwE;AAAA,EAC7E;AAAA,IACC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU;AAAA,IACV,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,aAAa;AAAA,EACd;AACD;AAGO,SAAS,yBACf,MAAoB,OAAM,oBAAI,KAAK,GAAE,YAAY,GAC3B;AACtB,QAAM,QAAQ,IAAI;AAClB,SAAO,mBAAmB,IAAI,CAAC,UAAU;AAAA,IACxC,GAAG;AAAA,IACH,SAAS;AAAA,EACV,EAAE;AACH;AAGO,SAAS,qBACf,IACA,MAAoB,OAAM,oBAAI,KAAK,GAAE,YAAY,GACjB;AAChC,QAAM,OAAO,mBAAmB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACvD,MAAI,CAAC,MAAM;AACV,WAAO;AAAA,EACR;AACA,SAAO,EAAE,GAAG,MAAM,SAAS,IAAI,EAAE;AAClC;AAOO,SAAS,sBACf,MAAoB,OAAM,oBAAI,KAAK,GAAE,YAAY,GACjD,UACY;AACZ,QAAM,WAAW,yBAAyB,GAAG;AAE7C,MAAI,CAAC,UAAU;AACd,WAAO;AAAA,MACN,SAAS;AAAA,MACT,eAAe;AAAA,MACf,OAAO;AAAA,IACR;AAAA,EACD;AAKA,QAAM,cAAc,IAAI,IAAI,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC3D,QAAM,kBAAkB,SAAS,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;AACrE,MAAI,gBAAgB,WAAW,GAAG;AACjC,WAAO;AAAA,EACR;AACA,SAAO;AAAA,IACN,GAAG;AAAA,IACH,OAAO,CAAC,GAAG,SAAS,OAAO,GAAG,eAAe;AAAA,IAC7C,eAAe,qBAAqB,UAAU,WAAW;AAAA,EAC1D;AACD;AAQO,SAAS,wBACf,UACA,MAAoB,OAAM,oBAAI,KAAK,GAAE,YAAY,GACJ;AAC7C,QAAM,WAAW,yBAAyB,GAAG;AAC7C,QAAM,cAAc,IAAI,IAAI,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC3D,QAAM,UAAU,SAAS,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;AAC7D,MAAI,QAAQ,WAAW,GAAG;AACzB,WAAO,EAAE,QAAQ,UAAU,WAAW,CAAC,EAAE;AAAA,EAC1C;AACA,QAAM,OAAkB;AAAA,IACvB,GAAG;AAAA,IACH,OAAO,CAAC,GAAG,SAAS,OAAO,GAAG,OAAO;AAAA,IACrC,eAAe,qBAAqB,UAAU,WAAW;AAAA,EAC1D;AACA,SAAO,EAAE,QAAQ,MAAM,WAAW,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE;AAC5D;AAWA,SAAS,qBAAqB,UAAqB,aAAkC;AACpF,QAAM,cAAc,SAAS;AAC7B,MAAI,eAAe,YAAY,IAAI,WAAW,GAAG;AAChD,WAAO;AAAA,EACR;AACA,SAAO;AACR;;;AC7KA,YAAYC,SAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,SAAS;AAuBlB,IAAM,wBAAwB;AAE9B,IAAM,eAAe,EACnB,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,MAAM,sBAAsB,uDAAuD;AAErF,IAAM,qBAAqB,EAAE,OAAO,EAAE,MAAM,uBAAuB;AAAA,EAClE,SAAS;AACV,CAAC;AAED,IAAM,iBAAiB;AAAA,EACtB,IAAI;AAAA,EACJ,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC/B,KAAK,EAAE,OAAO,EAAE,IAAI;AAAA,EACpB,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EACjC,SAAS,EAAE,QAAQ;AAAA,EACnB,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EAC7C,cAAc,EAAE,QAAQ;AAAA,EACxB,SAAS;AAAA,EACT,YAAY,mBAAmB,SAAS;AAAA,EACxC,mBAAmB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EACtD,gBAAgB,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,EAAE,SAAS;AAAA,EACjD,eAAe,EAAE,OAAO,EAAE,IAAI,GAAK,EAAE,SAAS;AAC/C;AAEA,IAAM,oBAAoB,EAAE,OAAO;AAAA,EAClC,GAAG;AAAA,EACH,QAAQ,EAAE,QAAQ,SAAS;AAAA,EAC3B,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAK;AACzC,CAAC;AAED,IAAM,iBAAiB,EAAE,OAAO;AAAA,EAC/B,GAAG;AAAA,EACH,QAAQ,EAAE,QAAQ,MAAM;AACzB,CAAC;AAED,IAAM,aAAa,EAAE,mBAAmB,UAAU,CAAC,mBAAmB,cAAc,CAAC;AAE9E,IAAM,kBAAkB,EAC7B,OAAO;AAAA,EACP,SAAS,EAAE,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA,EAIpB,eAAe,EAAE,OAAO;AAAA,EACxB,OAAO,EAAE,MAAM,UAAU;AAC1B,CAAC,EACA,YAAY,CAAC,OAAO,QAAQ;AAC5B,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,QAAQ,MAAM,OAAO;AAC/B,QAAI,IAAI,IAAI,KAAK,EAAE,GAAG;AACrB,UAAI,SAAS;AAAA,QACZ,MAAM,EAAE,aAAa;AAAA,QACrB,MAAM,CAAC,OAAO;AAAA,QACd,SAAS,sBAAsB,KAAK,EAAE;AAAA,MACvC,CAAC;AACD;AAAA,IACD;AACA,QAAI,IAAI,KAAK,EAAE;AAAA,EAChB;AACA,MAAI,MAAM,MAAM,WAAW,GAAG;AAC7B,QAAI,MAAM,kBAAkB,IAAI;AAC/B,UAAI,SAAS;AAAA,QACZ,MAAM,EAAE,aAAa;AAAA,QACrB,MAAM,CAAC,eAAe;AAAA,QACtB,SAAS;AAAA,MACV,CAAC;AAAA,IACF;AACA;AAAA,EACD;AACA,MAAI,CAAC,IAAI,IAAI,MAAM,aAAa,GAAG;AAClC,QAAI,SAAS;AAAA,MACZ,MAAM,EAAE,aAAa;AAAA,MACrB,MAAM,CAAC,eAAe;AAAA,MACtB,SAAS,kBAAkB,MAAM,aAAa;AAAA,IAC/C,CAAC;AAAA,EACF;AACD,CAAC;AAsCF,IAAM,+BAA+B;AAErC,SAAS,sBAA8B;AAKtC,QAAM,WAAW;AACjB,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,8BAA8B,KAAK;AACtD,WAAO,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,SAAS,MAAM,CAAC;AAAA,EAC5D;AACA,SAAO;AACR;AAEO,IAAM,cAAN,MAAkB;AAAA,EAMxB,YAAY,SAA6B;AACxC,SAAK,aAAa,QAAQ;AAC1B,SAAK,MAAM,QAAQ,QAAQ,OAAM,oBAAI,KAAK,GAAE,YAAY;AACxD,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,aAAa,QAAQ,cAAcC;AAAA,EACzC;AAAA;AAAA,EAGA,gBAAwB;AACvB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAA4B;AACjC,QAAI;AACJ,QAAI;AACH,YAAM,MAAM,KAAK,WAAW,SAAS,KAAK,YAAY,OAAO;AAAA,IAC9D,SAAS,OAAO;AACf,UAAI,YAAY,OAAO,QAAQ,GAAG;AACjC,eAAO;AAAA,UACN,QAAQ,KAAK,iBAAiB;AAAA,UAC9B,qBAAqB;AAAA,QACtB;AAAA,MACD;AACA,YAAM;AAAA,IACP;AAEA,UAAM,SAAS,cAAc,GAAG;AAChC,QAAI,WAAW,QAAW;AACzB,YAAM,aAAa,MAAM,KAAK,WAAW,mBAAmB;AAC5D,aAAO;AAAA,QACN,QAAQ,KAAK,iBAAiB;AAAA,QAC9B,qBAAqB;AAAA,MACtB;AAAA,IACD;AAEA,UAAM,SAAS,gBAAgB,UAAU,MAAM;AAC/C,QAAI,CAAC,OAAO,SAAS;AACpB,YAAM,aAAa,MAAM,KAAK,WAAW,gBAAgB;AACzD,aAAO;AAAA,QACN,QAAQ,KAAK,iBAAiB;AAAA,QAC9B,qBAAqB;AAAA,MACtB;AAAA,IACD;AAEA,WAAO;AAAA,MACN,QAAQ,OAAO;AAAA,MACf,qBAAqB;AAAA,IACtB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KAAK,QAAkC;AAC5C,UAAM,YAAY,gBAAgB,MAAM,MAAM;AAE9C,UAAM,MAAW,eAAQ,KAAK,UAAU;AACxC,UAAM,KAAK,WAAW,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAEpD,UAAM,aAAa,GAAG,KAAK,UAAU,WAAW,MAAM,CAAC,CAAC;AAAA;AACxD,UAAM,WAAW,GAAG,KAAK,UAAU,QAAQ,KAAK,aAAa,CAAC;AAE9D,UAAM,KAAK,WAAW,UAAU,UAAU,YAAY,OAAO;AAC7D,QAAI;AACH,YAAM,KAAK,WAAW,OAAO,UAAU,KAAK,UAAU;AAAA,IACvD,SAAS,OAAO;AAEf,YAAMC,UAAS,KAAK,WAAW,UAAa;AAC5C,YAAMA,QAAO,QAAQ,EAAE,MAAM,MAAM,MAAS;AAC5C,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,WAAW,QAAiC;AACzD,UAAM,QAAQ,oBAAoB,KAAK,IAAI,CAAC;AAC5C,UAAM,aAAa,GAAG,KAAK,UAAU,IAAI,KAAK,IAAI,MAAM;AACxD,QAAI;AACH,YAAM,KAAK,WAAW,OAAO,KAAK,YAAY,UAAU;AAAA,IACzD,SAAS,OAAO;AACf,UAAI,CAAC,YAAY,OAAO,QAAQ,GAAG;AAClC,cAAM;AAAA,MACP;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,mBAA8B;AACrC,WAAO;AAAA,MACN,SAAS;AAAA,MACT,eAAe;AAAA,MACf,OAAO,CAAC;AAAA,IACT;AAAA,EACD;AACD;AAEA,SAAS,cAAc,KAAkC;AACxD,MAAI;AACH,WAAO,KAAK,MAAM,GAAG;AAAA,EACtB,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAAS,YAAY,OAAgB,MAAuB;AAC3D,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAChD,WAAO;AAAA,EACR;AACA,SAAQ,MAA6B,SAAS;AAC/C;AAEA,SAAS,oBAAoB,KAAqB;AACjD,SAAO,IAAI,QAAQ,YAAY,GAAG,EAAE,QAAQ,OAAO,GAAG;AACvD;AAUO,SAAS,kBAAkB,OAA2B;AAC5D,SAAO,gBAAgB,MAAM,KAAK;AACnC;AAGO,SAAS,iBAAiB,MAAyD;AACzF,SAAO,KAAK,WAAW,YAAa,OAA8B;AACnE;;;ACtTA,SAAS,gBAAAC,qBAAoB;AAC7B,YAAYC,UAAQ;AAsFb,IAAM,aAAN,MAAiB;AAAA,EAgBvB,YAAY,UAA6B,CAAC,GAAG;AAN7C,SAAQ,SAA2B;AACnC,SAAiB,UAAU,IAAIC,cAAa;AAC5C,SAAQ,UAAkC;AAC1C,SAAQ,cAAqC;AAC7C,SAAQ,iBAAoC;AAG3C,SAAK,SAAS,QAAQ,UAAU,IAAI,YAAY,EAAE,YAAY,GAAG,CAAC;AAClE,SAAK,aAAa,QAAQ,cAAc,EAAE,OAAU,WAAM;AAC1D,SAAK,MAAM,QAAQ,QAAQ,OAAM,oBAAI,KAAK,GAAE,YAAY;AACxD,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,kBACJ,QAAQ,oBAAoB,CAAC,GAAG,OAAO,KAAK,uBAAuB,GAAG,EAAE;AAAA,EAC1E;AAAA;AAAA,EAGA,YAA8B;AAC7B,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,gBAAwB;AACvB,WAAO,KAAK,OAAO,cAAc;AAAA,EAClC;AAAA;AAAA,EAGA,UAAU,UAA0C;AACnD,SAAK,QAAQ,GAAG,UAAU,QAAQ;AAClC,WAAO,MAAM;AACZ,WAAK,QAAQ,IAAI,UAAU,QAAQ;AAAA,IACpC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAA4B;AACjC,UAAM,SAAS,MAAM,KAAK,OAAO,KAAK;AACtC,SAAK,iBAAiB;AACtB,SAAK,SAAS,OAAO;AACrB,SAAK,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,OAAO,CAAC;AACjD,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,MAAM,eAAmC;AACxC,UAAM,SAAS,MAAM,KAAK,KAAK;AAC/B,WAAO,OAAO;AAAA,EACf;AAAA;AAAA,EAGA,oBAAuC;AACtC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAsB;AACrB,QAAI,KAAK,SAAS;AACjB;AAAA,IACD;AACA,UAAM,aAAa,KAAK,OAAO,cAAc;AAC7C,QAAI,CAAC,YAAY;AAChB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC1D;AACA,SAAK,UAAU,KAAK,gBAAgB,YAAY;AAAA,MAC/C,UAAU,MAAM,KAAK,eAAe;AAAA,MACpC,UAAU,MAAM,KAAK,eAAe;AAAA,MACpC,SAAS,CAAC,QAAQ,KAAK,KAAK,EAAE,MAAM,SAAS,OAAO,IAAI,CAAC;AAAA,IAC1D,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,eAAqB;AACpB,QAAI,KAAK,aAAa;AACrB,mBAAa,KAAK,WAAW;AAC7B,WAAK,cAAc;AAAA,IACpB;AACA,QAAI,KAAK,SAAS;AACjB,WAAK,QAAQ,MAAM;AACnB,WAAK,UAAU;AAAA,IAChB;AAAA,EACD;AAAA;AAAA,EAIA,OAAwB;AACvB,WAAO,KAAK,QAAQ,MAAM,MAAM,KAAK,CAAC;AAAA,EACvC;AAAA,EAEA,cAA+B;AAC9B,WAAO,KAAK,KAAK,EAAE,OAAO,aAAa;AAAA,EACxC;AAAA,EAEA,WAA6B;AAC5B,WAAO,KAAK,KAAK,EAAE,OAAO,CAAC,MAA2B,EAAE,WAAW,MAAM;AAAA,EAC1E;AAAA,EAEA,IAAI,QAA2C;AAC9C,WAAO,KAAK,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,OAAgE;AACjF,UAAM,WAA2B;AAAA,MAChC,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,SAAS,MAAM,WAAW,KAAK,IAAI;AAAA,IACpC;AACA,UAAM,KAAK,OAAO,CAAC,YAAY;AAC9B,UAAI,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,SAAS,EAAE,GAAG;AACpD,cAAM,IAAI,uBAAuB,SAAS,EAAE;AAAA,MAC7C;AACA,aAAO;AAAA,QACN,GAAG;AAAA,QACH,OAAO,CAAC,GAAG,QAAQ,OAAO,QAAQ;AAAA,MACnC;AAAA,IACD,CAAC;AACD,SAAK,KAAK,EAAE,MAAM,OAAO,MAAM,SAAS,CAAC;AACzC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WAAW,QAAgB,OAAoD;AACpF,UAAM,UAAU,MAAM,KAAK,OAAO,CAAC,YAAY;AAC9C,YAAM,MAAM,QAAQ,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,MAAM;AAC1D,UAAI,QAAQ,IAAI;AACf,cAAM,IAAI,kBAAkB,MAAM;AAAA,MACnC;AACA,YAAM,WAAW,QAAQ,MAAM,GAAG;AAClC,UAAI,CAAC,UAAU;AAEd,cAAM,IAAI,kBAAkB,MAAM;AAAA,MACnC;AACA,YAAM,SAAwB;AAAA,QAC7B,GAAG;AAAA,QACH,GAAG;AAAA,QACH,IAAI,SAAS;AAAA,QACb,QAAQ,SAAS;AAAA,MAClB;AACA,YAAM,YAAY,QAAQ,MAAM,MAAM;AACtC,gBAAU,GAAG,IAAI;AACjB,aAAO,EAAE,GAAG,SAAS,OAAO,UAAU;AAAA,IACvC,CAAC;AACD,UAAM,aAAa,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM;AAC5D,QAAI,CAAC,YAAY;AAChB,YAAM,IAAI,kBAAkB,MAAM;AAAA,IACnC;AACA,SAAK,KAAK,EAAE,MAAM,UAAU,MAAM,WAAW,CAAC;AAC9C,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAe,QAA+B;AACnD,UAAM,UAAU,KAAK,IAAI,MAAM;AAC/B,QAAI,CAAC,SAAS;AACb,YAAM,IAAI,kBAAkB,MAAM;AAAA,IACnC;AACA,QAAI,QAAQ,WAAW,WAAW;AACjC,YAAM,IAAI,6BAA6B,MAAM;AAAA,IAC9C;AACA,UAAM,KAAK,OAAO,CAAC,aAAa;AAAA,MAC/B,GAAG;AAAA,MACH,OAAO,QAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,MAAM;AAAA,IACnD,EAAE;AACF,SAAK,KAAK,EAAE,MAAM,UAAU,OAAO,CAAC;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,QAAwC;AACzD,WAAO,KAAK,WAAW,QAAQ,EAAE,SAAS,MAAM,CAAC;AAAA,EAClD;AAAA;AAAA,EAGA,MAAM,WAAW,QAAwC;AACxD,WAAO,KAAK,WAAW,QAAQ,EAAE,SAAS,KAAK,CAAC;AAAA,EACjD;AAAA;AAAA,EAGA,MAAM,iBAAiB,QAA+B;AACrD,UAAM,SAAS,KAAK,IAAI,MAAM;AAC9B,QAAI,CAAC,QAAQ;AACZ,YAAM,IAAI,kBAAkB,MAAM;AAAA,IACnC;AACA,UAAM,KAAK,OAAO,CAAC,aAAa;AAAA,MAC/B,GAAG;AAAA,MACH,eAAe;AAAA,IAChB,EAAE;AACF,SAAK,KAAK,EAAE,MAAM,kBAAkB,eAAe,OAAO,CAAC;AAAA,EAC5D;AAAA,EAEA,mBAAkC;AACjC,WAAO,KAAK,QAAQ,iBAAiB;AAAA,EACtC;AAAA;AAAA,EAGA,SAAuB;AACtB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAAc,MAAqC;AACxD,UAAM,YAAY,kBAAkB,IAAI;AACxC,UAAM,KAAK,OAAO,KAAK,SAAS;AAChC,SAAK,SAAS;AACd,WAAO;AAAA,EACR;AAAA;AAAA,EAIA,MAAc,OAAO,SAAgE;AACpF,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,SAAS;AACb,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC1D;AACA,UAAM,OAAO,QAAQ,OAAO;AAC5B,UAAM,YAAY,kBAAkB,IAAI;AACxC,UAAM,KAAK,OAAO,KAAK,SAAS;AAChC,SAAK,SAAS;AACd,WAAO;AAAA,EACR;AAAA,EAEQ,KAAK,QAAgC;AAC5C,SAAK,QAAQ,KAAK,UAAU,MAAM;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,uBACP,YACA,WACkB;AAClB,UAAM,SAAS,KAAK,WAAW,MAAM,YAAY,EAAE,YAAY,MAAM,CAAC;AACtE,QAAI,UAAU,UAAU;AACvB,aAAO,GAAG,UAAU,MAAM,UAAU,WAAW,CAAC;AAAA,IACjD;AACA,QAAI,UAAU,UAAU;AACvB,aAAO,GAAG,UAAU,MAAM,UAAU,WAAW,CAAC;AAAA,IACjD;AACA,QAAI,UAAU,SAAS;AACtB,aAAO,GAAG,SAAS,CAAC,QAAe,UAAU,UAAU,GAAG,CAAC;AAAA,IAC5D;AACA,WAAO;AAAA,MACN,OAAO,MAAM,OAAO,MAAM;AAAA,IAC3B;AAAA,EACD;AAAA;AAAA,EAGQ,iBAAuB;AAC9B,QAAI,KAAK,aAAa;AACrB,mBAAa,KAAK,WAAW;AAAA,IAC9B;AACA,SAAK,cAAc,WAAW,MAAM;AACnC,WAAK,cAAc;AACnB,WAAK,KAAK,EAAE,MAAM,CAAC,QAAiB;AACnC,cAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,aAAK,KAAK,EAAE,MAAM,SAAS,MAAM,CAAC;AAAA,MACnC,CAAC;AAAA,IACF,GAAG,KAAK,UAAU;AAAA,EACnB;AACD;AAGO,SAAS,iBACf,YACA,UAA6C,CAAC,GACjC;AACb,SAAO,IAAI,WAAW;AAAA,IACrB,QAAQ,IAAI,YAAY,EAAE,WAAW,CAAC;AAAA,IACtC,GAAG;AAAA,EACJ,CAAC;AACF;AAGO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EACjD,YAA4B,QAAgB;AAC3C,UAAM,wBAAwB,MAAM,EAAE;AADX;AAE3B,SAAK,OAAO;AAAA,EACb;AACD;AAGO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC5C,YAA4B,QAAgB;AAC3C,UAAM,mBAAmB,MAAM,EAAE;AADN;AAE3B,SAAK,OAAO;AAAA,EACb;AACD;AAGO,IAAM,+BAAN,cAA2C,MAAM;AAAA,EACvD,YAA4B,QAAgB;AAC3C,UAAM,yDAAyD,MAAM,EAAE;AAD5C;AAE3B,SAAK,OAAO;AAAA,EACb;AACD;AAQA,eAAsB,kBAAkB,OAAuC;AAC9E,QAAM,SAAS,MAAM,UAAU,KAAM,MAAM,MAAM,aAAa;AAC9D,QAAM,SAAS,wBAAwB,QAAQ,MAAM,OAAO,CAAC;AAC7D,MAAI,OAAO,UAAU,WAAW,GAAG;AAClC,WAAO;AAAA,EACR;AACA,QAAM,MAAM,cAAc,OAAO,MAAM;AACvC,SAAO,MAAM,UAAU,KAAK,OAAO;AACpC;;;AC/aA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAEtB,IAAM,aAAa;AACnB,IAAM,WAAW;AACjB,IAAM,eAAe,OAAO;AAErB,IAAM,eAAN,MAAmB;AAAA,EAGzB,YAAY,eAAuB,UAAgC,CAAC,GAAG;AACtE,SAAK,UAAU,QAAQ,WAAgB,YAAK,eAAe,YAAY,QAAQ;AAC/E,UAAM,MAAW,eAAQ,KAAK,OAAO;AACrC,QAAI,CAAI,gBAAW,GAAG,GAAG;AACxB,MAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACtC;AAAA,EACD;AAAA,EAEA,aAAqB;AACpB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,IAAI,OAAkC,SAAuB;AAC5D,UAAM,MAAK,oBAAI,KAAK,GAAE,YAAY;AAClC,UAAM,OAAO,IAAI,EAAE,MAAM,MAAM,YAAY,CAAC,KAAK,OAAO;AAAA;AACxD,SAAK,eAAe;AACpB,IAAG,oBAAe,KAAK,SAAS,MAAM,OAAO;AAAA,EAC9C;AAAA,EAEQ,iBAAuB;AAC9B,QAAI;AACH,YAAM,QAAW,cAAS,KAAK,OAAO;AACtC,UAAI,MAAM,OAAO,cAAc;AAE9B,cAAM,UAAa,kBAAa,KAAK,SAAS,OAAO;AACrD,cAAM,UAAU,QAAQ,QAAQ,MAAM,KAAK,MAAM,QAAQ,SAAS,CAAC,CAAC;AACpE,YAAI,UAAU,GAAG;AAChB,UAAG,mBAAc,KAAK,SAAS,QAAQ,MAAM,UAAU,CAAC,GAAG,OAAO;AAAA,QACnE;AAAA,MACD;AAAA,IACD,QAAQ;AAAA,IAER;AAAA,EACD;AACD;;;AC5CA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAEtB,IAAMC,cAAa;AACnB,IAAM,WAAW;AAYV,IAAM,aAAN,MAAiB;AAAA,EAGvB,YAAY,eAAuB,UAA6B,CAAC,GAAG;AACnE,SAAK,UAAU,QAAQ,WAAgB,YAAK,eAAeA,aAAY,QAAQ;AAAA,EAChF;AAAA,EAEA,aAAqB;AACpB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,SAAS,KAAmB;AAC3B,UAAM,MAAW,eAAQ,KAAK,OAAO;AACrC,QAAI,CAAI,gBAAW,GAAG,GAAG;AACxB,MAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACtC;AACA,IAAG,mBAAc,KAAK,SAAS,OAAO,GAAG,GAAG,OAAO;AAAA,EACpD;AAAA,EAEA,UAAyB;AAQxB,QAAIC;AACJ,QAAI;AACH,MAAAA,QAAU,cAAS,KAAK,OAAO;AAAA,IAChC,QAAQ;AAEP,aAAO;AAAA,IACR;AACA,QAAI,CAACA,MAAK,OAAO,EAAG,QAAO;AAC3B,UAAM,MAAS,kBAAa,KAAK,SAAS,OAAO,EAAE,KAAK;AACxD,UAAM,MAAM,OAAO,SAAS,KAAK,EAAE;AACnC,WAAO,OAAO,MAAM,GAAG,IAAI,OAAO;AAAA,EACnC;AAAA,EAEA,YAAkB;AACjB,QAAO,gBAAW,KAAK,OAAO,GAAG;AAChC,MAAG,gBAAW,KAAK,OAAO;AAAA,IAC3B;AAAA,EACD;AAAA,EAEA,iBAAiB,KAAsB;AACtC,QAAI;AACH,cAAQ,KAAK,KAAK,CAAC;AACnB,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,gBAA+B;AAC9B,UAAM,MAAM,KAAK,QAAQ;AACzB,QAAI,QAAQ,KAAM,QAAO;AACzB,QAAI,KAAK,iBAAiB,GAAG,EAAG,QAAO;AAEvC,SAAK,UAAU;AACf,WAAO;AAAA,EACR;AACD;;;AC/EA,YAAYC,UAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,YAAU;;;ACFtB,SAAS,SAAAC,cAAa;AACtB,YAAYC,UAAQ;;;ACDb,SAAS,2BACf,gBACA,kBACqB;AACrB,MAAI,mBAAmB,GAAG;AACzB,WAAO;AAAA,EACR;AACA,MACC,OAAO,mBAAmB,YAC1B,CAAC,OAAO,SAAS,cAAc,KAC/B,iBAAiB,GAChB;AACD,WAAO;AAAA,EACR;AACA,SAAO,iBAAiB;AACzB;;;ADHA,IAAM,mBAAmB,IAAI,OAAO;AACpC,IAAMC,sBAAqB;AAW3B,SAAS,WAAW,MAA0B;AAC7C,QAAM,WAAqB,CAAC;AAC5B,MAAI,aAAa;AACjB,aAAW,OAAO,MAAM;AACvB,QAAI,YAAY;AACf,eAAS,KAAK,YAAY;AAC1B,mBAAa;AACb;AAAA,IACD;AACA,aAAS,KAAK,GAAG;AACjB,QAAI,QAAQ,YAAY;AACvB,mBAAa;AAAA,IACd;AAAA,EACD;AACA,SAAO;AACR;AAEA,SAAS,gBAAgB,SAAuB;AAC/C,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,SAAS;AACZ,IAAG,oBAAe,SAAS,OAAO;AAClC;AAAA,EACD;AACA,UAAQ,OAAO,MAAM,OAAO;AAC7B;AAEO,SAAS,iCACf,SACoC;AACpC,QAAM,OAAiB,CAAC,WAAW,UAAU,YAAY,QAAQ,MAAM;AACvE,MAAI,QAAQ,WAAW;AACtB,SAAK,KAAK,aAAa;AAAA,EACxB;AACA,MAAI,QAAQ,cAAc,QAAQ,WAAW,SAAS,GAAG;AACxD,SAAK,KAAK,iBAAiB,QAAQ,WAAW,KAAK,GAAG,CAAC;AAAA,EACxD;AACA,MAAI,QAAQ,OAAO;AAClB,SAAK,KAAK,WAAW,QAAQ,KAAK;AAAA,EACnC;AACA,MAAI,QAAQ,OAAO;AAClB,SAAK,KAAK,WAAW,QAAQ,KAAK;AAAA,EACnC;AACA,OAAK,KAAK,aAAa,OAAO,QAAQ,WAAW,CAAC,CAAC;AAEnD,SAAO;AAAA,IACN,SAAS;AAAA,IACT;AAAA,IACA,KAAK,QAAQ;AAAA,IACb,WAAW,2BAA2B,QAAQ,SAASA,mBAAkB;AAAA,IACzE,gBAAgB,WAAW,IAAI;AAAA,IAC/B,WAAW,QAAQ,OAAO;AAAA,EAC3B;AACD;AAEO,IAAM,2BAAN,MAAgE;AAAA,EACtE,MAAM,QAAQ,SAAkB,aAAoD;AAEnF,UAAM,gBAAgB,MAAM,uBAAuB;AACnD,QAAI,CAAC,eAAe;AACnB,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,OACC;AAAA,MACF;AAAA,IACD;AAEA,QAAI,SAAS;AACb,UAAM,SAAS,KAAK;AAAA,MACnB;AAAA,MACA,CAAC,SAAS,SAAS;AAClB,kBAAU;AAAA,MACX;AAAA,MACA;AAAA,IACD;AACA,UAAM,SAAS,MAAM,OAAO;AAC5B,WAAO,EAAE,GAAG,QAAQ,QAAQ,UAAU,OAAO,OAAO;AAAA,EACrD;AAAA,EAEA,iBACC,SACA,UACA,aAC0B;AAC1B,UAAM,IAAI;AACV,UAAM,YAAY,iCAAiC,CAAC;AAEpD,QAAIC;AACJ,UAAM,gBAAgB,IAAI,QAAwB,CAAC,MAAM;AACxD,MAAAA,WAAU;AAAA,IACX,CAAC;AACD,QAAI,UAAU;AAEd,UAAM,SAAS,CAAC,WAA2B;AAC1C,UAAI,QAAS;AACb,gBAAU;AACV,UAAI,MAAO,cAAa,KAAK;AAC7B,MAAAA,WAAU,MAAM;AAAA,IACjB;AAEA,QAAI,aAAa,SAAS;AACzB,aAAO;AAAA,QACN,QAAQ,QAAQ,QAAQ;AAAA,UACvB,QAAQ;AAAA,UACR,OAAO;AAAA,QACR,CAAC;AAAA,QACD,QAAQ,MAAM;AAAA,QAAC;AAAA,MAChB;AAAA,IACD;AAEA;AAAA,MACC,6CAA6C,UAAU,OAAO,UAAU,KAAK,UAAU,UAAU,cAAc,CAAC,eAAe,UAAU,SAAS,SAAS,UAAU,OAAO,WAAW,cAAc,QAAQ,QAAQ,2BAA2B,QAAQ,GAAG;AAAA;AAAA,IAC5P;AAEA,UAAM,QAAQC,OAAM,UAAU,SAAS,UAAU,MAAM;AAAA,MACtD,KAAK,UAAU;AAAA,MACf,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,MAChC,aAAa;AAAA,IACd,CAAC;AAED,QAAI,MAAM,KAAK;AACd,sBAAgB,gDAAgD,MAAM,GAAG;AAAA,CAAI;AAAA,IAC9E;AAEA,UAAM,YAAY,UAAU;AAC5B,UAAM,QACL,aAAa,OACV,WAAW,MAAM;AACjB,YAAM,KAAK,SAAS;AACpB,iBAAW,MAAM;AAChB,YAAI,CAAC,MAAM,OAAQ,OAAM,KAAK,SAAS;AAAA,MACxC,GAAG,GAAI;AACP,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,OAAO,yCAAyC,YAAY,GAAI;AAAA,MACjE,CAAC;AAAA,IACF,GAAG,SAAS,IACX;AAEJ,QAAI,YAAY;AAChB,QAAI,YAAY;AAEhB,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB;AAC1C,YAAM,OAAO,MAAM,SAAS;AAC5B,mBAAa;AACb,UAAI,UAAU,SAAS,iBAAkB,aAAY,UAAU,MAAM,CAAC,gBAAgB;AACtF,eAAS,UAAU,IAAI;AAAA,IACxB,CAAC;AAED,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB;AAC1C,YAAM,OAAO,MAAM,SAAS;AAC5B,mBAAa;AACb,UAAI,UAAU,SAAS,iBAAkB,aAAY,UAAU,MAAM,CAAC,gBAAgB;AACtF,eAAS,UAAU,IAAI;AAAA,IACxB,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,SAAS;AAC3B,UAAI,SAAS,GAAG;AACf,eAAO,EAAE,QAAQ,WAAW,QAAQ,aAAa,OAAU,CAAC;AAAA,MAC7D,OAAO;AACN,eAAO;AAAA,UACN,QAAQ;AAAA,UACR,QAAQ,aAAa;AAAA,UACrB,OAAO,aAAa,4BAA4B,IAAI;AAAA,QACrD,CAAC;AAAA,MACF;AAAA,IACD,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,QAAQ;AAC1B,aAAO,EAAE,QAAQ,WAAW,OAAO,IAAI,QAAQ,CAAC;AAAA,IACjD,CAAC;AAED,UAAM,WAAW,MAAM;AACtB,YAAM,KAAK,SAAS;AACpB,iBAAW,MAAM;AAChB,YAAI,CAAC,MAAM,OAAQ,OAAM,KAAK,SAAS;AAAA,MACxC,GAAG,GAAI;AACP,aAAO,EAAE,QAAQ,aAAa,OAAO,sBAAsB,CAAC;AAAA,IAC7D;AAEA,iBAAa,iBAAiB,SAAS,MAAM,SAAS,GAAG,EAAE,MAAM,KAAK,CAAC;AAEvE,WAAO,EAAE,QAAQ,eAAe,QAAQ,SAAS;AAAA,EAClD;AACD;;;AE3MA,IAAMC,sBAAqB;AAEpB,IAAM,sBAAN,MAAkD;AAAA,EACxD,MAAM,QAAQ,SAAkB,aAAoD;AACnF,UAAM,IAAI;AACV,UAAM,YAAY,2BAA2B,EAAE,SAASA,mBAAkB;AAE1E,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,WAAW;AACf,UAAM,QACL,aAAa,OACV,WAAW,MAAM;AACjB,iBAAW;AACX,SAAG,MAAM;AAAA,IACV,GAAG,SAAS,IACX;AAEJ,QAAI,aAAa,SAAS;AACzB,UAAI,MAAO,cAAa,KAAK;AAC7B,aAAO,EAAE,QAAQ,aAAa,OAAO,oBAAoB;AAAA,IAC1D;AAEA,QAAI,gBAAgB;AACpB,iBAAa;AAAA,MACZ;AAAA,MACA,MAAM;AACL,wBAAgB;AAChB,YAAI,MAAO,cAAa,KAAK;AAC7B,WAAG,MAAM;AAAA,MACV;AAAA,MACA,EAAE,MAAM,KAAK;AAAA,IACd;AAEA,QAAI;AACH,YAAM,WAAW,MAAM,MAAM,EAAE,KAAK;AAAA,QACnC,QAAQ,EAAE;AAAA,QACV,SAAS,EAAE;AAAA,QACX,MAAM,EAAE;AAAA,QACR,QAAQ,GAAG;AAAA,MACZ,CAAC;AACD,UAAI,MAAO,cAAa,KAAK;AAE7B,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAI,SAAS,IAAI;AAChB,eAAO;AAAA,UACN,QAAQ;AAAA,UACR,QAAQ,GAAG,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,EAAK,IAAI,GAAG,KAAK;AAAA,QACnE;AAAA,MACD;AACA,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,OAAO,QAAQ,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,EAAK,IAAI,GAAG,KAAK;AAAA,MACvE;AAAA,IACD,SAAS,KAAc;AACtB,UAAI,MAAO,cAAa,KAAK;AAC7B,UAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACtD,YAAI,eAAe;AAClB,iBAAO,EAAE,QAAQ,aAAa,OAAO,sBAAsB;AAAA,QAC5D;AACA,YAAI,CAAC,YAAY,aAAa,MAAM;AACnC,iBAAO,EAAE,QAAQ,WAAW,OAAO,IAAI,QAAQ;AAAA,QAChD;AACA,eAAO;AAAA,UACN,QAAQ;AAAA,UACR,OAAO,gCAAgC,YAAY,GAAI;AAAA,QACxD;AAAA,MACD;AACA,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,aAAO,EAAE,QAAQ,WAAW,OAAO,QAAQ;AAAA,IAC5C;AAAA,EACD;AACD;;;AC3EA,SAA4B,SAAAC,cAAa;AACzC,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAUtB,IAAMC,oBAAmB,OAAO;AAChC,IAAMC,sBAAqB;AAC3B,IAAM,yBAAyB,CAAC,YAAY,QAAQ;AAgB7C,SAAS,sBACf,QACA,UAA2C,CAAC,GACnB;AACzB,QAAMC,YAAW,QAAQ,YAAY,QAAQ;AAC7C,QAAM,MAAM,QAAQ,OAAO,QAAQ;AACnC,QAAM,aAAa,QAAQ,cAAiB;AAE5C,MAAIA,cAAa,SAAS;AACzB,UAAM,aAAa,qBAAqB,MAAM,IAAI,sBAAsB,KAAK,UAAU,IAAI;AAC3F,QAAI,YAAY;AACf,aAAO;AAAA,QACN,SAAS;AAAA,QACT,MAAM,CAAC,IAAI;AAAA,QACX,aAAa;AAAA,QACb,gBAAgB;AAAA,QAChB,WAAW;AAAA,MACZ;AAAA,IACD;AAEA,WAAO;AAAA,MACN,SAAS,IAAI,WAAW,IAAI,WAAW;AAAA,MACvC,MAAM,CAAC,MAAM,MAAM,MAAM,MAAM;AAAA,MAC/B,gBAAgB;AAAA,MAChB,WAAW;AAAA,IACZ;AAAA,EACD;AAEA,SAAO;AAAA,IACN,SAAS;AAAA,IACT,MAAM,CAAC,IAAI;AAAA,IACX,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,WAAW;AAAA,EACZ;AACD;AAEA,SAAS,qBAAqB,QAAyB;AACtD,SAAO,sBAAsB,KAAK,MAAM;AACzC;AAEA,SAAS,sBACR,KACA,YACgB;AAChB,QAAM,gBAAgB,IAAI;AAC1B,MAAI,iBAAiB,WAAW,aAAa,GAAG;AAC/C,WAAO;AAAA,EACR;AAEA,QAAM,oBAAoB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACA,aAAW,aAAa,mBAAmB;AAC1C,QAAI,WAAW,SAAS,EAAG,QAAO;AAAA,EACnC;AAEA,QAAM,YAAY,IAAI,QAAQ,IAAI,QAAQ;AAC1C,aAAW,OAAO,UAAU,MAAW,aAAM,SAAS,GAAG;AACxD,QAAI,CAAC,IAAK;AACV,eAAW,cAAc,wBAAwB;AAChD,YAAM,YAAiB,aAAM,KAAK,KAAK,UAAU;AACjD,UAAI,WAAW,SAAS,KAAK,CAAC,qBAAqB,SAAS,GAAG;AAC9D,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,qBAAqB,WAA4B;AACzD,QAAM,aAAkB,aAAM,UAAU,SAAS,EAAE,YAAY;AAC/D,SACC,WAAW,SAAS,+BAA+B,KACnD,WAAW,SAAS,+BAA+B;AAErD;AAEA,SAASC,iBAAgB,SAAuB;AAC/C,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,SAAS;AACZ,IAAG,oBAAe,SAAS,OAAO;AAClC;AAAA,EACD;AACA,UAAQ,OAAO,MAAM,OAAO;AAC7B;AAEO,IAAM,gBAAN,MAAqD;AAAA,EAC3D,MAAM,QAAQ,SAAkB,aAAoD;AACnF,QAAI,SAAS;AACb,UAAM,SAAS,KAAK;AAAA,MACnB;AAAA,MACA,CAAC,SAAS,SAAS;AAClB,kBAAU;AAAA,MACX;AAAA,MACA;AAAA,IACD;AACA,UAAM,SAAS,MAAM,OAAO;AAC5B,WAAO,EAAE,GAAG,QAAQ,QAAQ,UAAU,OAAO,OAAO;AAAA,EACrD;AAAA,EAEA,iBACC,SACA,UACA,aAC0B;AAC1B,UAAM,IAAI;AACV,UAAM,YAAY,2BAA2B,EAAE,SAASF,mBAAkB;AAE1E,QAAIG;AACJ,UAAM,gBAAgB,IAAI,QAAwB,CAAC,MAAM;AACxD,MAAAA,WAAU;AAAA,IACX,CAAC;AACD,QAAI,UAAU;AAEd,UAAM,SAAS,CAAC,WAA2B;AAC1C,UAAI,QAAS;AACb,gBAAU;AACV,UAAI,MAAO,cAAa,KAAK;AAC7B,MAAAA,WAAU,MAAM;AAAA,IACjB;AAEA,QAAI,aAAa,SAAS;AACzB,aAAO;AAAA,QACN,QAAQ,QAAQ,QAAQ;AAAA,UACvB,QAAQ;AAAA,UACR,OAAO;AAAA,QACR,CAAC;AAAA,QACD,QAAQ,MAAM;AAAA,QAAC;AAAA,MAChB;AAAA,IACD;AAEA,UAAM,iBAAiB,sBAAsB,EAAE,MAAM;AAErD,UAAM,YAA0B;AAAA,MAC/B,KAAK,EAAE;AAAA,MACP,OAAO,CAAC,eAAe,cAAc,SAAS,UAAU,QAAQ,MAAM;AAAA,MACtE,aAAa;AAAA,IACd;AAEA,IAAAD;AAAA,MACC;AAAA,aACU,QAAQ,QAAQ;AAAA,UACnB,eAAe,SAAS;AAAA,YACtB,eAAe,OAAO;AAAA,SACzB,KAAK,UAAU,eAAe,KAAK,OAAO,CAAC,MAAM,MAAM,EAAE,MAAM,CAAC,CAAC;AAAA,UAChE,eAAe,cAAc,SAAS,OAAO;AAAA,QAC/C,EAAE,OAAO,WAAW;AAAA,YAChB,aAAa,OAAO,GAAG,SAAS,OAAO,WAAW;AAAA,cAChD,EAAE,OAAO,MAAM;AAAA;AAAA,cAEf,QAAQ,GAAG;AAAA;AAAA,IACvB;AAEA,UAAM,QAAQE,OAAM,eAAe,SAAS,eAAe,MAAM,SAAS;AAE1E,QAAI,eAAe,eAAe,MAAM,OAAO;AAC9C,YAAM,MAAM,IAAI,eAAe,WAAW;AAAA,IAC3C;AAEA,QAAI,MAAM,KAAK;AACd,MAAAF,iBAAgB,qCAAqC,MAAM,GAAG;AAAA,CAAI;AAAA,IACnE;AAEA,UAAM,QACL,aAAa,OACV,WAAW,MAAM;AACjB,YAAM,KAAK,SAAS;AACpB,iBAAW,MAAM;AAChB,YAAI,CAAC,MAAM,OAAQ,OAAM,KAAK,SAAS;AAAA,MACxC,GAAG,GAAI;AACP,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,OAAO,mCAAmC,YAAY,GAAI;AAAA,MAC3D,CAAC;AAAA,IACF,GAAG,SAAS,IACX;AAEJ,QAAI,YAAY;AAChB,QAAI,YAAY;AAEhB,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC3C,YAAM,OAAO,MAAM,SAAS,eAAe,cAAc;AACzD,mBAAa;AACb,UAAI,UAAU,SAASH,kBAAkB,aAAY,UAAU,MAAM,CAACA,iBAAgB;AACtF,eAAS,UAAU,IAAI;AAAA,IACxB,CAAC;AAED,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC3C,YAAM,OAAO,MAAM,SAAS,eAAe,cAAc;AACzD,mBAAa;AACb,UAAI,UAAU,SAASA,kBAAkB,aAAY,UAAU,MAAM,CAACA,iBAAgB;AACtF,eAAS,UAAU,IAAI;AAAA,IACxB,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,SAAwB;AAC1C,MAAAG,iBAAgB,6BAA6B,MAAM,GAAG,iBAAiB,IAAI;AAAA,CAAI;AAC/E,UAAI,SAAS,GAAG;AACf,eAAO,EAAE,QAAQ,WAAW,QAAQ,aAAa,OAAU,CAAC;AAAA,MAC7D,OAAO;AACN,eAAO;AAAA,UACN,QAAQ;AAAA,UACR,QAAQ,aAAa;AAAA,UACrB,OAAO,aAAa,4BAA4B,IAAI;AAAA,QACrD,CAAC;AAAA,MACF;AAAA,IACD,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,QAAe;AACjC,aAAO,EAAE,QAAQ,WAAW,OAAO,IAAI,QAAQ,CAAC;AAAA,IACjD,CAAC;AAED,UAAM,WAAW,MAAM;AACtB,YAAM,KAAK,SAAS;AACpB,iBAAW,MAAM;AAChB,YAAI,CAAC,MAAM,OAAQ,OAAM,KAAK,SAAS;AAAA,MACxC,GAAG,GAAI;AACP,aAAO,EAAE,QAAQ,aAAa,OAAO,sBAAsB,CAAC;AAAA,IAC7D;AAEA,iBAAa,iBAAiB,SAAS,MAAM,SAAS,GAAG,EAAE,MAAM,KAAK,CAAC;AAEvE,WAAO,EAAE,QAAQ,eAAe,QAAQ,SAAS;AAAA,EAClD;AACD;;;ACrOO,SAAS,wBAAwB,UAA2D;AAClG,SACC,sBAAsB,YACtB,OAAQ,SAAmC,qBAAqB;AAElE;;;ACzBA,IAAM,YAAkD;AAAA,EACvD,OAAO,IAAI,cAAc;AAAA,EACzB,cAAc,IAAI,oBAAoB;AAAA,EACtC,oBAAoB,IAAI,yBAAyB;AAClD;AAEO,SAAS,YAAY,UAA2C;AACtE,QAAM,WAAW,UAAU,QAA0B;AACrD,MAAI,CAAC,UAAU;AACd,UAAM,IAAI,MAAM,iCAAiC,QAAQ,EAAE;AAAA,EAC5D;AACA,SAAO;AACR;;;ACrBA,SAAS,cAAAG,mBAAkB;AAC3B,YAAYC,UAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,YAAU;AAStB;AAAA,EACC,wBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AAGP,SAAS,cAAoC;AAC5C,SAAO,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE;AAChC;AAEA,SAASC,UAAS,OAAkD;AACnE,SAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC3E;AAUA,SAAS,yBAAyB,OAGhC;AACD,MAAI,CAACA,UAAS,KAAK,EAAG,QAAO;AAC7B,MAAI,MAAM,YAAY,EAAG,QAAO;AAChC,MAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,KAAK,MAAM,MAAM,WAAW,EAAG,QAAO;AACpE,QAAM,aAAa,MAAM,MAAM,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,KAAK,WAAW,CAAC,CAAC;AAClF,MAAI,WAAY,QAAO;AAEvB,SAAO,MAAM,MAAM,MAAM,CAAC,MAAM,kBAAkB,CAAC,CAAC;AACrD;AAEA,SAAS,WAAW,GAAqB;AACxC,SAAOA,UAAS,CAAC,KAAKA,UAAU,EAA8B,SAAS;AACxE;AAQA,SAAS,iBAAiB,OAAqD;AAC9E,SAAO,EAAE,SAAS,GAAG,OAAO,MAAM,MAAM,OAAO,iBAAiB,EAAE;AACnE;AAUA,SAAS,0BAA4C;AACpD,QAAM,OAAU,YAAQ,KAAK;AAC7B,SAAO,EAAE,MAAM,MAAM,MAAW,gBAAS,IAAI,KAAK,KAAK;AACxD;AAEA,SAAS,sBACR,SACA,OACA,UACO;AACP,MAAI,CAACA,UAAS,OAAO,KAAK,OAAO,QAAQ,KAAK,MAAM,YAAY,CAAC,QAAQ,KAAK,EAAE,KAAK,GAAG;AACvF,UAAMC,sBAAqB,mBAAmB,GAAG,QAAQ,YAAY,KAAK,cAAc;AAAA,EACzF;AACD;AAEO,SAAS,oBAAoB,UAA6B,SAA4B;AAC5F,UAAQ,UAAU;AAAA,IACjB,KAAK;AACJ,4BAAsB,SAAS,WAAW,QAAQ;AAClD;AAAA,IACD,KAAK;AACJ,4BAAsB,SAAS,UAAU,QAAQ;AACjD;AAAA,IACD,KAAK;AACJ,4BAAsB,SAAS,OAAO,QAAQ;AAC9C,4BAAsB,SAAS,UAAU,QAAQ;AACjD;AAAA,IACD,KAAK;AACJ,4BAAsB,SAAS,UAAU,QAAQ;AACjD;AAAA,EACF;AACD;AAmDO,IAAM,oBAAN,MAAwB;AAAA,EAM9B,YAAY,UAAoC,CAAC,GAAG;AACnD,SAAK,aAAa,QAAQ,cAAc,4BAA4B;AACpE,SAAK,mBAAmB,QAAQ;AAChC,SAAK,wBAAwB,QAAQ;AACrC,SAAK,SAAS,QAAQ;AAAA,EACvB;AAAA,EAEA,gBAAwB;AACvB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,aAAmC;AAClC,QAAI,CAAI,gBAAW,KAAK,UAAU,GAAG;AACpC,aAAO,YAAY;AAAA,IACpB;AACA,UAAM,MAAS,kBAAa,KAAK,YAAY,OAAO;AACpD,QAAI;AACJ,QAAI;AACH,eAAS,KAAK,MAAM,GAAG;AAAA,IACxB,SAAS,OAAO;AAGf,WAAK,6BAA6B,eAAe,OAAO,KAAK,GAAG,GAAG;AACnE,aAAO,YAAY;AAAA,IACpB;AAGA,QAAI,uBAAuB,MAAM,GAAG;AACnC,aAAO;AAAA,IACR;AAWA,QAAI,yBAAyB,MAAM,GAAG;AACrC,YAAM,KAAK,iBAAiB,MAAM;AAClC,YAAM,MAAM,KAAK,oBAAoB,wBAAwB;AAC7D,YAAM,EAAE,QAAQ,OAAO,IAAI,cAAc,IAAI,GAAG;AAChD,WAAK;AAAA,QACJ,oEAAoE,KAAK,UAAU,qEAC7C,IAAI,IAAI;AAAA,MAC/C;AACA,iBAAW,SAAS,OAAQ,MAAK,KAAK,sCAAsC,KAAK,EAAE;AACnF,UAAI;AACH,aAAK,YAAY,MAAM;AAAA,MACxB,SAAS,YAAY;AACpB,aAAK;AAAA,UACJ,2DAA2D,KAAK,UAAU,KAAK,OAAO,UAAU,CAAC;AAAA,QAClG;AAAA,MACD;AACA,aAAO;AAAA,IACR;AAMA,QAAI,yBAAyB,MAAM,GAAG;AACrC,UAAI,CAAC,KAAK,kBAAkB;AAC3B,cAAMA;AAAA,UACL;AAAA,UACA;AAAA,QAED;AAAA,MACD;AACA,YAAM,KAAK;AACX,aAAO,cAAc,IAAI,KAAK,gBAAgB,EAAE;AAAA,IACjD;AAKA,SAAK,6BAA6B,iBAAiB,wCAAwC,GAAG;AAC9F,WAAO,YAAY;AAAA,EACpB;AAAA,EAEQ,KAAK,SAAuB;AACnC,QAAI,KAAK,OAAQ,MAAK,OAAO,OAAO;AAAA,QAC/B,SAAQ,KAAK,uBAAuB,OAAO,EAAE;AAAA,EACnD;AAAA,EAEQ,6BAA6B,QAAgB,QAAgB,KAAmB;AACvF,SAAK;AAAA,MACJ,0CAA0C,KAAK,UAAU,KAAK,MAAM,MAAM,MAAM,uFAE9E,KAAK,yBAAyB,yBAAyB,CACxD;AAAA,IACF;AACA,QAAI;AACH,YAAM,SAAS,KAAK,yBAAyB,yBAAyB;AACtE,YAAM,SAAS,MAAM;AACpB,YAAI;AACH,iBAAO,KAAK,MAAS,kBAAa,QAAQ,OAAO,CAAC;AAAA,QACnD,QAAQ;AACP,iBAAO,CAAC;AAAA,QACT;AAAA,MACD,GAAG;AACH,YAAM,WAAW,MAAM,QAAQ,KAAK,IAAK,QAAsB,CAAC;AAChE,eAAS,KAAK;AAAA,QACb,MAAM,KAAK;AAAA,QACX;AAAA,QACA;AAAA,QACA,SAAS,IAAI,MAAM,GAAG,GAAG;AAAA,QACzB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC;AACD,MAAG,eAAe,eAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,MAAG,mBAAc,QAAQ,KAAK,UAAU,UAAU,MAAM,GAAI,GAAG,OAAO;AAAA,IACvE,SAAS,YAAY;AACpB,WAAK;AAAA,QACJ,mEAAmE,OAAO,UAAU,CAAC;AAAA,MACtF;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,YAAY,QAAoC;AACvD,UAAM,MAAW,eAAQ,KAAK,UAAU;AACxC,QAAI,CAAI,gBAAW,GAAG,GAAG;AACxB,MAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACtC;AAEA,UAAM,MAAM,GAAG,KAAK,UAAU;AAC9B,IAAG,mBAAc,KAAK,KAAK,UAAU,QAAQ,MAAM,GAAI,GAAG,OAAO;AACjE,IAAG,gBAAW,KAAK,KAAK,UAAU;AAAA,EACnC;AAAA,EAEA,YAA6B;AAC5B,WAAO,KAAK,WAAW,EAAE;AAAA,EAC1B;AAAA,EAEA,QAAQ,IAAuC;AAC9C,WAAO,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,EACvD;AAAA,EAEA,cAAc,MAAyC;AACtD,WAAO,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAAA,EAC3D;AAAA,EAEA,WAAW,OAAuC;AACjD,QAAI,CAAC,MAAM,aAAa,CAAC,MAAM,UAAU,QAAQ,CAAC,MAAM,UAAU,MAAM;AACvE,YAAMA;AAAA,QACL;AAAA,QACA;AAAA,MACD;AAAA,IACD;AACA,wBAAoB,MAAM,UAAU,MAAM,OAAO;AACjD,UAAM,SAAS,KAAK,WAAW;AAC/B,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,OAAsB;AAAA,MAC3B,IAAIC,YAAW;AAAA,MACf,MAAM,MAAM;AAAA,MACZ,aAAa,MAAM;AAAA,MACnB,SAAS,MAAM,WAAW;AAAA,MAC1B,cAAc,MAAM;AAAA,MACpB,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,MACf,WAAW,MAAM;AAAA,MACjB,WAAW;AAAA,MACX,WAAW;AAAA,IACZ;AACA,WAAO,MAAM,KAAK,IAAI;AACtB,SAAK,YAAY,MAAM;AACvB,WAAO;AAAA,EACR;AAAA,EAEA,SAAS,IAAY,OAAqC;AACzD,UAAM,SAAS,KAAK,WAAW;AAC/B,UAAM,MAAM,OAAO,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AACrD,QAAI,QAAQ,IAAI;AACf,YAAM,IAAI,MAAM,SAAS,EAAE,aAAa;AAAA,IACzC;AACA,UAAM,WAAW,OAAO,MAAM,GAAG;AACjC,QAAI,CAAC,UAAU;AACd,YAAM,IAAI,MAAM,SAAS,EAAE,aAAa;AAAA,IACzC;AACA,UAAM,eAAe,MAAM,YAAY,SAAS;AAChD,UAAM,cAAc,MAAM,WAAW,SAAS;AAC9C,UAAM,gBAAgB,MAAM,aAAa,SAAS;AAClD,wBAAoB,cAAc,WAAW;AAC7C,UAAM,UAAyB;AAAA,MAC9B,GAAG;AAAA,MACH,GAAI,MAAM,SAAS,UAAa,EAAE,MAAM,MAAM,KAAK;AAAA,MACnD,GAAI,MAAM,gBAAgB,UAAa;AAAA,QACtC,aAAa,MAAM;AAAA,MACpB;AAAA,MACA,GAAI,MAAM,iBAAiB,UAAa;AAAA,QACvC,cAAc,MAAM;AAAA,MACrB;AAAA,MACA,GAAI,MAAM,aAAa,UAAa,EAAE,UAAU,MAAM,SAAS;AAAA,MAC/D,GAAI,MAAM,aAAa,UAAa,EAAE,UAAU,MAAM,SAAS;AAAA,MAC/D,GAAI,MAAM,YAAY,UAAa,EAAE,SAAS,MAAM,QAAQ;AAAA,MAC5D,GAAI,MAAM,cAAc,UAAa,EAAE,WAAW,cAAc;AAAA,MAChE,GAAI,MAAM,YAAY,UAAa,EAAE,SAAS,MAAM,QAAQ;AAAA,MAC5D,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC;AACA,WAAO,MAAM,GAAG,IAAI;AACpB,SAAK,YAAY,MAAM;AACvB,WAAO;AAAA,EACR;AAAA,EAEA,WAAW,IAA2B;AACrC,UAAM,SAAS,KAAK,WAAW;AAC/B,UAAM,MAAM,OAAO,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AACrD,QAAI,QAAQ,IAAI;AACf,YAAM,IAAI,MAAM,SAAS,EAAE,aAAa;AAAA,IACzC;AACA,UAAM,UAAU,OAAO,MAAM,GAAG;AAChC,QAAI,CAAC,SAAS;AACb,YAAM,IAAI,MAAM,SAAS,EAAE,aAAa;AAAA,IACzC;AACA,WAAO,MAAM,OAAO,KAAK,CAAC;AAC1B,SAAK,YAAY,MAAM;AACvB,WAAO;AAAA,EACR;AAAA,EAEA,WAAW,IAAY,SAAiC;AACvD,WAAO,KAAK,SAAS,IAAI,EAAE,QAAQ,CAAC;AAAA,EACrC;AACD;;;AC/VA,SAAS,kBAAkB,OAAiC;AAC3D,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAC3D;AAEO,SAAS,4BACf,UACA,SACA,eACc;AACd,MAAI,CAAC,kBAAkB,aAAa,GAAG;AACtC,WAAO;AAAA,EACR;AAEA,MAAI,aAAa,SAAS;AACzB,UAAM,eAAe;AACrB,QAAI,kBAAkB,aAAa,GAAG,GAAG;AACxC,aAAO;AAAA,IACR;AACA,WAAO,EAAE,GAAG,cAAc,KAAK,cAAc;AAAA,EAC9C;AAEA,MAAI,aAAa,sBAAsB;AACtC,UAAM,iBAAiB;AACvB,QAAI,kBAAkB,eAAe,SAAS,GAAG;AAChD,aAAO;AAAA,IACR;AACA,WAAO,EAAE,GAAG,gBAAgB,WAAW,cAAc;AAAA,EACtD;AAEA,SAAO;AACR;AAEO,IAAM,sBAAN,MAA0B;AAAA,EAKhC,YAA6BC,cAAiD;AAAjD,uBAAAA;AAJ7B,SAAiB,UAAU,oBAAI,IAA8B;AAC7D,SAAiB,iBAAiB,oBAAI,IAAyB;AAC/D,SAAQ,WAAqC;AAAA,EAEkC;AAAA,EAE/E,YAAY,UAAmC;AAC9C,SAAK,WAAW;AAAA,EACjB;AAAA,EAEA,MAAM,QAAQ,UAAgD;AAC7D,UAAM,SAA4B,SAAS,qBAAqB;AAGhE,QAAI,WAAW,UAAU;AACxB,YAAM,cAAc,KAAK,eAAe,IAAI,SAAS,MAAM;AAC3D,UAAI,eAAe,YAAY,OAAO,GAAG;AACxC,cAAM,IAAI,MAAM,8BAA8B,SAAS,MAAM,qBAAqB;AAAA,MACnF;AAAA,IACD;AAEA,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAGzC,QAAI,CAAC,KAAK,eAAe,IAAI,SAAS,MAAM,GAAG;AAC9C,WAAK,eAAe,IAAI,SAAS,QAAQ,oBAAI,IAAI,CAAC;AAAA,IACnD;AACA,SAAK,eAAe,IAAI,SAAS,MAAM,GAAG,IAAI,SAAS,WAAW;AAGlE,UAAM,KAAK,IAAI,gBAAgB;AAC/B,UAAM,cAAgC;AAAA,MACrC,aAAa,SAAS;AAAA,MACtB,QAAQ,SAAS;AAAA,MACjB;AAAA,MACA,QAAQ,MAAM,GAAG,MAAM;AAAA,IACxB;AACA,SAAK,QAAQ,IAAI,SAAS,aAAa,WAAW;AAElD,SAAK,UAAU,UAAU;AAAA,MACxB,aAAa,SAAS;AAAA,MACtB,QAAQ,SAAS;AAAA,MACjB;AAAA,IACD,CAAC;AAED,QAAI;AACH,YAAM,mBAAmB;AAAA,QACxB,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,MACV;AACA,0BAAoB,SAAS,MAAM,gBAAgB;AACnD,YAAM,WAAW,KAAK,YAAY,SAAS,IAAI;AAC/C,UAAI;AACJ,UAAI,wBAAwB,QAAQ,GAAG;AACtC,cAAM,SAAS,SAAS;AAAA,UACvB;AAAA,UACA,CAAC,QAAQ,SAAS;AACjB,iBAAK,UAAU,SAAS;AAAA,cACvB,aAAa,SAAS;AAAA,cACtB;AAAA,cACA;AAAA,YACD,CAAC;AAAA,UACF;AAAA,UACA,GAAG;AAAA,QACJ;AACA,oBAAY,SAAS,MAAM;AAC1B,iBAAO,OAAO;AACd,aAAG,MAAM;AAAA,QACV;AACA,iBAAS,MAAM,OAAO;AAAA,MACvB,OAAO;AACN,iBAAS,MAAM,SAAS,QAAQ,kBAAkB,GAAG,MAAM;AAAA,MAC5D;AAEA,YAAM,MAAwB;AAAA,QAC7B,IAAI,SAAS;AAAA,QACb,QAAQ,SAAS;AAAA,QACjB,UAAU,SAAS;AAAA,QACnB;AAAA,QACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,QACnC,QAAQ,OAAO,WAAW,YAAY,YAAY,OAAO;AAAA,QACzD,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,MACf;AAEA,cAAQ,IAAI,QAAQ;AAAA,QACnB,KAAK;AACJ,eAAK,UAAU,YAAY;AAAA,YAC1B,aAAa,SAAS;AAAA,YACtB;AAAA,UACD,CAAC;AACD;AAAA,QACD,KAAK;AACJ,eAAK,UAAU,YAAY;AAAA,YAC1B,aAAa,SAAS;AAAA,YACtB;AAAA,UACD,CAAC;AACD;AAAA,QACD,KAAK;AACJ,eAAK,UAAU,SAAS;AAAA,YACvB,aAAa,SAAS;AAAA,YACtB,QAAQ;AAAA,YACR,QAAQ;AAAA,YACR;AAAA,UACD,CAAC;AACD;AAAA,QACD;AACC,eAAK,UAAU,SAAS;AAAA,YACvB,aAAa,SAAS;AAAA,YACtB,QAAQ;AAAA,YACR,QAAQ;AAAA,YACR;AAAA,UACD,CAAC;AACD;AAAA,MACF;AAAA,IACD,SAAS,KAAK;AACb,YAAM,MAAwB;AAAA,QAC7B,IAAI,SAAS;AAAA,QACb,QAAQ,SAAS;AAAA,QACjB,UAAU,SAAS;AAAA,QACnB;AAAA,QACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,QACnC,QAAQ,GAAG,OAAO,UAAU,cAAc;AAAA,QAC1C,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACvD;AAEA,UAAI,GAAG,OAAO,SAAS;AACtB,aAAK,UAAU,YAAY;AAAA,UAC1B,aAAa,SAAS;AAAA,UACtB;AAAA,QACD,CAAC;AAAA,MACF,OAAO;AACN,aAAK,UAAU,SAAS;AAAA,UACvB,aAAa,SAAS;AAAA,UACtB,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR;AAAA,QACD,CAAC;AAAA,MACF;AAAA,IACD,UAAE;AACD,WAAK,QAAQ,OAAO,SAAS,WAAW;AACxC,YAAM,cAAc,KAAK,eAAe,IAAI,SAAS,MAAM;AAC3D,UAAI,aAAa;AAChB,oBAAY,OAAO,SAAS,WAAW;AACvC,YAAI,YAAY,SAAS,GAAG;AAC3B,eAAK,eAAe,OAAO,SAAS,MAAM;AAAA,QAC3C;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEA,OAAO,aAA8B;AACpC,UAAM,OAAO,KAAK,QAAQ,IAAI,WAAW;AACzC,QAAI,CAAC,KAAM,QAAO;AAClB,SAAK,OAAO;AACZ,WAAO;AAAA,EACR;AAAA,EAEA,cAAiC;AAChC,WAAO,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,MACpD,aAAa,EAAE;AAAA,MACf,QAAQ,EAAE;AAAA,MACV,WAAW,EAAE;AAAA,IACd,EAAE;AAAA,EACH;AAAA,EAEA,UAAgB;AACf,eAAW,QAAQ,KAAK,QAAQ,OAAO,GAAG;AACzC,WAAK,OAAO;AAAA,IACb;AACA,SAAK,QAAQ,MAAM;AACnB,SAAK,eAAe,MAAM;AAAA,EAC3B;AACD;;;AChPA,SAAS,cAAAC,mBAAkB;AAC3B,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAQtB,IAAM,WAAW;AAEjB,SAAS,eAAsC;AAC9C,SAAO,EAAE,MAAM,CAAC,EAAE;AACnB;AAEA,SAAS,gBAAgB,OAA2C;AACnE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAI;AACV,SACC,OAAO,EAAE,OAAO,YAChB,OAAO,EAAE,WAAW,YACpB,OAAO,EAAE,aAAa,YACtB,OAAO,EAAE,cAAc,YACvB,OAAO,EAAE,eAAe,YACxB,OAAO,EAAE,WAAW;AAEtB;AAEA,SAAS,yBAAyB,KAAqC;AACtE,MAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACpC,WAAO,aAAa;AAAA,EACrB;AACA,QAAM,OAAO;AACb,MAAI,CAAC,MAAM,QAAQ,KAAK,IAAI,GAAG;AAC9B,WAAO,aAAa;AAAA,EACrB;AACA,QAAM,YAAY,KAAK,KAAK,OAAO,eAAe;AAClD,SAAO,EAAE,MAAM,UAAU;AAC1B;AAqBO,IAAM,iBAAN,MAAqB;AAAA,EAG3B,YAAY,UAAiC,CAAC,GAAG;AAChD,SAAK,UAAU,QAAQ,WAAW,yBAAyB;AAAA,EAC5D;AAAA,EAEA,aAAqB;AACpB,WAAO,KAAK;AAAA,EACb;AAAA,EAEQ,cAAqC;AAC5C,QAAI,CAAI,gBAAW,KAAK,OAAO,GAAG;AACjC,aAAO,aAAa;AAAA,IACrB;AACA,QAAI;AACH,YAAM,MAAS,kBAAa,KAAK,SAAS,OAAO;AACjD,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,YAAM,OAAO,yBAAyB,MAAM;AAE5C,UACC,CAAC,UACD,OAAO,WAAW,YAClB,CAAC,MAAM,QAAQ,OAAO,IAAI,KAC1B,OAAO,KAAK,WAAW,KAAK,KAAK,QAChC;AACD,aAAK,aAAa,IAAI;AAAA,MACvB;AACA,aAAO;AAAA,IACR,QAAQ;AAEP,WAAK,oBAAoB;AACzB,YAAM,QAAQ,aAAa;AAC3B,WAAK,aAAa,KAAK;AACvB,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEQ,sBAA4B;AACnC,QAAI;AACH,UAAO,gBAAW,KAAK,OAAO,GAAG;AAChC,cAAM,aAAa,GAAG,KAAK,OAAO,cAAc,KAAK,IAAI,CAAC;AAC1D,QAAG,kBAAa,KAAK,SAAS,UAAU;AAAA,MACzC;AAAA,IACD,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EAEQ,aAAa,MAAmC;AACvD,UAAM,MAAW,eAAQ,KAAK,OAAO;AACrC,QAAI,CAAI,gBAAW,GAAG,GAAG;AACxB,MAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACtC;AACA,UAAM,MAAM,GAAG,KAAK,OAAO;AAC3B,IAAG,mBAAc,KAAK,KAAK,UAAU,MAAM,MAAM,GAAI,GAAG,OAAO;AAC/D,IAAG,gBAAW,KAAK,KAAK,OAAO;AAAA,EAChC;AAAA,EAEA,UAAU,OAAyC;AAClD,UAAM,OAAO,KAAK,YAAY;AAC9B,UAAM,MAAwB;AAAA,MAC7B,IAAIC,YAAW;AAAA,MACf,QAAQ,MAAM;AAAA,MACd,UAAU,MAAM;AAAA,MAChB,WAAW,MAAM;AAAA,MACjB,YAAY,MAAM;AAAA,MAClB,QAAQ,MAAM;AAAA,MACd,QAAQ,MAAM;AAAA,MACd,OAAO,MAAM;AAAA,IACd;AACA,SAAK,KAAK,KAAK,GAAG;AAElB,QAAI,KAAK,KAAK,SAAS,UAAU;AAChC,WAAK,OAAO,KAAK,KAAK,MAAM,KAAK,KAAK,SAAS,QAAQ;AAAA,IACxD;AACA,SAAK,aAAa,IAAI;AACtB,WAAO;AAAA,EACR;AAAA,EAEA,QAAQ,SAGN;AACD,UAAM,OAAO,KAAK,YAAY;AAC9B,QAAI,OAAO,KAAK;AAChB,QAAI,SAAS,QAAQ;AACpB,aAAO,KAAK,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,MAAM;AAAA,IACtD;AACA,UAAM,QAAQ,KAAK;AAEnB,WAAO,KAAK,MAAM,EAAE,QAAQ;AAC5B,QAAI,SAAS,SAAS,QAAQ,QAAQ,GAAG;AACxC,aAAO,KAAK,MAAM,GAAG,QAAQ,KAAK;AAAA,IACnC;AACA,WAAO,EAAE,MAAM,MAAM;AAAA,EACtB;AAAA,EAEA,UAAU,QAAyB;AAClC,UAAM,OAAO,KAAK,YAAY;AAC9B,UAAM,SAAS,KAAK,KAAK;AACzB,QAAI,QAAQ;AACX,WAAK,OAAO,KAAK,KAAK,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM;AAAA,IACxD,OAAO;AACN,WAAK,OAAO,CAAC;AAAA,IACd;AACA,SAAK,aAAa,IAAI;AACtB,WAAO,SAAS,KAAK,KAAK;AAAA,EAC3B;AACD;;;AT9JA,IAAM,gBAAgB;AACtB,IAAM,wBAAwB;AAkBvB,IAAM,kBAAN,MAAsB;AAAA,EAiB5B,YAAY,eAAuB,UAAkC,CAAC,GAAG;AATzE,SAAQ,YAAmD;AAC3D,SAAQ,UAA+B;AACvC,SAAQ,UAAU;AAClB,SAAQ,YAAoB;AAG5B;AAAA,SAAQ,UAA+B,oBAAI,IAAI;AAC/C,SAAQ,cAA2B,oBAAI,IAAI;AAG1C,SAAK,gBAAgB;AAGrB,UAAM,YAAiB,YAAK,eAAe,YAAY;AACvD,SAAK,gBAAgB,IAAI,kBAAkB;AAAA,MAC1C,YAAiB,YAAK,WAAW,sBAAsB;AAAA,IACxD,CAAC;AACD,SAAK,aAAa,IAAI,eAAe;AAAA,MACpC,SAAc,YAAK,WAAW,0BAA0B;AAAA,IACzD,CAAC;AACD,SAAK,aAAa,IAAI,WAAW,aAAa;AAC9C,SAAK,SAAS,IAAI,aAAa,aAAa;AAC5C,SAAK,cAAc,QAAQ,eAAe;AAAA,EAC3C;AAAA,EAEA,QAAc;AACb,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,YAAY,KAAK,IAAI;AAE1B,SAAK,WAAW,SAAS,QAAQ,GAAG;AACpC,UAAM,aAAa,KAAK,cAAc,cAAc;AACpD,UAAM,UAAU,KAAK,OAAO,WAAW;AACvC,SAAK,OAAO,IAAI,QAAQ,IAAI,OAAO,EAAE,CAAC;AACtC,SAAK,OAAO;AAAA,MACX;AAAA,MACA;AAAA,eACY,QAAQ,GAAG;AAAA,eACX,QAAQ,OAAO;AAAA,eACZ,SAAK,CAAC,IAAO,YAAQ,CAAC,KAAK,QAAQ,IAAI;AAAA,eAC1C,QAAQ,QAAQ;AAAA,eACb,aAAS,CAAC;AAAA,eACV,aAAS,EAAE,QAAQ;AAAA,eACtB,KAAK,aAAa;AAAA,eAClB,UAAU;AAAA,eACV,OAAO;AAAA,eACP,QAAQ,QAAQ;AAAA,IAC7B;AAGA,SAAK,YAAY,YAAY,MAAM,KAAK,KAAK,GAAG,aAAa;AAG7D,SAAK,iBAAiB;AAGtB,YAAQ,GAAG,WAAW,MAAM,KAAK,KAAK,CAAC;AACvC,YAAQ,GAAG,UAAU,MAAM,KAAK,KAAK,CAAC;AAAA,EACvC;AAAA,EAEA,OAAa;AACZ,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,UAAU;AAEf,QAAI,KAAK,WAAW;AACnB,oBAAc,KAAK,SAAS;AAC5B,WAAK,YAAY;AAAA,IAClB;AACA,QAAI,KAAK,SAAS;AACjB,WAAK,QAAQ,MAAM;AACnB,WAAK,UAAU;AAAA,IAChB;AAEA,SAAK,WAAW,UAAU;AAC1B,SAAK,OAAO,IAAI,QAAQ,0BAA0B;AAClD,YAAQ,KAAK,CAAC;AAAA,EACf;AAAA,EAEQ,mBAAyB;AAChC,UAAM,aAAa,KAAK,cAAc,cAAc;AACpD,UAAM,MAAM,WAAW,UAAU,GAAG,WAAW,YAAY,GAAG,CAAC;AAC/D,QAAI;AACH,UAAO,gBAAW,GAAG,GAAG;AACvB,aAAK,UAAa,WAAM,KAAK,CAAC,YAAY,aAAa;AACtD,cAAI,aAAa,wBAAwB;AACxC,iBAAK,OAAO,IAAI,QAAQ,qCAAqC;AAAA,UAC9D;AAAA,QACD,CAAC;AAAA,MACF;AAAA,IACD,QAAQ;AACP,WAAK,OAAO,IAAI,QAAQ,kCAAkC;AAAA,IAC3D;AAAA,EACD;AAAA,EAEQ,OAAa;AACpB,UAAM,SAAS,KAAK,cAAc,WAAW;AAC7C,UAAM,MAAM,KAAK,IAAI;AAErB,eAAW,QAAQ,OAAO,OAAO;AAChC,UAAI,CAAC,KAAK,QAAS;AACnB,UAAI,KAAK,YAAY,IAAI,KAAK,EAAE,EAAG;AAGnC,UAAI,WAAW,KAAK,QAAQ,IAAI,KAAK,EAAE;AACvC,UAAI,aAAa,QAAW;AAC3B,aAAK,QAAQ,IAAI,KAAK,IAAI,GAAG;AAC7B,mBAAW;AAAA,MACZ;AACA,UAAI,KAAK,UAAU,MAAM,UAAU,GAAG,GAAG;AACxC,aAAK,YAAY,MAAM,GAAG;AAAA,MAC3B;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,UAAU,MAAqB,UAAkB,KAAsB;AAC9E,QAAI,KAAK,iBAAiB,YAAY;AACrC,YAAM,aAAa,gBAAgB,KAAK,QAAQ;AAChD,UAAI,aAAa,sBAAuB,QAAO;AAC/C,aAAO,MAAM,YAAY;AAAA,IAC1B;AAEA,QAAI,KAAK,iBAAiB,QAAQ;AAEjC,UAAI,MAAM,WAAW,IAAQ,QAAO;AACpC,aAAO,YAAY,KAAK,UAAU,IAAI,KAAK,GAAG,CAAC;AAAA,IAChD;AAEA,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,YAAY,MAAqB,KAA4B;AAC1E,SAAK,YAAY,IAAI,KAAK,EAAE;AAC5B,SAAK,QAAQ,IAAI,KAAK,IAAI,GAAG;AAE7B,UAAM,YAAY,IAAI,KAAK,GAAG,EAAE,YAAY;AAC5C,UAAM,UAAU,KAAK,IAAI;AACzB,SAAK,OAAO;AAAA,MACX;AAAA,MACA,mBAAmB,KAAK,IAAI,KAAK,KAAK,EAAE;AAAA,cAC7B,KAAK,QAAQ;AAAA,cACb,KAAK,QAAQ,KAAK,KAAK,YAAY;AAAA,cACnC,KAAK,OAAO;AAAA,IACxB;AAEA,QAAI;AACH,YAAM,mBAAmB;AAAA,QACxB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACN;AACA,0BAAoB,KAAK,UAAU,gBAAgB;AACnD,YAAM,WAAW,KAAK,YAAY,KAAK,QAAQ;AAC/C,YAAM,SAAS,MAAM,SAAS,QAAQ,gBAAgB;AACtD,YAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC1C,YAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,WAAK,WAAW,UAAU;AAAA,QACzB,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK;AAAA,QACf;AAAA,QACA;AAAA,QACA,QAAQ,OAAO,WAAW,YAAY,YAAY,OAAO;AAAA,QACzD,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,MACf,CAAC;AAED,YAAM,cAAc,OAAO,SAAS,OAAO,WAAW,OAAO,MAAM,IAAI;AACvE,WAAK,OAAO;AAAA,QACX;AAAA,QACA,mBAAmB,KAAK,IAAI,KAAK,KAAK,EAAE;AAAA,cAC9B,OAAO,MAAM;AAAA,cACb,UAAU;AAAA,iBACP,WAAW;AAAA,MACzB;AAAA,IACD,SAAS,KAAc;AACtB,YAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC1C,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAK,WAAW,UAAU;AAAA,QACzB,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK;AAAA,QACf;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,OAAO;AAAA,MACR,CAAC;AACD,WAAK,OAAO;AAAA,QACX;AAAA,QACA,gBAAgB,KAAK,IAAI,KAAK,KAAK,EAAE;AAAA,cAC3B,UAAU;AAAA,cACV,OAAO;AAAA,MAClB;AAAA,IACD,UAAE;AACD,WAAK,YAAY,OAAO,KAAK,EAAE;AAAA,IAChC;AAAA,EACD;AAAA,EAEA,YAAY;AACX,UAAM,SAAS,KAAK,cAAc,WAAW;AAC7C,WAAO;AAAA,MACN,SAAS,KAAK;AAAA,MACd,KAAK,QAAQ;AAAA,MACb,eAAe,KAAK,UAAU,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,aAAa,GAAI,IAAI;AAAA,MACjF,iBAAiB,OAAO,MAAM;AAAA,MAC9B,cAAc,OAAO,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,MACpD,eAAe,KAAK;AAAA,MACpB,SAAS,KAAK,WAAW,WAAW;AAAA,IACrC;AAAA,EACD;AACD;AAIA,SAAS,gBAAgB,UAA0B;AAClD,QAAM,QAAQ,gDAAgD,KAAK,QAAQ;AAC3E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,CAAC,EAAE,QAAQ,IAAI,IAAI;AACzB,QAAM,MAAM,OAAO,SAAS,UAAU,KAAK,EAAE;AAC7C,UAAQ,MAAM,YAAY,GAAG;AAAA,IAC5B,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,MAAM;AAAA,IACd,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,MAAM,KAAK;AAAA,IACnB,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,MAAM,KAAK,KAAK;AAAA,IACxB,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,MAAM,KAAK,KAAK,KAAK;AAAA,IAC7B;AACC,aAAO;AAAA,EACT;AACD;AAEA,SAAS,YAAY,YAAoB,MAAqB;AAE7D,QAAM,QAAQ,WAAW,KAAK,EAAE,MAAM,KAAK;AAC3C,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,CAAC,SAAS,UAAU,SAAS,WAAW,WAAW,IAAI;AAC7D,MAAI,CAAC,WAAW,CAAC,YAAY,CAAC,WAAW,CAAC,aAAa,CAAC,YAAa,QAAO;AAE5E,SACC,eAAe,SAAS,KAAK,WAAW,CAAC,KACzC,eAAe,UAAU,KAAK,SAAS,CAAC,KACxC,eAAe,SAAS,KAAK,QAAQ,CAAC,KACtC,eAAe,WAAW,KAAK,SAAS,IAAI,CAAC,KAC7C,eAAe,aAAa,KAAK,OAAO,CAAC;AAE3C;AAEA,SAAS,eAAe,OAAe,OAAwB;AAC9D,MAAI,UAAU,IAAK,QAAO;AAG1B,MAAI,MAAM,WAAW,IAAI,GAAG;AAC3B,UAAM,OAAO,OAAO,SAAS,MAAM,MAAM,CAAC,GAAG,EAAE;AAC/C,WAAO,OAAO,KAAK,QAAQ,SAAS;AAAA,EACrC;AAGA,QAAM,SAAS,MAAM,MAAM,GAAG;AAC9B,SAAO,OAAO,KAAK,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,MAAM,KAAK;AAC3D;;;AUhSA,YAAYC,UAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,YAAU;AAUtB,IAAMC,iBAAgB;AACtB,IAAMC,yBAAwB;AAC9B,IAAMC,0BAAyB;AA4BxB,IAAM,oBAAN,MAAwB;AAAA,EA4B9B,YAAY,UAAoC,CAAC,GAAG;AAlBpD,SAAQ,YAAmD;AAC3D,SAAQ,UAAU;AAMlB,SAAQ,YAAoB;AAI5B;AAAA;AAAA,SAAQ,UAA+B,oBAAI,IAAI;AAK/C;AAAA;AAAA;AAAA,SAAQ,iBAAgD,oBAAI,IAAI;AAG/D,SAAK,gBAAgB,QAAQ,iBAAiB,IAAI,kBAAkB;AACpE,SAAK,aAAa,QAAQ,cAAc,IAAI,eAAe;AAC3D,SAAK,aAAa,QAAQ,cAAc,IAAI,WAAW,IAAI,EAAE,SAAS,oBAAoB,EAAE,CAAC;AAI7F,SAAK,SACJ,QAAQ,UACR,IAAI,aAAgB,YAAQ,GAAG;AAAA,MAC9B,SAAc,YAAU,eAAQ,KAAK,WAAW,WAAW,CAAC,GAAGA,uBAAsB;AAAA,IACtF,CAAC;AACF,SAAK,cAAc,QAAQ,eAAe;AAC1C,SAAK,iBAAiB,QAAQ,mBAAmB,MAAM;AACvD,SAAK,cAAc,QAAQ,gBAAgB,MAAM;AAAA,IAAC;AAClD,SAAK,gBAAgB,QAAQ,kBAAkB,MAAM,KAAK,WAAW,cAAc;AAGnF,SAAK,iBAAiB,MAAM,KAAK,KAAK;AACtC,SAAK,gBAAgB,MAAM,KAAK,KAAK;AAAA,EACtC;AAAA,EAEA,QAAc;AACb,QAAI,KAAK,QAAS;AAIlB,QAAI,CAAC,KAAK,eAAe,GAAG;AAC3B,WAAK,OAAO,IAAI,QAAQ,iDAAiD;AACzE,cAAQ,KAAK,CAAC;AAAA,IACf;AAIA,UAAM,cAAc,KAAK,cAAc;AACvC,QAAI,gBAAgB,QAAQ,gBAAgB,QAAQ,KAAK;AACxD,WAAK,OAAO,IAAI,QAAQ,wCAAwC,WAAW,YAAY;AACvF,WAAK,YAAY;AACjB,cAAQ,KAAK,CAAC;AAAA,IACf;AAEA,SAAK,UAAU;AACf,SAAK,YAAY,KAAK,IAAI;AAC1B,SAAK,WAAW,SAAS,QAAQ,GAAG;AAEpC,SAAK,OAAO;AAAA,MACX;AAAA,MACA;AAAA,eACY,QAAQ,GAAG;AAAA,eACX,QAAQ,OAAO;AAAA,eACZ,SAAK,CAAC,IAAO,YAAQ,CAAC,KAAK,QAAQ,IAAI;AAAA,eAC1C,KAAK,cAAc,cAAc,CAAC;AAAA,eAClC,KAAK,OAAO,WAAW,CAAC;AAAA,eACxB,KAAK,WAAW,WAAW,CAAC;AAAA,IACzC;AAEA,SAAK,YAAY,YAAY,MAAM,KAAK,KAAK,GAAGF,cAAa;AAC7D,YAAQ,GAAG,WAAW,KAAK,cAAc;AACzC,YAAQ,GAAG,UAAU,KAAK,aAAa;AAAA,EACxC;AAAA,EAEA,OAAa;AACZ,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,UAAU;AACf,QAAI,KAAK,WAAW;AACnB,oBAAc,KAAK,SAAS;AAC5B,WAAK,YAAY;AAAA,IAClB;AACA,SAAK,WAAW,UAAU;AAC1B,SAAK,YAAY;AAIjB,YAAQ,eAAe,WAAW,KAAK,cAAc;AACrD,YAAQ,eAAe,UAAU,KAAK,aAAa;AACnD,SAAK,OAAO,IAAI,QAAQ,2BAA2B;AACnD,YAAQ,KAAK,CAAC;AAAA,EACf;AAAA,EAEQ,OAAa;AACpB,UAAM,SAAS,KAAK,cAAc,WAAW;AAC7C,UAAM,MAAM,KAAK,IAAI;AAErB,eAAW,QAAQ,OAAO,OAAO;AAChC,UAAI,CAAC,KAAK,QAAS;AAInB,UAAI,CAAI,gBAAW,KAAK,UAAU,IAAI,GAAG;AACxC,aAAK,+BAA+B,MAAM,MAAM;AAChD;AAAA,MACD;AAIA,UAAI,WAAW,KAAK,QAAQ,IAAI,KAAK,EAAE;AACvC,UAAI,aAAa,QAAW;AAC3B,aAAK,QAAQ,IAAI,KAAK,IAAI,GAAG;AAC7B,mBAAW;AAAA,MACZ;AACA,UAAI,CAAC,KAAK,UAAU,MAAM,UAAU,GAAG,EAAG;AAI1C,YAAM,SAAS,KAAK,UAAU;AAC9B,YAAM,OAAO,KAAK,eAAe,IAAI,MAAM,KAAK,QAAQ,QAAQ;AAChE,YAAM,OAAO,KACX,MAAM,MAAM,MAAS,EACrB,KAAK,MAAM,KAAK,YAAY,MAAM,GAAG,CAAC,EACtC,MAAM,CAAC,QAAiB;AACxB,aAAK,OAAO,IAAI,SAAS,QAAQ,KAAK,EAAE,sBAAsB,OAAO,GAAG,CAAC,EAAE;AAAA,MAC5E,CAAC;AACF,WAAK,eAAe,IAAI,QAAQ,IAAI;AAAA,IACrC;AAAA,EACD;AAAA,EAEQ,+BAA+B,MAAqB,QAAoC;AAC/F,UAAM,MAAM,OAAO,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,KAAK,EAAE;AAC1D,QAAI,QAAQ,GAAI;AAChB,UAAM,WAAW,OAAO,MAAM,GAAG;AACjC,QAAI,CAAC,SAAU;AACf,QAAI,SAAS,YAAY,SAAS,SAAS,iBAAiB,oBAAqB;AACjF,SAAK,OAAO;AAAA,MACX;AAAA,MACA,kBAAkB,KAAK,IAAI,KAAK,KAAK,EAAE,gBAAgB,KAAK,UAAU,IAAI;AAAA,IAC3E;AACA,WAAO,MAAM,GAAG,IAAI;AAAA,MACnB,GAAG;AAAA,MACH,SAAS;AAAA,MACT,cAAc;AAAA,IACf;AACA,SAAK,cAAc,SAAS,KAAK,IAAI;AAAA,MACpC,SAAS;AAAA,MACT,WAAW,OAAO,MAAM,GAAG,EAAE;AAAA,IAC9B,CAAC;AAAA,EACF;AAAA,EAEQ,UAAU,MAAqB,UAAkB,KAAsB;AAC9E,QAAI,KAAK,iBAAiB,YAAY;AACrC,YAAM,aAAaG,iBAAgB,KAAK,QAAQ;AAChD,UAAI,aAAaF,uBAAuB,QAAO;AAC/C,aAAO,MAAM,YAAY;AAAA,IAC1B;AACA,QAAI,KAAK,iBAAiB,QAAQ;AACjC,UAAI,MAAM,WAAW,IAAQ,QAAO;AACpC,aAAOG,aAAY,KAAK,UAAU,IAAI,KAAK,GAAG,CAAC;AAAA,IAChD;AACA,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,YAAY,MAAqB,KAA4B;AAC1E,SAAK,QAAQ,IAAI,KAAK,IAAI,GAAG;AAC7B,UAAM,YAAY,IAAI,KAAK,GAAG,EAAE,YAAY;AAC5C,UAAM,UAAU,KAAK,IAAI;AACzB,UAAM,UAAU,QAAQ,IAAI;AAE5B,SAAK,OAAO,IAAI,QAAQ,mBAAmB,KAAK,IAAI,KAAK,KAAK,EAAE,QAAQ,KAAK,UAAU,IAAI,EAAE;AAE7F,QAAI;AACH,cAAQ,MAAM,KAAK,UAAU,IAAI;AACjC,YAAM,mBAAmB;AAAA,QACxB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK,UAAU;AAAA,MAChB;AACA,0BAAoB,KAAK,UAAU,gBAAgB;AACnD,YAAM,WAAW,KAAK,YAAY,KAAK,QAAQ;AAC/C,YAAM,SAAS,MAAM,SAAS,QAAQ,gBAAgB;AACtD,YAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC1C,YAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,WAAK,WAAW,UAAU;AAAA,QACzB,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK;AAAA,QACf;AAAA,QACA;AAAA,QACA,QAAQ,OAAO,WAAW,YAAY,YAAY,OAAO;AAAA,QACzD,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,MACf,CAAC;AAGD,WAAK,cAAc,SAAS,KAAK,IAAI;AAAA,QACpC,SAAS,KAAK;AAAA,QACd,WAAW,KAAK;AAAA,MACjB,CAAC;AAED,WAAK,OAAO;AAAA,QACX;AAAA,QACA,mBAAmB,KAAK,IAAI,WAAW,OAAO,MAAM,aAAa,UAAU;AAAA,MAC5E;AAAA,IACD,SAAS,KAAc;AACtB,YAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC1C,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAK,WAAW,UAAU;AAAA,QACzB,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK;AAAA,QACf;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,OAAO;AAAA,MACR,CAAC;AACD,WAAK,OAAO;AAAA,QACX;AAAA,QACA,gBAAgB,KAAK,IAAI,KAAK,KAAK,EAAE,cAAc,UAAU,YAAY,OAAO;AAAA,MACjF;AAAA,IACD,UAAE;AACD,cAAQ,MAAM,OAAO;AAAA,IACtB;AAAA,EACD;AAAA,EAEA,YAAY;AACX,UAAM,SAAS,KAAK,cAAc,WAAW;AAC7C,WAAO;AAAA,MACN,SAAS,KAAK;AAAA,MACd,KAAK,QAAQ;AAAA,MACb,eAAe,KAAK,UAAU,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,aAAa,GAAI,IAAI;AAAA,MACjF,iBAAiB,OAAO,MAAM;AAAA,MAC9B,cAAc,OAAO,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,MACpD,SAAS,KAAK,WAAW,WAAW;AAAA,IACrC;AAAA,EACD;AACD;AAIA,SAASD,iBAAgB,UAA0B;AAClD,QAAM,QAAQ,gDAAgD,KAAK,QAAQ;AAC3E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,CAAC,EAAE,QAAQ,IAAI,IAAI;AACzB,QAAM,MAAM,OAAO,SAAS,UAAU,KAAK,EAAE;AAC7C,UAAQ,MAAM,YAAY,GAAG;AAAA,IAC5B,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,MAAM;AAAA,IACd,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,MAAM,KAAK;AAAA,IACnB,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,MAAM,KAAK,KAAK;AAAA,IACxB,KAAK;AAAA,IACL,KAAK;AACJ,aAAO,MAAM,KAAK,KAAK,KAAK;AAAA,IAC7B;AACC,aAAO;AAAA,EACT;AACD;AAEA,SAASC,aAAY,YAAoB,MAAqB;AAC7D,QAAM,QAAQ,WAAW,KAAK,EAAE,MAAM,KAAK;AAC3C,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,CAAC,SAAS,UAAU,SAAS,WAAW,WAAW,IAAI;AAC7D,MAAI,CAAC,WAAW,CAAC,YAAY,CAAC,WAAW,CAAC,aAAa,CAAC,YAAa,QAAO;AAC5E,SACCC,gBAAe,SAAS,KAAK,WAAW,CAAC,KACzCA,gBAAe,UAAU,KAAK,SAAS,CAAC,KACxCA,gBAAe,SAAS,KAAK,QAAQ,CAAC,KACtCA,gBAAe,WAAW,KAAK,SAAS,IAAI,CAAC,KAC7CA,gBAAe,aAAa,KAAK,OAAO,CAAC;AAE3C;AAEA,SAASA,gBAAe,OAAe,OAAwB;AAC9D,MAAI,UAAU,IAAK,QAAO;AAC1B,MAAI,MAAM,WAAW,IAAI,GAAG;AAC3B,UAAM,OAAO,OAAO,SAAS,MAAM,MAAM,CAAC,GAAG,EAAE;AAC/C,WAAO,OAAO,KAAK,QAAQ,SAAS;AAAA,EACrC;AACA,QAAM,SAAS,MAAM,MAAM,GAAG;AAC9B,SAAO,OAAO,KAAK,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,MAAM,KAAK;AAC3D;;;AC5VA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAOtB,SAAS,4BAAAC,2BAA0B,iBAAAC,sBAAqB;AA4CxD,IAAM,gBAAgB;AACtB,IAAM,cAAc;AAOpB,SAAS,aAAa,eAAyC;AAC9D,SAAO;AAAA,IACN,MAAM;AAAA,IACN,MAAW,gBAAS,aAAa,KAAK;AAAA,EACvC;AACD;AAEA,SAAS,aACR,QAC8E;AAC9E,MAAI;AACJ,MAAI;AACH,UAAS,kBAAa,QAAQ,OAAO;AAAA,EACtC,SAAS,KAAK;AACb,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,OAAO,gBAAgB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACxE;AAAA,EACD;AACA,MAAI;AACJ,MAAI;AACH,aAAS,KAAK,MAAM,GAAG;AAAA,EACxB,SAAS,KAAK;AACb,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,OAAO,sBAAsB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC9E;AAAA,EACD;AACA,MAAI,CAACC,0BAAyB,MAAM,GAAG;AACtC,WAAO,EAAE,IAAI,OAAO,OAAO,sCAAsC;AAAA,EAClE;AACA,SAAO,EAAE,IAAI,MAAM,QAAQ,OAAO;AACnC;AAEA,SAAS,WAAW,UAAwB;AAC3C,MAAI;AACH,IAAG,gBAAW,QAAQ;AAAA,EACvB,QAAQ;AAAA,EAER;AACD;AAEA,SAAS,UAAU,UAAwB;AAC1C,QAAM,MAAW,eAAQ,QAAQ;AACjC,MAAI,CAAI,gBAAW,GAAG,GAAG;AACxB,IAAG,eAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACtC;AACD;AAEA,SAAS,aAAgB,UAA4B;AACpD,MAAI,CAAI,gBAAW,QAAQ,EAAG,QAAO;AACrC,MAAI;AACH,WAAO,KAAK,MAAS,kBAAa,UAAU,OAAO,CAAC;AAAA,EACrD,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAAS,cAAc,UAAkB,MAAqB;AAC7D,YAAU,QAAQ;AAClB,EAAG,mBAAc,UAAU,KAAK,UAAU,MAAM,MAAM,GAAI,GAAG,OAAO;AACrE;AAMA,SAAS,iBACR,MACA,eACA,eACgB;AAChB,MAAI,CAAC,cAAc,IAAI,KAAK,IAAI,GAAG;AAClC,kBAAc,IAAI,KAAK,IAAI;AAC3B,WAAO;AAAA,EACR;AACA,QAAM,OAAO,GAAG,KAAK,IAAI,KAAK,aAAa;AAC3C,MAAI,YAAY;AAChB,MAAI,UAAU;AACd,SAAO,cAAc,IAAI,SAAS,GAAG;AACpC,gBAAY,GAAG,IAAI,KAAK,OAAO;AAC/B,eAAW;AAAA,EACZ;AACA,gBAAc,IAAI,SAAS;AAC3B,SAAO,EAAE,GAAG,MAAM,MAAM,UAAU;AACnC;AAOA,eAAsB,gBAAgB,SAA2D;AAChG,QAAM,mBAAmB,QAAQ,oBAAoB,4BAA4B;AACjF,QAAM,wBAAwB,QAAQ,yBAAyB,yBAAyB;AACxF,QAAM,QAAQ,QAAQ,SAAS;AAG/B,QAAM,WAAW,aAAmC,gBAAgB;AACpE,QAAM,aACL,YAAY,SAAS,YAAY,IAAI,WAAW,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE;AACzE,QAAM,gBAAgB,IAAI,IAAI,WAAW,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAGjE,QAAM,gBAAgB,aAA6B,qBAAqB,KAAK,CAAC;AAC9E,QAAM,WAA2B,CAAC,GAAG,aAAa;AAElD,MAAI,WAAW;AACf,QAAM,YAAsB,CAAC;AAC7B,QAAM,SAAmB,CAAC;AAE1B,aAAW,iBAAiB,QAAQ,gBAAgB;AACnD,UAAM,SAAc,YAAK,eAAe,eAAe,WAAW;AAClE,QAAI,CAAI,gBAAW,MAAM,EAAG;AAE5B,UAAM,KAAK,aAAa,MAAM;AAC9B,QAAI,CAAC,GAAG,IAAI;AACX,eAAS,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA,OAAO,GAAG;AAAA,QACV,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC5B,CAAC;AAED,iBAAW,MAAM;AACjB;AAAA,IACD;AAEA,QAAI;AACJ,QAAI;AACH,qBAAe,MAAM,MAAM,aAAa;AAAA,IACzC,SAAS,KAAK;AACb,eAAS,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA,OAAO,iBAAiB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QACxE,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC5B,CAAC;AACD,iBAAW,MAAM;AACjB;AAAA,IACD;AAEA,UAAM,EAAE,QAAQ,QAAQ,WAAW,IAAIC,eAAc,GAAG,QAAQ,YAAY;AAC5E,WAAO,KAAK,GAAG,UAAU;AAGzB,UAAM,WAA4B,OAAO,MAAM,IAAI,CAAC,MAAM;AACzD,UAAI,cAAc,IAAI,EAAE,IAAI,GAAG;AAC9B,kBAAU,KAAK,EAAE,IAAI;AACrB,eAAO,iBAAiB,GAAG,eAAe,aAAa,IAAI;AAAA,MAC5D;AACA,oBAAc,IAAI,EAAE,IAAI;AACxB,aAAO;AAAA,IACR,CAAC;AAED,eAAW,MAAM,KAAK,GAAG,QAAQ;AACjC,gBAAY;AAGZ,eAAW,MAAM;AAAA,EAClB;AAGA,MAAI,WAAW,GAAG;AAKjB,cAAU,gBAAgB;AAC1B,UAAM,MAAM,GAAG,gBAAgB;AAC/B,IAAG,mBAAc,KAAK,KAAK,UAAU,YAAY,MAAM,GAAI,GAAG,OAAO;AACrE,IAAG,gBAAW,KAAK,gBAAgB;AAAA,EACpC;AACA,MAAI,SAAS,SAAS,cAAc,QAAQ;AAC3C,kBAAc,uBAAuB,QAAQ;AAAA,EAC9C,WAAW,SAAS,WAAW,KAAK,cAAc,SAAS,GAAG;AAE7D,eAAW,qBAAqB;AAAA,EACjC;AAEA,SAAO;AAAA,IACN;AAAA,IACA,QAAQ,SAAS,SAAS,cAAc;AAAA,IACxC;AAAA,IACA;AAAA,EACD;AACD;;;AC1PA,SAAS,SAAAC,cAAa;AACtB,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAiCtB,IAAMC,sBAAqB;AAE3B,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACnC,cAAc;AACb,UAAM,aAAa;AACnB,SAAK,OAAO;AAAA,EACb;AACD;AAEA,SAAS,UAAU,KAAuB;AACzC,MAAI,eAAe,gBAAiB,QAAO;AAC3C,MAAI,eAAe,OAAO;AACzB,WAAO,IAAI,SAAS,qBAAqB,IAAI,YAAY;AAAA,EAC1D;AACA,SAAO;AACR;AAEA,SAAS,cACR,WACA,MACA,KACA,WAC4D;AAC5D,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACvC,QAAI,UAAU;AACd,UAAM,QAAQJ,OAAM,WAAW,MAAM;AAAA,MACpC;AAAA,MACA,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IACjC,CAAC;AACD,QAAI,SAAS;AACb,QAAI,SAAS;AAEb,UAAM,QAAQ,WAAW,MAAM;AAC9B,UAAI,QAAS;AACb,gBAAU;AACV,YAAM,KAAK,SAAS;AACpB,aAAO,IAAI,gBAAgB,CAAC;AAAA,IAC7B,GAAG,SAAS;AAEZ,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AAClC,gBAAU,MAAM,SAAS,OAAO;AAAA,IACjC,CAAC;AACD,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AAClC,gBAAU,MAAM,SAAS,OAAO;AAAA,IACjC,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,QAAQ;AAC1B,UAAI,QAAS;AACb,gBAAU;AACV,mBAAa,KAAK;AAClB,aAAO,GAAG;AAAA,IACX,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,SAAS;AAC3B,UAAI,QAAS;AACb,gBAAU;AACV,mBAAa,KAAK;AAClB,MAAAI,SAAQ,EAAE,QAAQ,QAAQ,MAAM,QAAQ,EAAE,CAAC;AAAA,IAC5C,CAAC;AAAA,EACF,CAAC;AACF;AAQO,IAAM,iBAAN,MAAqB;AAAA,EAQ3B,YAAY,UAAwB,CAAC,GAAG;AACvC,SAAK,YAAY,QAAQ,aAAaD;AACtC,SAAK,YAAY,QAAQ,aAAa;AACtC,QAAI,QAAQ,QAAQ;AACnB,WAAK,WAAW,QAAQ;AAAA,IACzB,OAAO;AACN,YAAM,SAAS,KAAK;AACpB,YAAM,YAAY,KAAK;AACvB,WAAK,WAAW,CAAC,MAAM,QAAQ,cAAc,QAAQ,MAAM,KAAK,SAAS;AAAA,IAC1E;AAAA,EACD;AAAA,EAEA,MAAM,MAAM,eAA6C;AACxD,UAAM,OAAY,gBAAS,aAAa,KAAK;AAE7C,QAAI,CAAC,iBAAiB,CAAI,gBAAW,aAAa,GAAG;AACpD,aAAO;AAAA,QACN,WAAW,EAAE,MAAM,eAAe,KAAK;AAAA,QACvC,OAAO;AAAA,MACR;AAAA,IACD;AAEA,QAAI;AACJ,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,SAAS,CAAC,UAAU,WAAW,QAAQ,GAAG,aAAa;AACjF,UAAI,OAAO,SAAS,KAAK,OAAO,OAAO,KAAK,GAAG;AAC9C,oBAAY,OAAO,OAAO,KAAK;AAAA,MAChC;AAAA,IACD,SAAS,KAAK;AACb,UAAI,UAAU,GAAG,GAAG;AACnB,eAAO;AAAA,UACN,WAAW,EAAE,MAAM,eAAe,MAAM,UAAU;AAAA,UAClD,OAAO;AAAA,QACR;AAAA,MACD;AACA,aAAO;AAAA,QACN,WAAW,EAAE,MAAM,eAAe,KAAK;AAAA,QACvC,OAAO;AAAA,MACR;AAAA,IACD;AAEA,QAAI;AACJ,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,SAAS,CAAC,aAAa,gBAAgB,MAAM,GAAG,aAAa;AACvF,UAAI,OAAO,SAAS,KAAK,OAAO,OAAO,KAAK,GAAG;AAC9C,oBAAY,OAAO,OAAO,KAAK;AAAA,MAChC;AAAA,IACD,SAAS,KAAK;AACb,UAAI,UAAU,GAAG,GAAG;AACnB,eAAO;AAAA,UACN,WAAW,EAAE,MAAM,eAAe,MAAM,WAAW,UAAU;AAAA,UAC7D,OAAO;AAAA,QACR;AAAA,MACD;AACA,aAAO;AAAA,QACN,WAAW,EAAE,MAAM,eAAe,MAAM,UAAU;AAAA,QAClD,OAAO;AAAA,MACR;AAAA,IACD;AAGA,QAAI,CAAC,aAAa,CAAC,WAAW;AAC7B,aAAO;AAAA,QACN,WAAW,EAAE,MAAM,eAAe,KAAK;AAAA,QACvC,OAAO;AAAA,MACR;AAAA,IACD;AAEA,WAAO;AAAA,MACN,WAAW;AAAA,QACV,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AAAA,MACA,OAAO;AAAA,IACR;AAAA,EACD;AACD;;;AC/LA,SAAS,wBAAAE,6BAA4B;AAa9B,IAAM,qBAAN,MAAyB;AAAA,EAI/B,YAAY,UAAqC,CAAC,GAAG;AACpD,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,UAAU,QAAQ;AAAA,EACxB;AAAA,EAEA,MAAM,aAAoC;AACzC,QAAI,CAAC,KAAK,SAAS;AAClB,YAAMA,sBAAqB,uBAAuB,0CAA0C;AAAA,IAC7F;AAEA,UAAM,WAAW,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,4BAA4B;AACjF,QAAI,CAAC,SAAS,IAAI;AACjB,YAAM,IAAI,MAAM,mCAAmC,SAAS,MAAM,EAAE;AAAA,IACrE;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAGlC,WAAO;AAAA,MACN,QAAQ,KAAK,UAAU,CAAC;AAAA,MACxB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC;AAAA,EACD;AAAA,EAEA,MAAM,cAAc,UAAgD;AACnE,QAAI,CAAC,KAAK,SAAS;AAClB,YAAMA,sBAAqB,uBAAuB,0CAA0C;AAAA,IAC7F;AAEA,UAAM,WAAW,MAAM,KAAK;AAAA,MAC3B,GAAG,KAAK,OAAO,uCAAuC,QAAQ;AAAA,IAC/D;AACA,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI,SAAS,WAAW,KAAK;AAC5B,cAAMA,sBAAqB,aAAa,UAAU,QAAQ,aAAa;AAAA,MACxE;AACA,YAAM,IAAI,MAAM,4BAA4B,QAAQ,KAAK,SAAS,MAAM,EAAE;AAAA,IAC3E;AAEA,UAAM,UAAW,MAAM,SAAS,KAAK;AAGrC,WAAO,QAAQ,MAAM,SAAS,CAAC;AAAA,EAChC;AACD;;;AC1BO,IAAM,kBAAN,MAAsB;AAAA,EAC5B,YAA6B,MAAmC;AAAnC;AAAA,EAAoC;AAAA,EAEjE,MAAM,OAAO,SAA2D;AACvE,QAAI,QAAQ,gBAAgB,eAAe,QAAQ,gBAAgB,QAAQ;AAC1E,YAAM,IAAI,MAAM,yBAAyB,OAAO,QAAQ,WAAW,CAAC,EAAE;AAAA,IACvE;AAEA,QACC,QAAQ,WAAW,eACnB,QAAQ,WAAW,UACnB,QAAQ,WAAW,kBAClB;AACD,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS,SAAS,QAAQ,MAAM;AAAA,MACjC;AAAA,IACD;AAEA,QAAI,QAAQ,WAAW,WAAW;AACjC,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS,uDAAuD,QAAQ,MAAM;AAAA,MAC/E;AAAA,IACD;AAEA,UAAM,UAAU,MAAM,KAAK,KAAK,cAAc,WAAW;AACzD,UAAM,cAAc,QAAQ,OAAO;AAAA,MAClC,CAAC,UAAU,KAAK,KAAK,WAAW,uBAAuB,MAAM,EAAE,MAAM,QAAQ;AAAA,IAC9E;AAEA,QAAI,CAAC,aAAa;AACjB,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS;AAAA,MACV;AAAA,IACD;AAEA,QAAI,CAAC,QAAQ,cAAc,YAAY,cAAc,YAAY,WAAW;AAC3E,aAAO;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,YAAY,QAAQ,YAAY,UAAU;AAAA,QAC1C,UAAU,QAAQ,YAAY,QAAQ;AAAA,QACtC,SAAS;AAAA,MACV;AAAA,IACD;AAEA,WAAO;AAAA,MACN,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AAAA,EACD;AACD;;;AC7FA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAGtB,IAAM,yBAAyB;AAC/B,IAAM,gCAAgC;AACtC,IAAM,iCAAiC;AACvC,IAAM,mCAAmC;AACzC,IAAMC,yBAAwB;AAE9B,SAAS,uBAAuB,SAAyB;AACxD,MACC,OAAO,YAAY,YACnB,QAAQ,WAAW,KACnB,YAAY,OACZ,YAAY,QACZ,QAAQ,SAAS,GAAG,KACpB,QAAQ,SAAS,IAAI,KACrB,CAACA,uBAAsB,KAAK,OAAO,GAClC;AACD,UAAM,IAAI,MAAM,qBAAqB,OAAO,EAAE;AAAA,EAC/C;AAEA,SAAO;AACR;AAEO,IAAM,aAAN,MAAiB;AAAA,EAKvB,YAAY,SAA4B;AACvC,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,aAAa,QAAQ,cAAcF;AAAA,EACzC;AAAA,EAEA,uBAAuB,UAA0B;AAChD,QAAI,SAAS,WAAW,WAAW,GAAG;AACrC,aAAO,uBAAuB,SAAS,MAAM,YAAY,MAAM,CAAC;AAAA,IACjE;AACA,QAAI,SAAS,WAAW,YAAY,GAAG;AACtC,YAAM,YAAY,SAAS,YAAY,GAAG;AAC1C,aAAO,uBAAuB,SAAS,MAAM,YAAY,CAAC,CAAC;AAAA,IAC5D;AACA,WAAO,uBAAuB,QAAQ;AAAA,EACvC;AAAA,EAEA,sBAAsB,SAAyB;AAC9C,WAAO,GAAG,8BAA8B,IAAI,OAAO;AAAA,EACpD;AAAA,EAEA,yBAAiC;AAChC,WAAO;AAAA,EACR;AAAA,EAEA,iBAAiB,SAAyB;AACzC,WAAY,YAAK,KAAK,gBAAgB,OAAO;AAAA,EAC9C;AAAA,EAEA,MAAM,wBAA2C;AAChD,UAAM,iBAAsB,YAAK,KAAK,eAAe,8BAA8B;AACnF,QAAI;AACH,YAAM,UAAU,MAAM,KAAK,WAAW,QAAQ,gBAAgB;AAAA,QAC7D,eAAe;AAAA,MAChB,CAAC;AACD,aAAO,QACL,OAAO,CAAC,UAAU,MAAM,YAAY,KAAK,CAAC,MAAM,KAAK,WAAW,GAAG,CAAC,EACpE,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,KAAK;AAAA,IACR,QAAQ;AACP,aAAO,CAAC;AAAA,IACT;AAAA,EACD;AAAA,EAEA,MAAM,mBAAsC;AAC3C,QAAI;AACH,YAAM,UAAU,MAAM,KAAK,WAAW,QAAQ,KAAK,gBAAgB;AAAA,QAClE,eAAe;AAAA,MAChB,CAAC;AACD,aAAO,QACL,OAAO,CAAC,UAAU,MAAM,YAAY,KAAK,CAAC,MAAM,KAAK,WAAW,GAAG,CAAC,EACpE,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,KAAK;AAAA,IACR,QAAQ;AACP,aAAO,CAAC;AAAA,IACT;AAAA,EACD;AAAA,EAEA,MAAM,4BAA4B,SAAgC;AACjE,UAAM,YAAY,KAAK,iBAAiB,OAAO;AAC/C,UAAM,KAAK,WAAW,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1D,UAAM,KAAK,WAAW;AAAA,MAChB,YAAK,WAAW,sBAAsB;AAAA,MAC3C,KAAK,UAAU,EAAE,SAAS,aAAa,YAAY,GAAG,MAAM,CAAC;AAAA,MAC7D;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,mBAAmB,SAAmC;AAC3D,UAAM,KAAK,6BAA6B,OAAO;AAC/C,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,WAAW;AAAA,QAC/B,YAAK,KAAK,iBAAiB,OAAO,GAAG,sBAAsB;AAAA,QAChE;AAAA,MACD;AACA,YAAM,SAAS,KAAK,MAAM,MAAM;AAChC,aAAO,OAAO,YAAY;AAAA,IAC3B,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,6BAA6B,SAAgC;AAC1E,UAAM,YAAY,KAAK,iBAAiB,OAAO;AAC/C,UAAM,UAAe,YAAK,WAAW,sBAAsB;AAC3D,UAAM,aAAkB,YAAK,WAAW,6BAA6B;AACrE,QAAI;AACH,YAAM,KAAK,WAAW,SAAS,SAAS,OAAO;AAC/C;AAAA,IACD,QAAQ;AAAA,IAER;AACA,QAAI;AACH,YAAM,gBAAgB,MAAM,KAAK,WAAW,SAAS,YAAY,OAAO;AACxE,YAAM,KAAK,WAAW,UAAU,SAAS,eAAe,OAAO;AAAA,IAChE,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EAEA,MAAM,gBACL,SACA,OACA,OACgB;AAChB,UAAM,OACL,UAAU,cACF,YAAK,KAAK,eAAe,8BAA8B,IAC5D,KAAK;AACT,UAAM,YAAiB,YAAK,MAAM,OAAO;AACzC,UAAM,KAAK,WAAW,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAE1D,eAAW,QAAQ,OAAO;AACzB,YAAM,WAAgB,YAAK,WAAW,KAAK,IAAI;AAC/C,YAAM,KAAK,WAAW,MAAW,eAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACvE,YAAM,KAAK,WAAW,UAAU,UAAU,KAAK,SAAS,OAAO;AAC/D,UAAI,KAAK,YAAY;AACpB,YAAI;AACH,gBAAM,KAAK,WAAW,MAAM,UAAU,GAAK;AAAA,QAC5C,QAAQ;AAAA,QAER;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;;;AClKA,YAAYG,UAAQ;AACpB,YAAYC,YAAU;;;ACoCf,IAAM,cAAN,cAA0B,MAAM;AAAA,EAKtC,YAAY,QAAoB,QAAgB,SAA+B;AAC9E,UAAM,kBAAkB,MAAM,MAAM,MAAM,EAAE;AAC5C,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,SAAS,SAAS;AAAA,EACxB;AACD;;;ADcO,IAAM,eAAN,MAAmB;AAAA,EAMzB,YAAY,MAA2B;AACtC,SAAK,MAAM,KAAK;AAChB,SAAK,gBAAgB,KAAK,kBAAkB,MAAM;AAClD,SAAK,UAAU,KAAK,WAAY,WAAW;AAC3C,SAAK,uBAAuB,KAAK,wBAAwB;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAS,KAAiE;AAC/E,UAAM,MAAM,GAAG,KAAK,oBAAoB;AACxC,UAAM,MAAM,MAAM,KAAK,QAAQ,KAAK;AAAA,MACnC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,GAAG;AAAA,IACzB,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACZ,YAAM,IAAI,YAAY,iBAAiB,iCAAiC,IAAI,MAAM,EAAE;AAAA,IACrF;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OACL,QACA,WACA,OACA,OAAsB,CAAC,GACC;AAExB,UAAM,IAAI,MAAM,KAAK,SAAS,EAAE,QAAQ,WAAW,MAAM,CAAC;AAC1D,QAAI,CAAC,EAAE,OAAO;AACb,YAAM,IAAI,YAAY,EAAE,UAAU,WAAW,EAAE,UAAU,mBAAmB;AAAA,IAC7E;AAGA,UAAM,gBAAgB,WAAW,MAAM;AACvC,UAAM,YAAiB,YAAK,eAAe,UAAU,SAAS;AAC9D,UAAS,WAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC7C,eAAW,KAAK,OAAO;AACtB,YAAM,OAAY,YAAK,WAAW,EAAE,IAAI;AACxC,YAAS,WAAW,eAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,YAAM,MAAM,GAAG,IAAI,IAAI,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;AAChD,YAAS,eAAU,KAAK,EAAE,SAAS,MAAM;AACzC,YAAS,YAAO,KAAK,IAAI;AAAA,IAC1B;AAGA,UAAM,gBAAgB,qBAAqB,SAAS;AACpD,UAAM,EAAE,UAAU,IAAI,MAAM,KAAK,IAAI,OAAO,eAAe,aAAa;AAGxE,QAAI;AACJ,QAAI;AACJ,QAAI,CAAC,KAAK,UAAU;AACnB,YAAM,SAAS,KAAK,UAAU,KAAK,cAAc,MAAM,KAAK;AAC5D,YAAM,aAAa,MAAM,KAAK,IAAI,KAAK,QAAQ,eAAe,MAAM;AACpE,kBAAY,WAAW;AACvB,kBAAY,WAAW;AAAA,IACxB;AAEA,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AACD;;;AE/HA,SAAS,kBAAkB,MAA4B;AACtD,MAAI,CAAC,KAAK,WAAY,QAAO;AAC7B,QAAM,KAAK,KAAK,MAAM,KAAK,UAAU;AACrC,SAAO,OAAO,SAAS,EAAE,IAAI,KAAK;AACnC;AAUO,SAAS,kBAAkB,OAAgD;AACjF,SAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM;AAChC,UAAM,MAAM,kBAAkB,CAAC;AAC/B,UAAM,MAAM,kBAAkB,CAAC;AAC/B,QAAI,QAAQ,IAAK,QAAO,MAAM;AAC9B,UAAM,SAAS,EAAE,SAAS,OAAO;AACjC,UAAM,SAAS,EAAE,SAAS,OAAO;AACjC,QAAI,WAAW,OAAQ,QAAO,SAAS;AACvC,WAAO,EAAE,GAAG,cAAc,EAAE,EAAE;AAAA,EAC/B,CAAC;AACF;AAMO,SAAS,gBAAgB,OAAgD;AAC/E,SAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM;AAChC,UAAM,SAAS,EAAE,SAAS,OAAO;AACjC,UAAM,SAAS,EAAE,SAAS,OAAO;AACjC,QAAI,WAAW,OAAQ,QAAO,SAAS;AACvC,WAAO,EAAE,GAAG,cAAc,EAAE,EAAE;AAAA,EAC/B,CAAC;AACF;AAuBO,SAAS,kBACf,UACA,WACiB;AACjB,QAAM,aAAa,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACpD,QAAM,kBAAkB,UAAU,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;AACrE,SAAO,gBAAgB,CAAC,GAAG,UAAU,GAAG,eAAe,CAAC;AACzD;AAMO,SAAS,aACf,OACA,aACiB;AACjB,QAAM,WAAW,oBAAI,IAAoB;AACzC,aAAW,CAAC,OAAO,EAAE,KAAK,YAAY,QAAQ,GAAG;AAChD,aAAS,IAAI,IAAI,KAAK;AAAA,EACvB;AACA,aAAW,QAAQ,OAAO;AACzB,UAAM,OAAO,SAAS,IAAI,KAAK,EAAE;AACjC,QAAI,SAAS,OAAW,MAAK,QAAQ;AAAA,EACtC;AACA,SAAO;AACR;AAMO,SAAS,gBACf,OACA,IACA,OAAa,oBAAI,KAAK,GACL;AACjB,SAAO,MAAM,IAAI,CAAC,SAAU,KAAK,OAAO,KAAK,EAAE,GAAG,MAAM,YAAY,KAAK,YAAY,EAAE,IAAI,IAAK;AACjG;;;ACpGA,YAAYC,UAAS;AACrB,YAAYC,YAAU;AACtB,SAAS,cAAcC,cAAa;;;ACJ7B,IAAM,8BAA8B;AAyBpC,IAAM,wBAAoD;AAAA,EAChE;AAAA,IACC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,KAAK;AAAA,EACN;AAAA,EACA;AAAA,IACC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,KAAK;AAAA,EACN;AAAA,EACA;AAAA,IACC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,KAAK;AAAA,EACN;AACD;;;AD5BA,IAAMC,aAAY;AAClB,IAAMC,iBAAgB;AACtB,IAAMC,2BAA0B;AAChC,IAAMC,yBAAwB;AAC9B,IAAMC,cAAa;AACZ,IAAM,kCAAuC,YAAK,WAAW,yBAAyB;AAC7F,IAAM,oCAAoC;AAQ1C,eAAe,kCAAkC,UAAwC;AACxF,MAAI,CAAC,SAAU;AACf,QAAM,aAAkB,YAAU,eAAQ,QAAQ,GAAG,iCAAiC;AACtF,MAAI,eAAe,SAAU;AAC7B,MAAI;AACH,UAAU,YAAO,QAAQ;AACzB;AAAA,EACD,QAAQ;AAAA,EAER;AACA,MAAI;AACH,UAAU,YAAO,YAAY,QAAQ;AAAA,EACtC,QAAQ;AAAA,EAER;AACD;AA0BO,IAAM,uBAAN,MAAyD;AAAA,EAC/D,MAAM,OAAO,UAAoC;AAChD,QAAI;AACH,YAAU,YAAO,QAAQ;AACzB,aAAO;AAAA,IACR,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAM,KAAK,UAAoD;AAC9D,QAAI;AACJ,QAAI;AACH,YAAM,MAAU,cAAS,UAAU,MAAM;AAAA,IAC1C,SAAS,KAAK;AACb,UAAIC,aAAY,GAAG,KAAK,IAAI,SAAS,SAAU,QAAO;AACtD,YAAM;AAAA,IACP;AACA,QAAI;AACH,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,aAAO,uBAAuB,MAAM;AAAA,IACrC,QAAQ;AAOP,YAAM,KAAK,oBAAoB,QAAQ;AACvC,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAc,oBAAoB,UAAiC;AAClE,QAAI;AACH,YAAM,aAAa,GAAG,QAAQ,cAAc,KAAK,IAAI,CAAC;AACtD,YAAU,cAAS,UAAU,UAAU;AAAA,IACxC,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EAEA,MAAM,MAAM,UAAkB,SAA0C;AACvE,UAAU,WAAW,eAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,UAAM,UAAU,GAAG,QAAQ,GAAGD,WAAU;AACxC,UAAM,QAAQ,OAAO,KAAK,KAAK,UAAU,SAAS,MAAM,GAAI,GAAG,MAAM;AACrE,UAAU,QAAG,SAAS,EAAE,OAAO,KAAK,CAAC;AACrC,UAAM,SAAS,MAAU,UAAK,SAAS,KAAKJ,UAAS;AACrD,QAAI;AACH,YAAM,OAAO,UAAU,KAAK;AAC5B,YAAM,OAAO,KAAK;AAAA,IACnB,UAAE;AACD,YAAM,OAAO,MAAM;AAAA,IACpB;AACA,UAAU,YAAO,SAAS,QAAQ;AAClC,UAAU,WAAM,UAAUA,UAAS,EAAE,MAAM,MAAM,MAAS;AAAA,EAC3D;AACD;AAEA,SAAS,uBAAuB,QAAmC;AAClE,MAAI,OAAO,WAAW,YAAY,WAAW,MAAM;AAClD,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC5D;AACA,QAAM,MAAM;AACZ,QAAM,UAAU,IAAI;AACpB,MAAI,YAAY,6BAA6B;AAC5C,UAAM,IAAI,MAAM,4CAA4C,OAAO,OAAO,CAAC,EAAE;AAAA,EAC9E;AACA,MAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,GAAG;AAC9B,UAAM,IAAI,MAAM,wCAAwC;AAAA,EACzD;AACA,SAAO,EAAE,SAAS,OAAO,IAAI,MAAwB;AACtD;AAEA,SAASK,aAAY,OAAgD;AACpE,SAAO,iBAAiB,SAAS,OAAQ,MAA6B,SAAS;AAChF;AAEA,IAAM,kBAAN,MAAsB;AAAA,EAMrB,YAAY,UAAkB,WAAmB,SAAiB;AAFlE,SAAQ,WAAW;AAGlB,SAAK,UAAU,GAAG,QAAQ;AAC1B,SAAK,YAAY;AACjB,SAAK,UAAU;AAAA,EAChB;AAAA,EAEA,MAAM,UAAyB;AAC9B,UAAM,QAAQ,KAAK,IAAI;AACvB,WAAO,MAAM;AACZ,UAAI;AACH,cAAU,WAAM,KAAK,SAAS,EAAE,MAAMJ,eAAc,CAAC;AACrD,aAAK,WAAW;AAChB;AAAA,MACD,SAAS,KAAK;AACb,YAAI,CAACI,aAAY,GAAG,KAAK,IAAI,SAAS,SAAU,OAAM;AACtD,YAAI,KAAK,IAAI,IAAI,SAAS,KAAK,WAAW;AACzC,gBAAM,IAAI,MAAM,+CAA+C,KAAK,OAAO,EAAE;AAAA,QAC9E;AACA,cAAMC,OAAM,KAAK,OAAO;AAAA,MACzB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,UAAyB;AAC9B,QAAI,CAAC,KAAK,SAAU;AACpB,SAAK,WAAW;AAChB,UAAU,QAAG,KAAK,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC5D;AACD;AAEA,SAAS,uBAAsC;AAG9C,MAAI,QAAQ,IAAI,mCAAmC,IAAK,QAAO;AAC/D,SAAY,YAAK,QAAQ,IAAI,GAAG,+BAA+B;AAChE;AAEO,IAAM,eAAN,MAAmB;AAAA,EASzB,YAAY,OAA4B,CAAC,GAAG;AAC3C,SAAK,eAAe,KAAK,gBAAgB,mBAAmB;AAC5D,SAAK,uBAAuB,KAAK,wBAAwB;AACzD,SAAK,WAAW,KAAK,gBAAgB;AACrC,SAAK,QAAQ,KAAK,SAAS,CAAC;AAC5B,SAAK,UAAU,KAAK,WAAW,IAAI,qBAAqB;AACxD,SAAK,gBAAgB,KAAK,iBAAiBJ;AAC3C,SAAK,cAAc,KAAK,eAAeC;AAAA,EACxC;AAAA;AAAA,EAGA,MAAM,KAAK,OAA+C;AACzD,UAAM,WAAW,KAAK,YAAY,KAAK;AACvC,QAAI,UAAU,aAAa;AAC1B,YAAM,kCAAkC,QAAQ;AAAA,IACjD;AACA,UAAM,SAAS,WAAW,MAAM,KAAK,QAAQ,KAAK,QAAQ,IAAI;AAC9D,UAAM,cAAc,QAAQ,SAAS,CAAC;AACtC,UAAM,WAAW,KAAK,SAAS,IAAI,CAAC,MAAM,UAAU,cAAc,MAAM,KAAK,CAAC;AAC9E,UAAM,SAAS,kBAAkB,UAAU,WAAW;AACtD,UAAM,WAAW,YAAY,OAAO,CAAC,MAAM,CAAC,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;AACpF,SAAK;AACL,WAAO,EAAE,OAAO,OAAO,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,OAAqB,OAA+C;AAC/E,UAAM,WAAW,KAAK,YAAY,KAAK;AACvC,QAAI,CAAC,UAAU;AACd,YAAM,IAAI,MAAM,+BAA+B,KAAK,6BAA6B;AAAA,IAClF;AACA,QAAI,UAAU,aAAa;AAC1B,YAAM,kCAAkC,QAAQ;AAAA,IACjD;AACA,UAAM,UAA4B;AAAA,MACjC,SAAS;AAAA,MACT,OAAO,gBAAgB,CAAC,GAAG,KAAK,CAAC;AAAA,IAClC;AACA,UAAM,KAAK,MAAM,cAAc,OAAO,OAAO;AAC7C,UAAM,OAAO,IAAI,gBAAgB,UAAU,KAAK,eAAe,KAAK,WAAW;AAC/E,UAAM,KAAK,QAAQ;AACnB,QAAI;AACH,YAAM,KAAK,QAAQ,MAAM,UAAU,OAAO;AAAA,IAC3C,UAAE;AACD,YAAM,KAAK,QAAQ;AAAA,IACpB;AACA,UAAM,KAAK,MAAM,aAAa,OAAO,OAAO;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OACL,OACA,SAC0B;AAC1B,UAAM,WAAW,KAAK,YAAY,KAAK;AACvC,QAAI,CAAC,UAAU;AACd,YAAM,IAAI,MAAM,gCAAgC,KAAK,6BAA6B;AAAA,IACnF;AACA,QAAI,UAAU,aAAa;AAC1B,YAAM,kCAAkC,QAAQ;AAAA,IACjD;AACA,UAAM,OAAO,IAAI,gBAAgB,UAAU,KAAK,eAAe,KAAK,WAAW;AAC/E,UAAM,KAAK,QAAQ;AACnB,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,QAAQ;AAC/C,YAAM,cAAc,QAAQ,SAAS,CAAC;AACtC,YAAM,aAAa,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACzD,YAAM,YAAY,YAAY,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;AACjE,YAAM,OAAO,MAAM,QAAQ,SAAS;AACpC,YAAM,UAA4B;AAAA,QACjC,SAAS;AAAA,QACT,OAAO,gBAAgB,CAAC,GAAG,IAAI,CAAC;AAAA,MACjC;AACA,YAAM,KAAK,MAAM,cAAc,OAAO,OAAO;AAC7C,YAAM,KAAK,QAAQ,MAAM,UAAU,OAAO;AAC1C,YAAM,KAAK,MAAM,aAAa,OAAO,OAAO;AAC5C,aAAO;AAAA,IACR,UAAE;AACD,YAAM,KAAK,QAAQ;AAAA,IACpB;AAAA,EACD;AAAA;AAAA,EAGA,MAAM,MAAM,OAAoC;AAC/C,UAAM,WAAW,KAAK,YAAY,KAAK;AACvC,QAAI,CAAC,SAAU;AACf,UAAU,QAAG,UAAU,EAAE,OAAO,KAAK,CAAC;AAAA,EACvC;AAAA;AAAA,EAGA,kBAA0B;AACzB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,uBAAsC;AACrC,WAAO,KAAK,qBAAqB;AAAA,EAClC;AAAA,EAEQ,YAAY,OAAoC;AACvD,WAAO,UAAU,SAAS,KAAK,eAAe,KAAK,qBAAqB;AAAA,EACzE;AACD;AAEA,SAAS,cAAc,MAAuB,OAA6B;AAC1E,SAAO;AAAA,IACN,IAAI,KAAK;AAAA,IACT,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,MAAM,KAAK;AAAA,IACX,KAAK,KAAK;AAAA,IACV,WAAW;AAAA,IACX;AAAA,IACA,OAAO;AAAA,EACR;AACD;;;AExTO,IAAM,4BAAN,cAAwC,MAAM;AAAA,EACpD,YAA4B,QAAgB;AAC3C,UAAM,wCAAwC,MAAM,EAAE;AAD3B;AAE3B,SAAK,OAAO;AAAA,EACb;AACD;AASO,IAAM,cAAN,MAAkB;AAAA,EAKxB,YAAY,OAA2B,CAAC,GAAG;AAC1C,SAAK,QAAQ,KAAK,SAAS,IAAI,aAAa;AAC5C,SAAK,WAAW,KAAK,gBAAgB;AACrC,SAAK,WAAW,KAAK,YAAY;AAAA,EAClC;AAAA;AAAA,EAGA,MAAM,KAAK,QAAsB,QAA8B;AAC9D,UAAM,WAAW,MAAM,KAAK,MAAM,KAAK,KAAK;AAC5C,WAAO;AAAA,MACN;AAAA,MACA,OAAO,KAAK,SAAS,SAAS,KAAK;AAAA,IACpC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,IAAI,MAAoB,QAAsB,QAA8B;AACjF,QAAI,KAAK,YAAY,KAAK,EAAE,GAAG;AAC9B,YAAM,IAAI,0BAA0B,KAAK,EAAE;AAAA,IAC5C;AACA,UAAM,OAAO,MAAM,KAAK,MAAM,OAAO,OAAO,OAAO,YAAY;AAC9D,YAAM,WAAW,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,EAAE;AACvD,aAAO;AAAA,QACN,GAAG;AAAA,QACH;AAAA,UACC,GAAG;AAAA,UACH;AAAA,UACA,WAAW;AAAA,UACX,OAAO,KAAK,SAAS,SAAS;AAAA,QAC/B;AAAA,MACD;AAAA,IACD,CAAC;AACD,WAAO,EAAE,OAAO,OAAO,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OACL,IACA,QAAsB,QAC+C;AACrE,QAAI,KAAK,YAAY,EAAE,GAAG;AACzB,YAAM,IAAI,0BAA0B,EAAE;AAAA,IACvC;AACA,UAAM,UAAU,MAAM,KAAK,MAAM;AAAA,MAAO;AAAA,MAAO,OAAO,YACrD,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,IAClC;AACA,WAAO,EAAE,OAAO,QAAQ,IAAI,SAAS,CAAC,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OACL,IACA,OACA,QAAsB,QACC;AACvB,UAAM,YAAY,KAAK,YAAY,EAAE;AACrC,UAAM,OAAO,MAAM,KAAK,MAAM,OAAO,OAAO,OAAO,YAAY;AAC9D,UAAI,WAAW;AAKd,eAAO;AAAA,MACR;AACA,aAAO,QAAQ;AAAA,QAAI,CAAC,SACnB,KAAK,OAAO,KAAK,EAAE,GAAG,MAAM,GAAG,OAAO,OAAO,WAAW,MAAM,IAAI;AAAA,MACnE;AAAA,IACD,CAAC;AACD,WAAO,EAAE,OAAO,OAAO,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YACL,IACA,QAAsB,QACtB,OAAa,oBAAI,KAAK,GACS;AAC/B,QAAI,KAAK,YAAY,EAAE,GAAG;AAGzB,YAAM,OAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAClD,UAAI,CAAC,KAAM,QAAO;AAClB,aAAO,EAAE,GAAG,MAAM,OAAO,WAAW,MAAM,OAAO,GAAG,YAAY,KAAK,YAAY,EAAE;AAAA,IACpF;AACA,QAAI,UAA+B;AACnC,UAAM,KAAK,MAAM,OAAO,OAAO,OAAO,YAAY;AACjD,YAAM,OAAO,gBAAgB,SAAS,IAAI,IAAI;AAC9C,gBAAU,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK;AAC3C,aAAO;AAAA,IACR,CAAC;AACD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAmC;AACxC,UAAM,CAAC,MAAM,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC3C,KAAK,MAAM,KAAK,MAAM;AAAA,MACtB,KAAK,MAAM,KAAK,WAAW;AAAA,IAC5B,CAAC;AACD,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,SAAyB,CAAC;AAChC,eAAW,QAAQ,UAAU,OAAO;AACnC,WAAK,IAAI,KAAK,EAAE;AAChB,aAAO,KAAK,EAAE,GAAG,MAAM,OAAO,YAAY,CAAC;AAAA,IAC5C;AACA,eAAW,QAAQ,KAAK,OAAO;AAC9B,UAAI,KAAK,IAAI,KAAK,EAAE,EAAG;AACvB,aAAO,KAAK,EAAE,GAAG,MAAM,OAAO,OAAO,CAAC;AAAA,IACvC;AACA,WAAO,EAAE,OAAO,QAAQ,OAAO,kBAAkB,MAAM,EAAE;AAAA,EAC1D;AAAA;AAAA,EAGA,WAAyB;AACxB,WAAO,KAAK;AAAA,EACb;AAAA,EAEQ,YAAY,IAAqB;AACxC,WAAO,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,EAC7C;AACD;","names":["resolve","createServicemeError","resolve","createServicemeError","COPILOT_ERROR_CODES","createServicemeError","COPILOT_COMMAND","createServicemeError","COPILOT_ERROR_CODES","createHash","platform","os","path","os","path","platform","fs","path","fs","path","fs","path","stat","createServicemeError","createServicemeError","isTimeout","spawn","path","resolve","spawn","fs","path","createServicemeError","createServicemeError","createServicemeError","randomUUID","fs","path","randomUUID","fs","path","createServicemeError","access","mkdir","readdir","rename","rm","dirname","join","resolve","createServicemeError","fs","path","fs","path","fs","unlink","EventEmitter","fs","EventEmitter","fs","path","fs","path","CONFIG_DIR","stat","fs","os","path","spawn","fs","DEFAULT_TIMEOUT_MS","resolve","spawn","DEFAULT_TIMEOUT_MS","spawn","fs","path","MAX_OUTPUT_BYTES","DEFAULT_TIMEOUT_MS","platform","writeDiagnostic","resolve","spawn","randomUUID","fs","os","path","createServicemeError","isRecord","createServicemeError","randomUUID","getExecutor","randomUUID","fs","path","randomUUID","fs","os","path","TICK_INTERVAL","MIN_SCHEDULE_INTERVAL","SCHEDULER_LOG_FILENAME","parseIntervalMs","matchesCron","matchCronField","fs","path","isScheduledTasksConfigV1","migrateV1ToV2","isScheduledTasksConfigV1","migrateV1ToV2","spawn","fs","path","DEFAULT_TIMEOUT_MS","resolve","createServicemeError","fs","path","SAFE_LOCAL_ID_PATTERN","fs","path","fsp","path","delay","FILE_MODE","LOCK_DIR_MODE","DEFAULT_LOCK_TIMEOUT_MS","DEFAULT_LOCK_RETRY_MS","TMP_SUFFIX","isNodeError","delay"]}