@openspecui/server 7.0.1 → 9.0.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.
@@ -2,9 +2,9 @@ import m, { createRequire } from "node:module";
2
2
  import a from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import se, { writeSync } from "node:fs";
5
+ import Ht from "node:crypto";
5
6
  import o from "node:os";
6
7
  import p from "node:net";
7
- import Ht from "node:crypto";
8
8
  import { inspect } from "node:util";
9
9
  import { MessageChannel } from "node:worker_threads";
10
10
  import { createRequire as createRequire$1 } from "module";
package/dist/index.mjs CHANGED
@@ -1,12 +1,13 @@
1
1
  import { a as resolveCt2ModelDownloadPlanFromRepositoryFiles } from "./src-Dh_UAz5C.mjs";
2
2
  import { t as resolveGgufModelDownloadPlanFromRepositoryFiles } from "./src-CZfiVL_-.mjs";
3
- import { AI_TOOLS, ActiveRootMutationSchema, ActiveRootRevisionSchema, AgentDeliveryPolicyUpdateSchema, BatchTranslateInputSchema, CliExecutor, CliProjectionCommandError, CliProjectionNoticeSchema, CodeEditorThemeSchema, ConfigManager, CustomSoundHashSchema, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, DashboardGitSnapshotSchema, DashboardSummaryInvalidationSchema, DashboardSummaryProjectionStateSchema, DashboardTrendsProjectionSchema, DocumentTranslationConfigUpdateSchema, EnvironmentDefaultStoreUpdateSchema, EnvironmentGlobalConfigValueSchema, EnvironmentGlobalFileProjectionDataSchema, EnvironmentGlobalFileProjectionStateSchema, EnvironmentGlobalProjectionDataSchema, EnvironmentGlobalProjectionStateSchema, GitConfigSchema, GlobalSettingsManager, LocalModelAssetStateSchema, LocalModelLifecycleFileStateSchema, LocalModelLifecycleGroupStateSchema, LocalModelProfileManifestSchema, MarkdownParser, NotificationPublishInputSchema, NotificationSettingsSchema, OPENSPECUI_DOCUMENT_HOOK_VERSION, OPENSPECUI_WORKFLOW_HOOK_VERSION, OPSX_ARTIFACT_INPUT_ACTIONS, OPSX_CHANGE_INPUT_ACTIONS, OPSX_COMMAND_CAPABLE_WORKFLOWS, OPSX_CORE_PROFILE_WORKFLOWS, OPSX_TEXT_INPUT_ACTIONS, OpenSpecAdapter, OpenSpecDataHomeObserver, OpenSpecUIGlobalSettingsUpdateSchema, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, OwnedSpecIdentitySchema, PINNED_AGENT_GENERATOR_VERSION, PlanningCliProjectionDataSchema, PlanningCliProjectionSelectorSchema, PlanningCliProjectionStateSchema, ProjectBindingUpdateSchema, ProjectWatcher, PtyClientMessageSchema, RUNTIME_INVALIDATION_FACETS, ReactiveContext, ReactiveObservationEnvironment, ReactiveState, RuntimeInvalidationIndex, RuntimeRootInvalidationRegistry, ServiceTranslationEngineIdSchema, SpecIdentitySchema, StoreDoctorResultSchema, StoreListResultSchema, StoreMutationLifecycleEventSchema, StoreMutationStartResponseSchema, TRANSLATION_ENGINE_MANIFESTS, TerminalConfigSchema, TerminalControlParser, TerminalRendererEngineSchema, TranslationCacheReadInputSchema, TranslationCacheWriteInputSchema, TranslationEngineIdSchema, TranslationEngineLifecycleStatusSchema, acquireWatcher, applyAgentDeliveryPolicy, buildBackendHealthPayload, buildLocalDownloadPlanFromRepositoryFiles, buildRuntimePackageInstallCommand, checkLocalDirectionalModelLanguagePair, classifyStoreCliResult, clearCache, compareAndWriteAtomicPhysicalReactiveFile, computeEnvUri, constantTimeEqual, createCleanCliEnv, createPhysicalReactiveDirectory, createToolInitStateProjection, createTranslationEngineLifecycleStatus, detectRuntimePackageManager, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getExternalCodexCommandObservationRoot, getManagedLocalTranslationEngineManifest, getOpsxEntityRootRelativePath, getRootContextCliSelector, getToolInitStates, getTranslationEngineLifecycleMessage, getWatcherRuntimeStatus, inferFileMime, inferFilePreviewKind, inspectActiveRootOfficialConfig, inspectEnvironmentDefaultStore, inspectProjectBinding, isDirectionalManagedLocalTranslationEngineId, isManagedLocalTranslationEngineId, isTerminalMutationStatus, isTranslationEngineDependencyReady, isTranslationEngineRuntimeReady, loadOpenSpecAgentCommandContents, normalizeAgentDeliveryPolicy, normalizeBatchTranslationError, parseCliCommandResult, parseOpsxEntityMetadata, parseOpsxSchemaDetail, patchActiveRootOfficialFields, reactiveReadFile, reactiveStat, removePhysicalReactivePath, requireCanonicalOpenSpecEntityId, requireCanonicalOpenSpecSpecId, requireOpenSpecEntityRelativePath, resolveOpenSpecDataScope, resolveRootContext, resolveRuntimePackageInstallStrategy, resolveTerminalShellDefaults, runPhysicalReactivePathMutation, selectLocalDownloadGroup, shouldShowTranslationEngineInstallGate, sniffGlobalCli, subscribeWatcherRuntimeStatus, terminalNotificationEventToPublishInput, toCliProjectionCommandEvidence, toCliProjectionFailure, toCliProjectionNotice, toStoreFeatureResult, toggleMarkdownTask, updateProjectBindingContent, updateReactiveFileCache, validateActiveRootRawYaml, writePhysicalReactiveFile } from "@openspecui/core";
3
+ import { ActiveRootMutationSchema, ActiveRootRevisionSchema, AgentDeliveryPolicyUpdateSchema, BatchTranslateInputSchema, CliExecutor, CliProjectionCommandError, CliProjectionNoticeSchema, CodeEditorThemeSchema, ConfigManager, CustomSoundHashSchema, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, DashboardGitSnapshotSchema, DashboardSummaryInvalidationSchema, DashboardSummaryProjectionStateSchema, DashboardTrendsProjectionSchema, DocumentTranslationConfigUpdateSchema, EnvironmentDefaultStoreUpdateSchema, EnvironmentGlobalConfigValueSchema, EnvironmentGlobalFileProjectionDataSchema, EnvironmentGlobalFileProjectionStateSchema, EnvironmentGlobalProjectionDataSchema, EnvironmentGlobalProjectionStateSchema, GitConfigSchema, GlobalSettingsManager, LocalModelAssetStateSchema, LocalModelLifecycleFileStateSchema, LocalModelLifecycleGroupStateSchema, LocalModelProfileManifestSchema, MarkdownParser, NotificationPublishInputSchema, NotificationSettingsSchema, OPENSPECUI_DOCUMENT_HOOK_VERSION, OPENSPECUI_WORKFLOW_HOOK_VERSION, OPSX_ARTIFACT_INPUT_ACTIONS, OPSX_CHANGE_INPUT_ACTIONS, OPSX_COMMAND_CAPABLE_WORKFLOWS, OPSX_CORE_PROFILE_WORKFLOWS, OPSX_TEXT_INPUT_ACTIONS, OpenSpecAdapter, OpenSpecDataHomeObserver, OpenSpecUIGlobalSettingsUpdateSchema, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, OwnedSpecIdentitySchema, PlanningCliProjectionDataSchema, PlanningCliProjectionSelectorSchema, PlanningCliProjectionStateSchema, ProjectBindingUpdateSchema, ProjectWatcher, PtyClientMessageSchema, RUNTIME_INVALIDATION_FACETS, ReactiveContext, ReactiveObservationEnvironment, ReactiveState, RuntimeInvalidationIndex, RuntimeRootInvalidationRegistry, ServiceTranslationEngineIdSchema, SpecIdentitySchema, StoreDoctorResultSchema, StoreListResultSchema, StoreMutationLifecycleEventSchema, StoreMutationStartResponseSchema, TRANSLATION_ENGINE_MANIFESTS, TerminalConfigSchema, TerminalControlParser, TerminalRendererEngineSchema, TranslationCacheReadInputSchema, TranslationCacheWriteInputSchema, TranslationEngineIdSchema, TranslationEngineLifecycleStatusSchema, acquireWatcher, applyAgentDeliveryPolicy, buildBackendHealthPayload, buildLocalDownloadPlanFromRepositoryFiles, buildRuntimePackageInstallCommand, checkLocalDirectionalModelLanguagePair, classifyStoreCliResult, clearCache, compareAndWriteAtomicPhysicalReactiveFile, computeEnvUri, constantTimeEqual, createCleanCliEnv, createPhysicalReactiveDirectory, createToolInitStateProjection, createTranslationEngineLifecycleStatus, detectRuntimePackageManager, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getExternalAgentSkillsObservationRoots, getExternalCodexCommandObservationRoot, getManagedLocalTranslationEngineManifest, getOpsxEntityRootRelativePath, getRootContextCliSelector, getToolInitStates, getTranslationEngineLifecycleMessage, getWatcherRuntimeStatus, inferFileMime, inferFilePreviewKind, inspectActiveRootOfficialConfig, inspectEnvironmentDefaultStore, inspectProjectBinding, isDirectionalManagedLocalTranslationEngineId, isManagedLocalTranslationEngineId, isTerminalMutationStatus, isTranslationEngineDependencyReady, isTranslationEngineRuntimeReady, loadOpenSpecAgentCommandContents, normalizeAgentDeliveryPolicy, normalizeBatchTranslationError, parseCliCommandResult, parseOpsxEntityMetadata, parseOpsxSchemaDetail, patchActiveRootOfficialFields, reactiveReadFile, reactiveStat, removePhysicalReactivePath, replaceFileAtomically, requireCanonicalOpenSpecEntityId, requireCanonicalOpenSpecSpecId, requireOpenSpecEntityRelativePath, resolveCommandInvocation, resolveOpenSpecDataScope, resolveRootContext, resolveRuntimePackageInstallStrategy, resolveTerminalShellDefaults, runPhysicalReactivePathMutation, selectAgentDeliveryRegistry, selectLocalDownloadGroup, shouldShowTranslationEngineInstallGate, sniffGlobalCli, spawnSafe, subscribeWatcherRuntimeStatus, terminalNotificationEventToPublishInput, terminateChildProcessTree, toCliProjectionCommandEvidence, toCliProjectionFailure, toCliProjectionNotice, toStoreFeatureResult, toggleMarkdownTask, updateProjectBindingContent, updateReactiveFileCache, validateActiveRootRawYaml, writePhysicalReactiveFile } from "@openspecui/core";
4
4
  import { basename, dirname, extname, isAbsolute, join, matchesGlob, relative, resolve, sep } from "node:path";
5
- import { access, copyFile, lstat, mkdir, open, readFile, readlink, realpath, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
5
+ import { access, copyFile, link, lstat, mkdir, open, readFile, readlink, realpath, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
7
  import { downloadFile, fileDownloadInfo, listFiles, modelInfo } from "@huggingface/hub";
8
8
  import { observable } from "@trpc/server/observable";
9
9
  import { existsSync, readFileSync, realpathSync, statSync, write, writeFileSync } from "node:fs";
10
+ import { createHash, randomUUID } from "node:crypto";
10
11
  import { freemem, homedir, hostname, platform, totalmem } from "node:os";
11
12
  import { z } from "zod";
12
13
  import { Agent, EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
@@ -20,7 +21,6 @@ import { cors } from "hono/cors";
20
21
  import { WebSocketServer } from "ws";
21
22
  import { Buffer as Buffer$1 } from "node:buffer";
22
23
  import { CustomSoundHashSchema as CustomSoundHashSchema$1, CustomSoundIdSchema, CustomSoundMetadataFileSchema, customHashFromSoundId, soundIdFromCustomHash } from "@openspecui/core/sounds";
23
- import { createHash, randomUUID } from "node:crypto";
24
24
  import { execFile, spawn } from "node:child_process";
25
25
  import { promisify } from "node:util";
26
26
  import { NotificationGroupKeySchema, NotificationPublishInputSchema as NotificationPublishInputSchema$1, getNotificationGroupKey } from "@openspecui/core/notifications";
@@ -33,6 +33,7 @@ import { NodeWorkerSearchProvider } from "@openspecui/search/node";
33
33
  import * as pty from "@lydell/node-pty";
34
34
  import { EventEmitter as EventEmitter$1 } from "events";
35
35
  import { HostedCliProjectionNoticeSchema, HostedRootContextProjectionStateSchema, HostedRootContextResolvedStateSchema, HostedStoreContentChangesProjectionStateSchema, HostedStoreContentSpecsProjectionStateSchema, HostedStoreDoctorEnvelopeSchema, HostedStoreDoctorProjectionStateSchema, HostedStoreListEnvelopeSchema, HostedStoreListProjectionStateSchema } from "@openspecui/core/hosted-contract";
36
+ import { deriveOpenSpecCliCapabilities, parseOpenSpecCliVersion } from "@openspecui/core/openspec-compat";
36
37
  import { StoreContentChangeListSchema, StoreContentSpecListSchema } from "@openspecui/core/store-content-projection";
37
38
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
38
39
  import { NodeSDK } from "@opentelemetry/sdk-node";
@@ -502,7 +503,7 @@ var ProjectHookRuntime = class {
502
503
  }
503
504
  async loadFresh() {
504
505
  if (!await pathExists$1(this.hooksPath)) return {};
505
- const { tsImport } = await import("./api-B4zHqR-f.mjs");
506
+ const { tsImport } = await import("./api-C_H23KUr.mjs");
506
507
  return normalizeHooksModule(await tsImport(`${pathToFileURL(this.hooksPath).href}?t=${Date.now()}`, { parentURL: pathToFileURL(this.hooksPath).href }));
507
508
  }
508
509
  };
@@ -524,6 +525,14 @@ function buildHuggingFaceApiBaseUrl(endpoint) {
524
525
 
525
526
  //#endregion
526
527
  //#region src/local-model-asset-store.ts
528
+ /**
529
+ * Orthogonal intents (updated 2026-08-05 Asia/Shanghai):
530
+ * 1. Persist normalized local-model lifecycle state through complete JSON replacements.
531
+ * 2. Survive transient Windows target locks without exposing partial metadata.
532
+ * 3. Retire reactive caches only after the replacement commits.
533
+ *
534
+ * Original request (2026-08-05): Continue the Windows adaptation and fix equivalent failures together.
535
+ */
527
536
  const LocalModelAssetIndexSchema = LocalModelAssetStateSchema.array();
528
537
  var LocalModelAssetStore = class {
529
538
  constructor(options) {
@@ -549,9 +558,13 @@ var LocalModelAssetStore = class {
549
558
  const normalized = LocalModelAssetIndexSchema.parse([...states].sort((left, right) => left.modelId.localeCompare(right.modelId)));
550
559
  const serialized = JSON.stringify(normalized, null, 2);
551
560
  await mkdir(dirname(this.options.indexPath), { recursive: true });
552
- const tempPath = `${this.options.indexPath}.${process.pid}.${Date.now()}.tmp`;
553
- await writeFile(tempPath, `${serialized}\n`, "utf8");
554
- await rename(tempPath, this.options.indexPath);
561
+ const tempPath = `${this.options.indexPath}.${process.pid}.${randomUUID()}.tmp`;
562
+ try {
563
+ await writeFile(tempPath, `${serialized}\n`, "utf8");
564
+ await replaceFileAtomically(tempPath, this.options.indexPath);
565
+ } finally {
566
+ await rm(tempPath, { force: true }).catch(() => void 0);
567
+ }
555
568
  clearCache();
556
569
  }
557
570
  async upsert(state) {
@@ -759,6 +772,14 @@ function getTransformersFileCacheModelPath(cacheDir, modelId) {
759
772
 
760
773
  //#endregion
761
774
  //#region src/local-model-profile-manifest-store.ts
775
+ /**
776
+ * Orthogonal intents (updated 2026-08-05 Asia/Shanghai):
777
+ * 1. Persist normalized local-model profile manifests through complete JSON replacements.
778
+ * 2. Survive transient Windows target locks without exposing partial metadata.
779
+ * 3. Retire reactive caches only after the replacement commits.
780
+ *
781
+ * Original request (2026-08-05): Continue the Windows adaptation and fix equivalent failures together.
782
+ */
762
783
  const LocalModelProfileManifestIndexSchema = z.object({
763
784
  version: z.literal(1).default(1),
764
785
  manifests: z.array(LocalModelProfileManifestSchema).default([])
@@ -786,9 +807,13 @@ var LocalModelProfileManifestStore = class {
786
807
  });
787
808
  const serialized = JSON.stringify(normalized, null, 2);
788
809
  await mkdir(dirname(this.options.manifestPath), { recursive: true });
789
- const tempPath = `${this.options.manifestPath}.${process.pid}.${Date.now()}.tmp`;
790
- await writeFile(tempPath, `${serialized}\n`, "utf8");
791
- await rename(tempPath, this.options.manifestPath);
810
+ const tempPath = `${this.options.manifestPath}.${process.pid}.${randomUUID()}.tmp`;
811
+ try {
812
+ await writeFile(tempPath, `${serialized}\n`, "utf8");
813
+ await replaceFileAtomically(tempPath, this.options.manifestPath);
814
+ } finally {
815
+ await rm(tempPath, { force: true }).catch(() => void 0);
816
+ }
792
817
  clearCache();
793
818
  }
794
819
  async upsert(manifest) {
@@ -1390,6 +1415,16 @@ function isRecord$2(value) {
1390
1415
 
1391
1416
  //#endregion
1392
1417
  //#region src/local-model-asset-service.ts
1418
+ /**
1419
+ * Orthogonal intents (updated 2026-08-09 Asia/Shanghai):
1420
+ * 1. Resolve local translation model catalogs and versioned profile manifests.
1421
+ * 2. Own resumable download, pause, cancellation, retry, progress, and bounded Windows finalization.
1422
+ * 3. Materialize downloaded profiles and platform-appropriate Hugging Face cache pointers.
1423
+ * 4. Preserve selected-group authority while background work settles.
1424
+ * 5. Publish typed lifecycle snapshots and bounded diagnostics.
1425
+ *
1426
+ * Original request (2026-08-04): "Adapt macOS-first development and runtime paths to Windows."
1427
+ */
1393
1428
  const DEFAULT_NETWORK_RETRY_LIMIT$2 = Number.POSITIVE_INFINITY;
1394
1429
  const DEFAULT_NETWORK_RETRY_DELAY_MS$2 = 500;
1395
1430
  const DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$2 = 5e3;
@@ -2742,10 +2777,10 @@ async function streamDownloadToIncompleteFile(input) {
2742
2777
  }
2743
2778
  return true;
2744
2779
  }
2780
+ /** Commit one complete model file without deleting the previous target before replacement succeeds. */
2745
2781
  async function finalizeDownloadedFile$1(input) {
2746
2782
  await mkdir(dirname(input.targetPath), { recursive: true });
2747
- await rm(input.targetPath, { force: true });
2748
- await rename(input.incompletePath, input.targetPath);
2783
+ await replaceFileAtomically(input.incompletePath, input.targetPath);
2749
2784
  }
2750
2785
  async function mirrorDownloadedFileToHubCache(input) {
2751
2786
  await mkdir(dirname(input.cachePaths.blobPath), { recursive: true });
@@ -2753,7 +2788,8 @@ async function mirrorDownloadedFileToHubCache(input) {
2753
2788
  await rm(input.cachePaths.blobPath, { force: true });
2754
2789
  await copyFile(input.targetPath, input.cachePaths.blobPath);
2755
2790
  await unlink(input.cachePaths.pointerPath).catch(() => void 0);
2756
- await symlink(input.cachePaths.blobPath, input.cachePaths.pointerPath);
2791
+ if (process.platform === "win32") await link(input.cachePaths.blobPath, input.cachePaths.pointerPath);
2792
+ else await symlink(input.cachePaths.blobPath, input.cachePaths.pointerPath);
2757
2793
  }
2758
2794
  function getHubCacheFilePaths(input) {
2759
2795
  const repoPath = getHubCacheRepoPath(input.cacheDir, input.modelId);
@@ -2948,13 +2984,15 @@ function resolveDefaultServerHostIdentity() {
2948
2984
  //#endregion
2949
2985
  //#region src/agent-delivery-projection-service.ts
2950
2986
  /**
2951
- * Orthogonal intents (created 2026-08-01 Asia/Shanghai):
2952
- * 1. Combine the complete OpenSpec 1.7 Agent registry with authoritative Environment delivery policy.
2987
+ * Orthogonal intents (updated 2026-08-15 Asia/Shanghai):
2988
+ * 1. Combine the complete OpenSpec 1.9 Agent registry with authoritative Environment delivery policy.
2953
2989
  * 2. Expose fresh one-shot physical projections through Core `getToolInitStates`.
2954
2990
  * 3. Retain reactive physical projections through Core `createToolInitStateProjection`.
2955
2991
  * 4. Rebind retained work when Environment policy changes and retire all work on dispose.
2992
+ * 5. Observe user-global skill roots (MiniMax Code) beside the project root and Codex prompt root.
2956
2993
  *
2957
2994
  * Original request (2026-08-01): "新增 Agent delivery projection service 及 checked tests。"
2995
+ * Original request (2026-08-15): "v9的适配需要同时适配 1.8和1.9。"
2958
2996
  */
2959
2997
  /** Raised when the CLI-owned Environment projection cannot provide an executable Agent policy. */
2960
2998
  var AgentDeliveryPolicyUnavailableError = class extends Error {
@@ -2999,8 +3037,8 @@ function cloneCleanup(cleanup) {
2999
3037
  replacementLabel: cleanup.replacementLabel
3000
3038
  };
3001
3039
  }
3002
- function cloneRegistry() {
3003
- return AI_TOOLS.map((tool) => ({
3040
+ function cloneRegistry(registry) {
3041
+ return registry.map((tool) => ({
3004
3042
  name: tool.name,
3005
3043
  value: tool.value,
3006
3044
  available: tool.available,
@@ -3011,6 +3049,9 @@ function cloneRegistry() {
3011
3049
  ...tool.detectionPaths ? { detectionPaths: [...tool.detectionPaths] } : {},
3012
3050
  ...tool.setupNote !== void 0 ? { setupNote: tool.setupNote } : {},
3013
3051
  ...tool.aliases ? { aliases: [...tool.aliases] } : {},
3052
+ ...tool.legacySkillsDirs ? { legacySkillsDirs: [...tool.legacySkillsDirs] } : {},
3053
+ ...tool.globalSkillsDir !== void 0 && tool.globalSkillsDir !== null ? { globalSkillsDir: tool.globalSkillsDir } : {},
3054
+ ...tool.requiresIdeRestart !== void 0 ? { requiresIdeRestart: tool.requiresIdeRestart } : {},
3014
3055
  ...tool.cleanup ? { cleanup: cloneCleanup(tool.cleanup) } : {},
3015
3056
  ...tool.migrations ? { migrations: tool.migrations.map((migration) => ({ ...migration })) } : {}
3016
3057
  }));
@@ -3032,13 +3073,13 @@ function policyFingerprint(policy) {
3032
3073
  async function resolveGeneratorEvidence(cliExecutor, cliCommandAuthority, workflows) {
3033
3074
  const availability = await cliExecutor.checkAvailability();
3034
3075
  return {
3035
- version: availability.available && availability.version ? availability.version : PINNED_AGENT_GENERATOR_VERSION,
3076
+ version: availability.available && availability.version ? availability.version : null,
3036
3077
  commandContents: availability.available ? await loadOpenSpecAgentCommandContents(await cliCommandAuthority.getCliCommand(), workflows) : null
3037
3078
  };
3038
3079
  }
3039
- function createProjection(policy, states) {
3080
+ function createProjection(policy, states, generatorEvidence) {
3040
3081
  return {
3041
- registry: cloneRegistry(),
3082
+ registry: cloneRegistry(selectAgentDeliveryRegistry(generatorEvidence.version)),
3042
3083
  policy: {
3043
3084
  profile: policy.profile,
3044
3085
  delivery: policy.delivery,
@@ -3112,7 +3153,7 @@ var RetainedAgentDeliveryProjection = class {
3112
3153
  });
3113
3154
  }
3114
3155
  startPhysicalProjection(policy, generatorEvidence, force) {
3115
- const fingerprint = `${policyFingerprint(policy)}:${generatorEvidence.version}`;
3156
+ const fingerprint = `${policyFingerprint(policy)}:${generatorEvidence.version ?? "no-cli"}`;
3116
3157
  if (!force && this.physicalController && fingerprint === this.currentPolicyFingerprint) return;
3117
3158
  this.stopPhysicalProjection();
3118
3159
  this.currentPolicyFingerprint = fingerprint;
@@ -3122,8 +3163,10 @@ var RetainedAgentDeliveryProjection = class {
3122
3163
  const context = new ReactiveContext();
3123
3164
  const projectToolInitStates = createToolInitStateProjection(this.options.projectDir, {
3124
3165
  ...policy,
3125
- generatorVersion: generatorEvidence.version,
3126
- commandContents: generatorEvidence.commandContents
3166
+ generatorVersion: generatorEvidence.version ?? void 0,
3167
+ commandContents: generatorEvidence.commandContents?.catalog ?? null,
3168
+ unavailableCommandTools: generatorEvidence.commandContents?.unavailableTools ?? null,
3169
+ registry: selectAgentDeliveryRegistry(generatorEvidence.version)
3127
3170
  });
3128
3171
  (async () => {
3129
3172
  try {
@@ -3131,7 +3174,7 @@ var RetainedAgentDeliveryProjection = class {
3131
3174
  if (this.disposed || controller.signal.aborted || generation !== this.physicalGeneration) return;
3132
3175
  this.listener({
3133
3176
  type: "snapshot",
3134
- projection: createProjection(policy, states)
3177
+ projection: createProjection(policy, states, generatorEvidence)
3135
3178
  });
3136
3179
  }
3137
3180
  } catch (cause) {
@@ -3168,11 +3211,13 @@ var AgentDeliveryProjectionService = class {
3168
3211
  const generatorEvidence = await resolveGeneratorEvidence(this.options.cliExecutor, this.options.cliCommandAuthority, policy.workflows);
3169
3212
  const states = await getToolInitStates(this.options.projectDir, {
3170
3213
  ...policy,
3171
- generatorVersion: generatorEvidence.version,
3172
- commandContents: generatorEvidence.commandContents
3214
+ generatorVersion: generatorEvidence.version ?? void 0,
3215
+ commandContents: generatorEvidence.commandContents?.catalog ?? null,
3216
+ unavailableCommandTools: generatorEvidence.commandContents?.unavailableTools ?? null,
3217
+ registry: selectAgentDeliveryRegistry(generatorEvidence.version)
3173
3218
  });
3174
3219
  this.assertActive();
3175
- return createProjection(policy, states);
3220
+ return createProjection(policy, states, generatorEvidence);
3176
3221
  }
3177
3222
  /** Retain Environment policy and physical Agent artifact observation until unsubscribed. */
3178
3223
  subscribe(listener) {
@@ -3214,6 +3259,7 @@ var AgentDeliveryProjectionService = class {
3214
3259
  try {
3215
3260
  releases.push(await this.options.observationEnvironment.acquireRoot(this.options.projectDir));
3216
3261
  releases.push(await this.options.observationEnvironment.acquireRoot(getExternalCodexCommandObservationRoot()));
3262
+ for (const globalSkillsRoot of getExternalAgentSkillsObservationRoots()) releases.push(await this.options.observationEnvironment.acquireRoot(globalSkillsRoot));
3217
3263
  if (this.disposed) {
3218
3264
  await Promise.all(releases.map((release) => release()));
3219
3265
  return;
@@ -4081,13 +4127,26 @@ var ChangesProjectionService = class {
4081
4127
  load: async (context) => {
4082
4128
  context.reportStage("root-ready");
4083
4129
  const ids = await this.options.adapter.listChanges();
4130
+ const cliEntries = await this.options.adapter.readCliChangeListEntries?.() ?? /* @__PURE__ */ new Map();
4084
4131
  const rows = [];
4085
4132
  const errors = [];
4086
4133
  const total = ids.length;
4087
4134
  for (const [index, id] of ids.entries()) {
4088
4135
  if (context.signal.aborted) throw new DOMException("Change projection was cancelled.", "AbortError");
4089
4136
  try {
4090
- const row = await this.options.adapter.readChangeMeta(id);
4137
+ const meta = await this.options.adapter.readChangeMeta(id);
4138
+ const entry = cliEntries.get(id);
4139
+ const row = entry ? {
4140
+ ...meta,
4141
+ cliTaskSummary: {
4142
+ completedTasks: entry.completedTasks,
4143
+ totalTasks: entry.totalTasks,
4144
+ status: entry.status
4145
+ }
4146
+ } : {
4147
+ ...meta,
4148
+ cliTaskSummary: meta.cliTaskSummary ?? null
4149
+ };
4091
4150
  rows.push(row);
4092
4151
  context.emitBatch({
4093
4152
  rows: [row],
@@ -6504,7 +6563,10 @@ var EnvironmentGlobalProjectionService = class {
6504
6563
  * 2. Distinguish a valid non-repository path from an identity-command failure.
6505
6564
  * 3. Canonicalize repository paths for binding identity and stale-intent checks.
6506
6565
  * 4. Propagate cooperative cancellation into Git subprocess ownership.
6566
+ * 5. Hide the Git subprocess console window (`windowsHide`) so Git executed by a console-less
6567
+ * Windows daemon never flashes a cmd window.
6507
6568
  *
6569
+ * Original request (2026-08-14): "在Windows平台上,执行命令总是会弹出cmd窗口,这个可否统一隐藏,你先调查一下原因"
6508
6570
  * Original request (2026-07-19): "代码已经提交,开始review。如果有问题,那么可更新change。"
6509
6571
  * Derived requirement (2026-07-19): Checkpoint 6.11 preserves objective Planning Git failures.
6510
6572
  * Original request (2026-07-31): "Code Git Snapshot,它非常慢,有时候甚至要十几秒"
@@ -6521,7 +6583,8 @@ async function defaultRunGit(cwd, args, signal) {
6521
6583
  cwd,
6522
6584
  encoding: "utf8",
6523
6585
  maxBuffer: 8 * 1024 * 1024,
6524
- signal
6586
+ signal,
6587
+ windowsHide: true
6525
6588
  });
6526
6589
  return {
6527
6590
  ok: true,
@@ -9076,7 +9139,10 @@ async function buildDashboardGitSnapshot(options) {
9076
9139
  * 2. Preserve backend-issued Code binding provenance and observable Git task lifecycle.
9077
9140
  * 3. Trigger explicit Git snapshot invalidation through a reactive stamp without broad Dashboard reloads.
9078
9141
  * 4. Propagate Projection Work cancellation into the Git subprocess tree.
9142
+ * 5. Hide Git identity-probe subprocess console windows (`windowsHide`) under the console-less
9143
+ * Windows daemon.
9079
9144
  *
9145
+ * Original request (2026-08-14): "在Windows平台上,执行命令总是会弹出cmd窗口,这个可否统一隐藏,你先调查一下原因"
9080
9146
  * Original request (2026-07-23): "现在页面数据的加载数据非常慢(比如dashboard页面、changes页面都要等待非常久,页面刷新后,似乎后台没有缓存一样,也要加载很久。"
9081
9147
  * Derived requirement (2026-07-25): P4.3 settles a cached refresh stamp before Dashboard Git refresh returns.
9082
9148
  * Original request (2026-07-31): "Code Git Snapshot,它非常慢,有时候甚至要十几秒"
@@ -9127,7 +9193,8 @@ async function resolveGitMetadataDir(projectDir) {
9127
9193
  const { stdout } = await execFileAsync$1("git", ["rev-parse", "--git-dir"], {
9128
9194
  cwd: projectDir,
9129
9195
  maxBuffer: 1024 * 1024,
9130
- encoding: "utf8"
9196
+ encoding: "utf8",
9197
+ windowsHide: true
9131
9198
  });
9132
9199
  const gitDirRaw = stdout.trim();
9133
9200
  if (!gitDirRaw) return null;
@@ -9297,17 +9364,26 @@ function selectRecentDashboardArchives(items, limit = DASHBOARD_RECENT_LIST_LIMI
9297
9364
  //#region src/dashboard-summary.ts
9298
9365
  /** Read the objective files behind Dashboard Summary without invoking Git or workflow commands. */
9299
9366
  async function loadDashboardPlanningFacts(ctx) {
9300
- const [specMetas, changeMetas, archiveMetas] = await Promise.all([
9367
+ const [specMetas, changeMetas, archiveMetas, cliEntries] = await Promise.all([
9301
9368
  ctx.adapter.listSpecsWithMeta(),
9302
9369
  ctx.adapter.listChangesWithMeta(),
9303
- ctx.adapter.listArchivedChangesWithMeta()
9370
+ ctx.adapter.listArchivedChangesWithMeta(),
9371
+ ctx.readCliChangeListEntries?.().catch(() => /* @__PURE__ */ new Map()) ?? Promise.resolve(/* @__PURE__ */ new Map())
9304
9372
  ]);
9305
- const allActiveChanges = changeMetas.map((changeMeta) => ({
9306
- id: changeMeta.id,
9307
- name: changeMeta.name ?? changeMeta.id,
9308
- trackedTaskProgress: changeMeta.trackedTaskProgress,
9309
- updatedAt: changeMeta.updatedAt
9310
- }));
9373
+ const allActiveChanges = changeMetas.map((changeMeta) => {
9374
+ const entry = cliEntries.get(changeMeta.id);
9375
+ return {
9376
+ id: changeMeta.id,
9377
+ name: changeMeta.name ?? changeMeta.id,
9378
+ trackedTaskProgress: changeMeta.trackedTaskProgress,
9379
+ cliTaskSummary: entry ? {
9380
+ completedTasks: entry.completedTasks,
9381
+ totalTasks: entry.totalTasks,
9382
+ status: entry.status
9383
+ } : changeMeta.cliTaskSummary,
9384
+ updatedAt: changeMeta.updatedAt
9385
+ };
9386
+ });
9311
9387
  return {
9312
9388
  specMetas,
9313
9389
  archiveMetas,
@@ -9667,12 +9743,13 @@ function resolveEntityEntryPath(input) {
9667
9743
  //#endregion
9668
9744
  //#region src/file-preview-service.ts
9669
9745
  /**
9670
- * Orthogonal intents (updated 2026-07-16 Asia/Shanghai):
9746
+ * Orthogonal intents (updated 2026-08-04 Asia/Shanghai):
9671
9747
  * 1. Prepare source-bound preview sessions for supported entity files.
9672
- * 2. Serve preview documents and bundled assets with confined path resolution.
9748
+ * 2. Serve preview documents and bundled assets with platform-neutral confined path resolution.
9673
9749
  * 3. Retire every preview session when its owning Planning root is replaced.
9674
9750
  *
9675
9751
  * Original request (2026-07-16): "A -> B 后必须清退 A 的 preview。"
9752
+ * Original request (2026-08-04): "Make pnpm openspecui start and equivalent package scripts work on Windows."
9676
9753
  */
9677
9754
  const PREVIEW_ENTRY_FILE_BY_KIND = {
9678
9755
  image: "image-preview.html",
@@ -9696,6 +9773,10 @@ function toHash(input) {
9696
9773
  function stripLeadingSlash(path) {
9697
9774
  return path.replace(/^\/+/, "");
9698
9775
  }
9776
+ function isPathInsideRoot(rootPath, candidatePath) {
9777
+ const relativePath$1 = relative(rootPath, candidatePath);
9778
+ return relativePath$1 === "" || relativePath$1 !== ".." && !relativePath$1.startsWith(`..${sep}`) && !isAbsolute(relativePath$1);
9779
+ }
9699
9780
  function inferPreviewAssetContentType(path) {
9700
9781
  switch (extname(path).toLowerCase()) {
9701
9782
  case ".html": return "text/html";
@@ -9773,7 +9854,7 @@ var FilePreviewService = class {
9773
9854
  const normalized = stripLeadingSlash(requestPath);
9774
9855
  if (session.previewKind === "html") {
9775
9856
  const absolutePath$1 = resolve(session.directoryPath, normalized);
9776
- if (!absolutePath$1.startsWith(session.directoryPath + "/")) return null;
9857
+ if (!isPathInsideRoot(session.directoryPath, absolutePath$1)) return null;
9777
9858
  if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
9778
9859
  return {
9779
9860
  content: readFileSync(absolutePath$1),
@@ -9783,7 +9864,7 @@ var FilePreviewService = class {
9783
9864
  if (normalized.startsWith("resource/")) {
9784
9865
  const resourcePath = normalized.slice(9);
9785
9866
  const absolutePath$1 = resolve(session.directoryPath, resourcePath);
9786
- if (!absolutePath$1.startsWith(session.directoryPath + "/")) return null;
9867
+ if (!isPathInsideRoot(session.directoryPath, absolutePath$1)) return null;
9787
9868
  if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
9788
9869
  return {
9789
9870
  content: readFileSync(absolutePath$1),
@@ -9793,7 +9874,7 @@ var FilePreviewService = class {
9793
9874
  const assetName = normalized || session.entryFileName;
9794
9875
  if (!assetName) return null;
9795
9876
  const absolutePath = resolve(this.previewAssetsDir, assetName);
9796
- if (!absolutePath.startsWith(resolve(this.previewAssetsDir) + "/")) return null;
9877
+ if (!isPathInsideRoot(this.previewAssetsDir, absolutePath)) return null;
9797
9878
  if (!existsSync(absolutePath) || !statSync(absolutePath).isFile()) return null;
9798
9879
  if (isRewritablePreviewAsset(assetName)) {
9799
9880
  const rewritten = rewritePreviewAssetPaths(readFileSync(absolutePath, "utf8"), hash);
@@ -10833,6 +10914,18 @@ var PlanningRootUnavailableError = class extends Error {
10833
10914
  }
10834
10915
  };
10835
10916
  /** Serialized deep owner for one replaceable Planning-root service record. */
10917
+ /** Read CLI Change-list task facts once per call; failures degrade to an empty index. */
10918
+ function readCliChangeListEntriesFor(service) {
10919
+ return async () => {
10920
+ try {
10921
+ const data = await service.getCurrent({ kind: "opsx-change-list" });
10922
+ if (data.kind !== "opsx-change-list") return /* @__PURE__ */ new Map();
10923
+ return new Map(data.entries.map((entry) => [entry.name, entry]));
10924
+ } catch {
10925
+ return /* @__PURE__ */ new Map();
10926
+ }
10927
+ };
10928
+ }
10836
10929
  var PlanningRootServiceManager = class {
10837
10930
  activeRecord = null;
10838
10931
  currentRootContextSnapshot = null;
@@ -10908,7 +11001,10 @@ var PlanningRootServiceManager = class {
10908
11001
  },
10909
11002
  codeGitBindingToken: this.options.codeBinding.bindingToken,
10910
11003
  loaders: {
10911
- loadSummary: () => loadDashboardSummary({ adapter }),
11004
+ loadSummary: () => loadDashboardSummary({
11005
+ adapter,
11006
+ readCliChangeListEntries: readCliChangeListEntriesFor(planningCliProjectionService)
11007
+ }),
10912
11008
  loadTrends: () => loadDashboardTrends({
10913
11009
  adapter,
10914
11010
  configManager: this.options.configManager
@@ -10919,16 +11015,6 @@ var PlanningRootServiceManager = class {
10919
11015
  }, "projection-work", signal)
10920
11016
  }
10921
11017
  });
10922
- const changesProjectionService = new ChangesProjectionService({
10923
- workOwner: this.changesProjectionWorkOwner,
10924
- root: {
10925
- path: projectDir,
10926
- source: planningRoot.source,
10927
- storeSelector: rootCliSelector.store ?? null,
10928
- generation: gitBindingToken
10929
- },
10930
- adapter
10931
- });
10932
11018
  const planningCliProjectionService = new PlanningCliProjectionService({
10933
11019
  rootContext: rootContextValue,
10934
11020
  gitBindingToken,
@@ -10939,6 +11025,28 @@ var PlanningRootServiceManager = class {
10939
11025
  storeObservation: this.options.storeObservation,
10940
11026
  workOwner: this.planningCliProjectionWorkOwner
10941
11027
  });
11028
+ const changesProjectionService = new ChangesProjectionService({
11029
+ workOwner: this.changesProjectionWorkOwner,
11030
+ root: {
11031
+ path: projectDir,
11032
+ source: planningRoot.source,
11033
+ storeSelector: rootCliSelector.store ?? null,
11034
+ generation: gitBindingToken
11035
+ },
11036
+ adapter: {
11037
+ listChanges: () => adapter.listChanges(),
11038
+ readChangeMeta: (id) => adapter.readChangeMeta(id),
11039
+ readCliChangeListEntries: async () => {
11040
+ try {
11041
+ const data = await planningCliProjectionService.getCurrent({ kind: "opsx-change-list" });
11042
+ if (data.kind !== "opsx-change-list") return /* @__PURE__ */ new Map();
11043
+ return new Map(data.entries.map((entry) => [entry.name, entry]));
11044
+ } catch {
11045
+ return /* @__PURE__ */ new Map();
11046
+ }
11047
+ }
11048
+ }
11049
+ });
10942
11050
  const filePreviewService = new FilePreviewService(projectDir, this.options.previewAssetsDir);
10943
11051
  const schemaMutationService = new SchemaMutationService({
10944
11052
  planningRoot: projectDir,
@@ -11828,11 +11936,12 @@ var PtyInputWriter = class {
11828
11936
  //#endregion
11829
11937
  //#region src/pty-manager.ts
11830
11938
  /**
11831
- * Orthogonal intents (updated 2026-07-22 Asia/Shanghai):
11939
+ * Orthogonal intents (updated 2026-08-09 Asia/Shanghai):
11832
11940
  * 1. Own PTY session process, ordered input, buffer, title, and lifecycle state.
11833
11941
  * 2. Spawn each session at an explicitly resolved launch-project or planning-root cwd and retain its immutable Root generation.
11834
11942
  * 3. Preserve the inherited backend environment, including XDG_DATA_HOME, across cwd targets.
11835
11943
  * 4. List and close server-owned sessions for reconnect and teardown.
11944
+ * 5. Resolve Windows Agent launchers to native argv while retaining the requested display command.
11836
11945
  *
11837
11946
  * Original request (2026-07-16): "3.8 Terminal exposes explicit launch-project cwd and planning-root cwd while preserving inherited XDG_DATA_HOME"
11838
11947
  * Owner-reported defect (2026-07-21): Pre-created Agent terminals are absent from Compose Send.
@@ -11919,7 +12028,11 @@ var PtySession = class extends EventEmitter$1 {
11919
12028
  this.rootGeneration = opts.rootGeneration;
11920
12029
  this.maxBufferLines = opts.scrollback ?? DEFAULT_SCROLLBACK;
11921
12030
  this.maxBufferBytes = opts.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
11922
- this.process = pty.spawn(this.command, this.args, {
12031
+ const invocation = resolveCommandInvocation(this.command, this.args, {
12032
+ cwd: opts.cwd,
12033
+ env: process.env
12034
+ });
12035
+ this.process = pty.spawn(invocation.command, invocation.args, {
11923
12036
  name: "xterm-256color",
11924
12037
  cols: opts.cols ?? 80,
11925
12038
  rows: opts.rows ?? 24,
@@ -14650,7 +14763,16 @@ const changeProjectionDataSchema = z.object({
14650
14763
  remaining: z.number().int().nonnegative()
14651
14764
  }),
14652
14765
  createdAt: z.number(),
14653
- updatedAt: z.number()
14766
+ updatedAt: z.number(),
14767
+ cliTaskSummary: z.object({
14768
+ completedTasks: z.number().int().nonnegative(),
14769
+ totalTasks: z.number().int().nonnegative(),
14770
+ status: z.enum([
14771
+ "no-tasks",
14772
+ "complete",
14773
+ "in-progress"
14774
+ ])
14775
+ }).nullable()
14654
14776
  })),
14655
14777
  errors: z.array(changeProjectionRowErrorSchema)
14656
14778
  });
@@ -15292,12 +15414,31 @@ const specRouter = router({
15292
15414
  /**
15293
15415
  * Change router - change proposal operations
15294
15416
  */
15417
+ /**
15418
+ * Join the CLI-owned Change-list task facts onto adapter ChangeMeta rows. The CLI entry is
15419
+ * the task-count authority (`openspec list` reports the same checkbox semantics as Apply);
15420
+ * rows the CLI did not list keep a null summary rather than a UI-side recomputation.
15421
+ */
15422
+ async function listChangesWithCliTaskSummary(ctx, adapter) {
15423
+ const [rows, cliList] = await Promise.all([adapter.listChangesWithMeta(), runPlanningRootRead(ctx, ({ planningCliProjectionService }) => planningCliProjectionService.getCurrent({ kind: "opsx-change-list" })).then((data) => data.kind === "opsx-change-list" ? new Map(data.entries.map((entry) => [entry.name, entry])) : /* @__PURE__ */ new Map(), () => /* @__PURE__ */ new Map())]);
15424
+ return rows.map((row) => {
15425
+ const entry = cliList.get(row.id);
15426
+ return entry ? {
15427
+ ...row,
15428
+ cliTaskSummary: {
15429
+ completedTasks: entry.completedTasks,
15430
+ totalTasks: entry.totalTasks,
15431
+ status: entry.status
15432
+ }
15433
+ } : row;
15434
+ });
15435
+ }
15295
15436
  const changeRouter = router({
15296
15437
  list: publicProcedure.query(async ({ ctx }) => {
15297
15438
  return runPlanningRootRead(ctx, ({ adapter }) => adapter.listChanges());
15298
15439
  }),
15299
15440
  listWithMeta: publicProcedure.query(async ({ ctx }) => {
15300
- return runPlanningRootRead(ctx, ({ adapter }) => adapter.listChangesWithMeta());
15441
+ return runPlanningRootRead(ctx, ({ adapter }) => listChangesWithCliTaskSummary(ctx, adapter));
15301
15442
  }),
15302
15443
  listArchived: publicProcedure.query(async ({ ctx }) => {
15303
15444
  return runPlanningRootRead(ctx, ({ adapter }) => adapter.listArchivedChanges());
@@ -15342,7 +15483,7 @@ const changeRouter = router({
15342
15483
  return { success: true };
15343
15484
  }),
15344
15485
  subscribe: publicProcedure.subscription(({ ctx }) => {
15345
- return createPlanningRootSubscription(ctx, ({ adapter }) => adapter.listChangesWithMeta());
15486
+ return createPlanningRootSubscription(ctx, ({ adapter }) => listChangesWithCliTaskSummary(ctx, adapter));
15346
15487
  }),
15347
15488
  subscribeBatches: publicProcedure.subscription(({ ctx }) => createPlanningRootProjectionWorkSubscription(ctx, (services, listener) => services.changesProjectionService.subscribe(listener), (event) => changeProjectionEventSchema().parse(event))),
15348
15489
  subscribeFiles: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
@@ -15620,32 +15761,45 @@ const cliRouter = router({
15620
15761
  subscribeConfiguredTools: publicProcedure.subscription(({ ctx }) => {
15621
15762
  return createReactiveSubscription(() => getConfiguredTools(ctx.projectDir));
15622
15763
  }),
15623
- validate: publicProcedure.input(z.discriminatedUnion("kind", [z.object({
15624
- kind: z.literal("item"),
15625
- id: z.string().min(1),
15626
- type: z.enum(["spec", "change"]).optional(),
15627
- strict: z.boolean().optional()
15628
- }), z.object({
15629
- kind: z.literal("scope"),
15630
- scope: z.enum([
15631
- "all",
15632
- "changes",
15633
- "specs"
15634
- ]),
15635
- strict: z.boolean().optional()
15636
- })])).mutation(async ({ ctx, input }) => {
15637
- return runPlanningRoot(ctx, ({ rootContext }) => ctx.cliExecutor.contracts.validate({
15638
- target: input.kind === "item" ? {
15639
- kind: "item",
15640
- id: input.id,
15641
- type: input.type
15642
- } : {
15643
- kind: "scope",
15644
- scope: input.scope
15645
- },
15646
- strict: input.strict,
15647
- ...getRootContextCliSelector(rootContext)
15648
- }));
15764
+ validate: publicProcedure.input(z.discriminatedUnion("kind", [
15765
+ z.object({
15766
+ kind: z.literal("item"),
15767
+ id: z.string().min(1),
15768
+ type: z.enum(["spec", "change"]).optional(),
15769
+ strict: z.boolean().optional()
15770
+ }),
15771
+ z.object({
15772
+ kind: z.literal("scope"),
15773
+ scope: z.enum([
15774
+ "all",
15775
+ "changes",
15776
+ "specs"
15777
+ ]),
15778
+ strict: z.boolean().optional()
15779
+ }),
15780
+ z.object({ kind: z.literal("archived") })
15781
+ ])).mutation(async ({ ctx, input }) => {
15782
+ return runPlanningRoot(ctx, ({ rootContext }) => {
15783
+ if (input.kind === "archived") {
15784
+ const cli = rootContext.cli;
15785
+ if (!deriveOpenSpecCliCapabilities(cli.available ? parseOpenSpecCliVersion(cli.version) : null).archivedValidation) throw new TRPCError({
15786
+ code: "PRECONDITION_FAILED",
15787
+ message: "Archived task validation requires the detected OpenSpec CLI to declare the 1.9 archived-validation capability."
15788
+ });
15789
+ }
15790
+ return ctx.cliExecutor.contracts.validate({
15791
+ target: input.kind === "item" ? {
15792
+ kind: "item",
15793
+ id: input.id,
15794
+ type: input.type
15795
+ } : input.kind === "scope" ? {
15796
+ kind: "scope",
15797
+ scope: input.scope
15798
+ } : { kind: "archived" },
15799
+ strict: input.kind === "archived" ? void 0 : input.strict,
15800
+ ...getRootContextCliSelector(rootContext)
15801
+ });
15802
+ });
15649
15803
  }),
15650
15804
  validateStream: publicProcedure.input(z.object({
15651
15805
  type: z.enum(["spec", "change"]).optional(),
@@ -15699,7 +15853,7 @@ const cliRouter = router({
15699
15853
  })
15700
15854
  });
15701
15855
  /** Config-owned Agent registry, policy, physical inventory, and CLI mutation boundary. */
15702
- const agentIntegrationToolIdSchema = z.string().min(1).refine((toolId) => getAvailableTools().some((tool) => tool.value === toolId), "Agent tool must be an available OpenSpec 1.7 registry id.");
15856
+ const agentIntegrationToolIdSchema = z.string().min(1).refine((toolId) => getAvailableTools().some((tool) => tool.value === toolId), "Agent tool must be an available OpenSpec 1.9 registry id.");
15703
15857
  const agentIntegrationsRouter = router({
15704
15858
  get: publicProcedure.query(({ ctx }) => ctx.agentDeliveryProjectionService.getCurrent()),
15705
15859
  refresh: publicProcedure.query(({ ctx }) => ctx.agentDeliveryProjectionService.refresh()),
@@ -15727,6 +15881,14 @@ const agentIntegrationsRouter = router({
15727
15881
  })).subscription(({ ctx, input }) => {
15728
15882
  return createCliStreamObservable(async (onEvent) => {
15729
15883
  const projection = await ctx.agentDeliveryProjectionService.getCurrent();
15884
+ if (Array.isArray(input.tools)) {
15885
+ const offered = new Set(projection.registry.map((tool) => tool.value));
15886
+ const unavailable = input.tools.filter((toolId) => !offered.has(toolId));
15887
+ if (unavailable.length > 0) throw new TRPCError({
15888
+ code: "PRECONDITION_FAILED",
15889
+ message: `Agent tool(s) unavailable on the admitted OpenSpec CLI line: ${unavailable.join(", ")}.`
15890
+ });
15891
+ }
15730
15892
  return streamOpenSpecCliMutation(ctx, ["init"], (mutationEvent) => ctx.cliExecutor.initStream({
15731
15893
  tools: input.tools,
15732
15894
  profile: projection.policy.profile,
@@ -17774,10 +17936,12 @@ var TranslationCacheService = class {
17774
17936
  //#endregion
17775
17937
  //#region src/runtime-package-host.ts
17776
17938
  /**
17777
- * Orthogonal intents (updated 2026-07-31 Asia/Shanghai):
17939
+ * Original request (2026-08-04): "The project was developed on macOS and now needs Windows adaptation."
17940
+ * Orthogonal intents (updated 2026-08-08 Asia/Shanghai):
17778
17941
  * 1. Resolve the physical host manifest that owns on-demand runtime installation.
17779
17942
  * 2. Read declared runtime ranges without treating optional peers as install-time dependencies.
17780
17943
  * 3. Normalize legacy on-demand optional dependency records after package-manager installation.
17944
+ * 4. Execute the npm dependency probe through Core's shell-independent Windows-safe spawn owner.
17781
17945
  *
17782
17946
  * Original request (2026-07-31): "这个依赖好像会导致安装的时候仍然会被强制装上去,可能要改成 peerDependencies 会更好"
17783
17947
  */
@@ -17814,7 +17978,7 @@ function readRuntimeHostPackageDependencyRequest(input) {
17814
17978
  return `${input.packageName}@${range}`;
17815
17979
  }
17816
17980
  async function readRuntimeHostPackageDependencyTree(input) {
17817
- const child = spawn("npm", [
17981
+ const started = spawnSafe("npm", [
17818
17982
  "list",
17819
17983
  "--json",
17820
17984
  "--omit=dev",
@@ -17822,9 +17986,10 @@ async function readRuntimeHostPackageDependencyTree(input) {
17822
17986
  ...input.packageNames
17823
17987
  ], {
17824
17988
  cwd: input.runtimeHost.packageDir,
17825
- shell: false,
17826
17989
  env: createCleanCliEnv()
17827
17990
  });
17991
+ if (!started.ok) throw new Error(`Unable to start npm list: ${started.error.message}`);
17992
+ const { child } = started;
17828
17993
  const stdoutChunks = [];
17829
17994
  const stderrChunks = [];
17830
17995
  const exitCode = await new Promise((resolve$1, reject) => {
@@ -18066,6 +18231,17 @@ function buildRuntimeConfig(engineId, profile) {
18066
18231
 
18067
18232
  //#endregion
18068
18233
  //#region src/translation-engine-service.ts
18234
+ /**
18235
+ * Orthogonal intents (updated 2026-08-09 Asia/Shanghai):
18236
+ * 1. Project translation-engine inventory, selection, lifecycle, and immutable settings snapshots.
18237
+ * 2. Install and cancel managed local runtime dependencies through observable, bounded process settlement.
18238
+ * 3. Own local model search, download plans, persisted asset readiness, and profile validation.
18239
+ * 4. Execute browser, OpenAI, and managed-local batch translation with cache-aware events.
18240
+ * 5. Preserve CT2/llama runtime policy, directional language constraints, and resource limits.
18241
+ *
18242
+ * Original request (2026-05-22): "Server provides engine registry, installation sessions, installation logs, selection, and translation."
18243
+ * Original request (2026-08-04): "Make pnpm openspecui start and equivalent package scripts work on Windows."
18244
+ */
18069
18245
  const __dirname$2 = dirname(fileURLToPath(import.meta.url));
18070
18246
  var TranslationEngineService = class {
18071
18247
  projectDir;
@@ -18796,55 +18972,82 @@ async function installManagedLocalRuntime(engineId, input) {
18796
18972
  input.onLifecycle?.(finalLifecycle);
18797
18973
  return finalLifecycle;
18798
18974
  }
18975
+ /** Run one package-manager installation with bounded cross-platform tree cancellation. */
18799
18976
  async function runRuntimeInstallCommand(input) {
18800
- let child;
18801
- try {
18802
- child = spawn(input.command.cmd, input.command.args, {
18803
- cwd: input.cwd,
18804
- shell: false,
18805
- env: createCleanCliEnv()
18806
- });
18807
- } catch (error) {
18808
- return error instanceof Error ? error.message : String(error);
18809
- }
18810
- const abort = () => {
18811
- try {
18812
- child.kill();
18813
- } catch {}
18814
- };
18815
- input.signal?.addEventListener("abort", abort, { once: true });
18816
- try {
18817
- await new Promise((resolve$1, reject) => {
18818
- child.stdout?.on("data", (data) => {
18819
- input.onLog?.({
18820
- stream: "stdout",
18821
- text: data.toString()
18822
- });
18823
- });
18824
- child.stderr?.on("data", (data) => {
18825
- input.onLog?.({
18826
- stream: "stderr",
18827
- text: data.toString()
18828
- });
18829
- });
18830
- child.on("error", (error) => {
18831
- reject(error);
18977
+ if (input.signal?.aborted) return "Translation runtime installation cancelled.";
18978
+ const started = spawnSafe(input.command.cmd, input.command.args, {
18979
+ cwd: input.cwd,
18980
+ shell: false,
18981
+ env: createCleanCliEnv()
18982
+ });
18983
+ if (!started.ok) return started.error.message;
18984
+ const { child } = started;
18985
+ const completion = new Promise((resolve$1) => {
18986
+ child.stdout?.on("data", (data) => {
18987
+ input.onLog?.({
18988
+ stream: "stdout",
18989
+ text: data.toString()
18832
18990
  });
18833
- child.on("close", (exitCode) => {
18834
- if (exitCode === 0) {
18835
- resolve$1();
18836
- return;
18837
- }
18838
- reject(/* @__PURE__ */ new Error(`${input.command.displayCommand} exited with code ${exitCode ?? "unknown"}.`));
18991
+ });
18992
+ child.stderr?.on("data", (data) => {
18993
+ input.onLog?.({
18994
+ stream: "stderr",
18995
+ text: data.toString()
18839
18996
  });
18840
18997
  });
18841
- return null;
18842
- } catch (error) {
18843
- return error instanceof Error ? error.message : String(error);
18998
+ child.once("error", (error) => resolve$1(error.message));
18999
+ child.once("close", (exitCode) => {
19000
+ resolve$1(exitCode === 0 ? null : `${input.command.displayCommand} exited with code ${exitCode ?? "unknown"}.`);
19001
+ });
19002
+ });
19003
+ let requestAbort = null;
19004
+ const abortRequested = new Promise((resolve$1) => {
19005
+ requestAbort = resolve$1;
19006
+ });
19007
+ const abort = () => requestAbort?.();
19008
+ input.signal?.addEventListener("abort", abort, { once: true });
19009
+ try {
19010
+ const outcome = await Promise.race([completion.then((result) => ({
19011
+ kind: "complete",
19012
+ result
19013
+ })), abortRequested.then(() => ({ kind: "abort" }))]);
19014
+ if (outcome.kind === "complete") return outcome.result;
19015
+ return settleRuntimeInstallCancellation(child, completion, input.terminationTimeoutMs ?? 5e3);
18844
19016
  } finally {
18845
19017
  input.signal?.removeEventListener("abort", abort);
18846
19018
  }
18847
19019
  }
19020
+ async function settleRuntimeInstallCancellation(child, completion, timeoutMs) {
19021
+ const failures = [];
19022
+ const terminate = async (signal) => {
19023
+ let timer = null;
19024
+ try {
19025
+ await Promise.race([terminateChildProcessTree(child, signal), new Promise((_resolve, reject) => {
19026
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out requesting ${signal} for runtime installation.`)), timeoutMs);
19027
+ })]);
19028
+ } catch (error) {
19029
+ failures.push(error);
19030
+ } finally {
19031
+ if (timer) clearTimeout(timer);
19032
+ }
19033
+ };
19034
+ const waitForCompletion = async () => {
19035
+ let timer = null;
19036
+ try {
19037
+ return await Promise.race([completion.then(() => true), new Promise((resolve$1) => {
19038
+ timer = setTimeout(() => resolve$1(false), timeoutMs);
19039
+ })]);
19040
+ } finally {
19041
+ if (timer) clearTimeout(timer);
19042
+ }
19043
+ };
19044
+ await terminate("SIGTERM");
19045
+ if (await waitForCompletion()) return "Translation runtime installation cancelled.";
19046
+ await terminate("SIGKILL");
19047
+ if (await waitForCompletion()) return "Translation runtime installation cancelled.";
19048
+ const failureText = failures.map((error) => error instanceof Error ? error.message : String(error)).join(" ");
19049
+ return `Translation runtime installation cancellation did not settle.${failureText ? ` ${failureText}` : ""}`;
19050
+ }
18848
19051
  function getManagedLocalRuntimeSpec(engineId) {
18849
19052
  if (engineId === "local") return {
18850
19053
  packageNames: ["@huggingface/transformers", "onnxruntime-node"],
@@ -18957,7 +19160,9 @@ function readCurrentRuntimeMemory() {
18957
19160
  * 2. Execute translation engines in isolated worker-thread or process hosts.
18958
19161
  * 3. Enforce cancellation and process memory limits while preserving batch evidence.
18959
19162
  * 4. Route worker-thread payloads by an explicit translation protocol kind.
19163
+ * 5. Hide the process-host bootstrap console window (`windowsHide`) under a console-less Windows daemon.
18960
19164
  *
19165
+ * Original request (2026-08-14): "在Windows平台上,执行命令总是会弹出cmd窗口,这个可否统一隐藏,你先调查一下原因"
18961
19166
  * Original request (2026-07-24): "Production worker kinds must not claim each other's payloads."
18962
19167
  */
18963
19168
  const execFileAsync = promisify(execFile);
@@ -19253,7 +19458,8 @@ function createManagedLocalTranslationProcess(input) {
19253
19458
  "inherit",
19254
19459
  "inherit",
19255
19460
  "ipc"
19256
- ]
19461
+ ],
19462
+ windowsHide: true
19257
19463
  });
19258
19464
  }
19259
19465
  function resolveProcessEntryUrl() {
@@ -19410,11 +19616,11 @@ if (!isMainThread) {
19410
19616
  //#endregion
19411
19617
  //#region src/server.ts
19412
19618
  /**
19413
- * Orthogonal intents (updated 2026-08-01 Asia/Shanghai):
19619
+ * Orthogonal intents (updated 2026-08-04 Asia/Shanghai):
19414
19620
  * 1. Bootstrap HTTP/tRPC, WebSocket, PTY, and launch-project runtime services.
19415
19621
  * 2. Delegate planning-root, Store, Environment Global, Agent, and Root Context projection ownership.
19416
19622
  * 3. Host notifications, sound, preview-resource, translation, and telemetry boundaries.
19417
- * 4. Admit dynamic loopback App origins and perform deterministic runtime teardown.
19623
+ * 4. Admit dynamic loopback App origins and await deterministic native-handle teardown.
19418
19624
  * 5. Bridge settled machine-global config changes into Root Context replacement.
19419
19625
  *
19420
19626
  * Original request (2026-07-15): "你先负责后端(内核)的开发。"
@@ -19936,7 +20142,7 @@ async function createWebSocketServer(server, httpServer, config) {
19936
20142
  () => ptyManager.closeAll(),
19937
20143
  () => closeWebSocketServer(ptyWss),
19938
20144
  () => closeWebSocketServer(wss),
19939
- () => server.watcher?.stop()
20145
+ () => server.watcher?.close()
19940
20146
  ]);
19941
20147
  await settleCleanupPhase(failures, [() => server.storeObservationFallback.dispose()]);
19942
20148
  await settleCleanupPhase(failures, [() => server.storeMutationService.dispose()]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openspecui/server",
3
- "version": "7.0.1",
3
+ "version": "9.0.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "exports": {
@@ -25,7 +25,7 @@
25
25
  "typecheck:nested-spec-tests": "tsc -p tsconfig.nested-spec-tests.json --noEmit",
26
26
  "typecheck:project-initialization-tests": "tsc -p tsconfig.project-initialization-tests.json --noEmit",
27
27
  "typecheck:default-store-tests": "tsc -p tsconfig.default-store-tests.json --noEmit",
28
- "dev": "NODE_OPTIONS=\"${NODE_OPTIONS:+$NODE_OPTIONS }--conditions=development\" tsx watch --include '../core/dist/**' --include '../search/dist/**' src/standalone.ts",
28
+ "dev": "tsx watch --conditions=development --include '../core/dist/**' --include '../search/dist/**' src/standalone.ts",
29
29
  "test": "vitest run",
30
30
  "test:watch": "vitest"
31
31
  },
@@ -33,8 +33,8 @@
33
33
  "@hono/node-server": "^1.14.1",
34
34
  "@huggingface/hub": "^2.12.0",
35
35
  "@lydell/node-pty": "^1.1.0",
36
- "@openspecui/core": "7.0.1",
37
- "@openspecui/search": "7.0.1",
36
+ "@openspecui/core": "9.0.0",
37
+ "@openspecui/search": "9.0.0",
38
38
  "@opentelemetry/api": "^1.9.1",
39
39
  "@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
40
40
  "@opentelemetry/sdk-node": "^0.221.0",